7
\$\begingroup\$

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;
 }
}
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Sep 28, 2015 at 22:13
\$\endgroup\$
2
  • \$\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\$ Commented Sep 29, 2015 at 19:54
  • \$\begingroup\$ @TemplateRex: That's going to be the next version. Be patient. \$\endgroup\$ Commented Sep 29, 2015 at 20:09

1 Answer 1

4
\$\begingroup\$
  • 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()) with Calander::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 for WeekDay? You never use it, for numerical values or anything else, so there's no point in injecting Sun, 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 use const. constexpr is implicitly const, 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.

answered Sep 30, 2015 at 23:29
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.