I was at CppCon last week and watched a clever talk about using ranges and calendars. This is just me trying to play around with the new technology.
Step 1: dump out a calendar.
Usage:
cal <year>
Output:
January Sun Mon Tue Wed Thu Fri Sat 01 02 03 04 05 06 07 08 09 10 11 ...etc
#include <iostream>
#include <time.h>
#include <iomanip>
enum WeekDay {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
class Calander
{
/*
* Line 0: <Month>
* Line 1: Sun Mon Tue Wed Thu Fri Sat
* Line 2: Week 1 (May need leading space)
* Line 3: Week 2
* Line 4: Week 3
* Line 5: Week 5 or (blank)
* Line 6: Week 6 or (blank)
* Line 7: (blank)
*/
class WeekLine
{
static int getDaysInMonth(int month, bool isLeap)
{
static constexpr int normal[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
static constexpr int leap[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
return isLeap ? leap[month] : normal[month];
}
static bool isLeapYear(int year)
{
return (year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0));
}
static int getOffsetOfFirstDay(int year, int month)
{
struct ::tm timeS;
timeS.tm_sec = 0;
timeS.tm_min = 0;
timeS.tm_hour = 0;
timeS.tm_mday = 1;
timeS.tm_mon = month;
timeS.tm_year = year - 1900;
timeS.tm_isdst = 0;
timeS.tm_zone = nullptr;
timeS.tm_gmtoff = 0;
timegm(&timeS);
// TODO:
// Other week start days apart from Sunday.
return timeS.tm_wday; // day of week (Sunday = 0)
}
int year;
bool isLeap;
int month;
int dayOfMonth;
WeekDay firstDayOfWeek;
int offset;
int linesPrinted;
public:
WeekLine()
: year(-1)
, month(0)
, dayOfMonth(0)
{}
WeekLine(int year, WeekDay firstDayOfWeek)
: year(year)
, isLeap(isLeapYear(year))
, month(0)
, dayOfMonth(0)
, firstDayOfWeek(firstDayOfWeek)
, offset(getOffsetOfFirstDay(year, 0))
, linesPrinted(0)
{}
bool operator!=(WeekLine const& rhs) {return year * 10'000 + month * 100 + dayOfMonth != rhs.year * 10'000 + rhs.month * 100 + rhs.dayOfMonth;}
WeekLine& operator*() {return *this;}
WeekLine& operator++()
{
++linesPrinted;
if (linesPrinted > 2)
{
dayOfMonth += 7 - offset;
offset = 0;
}
if (linesPrinted == 9)
{
// We have reached the end of the current
// month. So reset to next month.
linesPrinted= 0;
month += 1;
dayOfMonth = 0;
offset = getOffsetOfFirstDay(year, month);
}
if (month >= 12)
{
// We have reached the end.
// Make the same as the end iterator.
year = -1;
month = 0;
dayOfMonth = 0;
}
return *this;
}
friend std::ostream& operator<<(std::ostream& str, WeekLine const& val)
{
static const std::string month[] = {"January", "February", "March",
"April", "May", "June",
"July", "August", "September",
"October", "November", "December"};
if (val.linesPrinted == 0)
{
std::string const& name = month[val.month];
return str << std::string((26 - name.size()) / 2, ' ') << name << "\n";
}
if (val.linesPrinted == 1) {
return str << "Sun Mon Tue Wed Thu Fri Sat \n";
}
for(int loop = 0;loop < val.offset;++loop) {
str << " ";
}
int max = std::min(7, getDaysInMonth(val.month, val.isLeap) - val.dayOfMonth);
for(int loop = 0;loop < (max - val.offset);++loop) {
str << std::setw(2) << std::setfill('0') << (val.dayOfMonth + loop + 1) << " ";
}
return str << "\n";
}
};
int year;
WeekDay firstDayOfWeek;
public:
Calander(int year)
: year(year)
, firstDayOfWeek(Sun)
{}
WeekLine begin() const {return WeekLine(year, firstDayOfWeek);}
WeekLine end() const {return WeekLine();}
};
int main(int argc, char* argv[])
{
if (argc != 2) {
return 1;
}
int year = std::atoi(argv[1]);
Calander cal(year);
for(auto week: cal) {
std::cout << week;
}
}
-
\$\begingroup\$ Ah, but can you also print it in 3 month column format? See also e.g. this attempt to avoid range-based code: pastebin.com/AyfLaBVU The key advantage of range-based code is not that you can't do things using clever use of nested loops (like the linked example), but that maintaining and debugging such nested loopy code is more burdensome. The range-based code lets you transform your input in a series of sequential steps to the desired output. Like Unix pipes and filters. \$\endgroup\$TemplateRex– TemplateRex2015年09月29日 19:54:42 +00:00Commented Sep 29, 2015 at 19:54
-
\$\begingroup\$ @TemplateRex: That's going to be the next version. Be patient. \$\endgroup\$Loki Astari– Loki Astari2015年09月29日 20:09:18 +00:00Commented Sep 29, 2015 at 20:09
1 Answer 1
One small change
class WeekLine : public std::iterator<std::forward_iterator_tag, void, void, void, void>
will allow it to be used like this
std::copy(cal.begin(), cal.end(), std::ostream_iterator<decltype(cal.begin())>(std::cout));
Somewhat useless, but at least it'll be usable with the standard library.
Speaking of which, is there a reason why your
WeekLine
is private? At least do something like this:using iterator = WeekLine;
That way I can replace
decltype(cal.begin())
withCalander::iterator
.You should use
<ctime>
instead of<time.h>
.std::atoi
comes from<cstdlib>
. Don't omit headers since it may fail to compile on another implementation.Why not use an
enum class
forWeekDay
? You never use it, for numerical values or anything else, so there's no point in injectingSun
,Mon
, etc. into the global namespace. Make it private like the other classes.In one case of declaring an array, you use
constexpr
, in another you useconst
.constexpr
is implicitlyconst
, so for consistency, choose one or the other. There also probably isn't any difference semantically in these situations.I noticed you using C++14 digit separators. In this case, I recommend against it since 1) support may not be available on all compilers, 2) it's confusing to read even for people who know what they are. At least format your function:
bool operator!=(WeekLine const& rhs) { return year * 10'000 + month * 100 + dayOfMonth != (rhs.year * 10'000) + (rhs.month * 100) + rhs.dayOfMonth; }
Here's a more efficient way to check for a leap year:
if year is not divisible by 4 then not leap year else if year is not divisible by 100 then leap year else if year is divisible by 400 then leap year else not leap year
This "most-efficient" pseudo-code simply changes the order of tests so the division by 4 takes place first, followed by the less-frequently occurring tests. Because "year" does not divide by four 75-percent of the time, the algorithm ends after only one test in three out of four cases.
And the actual code:
if ((year & 3) == 0 && ((year % 25) != 0 || (year & 15) == 0)) { /* leap year */ }
A comprehensive explanation/rationale can be found in the linked page.