Updated Examples and Recipes (markdown)

Howard Hinnant
2016-09-05 13:55:17 -04:00
parent 399895044a
commit c076ae6fc1

@@ -23,6 +23,7 @@ This page contains examples and recipes contributed by community members. Feel f
- [Find all instances when a daylight savings shift is not 1 hour](#tz_search) - [Find all instances when a daylight savings shift is not 1 hour](#tz_search)
- [How many timezones are using daylight saving?](#tz_daylight) - [How many timezones are using daylight saving?](#tz_daylight)
- [What is the epoch difference between Unix Time and GPS time?](#unix_gps_epoch_diff) - [What is the epoch difference between Unix Time and GPS time?](#unix_gps_epoch_diff)
- [How to convert to/from C++ Builder's TDate and TDateTime](#TDate)
*** ***
@@ -1062,6 +1063,158 @@ Now wait a second, doesn't `gps_time` ignore leap seconds?! Yes, but it does so
You can discover all kinds of neat subtleties by playing with `sys_time`, `utc_time`, `tai_time`, and `gps_time`. You can discover all kinds of neat subtleties by playing with `sys_time`, `utc_time`, `tai_time`, and `gps_time`.
<a name="TDate"></a>
### How to convert to/from C++ Builder's TDate and TDateTime
(by [Howard Hinnant](https://github.com/HowardHinnant))
If you are using `TDateTime` from [C++ Builder](http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/System__TDateTime.html) you may occasionally need functionality that is not present in that library, but is in this one. This article explains how to convert back and forth between these two libraries, so that you can easily use the functionality of both.
Two important facts:
1. The epoch for `TDateTime` is 1899-12-30 00:00:00 UTC.
2. The epoch for `std::chrono::system_clock` is (unspecified but de facto): 1970-01-01 00:00:00 UTC.
`TDateTime` stores a `double`. The integral part of that double counts days since the epoch. The fractional part stores fractions of a day, but in a strange encoding for times prior to its epoch:
> When working with negative System::TDateTime values, computations must handle time portion separately. The fractional part reflects the fraction of a 24-hour day without regard to the sign of the System::TDateTime value. For example, 6:00 am on 12/29/1899 is 1.25, not 1 + 0.25, which would be 0.75. There are no System::TDateTime values between 1 and 0.
We'll provide two bidirectional conversions:
TDate <---> sys_days
TDateTime <---> sys_time<D>
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}));
}
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()));
}
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';
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:
1899-12-30
1900-01-01
1899-12-29
1996-01-01
0
2
-1
35065
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})
while this converts to a precision of `milliseconds`:
to_sys_time<milliseconds>(TDateTime{2.75})
Here's the implementation:
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{})
{
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.
If this is a pre-epoch `TDateTime`, then there's an extra dance to treat the integral and fractional parts of the `double` separately.
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());
}
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';
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:
1899-12-30 00:00
1900-01-01 18:00
1899-12-29 06:00
1996-01-01 00:00
0
2.75
-1.25
35065
*** ***
![CC BY Logo](http://mirrors.creativecommons.org/presskit/buttons/80x15/svg/by.svg) _This work is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/)._ ![CC BY Logo](http://mirrors.creativecommons.org/presskit/buttons/80x15/svg/by.svg) _This work is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/)._