Updated Examples and Recipes (markdown)

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

@@ -1050,6 +1050,7 @@ 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.
```c++
#include "tz.h"
#include <iostream>
@@ -1077,11 +1078,13 @@ Let's say you want to search the globe, and all time, for time zones when the da
} 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:
```c++
struct sys_info
{
second_point begin;
@@ -1090,6 +1093,7 @@ Starting at the beginning of time, get an `sys_info` for that UTC `time_point`.
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,6 +1114,7 @@ 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:
```c++
#include "tz.h"
#include <iostream>
@@ -1136,6 +1141,7 @@ Ever wonder how the global use of daylight saving time is trending with time? H
<< " = " << 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`:
```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:
```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:
```c++
std::cout << (to_sys_time(gps_epoch) - unix_epoch).count() << "s\n";
```
which outputs:
@@ -1208,6 +1220,7 @@ 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:
```c++
date::sys_days
to_sys_days(System::TDate td)
{
@@ -1215,9 +1228,11 @@ The `TDate`/`sys_days` conversions don't have to worry about the fractional day
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:
```c++
System::TDate
to_TDate(date::sys_days sd)
{
@@ -1225,9 +1240,11 @@ The only thing to do here is to extract the integral value from the `TDate`, con
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:
```c++
int
main()
{
@@ -1243,6 +1260,7 @@ These can be exercised like this:
std::cout << (int)to_TDate(1899_y/dec/29) << '\n';
std::cout << (int)to_TDate(1996_y/jan/1) << '\n';
}
```
which outputs:
@@ -1259,14 +1277,19 @@ 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`:
```c++
to_sys_time<minutes>(TDateTime{2.75})
```
while this converts to a precision of `milliseconds`:
```c++
to_sys_time<milliseconds>(TDateTime{2.75})
```
Here's the implementation:
```c++
template <class D>
date::sys_time<D>
to_sys_time(System::TDateTime dt)
@@ -1285,6 +1308,7 @@ Here's the implementation:
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,6 +1316,7 @@ If this is a pre-epoch `TDateTime`, then there's an extra dance to treat the int
The reverse conversion is similar:
```c++
template <class D>
System::TDateTime
to_TDateTime(date::sys_time<D> tp)
@@ -1307,9 +1332,11 @@ The reverse conversion is similar:
auto t = d - ft;
return System::TDateTime((d + t).time_since_epoch().count());
}
```
This can all be exercised like this:
```c++
int
main()
{
@@ -1326,6 +1353,7 @@ This can all be exercised like this:
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,6 +1372,7 @@ which outputs:
Here are functions you can use to convert between [`QDate`](http://doc.qt.io/qt-5/qdate.html) and `sys_days`:
```c++
date::sys_days
to_sys_days(QDate qd)
{
@@ -1359,6 +1388,7 @@ Here are functions you can use to convert between [`QDate`](http://doc.qt.io/qt-
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,6 +1400,7 @@ Here are functions and typedefs you can use to work with Windows' [FILETIME](htt
Assuming `system_clock` is based on the Unix epoch:
```c++
using std::ratio;
using std::chrono::duration;
using std::chrono::duration_cast;
@@ -1399,6 +1430,7 @@ Assuming `system_clock` is based on the Unix epoch:
result.dwHighDateTime = static_cast<DWORD>(rawCount >> 32);
return result;
}
```
<a name="calendar"></a>
### Print out a compact calendar for the year
@@ -1408,6 +1440,7 @@ Printing out the calendar for an entire year is an interesting exercise. You ca
First the code, and then a detailed explanation:
```c++
#include "date.h"
#include <algorithm>
#include <iomanip>
@@ -1537,6 +1570,7 @@ First the code, and then a detailed explanation:
{
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.
```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.
```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.
```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!
```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):
```c++
using namespace date::literals;
std::cout.imbue(std::locale("de_DE"));
print_calendar_year(std::cout, 4, 2016_y, mon);
```
which outputs: