Working on calendar duration arithmetic using date.h and std::chrono, but getting an unexpected result.
Sample code is:
#include "date.h"
#include <string>
#include <chrono>
#include <iostream>
int main() {
date::sys_seconds calendarDate = {};
calendarDate = std::chrono::years(30) + date::sys_seconds(std::chrono::days(10));
std::string stringDate = date::format("%Y-%m-%d %H:%M:%S", calendarDate);
std::cout << "{} + 30 years + 10 days = " << stringDate << "\n";
return 0;
}
Actual Output:
{} + 30 years + 10 days = 2000-01-11 06:36:00
Expected Output:
{} + 30 years + 10 days = 2000-01-11 00:00:00
Using Ubuntu 22.04; g++ 11.3.0
Compiled with: gcc -g -std=c++20 main.cpp -lstdc++
Using date.h fromm here: https://raw.githubusercontent.com/HowardHinnant/date/master/include/date/date.h
Any insight into what's adding in the extra 6hours and 36minutes?
It is because of leap time. For the Gregorian calendar, the average length of the calendar year (the mean year) across the complete leap cycle of 400 years is 365.2425 days (97 out of 400 years are leap years).
This is taken into account in the definition of std::chrono::years which is defined as a duration type with Period std::ratio<31556952> (60 * 60 * 24 * 365.2425 = 31556952).
However, if you compare this to the non-leap-aware duration of a year (60 * 60 * 24 * 365 = 31536000), then you get a difference of 20952 seconds or 5 hours 49 minutes and 12 seconds. If you multiply that difference by 30 it grows to:
7 days 6 hours 36 minutes and 0 seconds - and at least the latter part should look familiar to you.
But what about the 7 days? Well, it turns out that between 1970 and 2000: 1972, 1976, 1980, 1984, 1988, 1992, and 1996 were leap years - if you count that is 7, and that is where the days went.
You can check this by yourself by changing your code and looking at the output:
calendarDate += std::chrono::seconds(60 * 60 * 24 * 365 * 30); - "1999-12-25 00:00:00"
calendarDate += std::chrono::seconds(60 * 60 * 24 * 365 * 30) + day(7); - "2000-01-01 00:00:00"
By the way, the problem is that std::chrono::years gives you the length of an average year, while the date.h library knows (and cares about) the specific years: It knows that 1970 was not a leap year (so 60 * 60 * 24 * 365 = 31536000 seconds), but 1972 was a leap year (so 60 * 60 * 24 * 366 = 31622400 seconds).
If you had added 31 years instead, then you would have added 5:49:12 more skew from the average year, but then removed 1 more day since 2000 was also a leap year - so the time it would print would still be in 2000, but ~12 hours before new-years 2001.
In addition to Frodyne's good answer (which I've upvoted), I wanted to add that you can do either chronological arithmetic or calendrical arithmetic with date.h.
You've done chronological arithmetic, where years is considered to be a uniform unit, and is equal to the average civil year.
Calendrical arithmetic follows the irregularity of calendars, and is what you expected. This is how you would accomplish that:
#include "date/date.h"
#include <string>
#include <chrono>
#include <iostream>
int main() {
auto chronologicalDateTime = date::sys_seconds(date::days(10));
auto chronologicalDate = date::floor<date::days>( chronologicalDateTime);
date::year_month_day calendarDate = chronologicalDate;
auto timeOfDay = chronologicalDateTime - chronologicalDate;
calendarDate += date::years{30};
chronologicalDateTime = date::sys_days{calendarDate} + timeOfDay;
std::string stringDate = date::format("%Y-%m-%d %H:%M:%S", chronologicalDateTime);
std::cout << "{} + 30 years + 10 days = " << stringDate << "\n";
return 0;
}
You first convert the chronological date/time to a calendrical date, and add the years to the calendrical date. Then convert that back into a chronological date/time.
{} + 30 years + 10 days = 2000-01-11 00:00:00
Here's another SO answer that goes over the same principle, except using months: https://stackoverflow.com/a/43018120/576911
Linux stores dates as UTC. My guess is that your machine is located at Central Time zone (CT) which is +6 hours from UTC.
To find the day (number) for a given date, I wrote below code using <ctime>:
tm time {ANY_SECOND, ANY_MINUTE, ANY_HOUR, 21, 7, 2015 - 1900};
mktime(&time); // today's date
PRINT(time.tm_wday); // prints 5 instead of 2 for Tuesday
According to the documentation, tm_wday can hold value among [0-6], where 0 is Sunday. Hence for Tuesday (today), it should print 2; but it prints 5.
Actually tm_wday gives consistent results, but with a difference of 3 days.
What is wrong here?
You got the month wrong, tm_mon is the offset since January, so July is 6. From the manpage:
tm_mon The number of months since January, in the range 0 to 11.
This outputs 2:
#include <stdio.h>
#include <string.h>
#include <time.h>
int main(void) {
struct tm time;
memset(&time, 0, sizeof(time));
time.tm_mday = 21;
time.tm_mon = 6;
time.tm_year = 2015-1900;
mktime(&time);
printf("%d\n", time.tm_wday);
return 0;
}
Note that you should initialize the other fields to 0 with memset(3) or similar.
The reason you are getting invalid output is that you are using the wrong month. tm_mon starts at 0 and not 1. you can see tghis by using this code:
tm time {50, 50, 12, 21, 7, 2015 - 1900};
time_t epoch = mktime(&time);
printf("%s", asctime(gmtime(&epoch)));
Output:
Fri Aug 21 12:50:50 2015
Live Example
This program takes any user input year since 1753 and month and creates a calendar for it. However I'm having issues with the offset which is the day the month starts out on. As far as I can tell it is just the offset that is off and everything else seems to work great.
Here is my code.
#include <iostream>
#include <iomanip>
using namespace std;
int getMonth(int month);
int getYear(int year);
int computeOffset(int year, int month);
int numDaysYear(int year);
int numDaysMonth(int year, int month);
bool isLeapYear(int year);
void display(int year, int month, int offset);
/**********************************************************************
* This function will call all the functions necessary to make a calendar
* for any given month and year.
***********************************************************************/
int main()
{
int numDays;
int offset;
int month;
int year;
month = getMonth(month);
year = getYear(year);
offset = computeOffset(year, month);
display(year, month, offset);
return 0;
}
/***********************************************************************
* Gets the month number.
**********************************************************************/
int getMonth(int month)
{
cout << "Enter a month number: ";
cin >> month;
while ( month < 1 || month > 12)
{
cout << "Month must be between 1 and 12.\n"
<< "Enter a month number: ";
cin >> month;
}
return month;
}
/***********************************************************************
* Gets the year.
**********************************************************************/
int getYear(int year)
{
cout << "Enter year: ";
cin >> year;
while ( year < 1753)
{
cout << "Year must be 1753 or later.\n"
<< "Enter year: ";
cin >> year;
}
return year;
}
/***********************************************************************
* Computes the offset.
**********************************************************************/
int computeOffset(int year, int month)
{
int offset = 0;
int count = year - 1753;
for ( int iYear = 0; iYear < count; iYear++)
{
offset = ( offset + 365 + isLeapYear(year)) % 7;
}
for ( int iMonth = 1; iMonth < month; iMonth++)
{
offset = ( offset + numDaysMonth(year, iMonth)) % 7;
}
return offset;
}
/***********************************************************************
* Computes the number of days in the given year.
**********************************************************************/
int numDaysYear(int year)
{
int daysYear = 365 + isLeapYear(year);
return daysYear;
}
/***********************************************************************
* Calculates the number of days in the given month.
**********************************************************************/
int numDaysMonth(int year, int month)
{
int daysMonth;
if ( month == 1)
daysMonth = 31;
else if ( month == 2)
{
if (isLeapYear(year) == true)
daysMonth = 29;
else
daysMonth = 28;
}
else if ( month == 3)
daysMonth = 31;
else if ( month == 4)
daysMonth = 30;
else if ( month == 5)
daysMonth = 31;
else if ( month == 6)
daysMonth = 30;
else if ( month == 7)
daysMonth = 31;
else if ( month == 8)
daysMonth = 31;
else if ( month == 9)
daysMonth = 30;
else if ( month == 10)
daysMonth = 31;
else if ( month == 11)
daysMonth = 30;
else if ( month == 12)
daysMonth = 31;
return daysMonth;
}
/***********************************************************************
* Determines if given year is a leap year.
**********************************************************************/
bool isLeapYear(int year)
{
if ( year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
return true;
else
return false;
}
/**********************************************************************
* Displays the calender table.
**********************************************************************/
void display(int year, int month, int offset)
{
int dayOfWeek;
int day;
cout << endl;
if ( month == 1)
cout << "January";
else if ( month == 2)
cout << "February";
else if ( month == 3)
cout << "March";
else if ( month == 4)
cout << "April";
else if ( month == 5)
cout << "May";
else if ( month == 6)
cout << "June";
else if ( month == 7)
cout << "July";
else if ( month == 8)
cout << "August";
else if ( month == 9)
cout << "September";
else if ( month == 10)
cout << "October";
else if ( month == 11)
cout << "November";
else if ( month == 12)
cout << "December";
cout << ", " << year << "\n";
// Display month header
cout << " Su Mo Tu We Th Fr Sa\n";
// Gets the correct offset width and end the line on the right
//day of the week
if (offset == 0)
{
day = 2;
cout << setw(6);
}
else if (offset == 1)
{
day = 3;
cout << setw(10);
}
else if (offset == 2)
{
day = 4;
cout << setw(14);
}
else if (offset == 3)
{
day = 5;
cout << setw(18);
}
else if (offset == 4)
{
day = 6;
cout << setw(22);
}
else if (offset == 5)
{
day = 7;
cout << setw(26);
}
else if (offset == 6)
{
day = 1;
cout << setw(2);
}
else
cout << "Error offset must be >= 0 and <=6\n";
// The loop for displaying the days and ending the line in the right place
for ( dayOfWeek = 1; dayOfWeek <= numDaysMonth(year, month); dayOfWeek++ )
{
cout << " " << setw(2) << dayOfWeek;
++day;
if (day == 8)
{
cout << "\n";
day = 1;
}
}
if ( day >= 2 && day <= 7)
cout << "\n";
return;
}`
New answer for old question. Rationale for new answer: Better tools and technology in this area.
This answer is heavily using this free, open-source header-only library. I'm going to present this answer starting at the highest level, and drilling down to the lower level details. But at all levels at no time do we have to get into detailed calendrical computations. "date.h" handles that for us.
Here's main:
#include "date.h"
#include <iomanip>
#include <ostream>
#include <string>
#include <iostream>
int
main()
{
print_calendar_year(std::cout);
}
This just output for me:
January 2016
S M T W T F S
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
February 2016
S M T W T F S
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29
March 2016
S M T W T F S
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
April 2016
S M T W T F S
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
May 2016
S M T W T F S
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
June 2016
S M T W T F S
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
July 2016
S M T W T F S
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
August 2016
S M T W T F S
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
September 2016
S M T W T F S
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30
October 2016
S M T W T F S
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
November 2016
S M T W T F S
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
December 2016
S M T W T F S
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
One could print out next year's calendar with:
using namespace date::literals;
print_calendar_year(std::cout, 2017_y);
I'll start out with the statement:
This is a type-safe system.
The literal 2017_y is an object of type date::year, not a simple integer. Having types that mean year and month means it is far less likely to mix up these concepts. Mistakes tend to be caught at compile time.
print_calendar_year is pretty simple:
void
print_calendar_year(std::ostream& os, date::year y = current_year())
{
using namespace date;
for (auto ym = y/jan; ym < y/jan + years{1}; ym += months{1})
{
print_calendar_month(os, ym);
os << '\n';
}
}
The expression year/month creates a type called date::year_month which is nothing more than a simple struct {year, month}. So this function simply sets up a loop to iterate from Jan of the year y, to the next Jan, excluding the next Jan. It is all quite readable. And note that "bare ints" are not allowed. Everything has a non-integral type.
print_calendar_month is where the rubber meets the road:
void
print_calendar_month(std::ostream& os, date::year_month ym = current_year_month())
{
using namespace std;
using namespace date;
os << format("%B %Y\n", sys_days{ym/1});
os << " S M T W T F S\n";
auto wd = unsigned{weekday{ym/1}};
os << string(wd*3, ' ');
auto const e = (ym/last).day();
for (day d = 1_d; d <= e; wd = 0)
{
for (; wd < 7 && d <= e; ++wd, ++d)
os << setw(3) << unsigned{d};
os << '\n';
}
}
os << format("%B %Y\n", sys_days{ym/1}); is what prints out the title for each month (e.g. January 2016). These are strftime-like formatting flags that will respect the localization settings of the current global std::locale (as much as the OS supports).
The subexpression ym/1 creates a type date::year_month_day which stands for the first day of the indicated month and year. date::year_month_day is a simply class holding {year, month, day}.
sys_days is a chrono::time_point based on system_clock with a precision of days. date::format can take any precision system_clock time_point and format it using strftime-like formatting flags. A year_month_day can be converted to a sys_days as shown. This is a conversion from a {year, month, day} field type to a serial {count of days} type.
os << " S M T W T F S\n"; obviously prints out the day-of-the-week header for the calendar.
auto wd = unsigned{weekday{ym/1}}; finds the day of the week of the first day of the month and converts that weekday into an unsigned using the encoding [Sun == 0, Sat == 6]. [Note: gcc requires the syntax unsigned(weekday{ym/1}). It doesn't like the {} for unsigned. — end note]
os << string(wd*3, ' '); just prints out 3 spaces for each day before the first day of the month to pad out the first row.
auto const e = (ym/last).day(); is a constant of type date::day that is equal to the last day of the month for this year and month combination.
for (day d = 1_d; d <= e; wd = 0)
Starting with day 1 loop until the last day of the month (inclusive) and set the unsigned wd back to the encoding for Sunday on each iteration.
for (; wd < 7 && d <= e; ++wd, ++d): Until you reach the end of the week or the end of the month, increment both day of the week and day of the month.
os << setw(3) << unsigned{d};: Convert the day of the month to an unsigned and print it out right-aligned in a width a of 3 spaces.
os << '\n'; return after printing the week.
And that's the bulk of the program! Almost all of the tricky calendrical logic is encapsulated within these two lines of code:
auto wd = unsigned{weekday{ym/1}};
auto const e = (ym/last).day();
For completeness here are the functions to get the current date::year and the current date::year_month:
date::year_month
current_year_month()
{
using namespace std::chrono;
using namespace date;
year_month_day ymd = floor<days>(system_clock::now());
return ymd.year()/ymd.month();
}
date::year
current_year()
{
using namespace std::chrono;
using namespace date;
year_month_day ymd = floor<days>(system_clock::now());
return ymd.year();
}
Both of these simply truncate a system_clock::time_point returned from system_clock::now() to a precision of days using floor, and then convert that days-precision time_point to a date::year_month_day type. This type then has getters for year and month to pick out the desired partial calendar types.
Update
Well, TemplateRex asked a question below that I didn't want to answer at first, and then I couldn't help myself because the answer highlights how powerful "date.h" is to work with. ;-)
The question is:
Can you print out the calendars in a 3x4 format like this?
January 2016 February 2016 March 2016
Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
1 2 1 2 3 4 5 6 1 2 3 4 5
3 4 5 6 7 8 9 7 8 9 10 11 12 13 6 7 8 9 10 11 12
10 11 12 13 14 15 16 14 15 16 17 18 19 20 13 14 15 16 17 18 19
17 18 19 20 21 22 23 21 22 23 24 25 26 27 20 21 22 23 24 25 26
24 25 26 27 28 29 30 28 29 27 28 29 30 31
31
April 2016 May 2016 June 2016
Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
1 2 1 2 3 4 5 6 7 1 2 3 4
3 4 5 6 7 8 9 8 9 10 11 12 13 14 5 6 7 8 9 10 11
10 11 12 13 14 15 16 15 16 17 18 19 20 21 12 13 14 15 16 17 18
17 18 19 20 21 22 23 22 23 24 25 26 27 28 19 20 21 22 23 24 25
24 25 26 27 28 29 30 29 30 31 26 27 28 29 30
July 2016 August 2016 September 2016
Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
1 2 1 2 3 4 5 6 1 2 3
3 4 5 6 7 8 9 7 8 9 10 11 12 13 4 5 6 7 8 9 10
10 11 12 13 14 15 16 14 15 16 17 18 19 20 11 12 13 14 15 16 17
17 18 19 20 21 22 23 21 22 23 24 25 26 27 18 19 20 21 22 23 24
24 25 26 27 28 29 30 28 29 30 31 25 26 27 28 29 30
31
October 2016 November 2016 December 2016
Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
1 1 2 3 4 5 1 2 3
2 3 4 5 6 7 8 6 7 8 9 10 11 12 4 5 6 7 8 9 10
9 10 11 12 13 14 15 13 14 15 16 17 18 19 11 12 13 14 15 16 17
16 17 18 19 20 21 22 20 21 22 23 24 25 26 18 19 20 21 22 23 24
23 24 25 26 27 28 29 27 28 29 30 25 26 27 28 29 30 31
30 31
Evidently so, because I wasn't about to type in all that above manually! ;-)
It requires a rewrite of print_calendar_year and the introduction of a a couple of new functions, most notably:
void
print_line_of_calendar_month(std::ostream& os, date::year_month ym, unsigned line,
date::weekday firstdow);
This function prints just one line of the calendar associated with the year_month ym and is the heart of this 3x4 format.
I also thought it would be fun to make this program localizable so that the desired first-day-of-week could be printed out, as well as localized names for the month and day-of-week (as much as the std::locale on your platform allows).
The lines are numbered [0, infinity]. Line 0 prints out the month year such as January 2016. Line 1 prints out the day-of-week headers: Su Mo Tu We Th Fr Sa. And then lines [2, infinity] print out the days of the month.
Why infinity?
Because different months take different number of lines, so I wanted to be able to tell a year/month to print a next line even if it didn't need to (because another month in the quarter needed it). So when you ask for a calendar to print out a line that it doesn't need, it just outputs the proper number of ' ' for padding purposes.
Enough intro, here's the function:
void
print_line_of_calendar_month(std::ostream& os, date::year_month ym, unsigned line,
date::weekday firstdow)
{
using namespace std;
using namespace date;
switch (line)
{
case 0:
os << left << setw(21) << format(os.getloc(), " %B %Y", sys_days{ym/1}) << right;
break;
case 1:
{
auto sd = sys_days{ym/firstdow[1]};
for (auto const esd = sd + weeks{1}; sd < esd; sd += days{1})
{
auto d = format(os.getloc(), "%a", sd);
d.resize(2);
os << ' ' << d;
}
break;
}
case 2:
{
auto wd = weekday{ym/1}; // This line and the next are the "offset"
os << string((wd-firstdow).count()*3, ' '); // referred to in the question.
auto d = 1_d;
do
{
os << setw(3) << unsigned(d);
++d;
} while (++wd != firstdow);
break;
}
default:
{
unsigned index = line - 2;
auto sd = sys_days{ym/1};
if (weekday{sd} == firstdow)
++index;
auto ymdw = ym/firstdow[index];
if (ymdw.ok())
{
auto d = year_month_day{ymdw}.day();
auto const e = (ym/last).day();
auto wd = firstdow;
do
{
os << setw(3) << unsigned(d);
} while (++wd != firstdow && ++d <= e);
os << string((firstdow-wd).count()*3, ' ');
}
else
os << string(21, ' ');
break;
}
}
}
So switch on line number [0, infinity], and for each line number, do the right thing:
0. Print out the Month Year heading.
This passes to format the locale of the os to get the localized month name.
1. Print out the day-of-the-week heading.
This passes to format the locale of the os to get the localized weekday names, and prints the first 2 characters. This is (unfortunately) only approximately correct when these are multi-byte characters, but this post is mostly about calendars, not Unicode.
2. Print out the first week, which might be prefixed with spaces. The number of spaces to prefix with is 3*(number of days the first of the month is past the first day of the week). Then append days until you reach the last day of the week. Note that weekday subtraction is always modulo 7 so you don't have to worry about the underlying encoding of the days of the weeks. The weekdays form a circular range. This does require something along the lines of this do-while as opposed to a traditional for when looping over all the days in a week.
3 - infinity. Ah, here's the fun part.
There's a type in "date.h" called year_month_weekday which is a type storing {year, month, weekday, unsigned}. This is how you might specify Mother's day: The second Sunday of May: sun[2]/may/2016. This expression creates a struct {2016, 5, 0, 2} (roughly speaking). And so if the switch lands here, then we are looking for the [first, last] Sunday of this month and year, where the exact index is dependent upon line, and whether or not we printed a Sunday out on line 2.
Also key, this library allows any index to be used:
auto ymdw = ym/firstdow[index];
index could be 1, or it could be 57. The above line compiles and is not a run-time error.
But months can't have 57 Sundays (or Mondays or whatever)!
No problem. You can ask if ym/firstdow[index] is a valid date. This is what the next line does:
if (ymdw.ok())
If the date is valid, then you've got work to do. Else you just print out a blank row.
If we've got work to do, then convert the year_month_weekday to a year_month_day so that you can get the day of the month from it (d). And find the last day of the month:
auto const e = (ym/last).day();
Then iterate from the first day of the week to whichever comes first: the end of the month or the last day of the week. Print out the day of the month for each spot. And then if you didn't end on the last day of the week, print spaces to pad out to the last day of the week.
And we're done with print_line_of_calendar_month! Note that newlines were never output on this level. Not even inter-month padding is output on this level. Each calendar is exactly 21 char wide, and can be printed out to an arbitrary number of rows.
Now we need another minor utility: What is the number of rows a calendar month needs before it starts padding with blank rows?
unsigned
number_of_lines_calendar(date::year_month ym, date::weekday firstdow)
{
using namespace date;
return ceil<weeks>((weekday{ym/1} - firstdow) +
((ym/last).day() - day{0})).count() + 2;
}
This is the number of days in the month, plus the number of days from the first day of the week to the first of the month, plus 2 more rows for the day-of-the-week heading and the year-month heading. Fractional weeks at the end are rounded up!
Notes:
The number of days from the first day of the week to the first of the month is simply: (weekday{ym/1} - firstdow).
The number of days in the month is encoded here as ((ym/last).day() - day{0}). Note that day{0} is not a valid day, but can still be useful in the subtraction: day - day gives a result of the chrono::duration days. Another way to say this would have been ((ym/last).day() - day{1} + days{1}).
Note that ceil<weeks> is used here to convert the number of days to number of weeks, rounding up to the next weeks if the conversion is not exact. 1 week == 1 row. This roundup accounts for the last week that ends prior to the last day of the week.
Now print_calendar_year can be rewritten in terms of these primitives:
void
print_calendar_year(std::ostream& os, unsigned const cols = 3,
date::year const y = current_year(),
date::weekday const firstdow = date::sun)
{
using namespace date;
if (cols == 0 || 12 % cols != 0)
throw std::runtime_error("The number of columns " + std::to_string(cols)
+ " must be one of [1, 2, 3, 4, 6, 12]");
// Compute number of lines needed for each calendar month
unsigned ml[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
for (auto& m : ml)
m = number_of_lines_calendar(y/month{m}, firstdow);
for (auto r = 0u; r < 12/cols; ++r) // for each row
{
const auto lines = *std::max_element(std::begin(ml) + (r*cols),
std::begin(ml) + ((r+1)*cols));
for (auto l = 0u; l < lines; ++l) // for each line
{
for (auto c = 0u; c < cols; ++c) // for each column
{
if (c != 0)
os << " ";
print_line_of_calendar_month(os, y/month{r*cols + c+1}, l, firstdow);
}
os << '\n';
}
os << '\n';
}
}
First compute for each month how many lines it needs:
// Compute number of lines needed for each calendar month
unsigned ml[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
for (auto& m : ml)
m = number_of_lines_calendar(y/month{m}, firstdow);
Then for each "calendar row", find the number of lines needed for that row by searching the proper subset of ml.
And then for each line, and for each "calendar column", print out the line of the corresponding calendar month for that column.
After each line print a '\n'.
After each calendar row, print a '\n'.
Note that still at no time did we need to sink down into calendrical arithmetic. At this level we needed to know "7 days per week", "3 spaces per day" and "12/cols months per calendar row".
On macOS this driver:
using namespace date::literals;
std::cout.imbue(std::locale("de_DE"));
print_calendar_year(std::cout, 3, 2016_y, mon);
Outputs:
Januar 2016 Februar 2016 März 2016
Mo Di Mi Do Fr Sa So Mo Di Mi Do Fr Sa So Mo Di Mi Do Fr Sa So
1 2 3 1 2 3 4 5 6 7 1 2 3 4 5 6
4 5 6 7 8 9 10 8 9 10 11 12 13 14 7 8 9 10 11 12 13
11 12 13 14 15 16 17 15 16 17 18 19 20 21 14 15 16 17 18 19 20
18 19 20 21 22 23 24 22 23 24 25 26 27 28 21 22 23 24 25 26 27
25 26 27 28 29 30 31 29 28 29 30 31
April 2016 Mai 2016 Juni 2016
Mo Di Mi Do Fr Sa So Mo Di Mi Do Fr Sa So Mo Di Mi Do Fr Sa So
1 2 3 1 1 2 3 4 5
4 5 6 7 8 9 10 2 3 4 5 6 7 8 6 7 8 9 10 11 12
11 12 13 14 15 16 17 9 10 11 12 13 14 15 13 14 15 16 17 18 19
18 19 20 21 22 23 24 16 17 18 19 20 21 22 20 21 22 23 24 25 26
25 26 27 28 29 30 23 24 25 26 27 28 29 27 28 29 30
30 31
Juli 2016 August 2016 September 2016
Mo Di Mi Do Fr Sa So Mo Di Mi Do Fr Sa So Mo Di Mi Do Fr Sa So
1 2 3 1 2 3 4 5 6 7 1 2 3 4
4 5 6 7 8 9 10 8 9 10 11 12 13 14 5 6 7 8 9 10 11
11 12 13 14 15 16 17 15 16 17 18 19 20 21 12 13 14 15 16 17 18
18 19 20 21 22 23 24 22 23 24 25 26 27 28 19 20 21 22 23 24 25
25 26 27 28 29 30 31 29 30 31 26 27 28 29 30
Oktober 2016 November 2016 Dezember 2016
Mo Di Mi Do Fr Sa So Mo Di Mi Do Fr Sa So Mo Di Mi Do Fr Sa So
1 2 1 2 3 4 5 6 1 2 3 4
3 4 5 6 7 8 9 7 8 9 10 11 12 13 5 6 7 8 9 10 11
10 11 12 13 14 15 16 14 15 16 17 18 19 20 12 13 14 15 16 17 18
17 18 19 20 21 22 23 21 22 23 24 25 26 27 19 20 21 22 23 24 25
24 25 26 27 28 29 30 28 29 30 26 27 28 29 30 31
31
Your milage may vary on how well your std::lib/OS supports localization. But now you can print your calendar out in columns of months varying among any divisor of 12 ([1, 2, 3, 4, 6, 12]), using any year, using any day of the week as the first day of the week, and using any locale (modulo OS support for locales).
Here's the output for print_calendar_year(std::cout, 4, 2017_y);
January 2017 February 2017 March 2017 April 2017
Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
1 2 3 4 5 6 7 1 2 3 4 1 2 3 4 1
8 9 10 11 12 13 14 5 6 7 8 9 10 11 5 6 7 8 9 10 11 2 3 4 5 6 7 8
15 16 17 18 19 20 21 12 13 14 15 16 17 18 12 13 14 15 16 17 18 9 10 11 12 13 14 15
22 23 24 25 26 27 28 19 20 21 22 23 24 25 19 20 21 22 23 24 25 16 17 18 19 20 21 22
29 30 31 26 27 28 26 27 28 29 30 31 23 24 25 26 27 28 29
30
May 2017 June 2017 July 2017 August 2017
Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
1 2 3 4 5 6 1 2 3 1 1 2 3 4 5
7 8 9 10 11 12 13 4 5 6 7 8 9 10 2 3 4 5 6 7 8 6 7 8 9 10 11 12
14 15 16 17 18 19 20 11 12 13 14 15 16 17 9 10 11 12 13 14 15 13 14 15 16 17 18 19
21 22 23 24 25 26 27 18 19 20 21 22 23 24 16 17 18 19 20 21 22 20 21 22 23 24 25 26
28 29 30 31 25 26 27 28 29 30 23 24 25 26 27 28 29 27 28 29 30 31
30 31
September 2017 October 2017 November 2017 December 2017
Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
1 2 1 2 3 4 5 6 7 1 2 3 4 1 2
3 4 5 6 7 8 9 8 9 10 11 12 13 14 5 6 7 8 9 10 11 3 4 5 6 7 8 9
10 11 12 13 14 15 16 15 16 17 18 19 20 21 12 13 14 15 16 17 18 10 11 12 13 14 15 16
17 18 19 20 21 22 23 22 23 24 25 26 27 28 19 20 21 22 23 24 25 17 18 19 20 21 22 23
24 25 26 27 28 29 30 29 30 31 26 27 28 29 30 24 25 26 27 28 29 30
31
This code does not address your issue (I could not tell what the issue was) but it may be informative to contrast it with your version.
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int getMonth();
int getYear();
int computeOffset(int year, int month);
int numDaysYear(int year);
int numDaysMonth(int year, int month);
bool isLeapYear(int year);
void display(int year, int month, int offset);
/**********************************************************************
* This function will call all the functions necessary to make a calendar
* for any given month and year.
***********************************************************************/
int main()
{
int offset;
int month;
int year;
month = getMonth();
year = getYear();
offset = computeOffset(year, month);
display(year, month, offset);
return 0;
}
/***********************************************************************
* Gets the month number.
**********************************************************************/
int getMonth()
{
int month = 0;
cout << "Enter a month number: ";
cin >> month;
while (month < 1 || month > 12)
{
cout << "Month must be between 1 and 12.\n"
<< "Enter a month number: ";
cin >> month;
}
return month;
}
/***********************************************************************
* Gets the year.
**********************************************************************/
int getYear()
{
int year = 0;
cout << "Enter year: ";
cin >> year;
while ( year < 1753)
{
cout << "Year must be 1753 or later.\n"
<< "Enter year: ";
cin >> year;
}
return year;
}
/***********************************************************************
* Computes the offset.
**********************************************************************/
int computeOffset(int year, int month)
{
int offset = 0;
int count = year - 1753;
for (int iYear = 0; iYear < count; iYear++)
{
offset = (offset + 365 + isLeapYear(year)) % 7;
}
for (int iMonth = 1; iMonth < month; iMonth++)
{
offset = (offset + numDaysMonth(year, iMonth)) % 7;
}
return offset;
}
/***********************************************************************
* Computes the number of days in the given year.
**********************************************************************/
int numDaysYear(int year)
{
return 365 + isLeapYear(year);
}
/***********************************************************************
* Calculates the number of days in the given month.
**********************************************************************/
int numDaysMonth(int year, int month)
{
std::vector<int> days { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int daysMonth = days[month-1];
if (month == 2 && isLeapYear(year))
{
daysMonth = 29;
}
return daysMonth;
}
/***********************************************************************
* Determines if given year is a leap year.
**********************************************************************/
bool isLeapYear(int year)
{
return (year % 400 == 0) || (year % 100 != 0 && year % 4 == 0);
}
/**********************************************************************
* Displays the calender table.
**********************************************************************/
void display(int year, int month, int offset)
{
int day;
cout << endl;
std::vector<std::string> mth { "January", "February", "March",
"April", "May", "June", "July", "August", "September",
"October", "November", "December" };
cout << mth[month-1];
cout << ", " << year << "\n";
// Display month header
cout << " Su Mo Tu We Th Fr Sa\n";
// Gets the correct offset width and end the line on the right
// day of the week
if (0 <= offset && offset <= 6)
{
day = ((offset + 1) % 7) + 1;
cout << setw((day - 2) * 4 + 6);
}
else
cout << "Error offset must be >= 0 and <=6\n";
// The loop for displaying the days and ending the line in the right place
for (int dayOfWeek = 1; dayOfWeek <= numDaysMonth(year, month); dayOfWeek++ )
{
cout << " " << setw(2) << dayOfWeek;
++day;
if (day == 8)
{
cout << "\n";
day = 1;
}
}
if (day >= 2 && day <= 7)
cout << "\n";
return;
}
I know this was posted over a year ago, but I do have a solution for future visitors. In the computeOffset function in the original post, the function isn't actually counting how many days have passed in all. You need to count how many years have passed, and how many days were in those years, as well as the days in the months after those years, if that makes any sense. Here is the code for my working computeOffset function:
int computeOffset(int month, int year)
{
int numDaysYear = 0;
int yearCounter = 0;
int monthCounter = 0;
int months = 1 // NOT the same at the "month" variable coming in
// This loop counts how many days have passed in the full years up to the given year
for (int iYear = 1753; iYear < year; iYear++)
{
numDaysYear = numDaysYear + 365;
if (isLeapYear(iYear))
yearCounter++;
}
// This loop counts the days in the remaining months after all the years
for (int iMonth = 1; iMonth < month; iMonth++)
{
monthCounter = monthCounter + numDaysMonth(months, year); //MONTHS not month
months++;
}
int offset = (numDaysYear + yearCounter + monthCoutner) % 7;
return offset;
Hopefully this makes sense, sorry if some variables have weird names or if the style isn't what you're used to. Hope this helped!
I'm currently working on a problem (Friday the thirteenth) on the USACO site and I've coded a program which, given a number N,computes how often the 13th lands on each day of the week from Jan 1st,1900 to Dec 31st,1900+N-1.The algorithm is inspired from a mental calculation trick : http://www.jimloy.com/math/day-week.htm
Things to keep in mind:
January 1, 1900 was on a Monday.
Thirty days has September, April, June, and November, all the rest have 31 except for February which has 28 except in leap years when it has 29.
Every year evenly divisible by 4 is a leap year (1992 = 4*498 so 1992 will be a leap year, but the year 1990 is not a leap year)
The rule above does not hold for century years. Century years divisible by 400 are leap years, all other are not. Thus, the century years 1700, 1800, 1900 and 2100 are not leap years, but 2000 is a leap year.*
The program works well except when N=256 (1900 to 2156)
My program outputs :
440 438 439 439 437 439 440
while on USACO, there is:
440 439 438 438 439 439 439
The program takes a file (friday.in) containing N as input and outputs seven space separated integers representing the number of occurrences of each day (starting by Saturday) on one line in another file(friday.out).
I've put several cout for debugging purposes.
Here's the code:
/*
ID: freebie1
PROG:friday
LANG: C++
*/
#include <fstream>
#include <vector>
#include <iostream>
using namespace std;
class Date
{
public:
Date(int month=0,int year=0):m_month(month),m_year(year)
{
}
int monthDiff()
{
if(m_month<=3)
{
if(m_month==1)
{
return 0;
}
else if(m_month==2)
{
return 31;
}
}
if(m_month>=3)
{
if((m_year%4==0 && m_year%100!=0)||(m_year%100==0 && m_year%400==0))
{
if(m_month<9)
{
if(m_month%2==0)
return 60+(31*int(m_month/2.6)+30*(int(m_month/2.6)-1));
else
return 60+(61*int(m_month/3.5));
}
else if(m_month>=9)
{
if(m_month%2==0)
return 91+61*(m_month/3);
else
return 91+(31*int(m_month/2.75)+30*int(m_month/3.6));
}
}
else
{
if(m_month<9)
{
if(m_month%2==0)
return 59+(31*int(m_month/2.6)+30*(int(m_month/2.6)-1));
else
return 59+(61*int(m_month/3.5));
}
else if(m_month>=9)
{
if(m_month%2==0)
return 90+61*(m_month/3);
else
return 90+(31*int(m_month/2.75)+30*int(m_month/3.6));
}
}
}
}
void show()
{
cout<<m_month<<"/"<<m_year<<": ";
}
int tellDay()
{
int daysInYears=int((m_year-1900))*365;
int daysInMonths=this->monthDiff();
int leapDays;
if(m_year%4==0 && m_year!=1900)
leapDays=int((m_year-1900)/4)-1;
else if(m_year>2100)
leapDays=int((m_year-1900)/4)-1;
else if(m_year>2200)
leapDays=int((m_year-1900))/4-2;
else
leapDays=int((m_year-1900))/4;
int days=13+leapDays;
int res=daysInYears+daysInMonths+days;
cout<<"MonthDiff: "<<this->monthDiff()<<" In years: "<<daysInYears<<" days: "<<days<<" ";
return res%7;
}
private:
int m_month;
int m_year;
};
int main()
{
ifstream fin("friday.in");
ofstream fout("friday.out");
if(fin)
{
int n(0),day(0),sMonth(1),sYear(1900);
fin>>n;
vector<int> weekDays(7,0);
for(int i(0);i<n;i++)
{
for(int j(0);j<12;j++)
{
Date date(sMonth+j,sYear+i);
day=date.tellDay();
date.show();
cout<<day<<endl;
switch(day)
{
case 0:
weekDays[1]+=1;
break;
case 1:
weekDays[2]+=1;
break;
case 2:
weekDays[3]+=1;
break;
case 3:
weekDays[4]+=1;
break;
case 4:
weekDays[5]+=1;
break;
case 5:
weekDays[6]+=1;
break;
case 6:
weekDays[0]+=1;
break;
}
}
}
for(int i(0);i<6;i++)
{
fout<<weekDays[i]<<" ";
}
fout<<weekDays[6]<<endl;
}
return 0;
}
You get wrong results from 205 years on, that is from the year 2104.
if(m_year%4==0 && m_year!=1900)
leapDays=int((m_year-1900)/4)-1;
For years divisible by 4, you don't subtract the non-leap years 2100, 2200, 2300, 2500, ... from the count.
Fix:
if(m_year%4==0 && m_year!=1900)
leapDays=int((m_year-1900)/4)-1 + (m_year-1604)/400 - (m_year-1904)/100;
subtract them. That leaves the wrong leap year count for years not divisible by 4 after 2300, you can correct that by adding a similar correction (and that doesn't need multiple branches then):
else
leapDays=int((m_year-1900)/4) + (m_year-1600)/400 - (m_year-1900)/100;
The formula shall determine the number of leap years that have passed between 1900 and m_year. If m_year is not a multiple of 4, the simple "multiples of 4 are leap years" gives that count as (m_year - 1900)/4. But that doesn't take into account that multiples of 100 are not leap years in general, so we subtract the number of such years that have passed in between, - (m_year - 1900)/100. However, now the multiples of 400 that are leap years have been subtracted too, so add their count back, + (m_year - 1600)/400 (base year here is 1600, the largest multiple of 400 not after 1900).
For multiples of 4, we have to correct for the fact that the current year is not a leap year before the current year, so I let the correction take place only after and shift the base years.
Better fix, making the leap year count uniform (no branches):
int leapDays = (m_year - 1901)/4 + (m_year-1601)/400 - (m_year-1901)/100;