Updated Examples and Recipes (markdown)

Howard Hinnant
2017-04-20 16:54:09 -04:00
parent ab29630469
commit 23a0e3cf34

@@ -1050,46 +1050,50 @@ How you choose between these time zones is beyond the scope of this library. Pe
Let's say you want to search the globe, and all time, for time zones when the daylight savings shift was not 1 hour. Sound strange? Maybe, but this code teaches you how to _efficiently_ iterate over **all** timezone transitions and inspect their characteristics. So you can use this code for all kinds of searches over time zones.
#include "tz.h"
#include <iostream>
```c++
#include "tz.h"
#include <iostream>
int
main()
int
main()
{
using namespace date;
using namespace std::chrono_literals;
auto& db = get_tzdb();
for (auto const& z : db.zones)
{
using namespace date;
using namespace std::chrono_literals;
auto& db = get_tzdb();
for (auto const& z : db.zones)
auto begin = sys_days{jan/1/year::min()} + 0s;
auto end = sys_days{jan/1/2035} + 0s;
do
{
auto begin = sys_days{jan/1/year::min()} + 0s;
auto end = sys_days{jan/1/2035} + 0s;
do
auto info = z.get_info(begin);
if (info.save != 0h && info.save != 1h)
{
auto info = z.get_info(begin);
if (info.save != 0h && info.save != 1h)
{
std::cout << z.name() << " has a daylight savings offset of "
<< info.save.count() << "min from " << info.begin
<< " UTC to " << info.end << " UTC with the abbreviation "
<< info.abbrev << '\n';
}
begin = info.end;
} while (begin < end);
}
std::cout << z.name() << " has a daylight savings offset of "
<< info.save.count() << "min from " << info.begin
<< " UTC to " << info.end << " UTC with the abbreviation "
<< info.abbrev << '\n';
}
begin = info.end;
} while (begin < end);
}
}
```
You first get a reference to the tz database, then iterate over each zone in the database. For each zone, set a range of time points to search over. In this example I start searching as far back as possible, and search forward to the year 2035.
Starting at the beginning of time, get an `sys_info` for that UTC `time_point`. An `sys_info` looks like this:
struct sys_info
{
second_point begin;
second_point end;
std::chrono::seconds offset;
std::chrono::minutes save;
std::string abbrev;
};
```c++
struct sys_info
{
second_point begin;
second_point end;
std::chrono::seconds offset;
std::chrono::minutes save;
std::string abbrev;
};
```
Each time zone transition happens at `begin` (UTC). The total offset from UTC for this timezone and period is `offset`. This `offset` will be in effect until `end` (UTC). The difference between this period's "normal" `offset`, and this `offset` is `save`. And this period's timezone abbreviation is `abbrev`.
@@ -1110,32 +1114,34 @@ Sample output of this program:
Ever wonder how the global use of daylight saving time is trending with time? Here's one way to find out:
#include "tz.h"
#include <iostream>
```c++
#include "tz.h"
#include <iostream>
int
main()
int
main()
{
using namespace date;
using namespace std::chrono_literals;
auto& db = get_tzdb();
std::cout << db.version << '\n';
for (auto y = 1850_y; y < 2020_y; ++y)
{
using namespace date;
using namespace std::chrono_literals;
auto& db = get_tzdb();
std::cout << db.version << '\n';
for (auto y = 1850_y; y < 2020_y; ++y)
auto use_daylight = 0;
auto total = 0;
for (auto& z : db.zones)
{
auto use_daylight = 0;
auto total = 0;
for (auto& z : db.zones)
{
++total;
auto info1 = z.get_info(sys_days{y/jan/15});
auto info2 = z.get_info(sys_days{y/jul/15});
if (info1.save != 0min || info2.save != 0min)
++use_daylight;
}
std::cout << y << " : " << use_daylight << '/' << total
<< " = " << static_cast<float>(use_daylight)/total << '\n';
++total;
auto info1 = z.get_info(sys_days{y/jan/15});
auto info2 = z.get_info(sys_days{y/jul/15});
if (info1.save != 0min || info2.save != 0min)
++use_daylight;
}
std::cout << y << " : " << use_daylight << '/' << total
<< " = " << static_cast<float>(use_daylight)/total << '\n';
}
}
```
This code loops over a wide range of years, and then for each year, loops over all timezones in the database, and for each timezone, detects whether it is switching back and forth between standard time and daylight time for that year. The switch detection is rather crude, but you can make this detection as elaborate as you want. Currently it picks two dates 6 months apart which are unlikely to both be using standard time if the zone is using daylight saving time that year. For each of those two dates, the `sys_info.save` member is checked. If either `save != 0min`, daylight saving is in use that year.
@@ -1155,12 +1161,16 @@ This all means that subtracting two `sys_time` time points can give a different
First of all we need to get the `gps_time` epoch and the `sys_time` epoch. We do not even need to know the dates of these epochs. We can just use `0`:
auto gps_epoch = gps_seconds{0s}; // 1980-01-06 00:00:00 UTC
auto unix_epoch = sys_seconds{0s}; // 1970-01-01 00:00:00 UTC
```c++
auto gps_epoch = gps_seconds{0s}; // 1980-01-06 00:00:00 UTC
auto unix_epoch = sys_seconds{0s}; // 1970-01-01 00:00:00 UTC
```
These are both `std::chrono::time_point`s, but if we try to subtract them we will get a compile-time error because they refer to different clocks. So to subtract them we must convert one to the other prior to subtracting. Here is one way to do this:
std::cout << (gps_epoch - to_gps_time(unix_epoch)).count() << "s\n";
```c++
std::cout << (gps_epoch - to_gps_time(unix_epoch)).count() << "s\n";
```
This converts the `sys_time` to a `gps_time`, does the subtraction, and outputs:
@@ -1170,7 +1180,9 @@ In `gps_time`, there are 315,964,809 seconds between these two epochs.
Here is another way:
std::cout << (to_sys_time(gps_epoch) - unix_epoch).count() << "s\n";
```c++
std::cout << (to_sys_time(gps_epoch) - unix_epoch).count() << "s\n";
```
which outputs:
@@ -1208,41 +1220,47 @@ We'll provide two bidirectional conversions:
The `TDate`/`sys_days` conversions don't have to worry about the fractional day issue for negative `TDateTime` values, and so they are both easier and more efficient:
date::sys_days
to_sys_days(System::TDate td)
{
using namespace date;
return sys_days(days{static_cast<int>(td)} -
(sys_days{1970_y/jan/1} - sys_days{1899_y/dec/30}));
}
```c++
date::sys_days
to_sys_days(System::TDate td)
{
using namespace date;
return sys_days(days{static_cast<int>(td)} -
(sys_days{1970_y/jan/1} - sys_days{1899_y/dec/30}));
}
```
The only thing to do here is to extract the integral value from the `TDate`, convert that into `sys_days` and subtract the difference between the two epochs. The reverse conversion is just as easy:
System::TDate
to_TDate(date::sys_days sd)
{
using namespace date;
return System::TDate(static_cast<int>((sd.time_since_epoch() +
(sys_days{1970_y/jan/1} - sys_days{1899_y/dec/30})).count()));
}
```c++
System::TDate
to_TDate(date::sys_days sd)
{
using namespace date;
return System::TDate(static_cast<int>((sd.time_since_epoch() +
(sys_days{1970_y/jan/1} - sys_days{1899_y/dec/30})).count()));
}
```
These can be exercised like this:
int
main()
{
using namespace date;
using namespace System;
std::cout << to_sys_days(TDate{0}) << '\n';
std::cout << to_sys_days(TDate{2}) << '\n';
std::cout << to_sys_days(TDate{-1}) << '\n';
std::cout << to_sys_days(TDate{35065}) << '\n';
```c++
int
main()
{
using namespace date;
using namespace System;
std::cout << to_sys_days(TDate{0}) << '\n';
std::cout << to_sys_days(TDate{2}) << '\n';
std::cout << to_sys_days(TDate{-1}) << '\n';
std::cout << to_sys_days(TDate{35065}) << '\n';
std::cout << (int)to_TDate(1899_y/dec/30) << '\n';
std::cout << (int)to_TDate(1900_y/jan/1) << '\n';
std::cout << (int)to_TDate(1899_y/dec/29) << '\n';
std::cout << (int)to_TDate(1996_y/jan/1) << '\n';
}
std::cout << (int)to_TDate(1899_y/dec/30) << '\n';
std::cout << (int)to_TDate(1900_y/jan/1) << '\n';
std::cout << (int)to_TDate(1899_y/dec/29) << '\n';
std::cout << (int)to_TDate(1996_y/jan/1) << '\n';
}
```
which outputs:
@@ -1259,32 +1277,38 @@ This output is consistent with what is in the `TDateTime` documentation.
For converting to/from `TDateTime` it is convenient to allow the client to choose the precision of the `sys_time` to convert to or from. For example this converts to a precision of `minutes`:
to_sys_time<minutes>(TDateTime{2.75})
```c++
to_sys_time<minutes>(TDateTime{2.75})
```
while this converts to a precision of `milliseconds`:
to_sys_time<milliseconds>(TDateTime{2.75})
```c++
to_sys_time<milliseconds>(TDateTime{2.75})
```
Here's the implementation:
template <class D>
date::sys_time<D>
to_sys_time(System::TDateTime dt)
```c++
template <class D>
date::sys_time<D>
to_sys_time(System::TDateTime dt)
{
using namespace date;
using namespace std::chrono;
using fdays = duration<double, days::period>;
using ftime = time_point<system_clock, fdays>;
auto ft = ftime{fdays{static_cast<double>(dt)}};
if (ft < ftime{})
{
using namespace date;
using namespace std::chrono;
using fdays = duration<double, days::period>;
using ftime = time_point<system_clock, fdays>;
auto ft = ftime{fdays{static_cast<double>(dt)}};
if (ft < ftime{})
{
auto d = time_point_cast<days>(ft);
auto t = d - ft;
ft = d + t;
}
ft -= sys_days{1970_y/jan/1} - sys_days{1899_y/dec/30};
return round<D>(ft);
auto d = time_point_cast<days>(ft);
auto t = d - ft;
ft = d + t;
}
ft -= sys_days{1970_y/jan/1} - sys_days{1899_y/dec/30};
return round<D>(ft);
}
```
For time points not prior to the `TDateTime` epoch, it is quite straightforward. It helps to create a `duration` and chrono `time_point` based on `double` that counts `days`. Then one simply extracts the `double` from the `TDateTime` and converts it to our double-based `time_point`, subtracts the difference in the epochs, and then uses the `round<D>` facility to truncate the result to the requested precision.
@@ -1292,40 +1316,44 @@ If this is a pre-epoch `TDateTime`, then there's an extra dance to treat the int
The reverse conversion is similar:
template <class D>
System::TDateTime
to_TDateTime(date::sys_time<D> tp)
{
using namespace date;
using namespace std::chrono;
using fdays = duration<double, days::period>;
using ftime = time_point<system_clock, fdays>;
auto ft = ftime{tp} + (sys_days{1970_y/jan/1} - sys_days{1899_y/dec/30});
if (ft >= ftime{})
return System::TDateTime(ft.time_since_epoch().count());
auto d = floor<days>(ft);
auto t = d - ft;
return System::TDateTime((d + t).time_since_epoch().count());
}
```c++
template <class D>
System::TDateTime
to_TDateTime(date::sys_time<D> tp)
{
using namespace date;
using namespace std::chrono;
using fdays = duration<double, days::period>;
using ftime = time_point<system_clock, fdays>;
auto ft = ftime{tp} + (sys_days{1970_y/jan/1} - sys_days{1899_y/dec/30});
if (ft >= ftime{})
return System::TDateTime(ft.time_since_epoch().count());
auto d = floor<days>(ft);
auto t = d - ft;
return System::TDateTime((d + t).time_since_epoch().count());
}
```
This can all be exercised like this:
int
main()
{
using namespace date;
using namespace std::chrono;
using namespace System;
std::cout << to_sys_time<minutes>(TDateTime{0.}) << '\n';
std::cout << to_sys_time<minutes>(TDateTime{2.75}) << '\n';
std::cout << to_sys_time<minutes>(TDateTime{-1.25}) << '\n';
std::cout << to_sys_time<minutes>(TDateTime{35065.}) << '\n';
```c++
int
main()
{
using namespace date;
using namespace std::chrono;
using namespace System;
std::cout << to_sys_time<minutes>(TDateTime{0.}) << '\n';
std::cout << to_sys_time<minutes>(TDateTime{2.75}) << '\n';
std::cout << to_sys_time<minutes>(TDateTime{-1.25}) << '\n';
std::cout << to_sys_time<minutes>(TDateTime{35065.}) << '\n';
std::cout << (double)to_TDateTime(sys_days{1899_y/dec/30} + 0min) << '\n';
std::cout << (double)to_TDateTime(sys_days{1900_y/jan/1} + 18h + 0min) << '\n';
std::cout << (double)to_TDateTime(sys_days{1899_y/dec/29} + 6h + 0min) << '\n';
std::cout << (double)to_TDateTime(sys_days{1996_y/jan/1} + 0min) << '\n';
}
std::cout << (double)to_TDateTime(sys_days{1899_y/dec/30} + 0min) << '\n';
std::cout << (double)to_TDateTime(sys_days{1900_y/jan/1} + 18h + 0min) << '\n';
std::cout << (double)to_TDateTime(sys_days{1899_y/dec/29} + 6h + 0min) << '\n';
std::cout << (double)to_TDateTime(sys_days{1996_y/jan/1} + 0min) << '\n';
}
```
which outputs:
@@ -1344,21 +1372,23 @@ which outputs:
Here are functions you can use to convert between [`QDate`](http://doc.qt.io/qt-5/qdate.html) and `sys_days`:
date::sys_days
to_sys_days(QDate qd)
{
using namespace date;
return sys_days{days{qd.toJulianDay()} -
(sys_days{1970_y/jan/1} - sys_days{year{-4713}/nov/24})};
}
```c++
date::sys_days
to_sys_days(QDate qd)
{
using namespace date;
return sys_days{days{qd.toJulianDay()} -
(sys_days{1970_y/jan/1} - sys_days{year{-4713}/nov/24})};
}
QDate
to_QDate(date::sys_days sd)
{
using namespace date;
return QDate::fromJulianDay((sd.time_since_epoch() +
(sys_days{1970_y/jan/1} - sys_days{year{-4713}/nov/24})).count());
}
QDate
to_QDate(date::sys_days sd)
{
using namespace date;
return QDate::fromJulianDay((sd.time_since_epoch() +
(sys_days{1970_y/jan/1} - sys_days{year{-4713}/nov/24})).count());
}
```
These work by simply adjusting the epoch of these two types.
@@ -1370,35 +1400,37 @@ Here are functions and typedefs you can use to work with Windows' [FILETIME](htt
Assuming `system_clock` is based on the Unix epoch:
using std::ratio;
using std::chrono::duration;
using std::chrono::duration_cast;
using std::chrono::system_clock;
```c++
using std::ratio;
using std::chrono::duration;
using std::chrono::duration_cast;
using std::chrono::system_clock;
// filetime_duration has the same layout as FILETIME; 100ns intervals
using filetime_duration = duration<int64_t, ratio<1, 10'000'000>>;
// January 1, 1601 (NT epoch) - January 1, 1970 (Unix epoch):
constexpr duration<int64_t> nt_to_unix_epoch{INT64_C(-11644473600)};
// filetime_duration has the same layout as FILETIME; 100ns intervals
using filetime_duration = duration<int64_t, ratio<1, 10'000'000>>;
// January 1, 1601 (NT epoch) - January 1, 1970 (Unix epoch):
constexpr duration<int64_t> nt_to_unix_epoch{INT64_C(-11644473600)};
system_clock::time_point FILETIME_to_system_clock(FILETIME fileTime) {
const filetime_duration asDuration{static_cast<int64_t>(
(static_cast<uint64_t>(fileTime.dwHighDateTime) << 32)
| fileTime.dwLowDateTime)};
const auto withUnixEpoch = asDuration + nt_to_unix_epoch;
return system_clock::time_point{
duration_cast<system_clock::duration>(withUnixEpoch)};
}
system_clock::time_point FILETIME_to_system_clock(FILETIME fileTime) {
const filetime_duration asDuration{static_cast<int64_t>(
(static_cast<uint64_t>(fileTime.dwHighDateTime) << 32)
| fileTime.dwLowDateTime)};
const auto withUnixEpoch = asDuration + nt_to_unix_epoch;
return system_clock::time_point{
duration_cast<system_clock::duration>(withUnixEpoch)};
}
FILETIME system_clock_to_FILETIME(system_clock::time_point systemPoint) {
const auto asDuration = duration_cast<filetime_duration>(
systemPoint.time_since_epoch());
const auto withNtEpoch = asDuration - nt_to_unix_epoch;
const uint64_t rawCount = withNtEpoch.count();
FILETIME result;
result.dwLowDateTime = static_cast<DWORD>(rawCount); // discards upper bits
result.dwHighDateTime = static_cast<DWORD>(rawCount >> 32);
return result;
}
FILETIME system_clock_to_FILETIME(system_clock::time_point systemPoint) {
const auto asDuration = duration_cast<filetime_duration>(
systemPoint.time_since_epoch());
const auto withNtEpoch = asDuration - nt_to_unix_epoch;
const uint64_t rawCount = withNtEpoch.count();
FILETIME result;
result.dwLowDateTime = static_cast<DWORD>(rawCount); // discards upper bits
result.dwHighDateTime = static_cast<DWORD>(rawCount >> 32);
return result;
}
```
<a name="calendar"></a>
### Print out a compact calendar for the year
@@ -1408,135 +1440,137 @@ Printing out the calendar for an entire year is an interesting exercise. You ca
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>
```c++
#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();
}
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;
}
// 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)
// 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)
{
using namespace std;
using namespace date;
switch (line)
case 0:
// Output month and year title
os << left << setw(21) << format(os.getloc(), " %B %Y", ym) << right;
break;
case 1:
{
case 0:
// Output month and year title
os << left << setw(21) << format(os.getloc(), " %B %Y", ym) << right;
break;
case 1:
{
// Output weekday names title
auto wd = first dow;
do
{
auto d = format(os.getloc(), "%a", wd);
d.resize(2);
os << ' ' << d;
} while (++wd != firstdow);
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;
// Output weekday names title
auto wd = first dow;
do
{
auto d = format(os.getloc(), "%a", wd);
d.resize(2);
os << ' ' << d;
} while (++wd != firstdow);
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 << format(" %e", 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 << format(" %e", 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 << format(" %e", 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;
}
} 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)
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
{
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
{
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
{
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';
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);
}
int
main()
{
print_calendar_year(std::cout);
}
```
As written this program outputs:
@@ -1577,13 +1611,17 @@ As written this program outputs:
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();
```c++
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);
```c++
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`.
@@ -1591,9 +1629,11 @@ Add to that the number of days in the month. This is computed by `((ym/last).da
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);
```c++
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:
@@ -1611,10 +1651,12 @@ Finally we check if we output a full week, and if not, how many days for the wee
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);
```c++
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.
@@ -1628,9 +1670,11 @@ Now just print out one line for each calendar for this `{row, col, line}` combin
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);
```c++
using namespace date::literals;
std::cout.imbue(std::locale("de_DE"));
print_calendar_year(std::cout, 4, 2016_y, mon);
```
which outputs: