mirror of
https://github.com/HowardHinnant/date.git
synced 2025-08-05 21:54:27 +02:00
Updated Examples and Recipes (markdown)
@@ -26,6 +26,7 @@ This page contains examples and recipes contributed by community members. Feel f
|
|||||||
- [How to convert to/from C++ Builder's TDate and TDateTime](#TDate)
|
- [How to convert to/from C++ Builder's TDate and TDateTime](#TDate)
|
||||||
- [How to convert to/from QDate](#QDate)
|
- [How to convert to/from QDate](#QDate)
|
||||||
- [How to convert to/from Windows' FILETIME](#FILETIME)
|
- [How to convert to/from Windows' FILETIME](#FILETIME)
|
||||||
|
- [Print out a compact calendar for the year](#calendar)
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
@@ -1279,6 +1280,266 @@ Assuming `system_clock` is based on the Unix epoch:
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<a name="calendar"></a>
|
||||||
|
### Print out a compact calendar for the year
|
||||||
|
(by [Howard Hinnant](https://github.com/HowardHinnant))
|
||||||
|
|
||||||
|
Printing out the calendar for an entire year is an interesting exercise. You can either just take code and use it (a neat and useful utility), or you can study its implementation and learn much more about `"date.h"`.
|
||||||
|
|
||||||
|
First the code, and then a detailed explanation:
|
||||||
|
|
||||||
|
#include "date.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <locale>
|
||||||
|
#include <ostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
date::year
|
||||||
|
current_year()
|
||||||
|
{
|
||||||
|
using namespace std::chrono;
|
||||||
|
using namespace date;
|
||||||
|
year_month_day ymd = floor<days>(system_clock::now());
|
||||||
|
return ymd.year();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The number of weeks in a calendar month layout plus 2 more for the calendar titles
|
||||||
|
unsigned
|
||||||
|
number_of_lines_calendar(date::year_month const ym, date::weekday const firstdow)
|
||||||
|
{
|
||||||
|
using namespace date;
|
||||||
|
return static_cast<unsigned>(
|
||||||
|
ceil<weeks>((weekday{ym/1} - firstdow) + ((ym/last).day() - day{0})).count()) + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print one line of a calendar month
|
||||||
|
void
|
||||||
|
print_line_of_calendar_month(std::ostream& os, date::year_month const ym,
|
||||||
|
unsigned const line, date::weekday const firstdow)
|
||||||
|
{
|
||||||
|
using namespace std;
|
||||||
|
using namespace date;
|
||||||
|
switch (line)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
// Output month and year title
|
||||||
|
os << left << setw(21) << format(os.getloc(), " %B %Y", sys_days{ym/1}) << right;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
// Output weekday names title
|
||||||
|
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:
|
||||||
|
{
|
||||||
|
// Output first week prefixed with spaces if necessary
|
||||||
|
auto wd = weekday{ym/1};
|
||||||
|
os << string(static_cast<unsigned>((wd-firstdow).count())*3, ' ');
|
||||||
|
auto d = 1_d;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
os << setw(3) << unsigned(d);
|
||||||
|
++d;
|
||||||
|
} while (++wd != firstdow);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
// Output a non-first week:
|
||||||
|
// First find first day of week
|
||||||
|
unsigned index = line - 2;
|
||||||
|
auto sd = sys_days{ym/1};
|
||||||
|
if (weekday{sd} == firstdow)
|
||||||
|
++index;
|
||||||
|
auto ymdw = ym/firstdow[index];
|
||||||
|
if (ymdw.ok()) // If this is a valid week, print it out
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
// Append row with spaces if the week did not complete
|
||||||
|
os << string(static_cast<unsigned>((firstdow-wd).count())*3, ' ');
|
||||||
|
}
|
||||||
|
else // Otherwise not a valid week, output a blank row
|
||||||
|
os << string(21, ' ');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main()
|
||||||
|
{
|
||||||
|
print_calendar_year(std::cout);
|
||||||
|
}
|
||||||
|
|
||||||
|
As written this program outputs:
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
But the above program is very flexible and can localize this output into a wide variety of formats to accommodate your preferences.
|
||||||
|
|
||||||
|
date::year
|
||||||
|
current_year();
|
||||||
|
|
||||||
|
All `current_year()` does is find the current `year`. The UTC time zone is used for simplicity. If this is not sufficient for your needs, it is easy enough to specify which year you want a calendar for. This works by calling `system_clock::now()`, truncating that result into `sys_days` (a count of days), converting the `sys_days` into a `year_month_day`, and returning the `year()` field of that.
|
||||||
|
|
||||||
|
unsigned
|
||||||
|
number_of_lines_calendar(date::year_month const ym, date::weekday const firstdow);
|
||||||
|
|
||||||
|
This function computes the number of lines that printing out the calendar will take for the year/month combination `ym` and using `firstdow` as the first day of the week for that calendar. The first thing to compute is the number of days the first of the month is past the first day of the week: `(weekday{ym/1} - firstdow)`. Week day subtraction is unsigned modulo 7, so this is _always_ a positive number in the range [0, 6], no matter the underlying encoding of `weekday`.
|
||||||
|
|
||||||
|
Add to that the number of days in the month. This is computed by `((ym/last).day() - day{0})`. The expression `ym/last` creates a `year_month_day_last` which represents the date of the last day of the month. `.day()` extracts the day field. We could have subtracted `day{1}`, and then added `days{1}` to that difference, but it is simpler to just subtract the invalid `day{0}`. The type of this difference is `days`, a `chrono::duration`.
|
||||||
|
|
||||||
|
Next we want to convert this number of `days` into `weeks` using `ceil` which will round up if the conversion is not exact. This allows for months that don't completely fill out their last row. We then extract the number of weeks with `.count()` and add `2` more lines: One for the day-of-the-week title, and one for the month year title.
|
||||||
|
|
||||||
|
void
|
||||||
|
print_line_of_calendar_month(std::ostream& os, date::year_month const ym,
|
||||||
|
unsigned const line, date::weekday const firstdow);
|
||||||
|
|
||||||
|
This is the heart of the calendar-printing logic. This prints _one_ line of a month calendar, with no line break afterwards. The argument `line` says which line [0, infinity]. If more lines are asked for than the calendar takes up, blank lines are printed. The calendar starts with the `weekday firstdow`. The entire function is just a switch on `line` to see which line to print out:
|
||||||
|
|
||||||
|
`0`: Print out the month and year title using `ym` and the `locale` extracted from `os`. Left align this output in a field of 21 spaces, except indent by one space.
|
||||||
|
|
||||||
|
`1`: Print out the day-of-the-week header using the first two letters of the localized abbreviation for the days of the week. Start with `first dow` and repeat for 1 week. Each weekday name is two letters long and right justified in a width of 3 spaces.
|
||||||
|
|
||||||
|
`2`: Print out the first week. This is the only week that may be prefixed with spaces. The number of spaces to prefix with is 3 for every day that the first of the month is past the first day of the week: `weekday{ym/1} - firstdow`. Then starting with `1_d`, print each day (not 0-prefixed) right-justified in a field of 3 spaces. Iterate until incrementing the day of the week comes back around to `firstdow`.
|
||||||
|
|
||||||
|
`3 - infinity`: This can print either a week, beginning with `ym/firstdow[index]`, or a blank line. It will print the latter if `ym/firstdow[index]` is not a valid date. The expression `ym/firstdow[index]` means the n<sup>_th_</sup> `firstdow` of the month for this year. The `index` is a function of the `line` count, and whether or not we output a `firstdow` for `line == 2`. If we output a `firstdow` for line 2, then `index == line - 1`, other `index == line - 2`. For example when `line == 3`, most of the time we are looking for the first `firstdow` for this year/month.
|
||||||
|
|
||||||
|
Once we have `ymdw` (the first date to print out for this line), we need to convert that to a `year_month_day` so we can extract the `day` field from that. And we also need to compute the last `day` of the month (stored in `e`). Then we iterate from `firstdow`, printing out the day field (right-justified in a field of 3 spaces) until either we've printed a full week, or until we've printed the last day of the month, whichever comes first.
|
||||||
|
|
||||||
|
Finally we check if we output a full week, and if not, how many days for the week did we not print out (`firstdow - wd)`. If this is non-zero, then we pad the line with 3 spaces for each day.
|
||||||
|
|
||||||
|
And that concludes the hardest part of the hardest function for this entire utility!
|
||||||
|
|
||||||
|
void
|
||||||
|
print_calendar_year(std::ostream& os, unsigned const cols = 3,
|
||||||
|
date::year const y = current_year(),
|
||||||
|
date::weekday const firstdow = date::sun);
|
||||||
|
|
||||||
|
This function prints the yearly calendar to `os` by calling the functions we've already defined. The calendar is in a format of `cols` by `rows` months, where `cols` is input by the client and represents how many months you want to print out horizontally. This argument must be one of `[1, 2, 3, 4, 6, 12]`. The example output above defaulted this to 3 months across by 4 down. The year can be input, or defaults to the current year UTC. And the day of the week which the calendar starts with can be specified and defaults to Sunday.
|
||||||
|
|
||||||
|
The first thing to do is check that `cols` has a proper value and throw an exception if it doesn't.
|
||||||
|
|
||||||
|
Next we need to find out how many lines each month of the year `y` needs. This is done by calling `number_of_lines_calendar` for each month of this year and storing the result in `ml`.
|
||||||
|
|
||||||
|
Then we have 3 nested loops. The outer loop runs over the number of rows (the number of calendar months vertically) which is `12/cols`. Then for each row, compute the maximum number of lines necessary to output each month for this row. Then for each line, loop over the number of columns (monthly calendars to output horizontally).
|
||||||
|
|
||||||
|
Now just print out one line for each calendar for this `{row, col, line}` combination. The month for this combination is the one numbered `r*cols + c + 1` (one-based row-major indexing). Also print out 3 spaces between each month horizontally and one blank line between each row of calendars for nice padding.
|
||||||
|
|
||||||
|
This program can now be used to localize and output a wide variety of calendars. For example is a German calendar in a 4x3 output for the year 2016 (output on macOS which supports this localization):
|
||||||
|
|
||||||
|
using namespace date::literals;
|
||||||
|
std::cout.imbue(std::locale("de_DE"));
|
||||||
|
print_calendar_year(std::cout, 4, 2016_y, mon);
|
||||||
|
|
||||||
|
which outputs:
|
||||||
|
|
||||||
|
Januar 2016 Februar 2016 März 2016 April 2016
|
||||||
|
Mo Di Mi Do Fr Sa So 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 1 2 3
|
||||||
|
4 5 6 7 8 9 10 8 9 10 11 12 13 14 7 8 9 10 11 12 13 4 5 6 7 8 9 10
|
||||||
|
11 12 13 14 15 16 17 15 16 17 18 19 20 21 14 15 16 17 18 19 20 11 12 13 14 15 16 17
|
||||||
|
18 19 20 21 22 23 24 22 23 24 25 26 27 28 21 22 23 24 25 26 27 18 19 20 21 22 23 24
|
||||||
|
25 26 27 28 29 30 31 29 28 29 30 31 25 26 27 28 29 30
|
||||||
|
|
||||||
|
Mai 2016 Juni 2016 Juli 2016 August 2016
|
||||||
|
Mo Di Mi Do Fr Sa So Mo Di Mi Do Fr Sa So Mo Di Mi Do Fr Sa So Mo Di Mi Do Fr Sa So
|
||||||
|
1 1 2 3 4 5 1 2 3 1 2 3 4 5 6 7
|
||||||
|
2 3 4 5 6 7 8 6 7 8 9 10 11 12 4 5 6 7 8 9 10 8 9 10 11 12 13 14
|
||||||
|
9 10 11 12 13 14 15 13 14 15 16 17 18 19 11 12 13 14 15 16 17 15 16 17 18 19 20 21
|
||||||
|
16 17 18 19 20 21 22 20 21 22 23 24 25 26 18 19 20 21 22 23 24 22 23 24 25 26 27 28
|
||||||
|
23 24 25 26 27 28 29 27 28 29 30 25 26 27 28 29 30 31 29 30 31
|
||||||
|
30 31
|
||||||
|
|
||||||
|
September 2016 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 Mo Di Mi Do Fr Sa So
|
||||||
|
1 2 3 4 1 2 1 2 3 4 5 6 1 2 3 4
|
||||||
|
5 6 7 8 9 10 11 3 4 5 6 7 8 9 7 8 9 10 11 12 13 5 6 7 8 9 10 11
|
||||||
|
12 13 14 15 16 17 18 10 11 12 13 14 15 16 14 15 16 17 18 19 20 12 13 14 15 16 17 18
|
||||||
|
19 20 21 22 23 24 25 17 18 19 20 21 22 23 21 22 23 24 25 26 27 19 20 21 22 23 24 25
|
||||||
|
26 27 28 29 30 24 25 26 27 28 29 30 28 29 30 26 27 28 29 30 31
|
||||||
|
31
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
 _This work is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/)._
|
 _This work is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/)._
|
Reference in New Issue
Block a user