mirror of
https://github.com/HowardHinnant/date.git
synced 2025-08-03 20:54:27 +02:00
Updated Examples and Recipes (markdown)
@@ -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.
|
||||
|
||||
|
Reference in New Issue
Block a user