Updated Examples and Recipes (markdown)

Howard Hinnant
2017-04-20 16:45:59 -04:00
parent 7dd89c5173
commit ab29630469

@@ -675,7 +675,9 @@ These are all reasonable possibilities, and you can compute all of these things
If we want to just ignore the day-field in these dates, and then compute the number of months, that is easily done like so:
std::cout << (d2.year()/d2.month() - d1.year()/d1.month()).count() << '\n';
```c++
std::cout << (d2.year()/d2.month() - d1.year()/d1.month()).count() << '\n';
```
This creates two `year_month` objects and subtracts them. This gives a `std::chrono::duration` that represents a signed-integral number of months. The output is:
@@ -683,12 +685,16 @@ This creates two `year_month` objects and subtracts them. This gives a `std::ch
To include the influence of the day-fields, it is best to convert `d1` and `d2` to `sys_days`s:
auto dp1 = sys_days(d1);
auto dp2 = sys_days(d2);
```c++
auto dp1 = sys_days(d1);
auto dp2 = sys_days(d2);
```
Now we could (for example) subtract the two `sys_days`s, and round the result to the nearest integral month:
std::cout << round<months>(dp2-dp1).count() << '\n';
```c++
std::cout << round<months>(dp2-dp1).count() << '\n';
```
This outputs:
@@ -696,7 +702,9 @@ This outputs:
Or we could create a new `std::chrono::duration` type based on `float`, but with a period of months, and convert the difference to that:
std::cout << duration<float, months::period>(dp2-dp1).count() << '\n';
```c++
std::cout << duration<float, months::period>(dp2-dp1).count() << '\n';
```
This outputs:
@@ -710,22 +718,28 @@ These are all reasonable answers to the question, and all easily computable with
The [ISO week date](https://en.wikipedia.org/wiki/ISO_week_date) is an internationally recognized system of counting weeks of the year. This is in effect a separate calendar system, though it is closely related to the Gregorian calendar. Instead of specifying a year, month and day, each day is specified by year, week number, and day of the week. For example 2015-W51-Sat is a fully specified date. One can form such a date using the `<iso_week.h>` header like so:
using namespace iso_week::literals;
auto iso_date = 2015_y/51/sat;
```c++
using namespace iso_week::literals;
auto iso_date = 2015_y/51/sat;
```
Like `<date.h>`, you can specify an ISO week date in any of the three orders: y/wn/wd, wd/wn/y, wn/wd/y (big endian, little endian, mixed (american) endian).
Also like `<date.h>`, you can implicitly convert a ISO week date to `sys_days`, and vice-versa. For convenience, an alias of `date:: sys_days ` exists as `iso_week:: sys_days `:
iso_week:: sys_days dp = iso_date;
```c++
iso_week::sys_days dp = iso_date;
```
And recall that `sys_days` is just a type alias for a `std::chrono::time_point<std::chrono::system_clock, days>`. So the ISO week date (`iso_week:year_weeknum_weekday`) is immediately interoperable with the entire `<chrono>` library, just like `date::year_month_day` is.
auto now = std::chrono::system_clock::now();
auto dp = date::floor<iso_week::days>(now);
iso_week::year_weeknum_weekday iso_date = dp;
auto time = date::make_time(now-dp);
std::cout << iso_date << ' ' << time << '\n';
```c++
auto now = std::chrono::system_clock::now();
auto dp = date::floor<iso_week::days>(now);
iso_week::year_weeknum_weekday iso_date = dp;
auto time = date::make_time(now-dp);
std::cout << iso_date << ' ' << time << '\n';
```
Which just output for me:
@@ -733,8 +747,10 @@ Which just output for me:
And because `iso_week:year_weeknum_weekday` is implicitly convertible to and from `sys_days`, that makes it immediately (and explicitly) convertible to any other calendar system that is implicitly convertible to and from `sys_days`:
auto civil_date = date::year_month_day{iso_date};
std::cout << civil_date << ' ' << time << '\n';
```c++
auto civil_date = date::year_month_day{iso_date};
std::cout << civil_date << ' ' << time << '\n';
```
which outputs:
@@ -742,12 +758,14 @@ which outputs:
And there you have it: `sys_days` is a _Rosetta Stone_ for translating _any_ calendar to any other calendar. Just make your calendar convert to and from `sys_days`, and you have interoperability with _every_ other calendar which does so.
using namespace date::literals;
auto today = 2016_y/mar/19;
std::cout << "civil : " << today << '\n';
std::cout << "Julian : " << julian::year_month_day{today} << '\n';
std::cout << "Coptic : " << coptic::year_month_day{today} << '\n';
std::cout << "iso_week: " << iso_week::year_weeknum_weekday{today} << '\n';
```c++
using namespace date::literals;
auto today = 2016_y/mar/19;
std::cout << "civil : " << today << '\n';
std::cout << "Julian : " << julian::year_month_day{today} << '\n';
std::cout << "Coptic : " << coptic::year_month_day{today} << '\n';
std::cout << "iso_week: " << iso_week::year_weeknum_weekday{today} << '\n';
```
Output:
@@ -764,24 +782,26 @@ This is somewhat of a teaser because as I write this the Julian and Coptic calen
This example demonstrates both some simple date arithmetic, and how to handle discontinuities in a timezone. Dave was born in the "America/Los_Angeles" timezone at 10:03am on April 24, 1954. When will he be 2,000,000,000 seconds old in the same timezone?
#include <chrono>
#include <iostream>
#include "date.h"
#include "tz.h"
```c++
#include <chrono>
#include <iostream>
#include "date.h"
#include "tz.h"
int
main()
{
using namespace std::chrono_literals;
using namespace date;
// Dave was born April 24, 1954. 10:03 AM pst
// Want to know when he is 2 Gigaseconds old
auto birthday = make_zoned("America/Los_Angeles",
local_days{apr/24/1954} + 10h + 3min);
std::cout << "born : " << birthday << '\n';
birthday = birthday.get_sys_time() + 2'000'000'000s;
std::cout << "2Gs birthday: " << birthday << '\n';
}
int
main()
{
using namespace std::chrono_literals;
using namespace date;
// Dave was born April 24, 1954. 10:03 AM pst
// Want to know when he is 2 Gigaseconds old
auto birthday = make_zoned("America/Los_Angeles",
local_days{apr/24/1954} + 10h + 3min);
std::cout << "born : " << birthday << '\n';
birthday = birthday.get_sys_time() + 2'000'000'000s;
std::cout << "2Gs birthday: " << birthday << '\n';
}
```
One first creates the local time, and then pairs that to the time zone "America/Los_Angeles" using `make_zoned`. Then add 2Gs to the `sys_time` (not the `local_time`) of `birthday`. This outputs:
@@ -800,13 +820,17 @@ Atomic time keeping started experimentally in 1955, about a year after Dave's bi
Assuming the birthdate is exactly synchronized with TAI (offset by the timezone), then we can form the birthday as `tai_time`:
auto zone = locate_zone("America/Los_Angeles");
auto birthday = to_tai_time(make_zoned(zone,
local_days{apr/24/1954} + 10h + 3min - 10s).get_sys_time());
```c++
auto zone = locate_zone("America/Los_Angeles");
auto birthday = to_tai_time(make_zoned(zone,
local_days{apr/24/1954} + 10h + 3min - 10s).get_sys_time());
```
We have to subtract 10s manually because we want the birthday to be `1954-04-24 18:03:00 TAI` and without that 10s subtraction we have UTC modeled back to 1954 instead of modeling TAI in 1954. Then we add the 2Gs in `tai_time` and convert that result back to `sys_time`, and then to a `zoned_time`:
auto Day2Gs = make_zoned(zone, to_sys_time(birthday + 2'000'000'000s));
```c++
auto Day2Gs = make_zoned(zone, to_sys_time(birthday + 2'000'000'000s));
```
Now the output is:
@@ -821,23 +845,25 @@ which is 37s earlier than we reported without taking leap seconds into account.
An [ordinal date](https://en.wikipedia.org/wiki/Ordinal_date) consists of a year and a day of year (1st of January being day 1, 31st of December being day 365 or day 366). The year can be obtained directly from year_month_day. And calculating the day is wonderfully easy. In the code below we make us of the fact that year_month_day can deal with invalid dates like the 0th of January:
int main()
{
using namespace date;
const auto time = std::chrono::system_clock::now();
const auto daypoint = floor<days>(time);
const auto ymd = year_month_day{daypoint};
// calculating the year and the day of the year
const auto year = ymd.year();
const auto year_day = daypoint - sys_days{year/jan/0};
std::cout << year << '-' << std::setfill('0') << std::setw(3) << year_day.count() << std::endl;
// inverse calculation and check
assert(ymd == year_month_day{sys_days{year/jan/0} + year_day});
}
```c++
int main()
{
using namespace date;
const auto time = std::chrono::system_clock::now();
const auto daypoint = floor<days>(time);
const auto ymd = year_month_day{daypoint};
// calculating the year and the day of the year
const auto year = ymd.year();
const auto year_day = daypoint - sys_days{year/jan/0};
std::cout << year << '-' << std::setfill('0') << std::setw(3) << year_day.count() << std::endl;
// inverse calculation and check
assert(ymd == year_month_day{sys_days{year/jan/0} + year_day});
}
```
<a name="local_arithmetic"></a>
### Local time arithmetic
@@ -847,21 +873,23 @@ It is generally accepted knowledge that doing time point arithmetic in "local ti
However this library can be used to correctly do such computations very easily. Below is code that prints out 9am for several days in the "America/New_York", both just before the DST transition, and just after:
#include "tz.h"
#include <iostream>
```c++
#include "tz.h"
#include <iostream>
int
main()
int
main()
{
using namespace std::chrono;
using namespace date;
auto base = make_zoned("America/New_York", local_days{mar/11/2016} + 9h);
for (int i = 0; i < 4; ++i)
{
using namespace std::chrono;
using namespace date;
auto base = make_zoned("America/New_York", local_days{mar/11/2016} + 9h);
for (int i = 0; i < 4; ++i)
{
std::cout << format("%F %T %z", base) << '\n';
base = base.get_local_time() + days{1};
}
std::cout << format("%F %T %z", base) << '\n';
base = base.get_local_time() + days{1};
}
}
```
This code outputs:
@@ -888,37 +916,39 @@ First problem: Is `2016-04-03 03:15` a time local to the time zone, or is it UT
So we need two `find_by_abbrev`: One which takes a `sys_time`, and one which takes a `local_time`. Let's do `sys_time` first:
#include "tz.h"
#include <string>
#include <iostream>
#include <vector>
```c++
#include "tz.h"
#include <string>
#include <iostream>
#include <vector>
template <class Duration>
std::vector<date::zoned_time<std::common_type_t<Duration, std::chrono::seconds>>>
find_by_abbrev(date::sys_time<Duration> tp, const std::string& abbrev)
template <class Duration>
std::vector<date::zoned_time<std::common_type_t<Duration, std::chrono::seconds>>>
find_by_abbrev(date::sys_time<Duration> tp, const std::string& abbrev)
{
using namespace std::chrono;
using namespace date;
std::vector<zoned_time<std::common_type_t<Duration, std::chrono::seconds>>> results;
auto& db = get_tzdb();
for (auto& z : db.zones)
{
using namespace std::chrono;
using namespace date;
std::vector<zoned_time<std::common_type_t<Duration, std::chrono::seconds>>> results;
auto& db = get_tzdb();
for (auto& z : db.zones)
{
if (z.get_info(tp).abbrev == abbrev)
results.push_back(make_zoned(&z, tp));
}
return results;
if (z.get_info(tp).abbrev == abbrev)
results.push_back(make_zoned(&z, tp));
}
return results;
}
int
main()
{
using namespace std::chrono;
using namespace date;
auto now = sys_days{2016_y/4/3} + 3h + 15min;
auto v = find_by_abbrev(now, "CST");
for (auto const& zt : v)
std::cout << zt << " " << zt.get_time_zone()->name() << '\n';
}
int
main()
{
using namespace std::chrono;
using namespace date;
auto now = sys_days{2016_y/4/3} + 3h + 15min;
auto v = find_by_abbrev(now, "CST");
for (auto const& zt : v)
std::cout << zt << " " << zt.get_time_zone()->name() << '\n';
}
```
The `find_by_abbrev` function is surprisingly simple: Loop over all `time_zone`s in the database, get the `sys_info` for each one at time `tp`, and if that `sys_info` has an abbreviation equal to `abbrev`, create a `zoned_time` for this `time_zone` and `tp` and append it to the `vector`. The most complicated part is figuring out the precision of the duration of the `zoned_time` which is going be the finer of `seconds` and `Duration`. The facility `std::common_type` figures that out for us.
@@ -946,43 +976,49 @@ Now the really interesting part of this problem is what happens if we are talkin
The driver program is identical to that shown above except that instead of:
auto now = sys_days{2016_y/4/3} + 3h + 15min;
```c++
auto now = sys_days{2016_y/4/3} + 3h + 15min;
```
we have:
auto now = local_days{2016_y/4/3} + 3h + 15min;
```c++
auto now = local_days{2016_y/4/3} + 3h + 15min;
```
The `find_by_abbrev` takes a `local_time<Duration>` instead of a `sys_time<Duration>`, and the logic is slightly more complicated:
template <class Duration>
std::vector<date::zoned_time<std::common_type_t<Duration, std::chrono::seconds>>>
find_by_abbrev(date::local_time<Duration> tp, const std::string& abbrev)
```c++
template <class Duration>
std::vector<date::zoned_time<std::common_type_t<Duration, std::chrono::seconds>>>
find_by_abbrev(date::local_time<Duration> tp, const std::string& abbrev)
{
using namespace std::chrono;
using namespace date;
std::vector<zoned_time<std::common_type_t<Duration, std::chrono::seconds>>> results;
auto& db = get_tzdb();
for (auto& z : db.zones)
{
using namespace std::chrono;
using namespace date;
std::vector<zoned_time<std::common_type_t<Duration, std::chrono::seconds>>> results;
auto& db = get_tzdb();
for (auto& z : db.zones)
auto i = z.get_info(tp);
switch (i.result)
{
auto i = z.get_info(tp);
switch (i.result)
{
case local_info::unique:
if (i.first.abbrev == abbrev)
results.push_back(make_zoned(&z, tp));
break;
case local_info::ambiguous:
if (i.first.abbrev == abbrev)
results.push_back(make_zoned(&z, tp, choose::earliest));
else if (i.second.abbrev == abbrev)
results.push_back(make_zoned(&z, tp, choose::latest));
break;
default:
break;
}
case local_info::unique:
if (i.first.abbrev == abbrev)
results.push_back(make_zoned(&z, tp));
break;
case local_info::ambiguous:
if (i.first.abbrev == abbrev)
results.push_back(make_zoned(&z, tp, choose::earliest));
else if (i.second.abbrev == abbrev)
results.push_back(make_zoned(&z, tp, choose::latest));
break;
default:
break;
}
return results;
}
return results;
}
```
A `local_time` may or may not have a unique mapping to UTC. It might be unique, it might be ambiguous, or it might not exist at all. This function discovers if the mapping is unique, ambiguous or non-existent with `i.result`. If it is unique, the logic is exactly as with the `find_by_abbrev` for `sys_time` search. Note that `make_zoned` knows the difference between a `sys_time` and a `local_time`, and always does the right thing.