diff --git a/Examples-and-Recipes.md b/Examples-and-Recipes.md index 8aab121..7d47019 100644 --- a/Examples-and-Recipes.md +++ b/Examples-and-Recipes.md @@ -5,6 +5,7 @@ This page contains examples and recipes contributed by community members. Feel f - [The current time somewhere else](#elsetime) - [Get the current difference between any two arbitrary time zones](#deltatz) - [Set simultaneous meeting in two different time zones](#meeting) +- [Interfacing with the C API](#Ccompatiblity) - [Get milliseconds since the local midnight](#since_midnight) - [What is my timezone database version?](#version) - [Obtaining a `time_point` from `y/m/d h:m:s` components](#time_point_to_components) @@ -179,6 +180,202 @@ In any event, the output is still: 2016-07-08 09:00:00 EDT 2016-07-08 16:00:00 MSK + +### Interfacing with the C API +(by [Howard Hinnant](https://github.com/HowardHinnant)) + +The goal of this library is to ensure that you never have to bother with the C timing API again. That being said, the real world interrupts my goals sometimes, and one just has to convert to/from a `tm`, or `timespec` now and then for interfacing with somebody else's code that still uses the C timing API. So this note is about how to write those conversion functions: + +#### Converting from a `tm` + +C allows a `tm` to contain more members than are documented by the C specification. This example assumes only the portable members of `tm`. Hopefully once you understand how to work with the portable members, you will be able to extend your knowledge to the non-portable members for your platform if necessary. The portable `tm` is: + +```c++ +struct tm +{ + int tm_sec; // seconds after the minute — [0, 60] + int tm_min; // minutes after the hour — [0, 59] + int tm_hour; // hours since midnight — [0, 23] + int tm_mday; // day of the month — [1, 31] + int tm_mon; // months since January — [0, 11] + int tm_year; // years since 1900 + int tm_wday; // days since Sunday — [0, 6] + int tm_yday; // days since January 1 — [0, 365] + int tm_isdst; // Daylight Saving Time flag +}; +``` +The `tm` struct can be used to hold a UTC time (`sys_time` or `utc_time`), or a local time (`local_time`). However the `tm` does not contain enough information to identify the time zone of a local time, or even the UTC offset (some platforms include a utc offset member as a conforming extension). Additionally the `tm` is limited to seconds precision. This limits what we can convert to. For example it is not possible to convert to a `zoned_time` because there is not enough information to do so. And it only makes sense to convert to a destination with seconds precision. If you want to convert to something with higher precision than that, it is an implicit conversion from the seconds-precision result of these functions. + +So we can convert from a `tm` to either a `sys_seconds`, or a `local_seconds`. Here is the first: + +```c++ +date::sys_seconds +to_sys_time(std::tm const& t) +{ + using namespace date; + using namespace std::chrono; + return sys_days{year{t.tm_year + 1900}/(t.tm_mon+1)/t.tm_mday} + + hours{t.tm_hour} + minutes{t.tm_min} + seconds{t.tm_sec}; +} +``` + +As can be seen, not all of the fields of the `tm` are needed for this conversion. And this conversion assumes that the `tm` represents a UTC time point. The conversion itself is quite straightforward: Convert the year/month/day information into a `sys_days`, and then add the H:M:S information, converting into chrono durations along the way. One has to be careful with the year and month data from `tm` to bias it correctly. + +Converting to a `local_seconds` is identical except for the use of `local_days` in place of `sys_days`: + +```c++ +date::local_seconds +to_local_time(std::tm const& t) +{ + using namespace date; + using namespace std::chrono; + return local_days{year{t.tm_year + 1900}/(t.tm_mon+1)/t.tm_mday} + + hours{t.tm_hour} + minutes{t.tm_min} + seconds{t.tm_sec}; +} +``` + +This can give you a local time in an _as-yet-unspecified_ time zone. One can subsequently pair that `local_time` with a time zone such as `current_zone()` to complete the conversion (assuming `current_zone()` is the correct time zone). + +#### Converting to a `tm` + +Conversion _to_ a `tm` is a bit more flexible since we generally have more than enough information to fill out the `tm`. Let's start with converting from `zoned_seconds` to a `tm`: + +```c++ +std::tm +to_tm(date::zoned_seconds tp) +{ + using namespace date; + using namespace std; + using namespace std::chrono; + auto lt = tp.get_local_time(); + auto ld = floor(lt); + time_of_day tod{lt - ld}; // can be omitted in C++17 + year_month_day ymd{ld}; + tm t{}; + t.tm_sec = tod.seconds().count(); + t.tm_min = tod.minutes().count(); + t.tm_hour = tod.hours().count(); + t.tm_mday = unsigned{ymd.day()}; + t.tm_mon = unsigned{ymd.month()} - 1; + t.tm_year = int{ymd.year()} - 1900; + t.tm_wday = unsigned{weekday{ld}}; + t.tm_yday = (ld - local_days{ymd.year()/jan/1}).count(); + t.tm_isdst = tp.get_info().save != minutes{0}; + return t; +} +``` + +A presumption here is that one desires to convert the _local time_ in the `zoned_time` to the `tm`. If that assumption is not the case, we cover putting a UTC time into a `tm` below. So the first thing to do is to get the local time out of the `zoned_seconds` with `tp.get_local_time()`. + +Next we truncate the `local_seconds` into `local_days` using the `floor(lt)` function. Now we have two `local_time` `time_point`s, one with a precision of seconds, and the other with a precision of days. The days-precision `time_point` can be explicitly converted to a `year_month_day` object so that we can retrieve the year, month and day fields. + +The time of day is just the difference between the seconds-precision `time_point` and the days-precision `time_point`, which gives us the seconds since midnight. This duration can be broken down into a `{hours, minutes, seconds}` struct by converting it to a `time_of_day`. In C++17 the `` template parameter will be deduced by the seconds-presion duration used in the constructor. + +Now we just start filling out the `tm`, being careful to bias the month and year correctly. It is also good practice to first zero the entire `tm` so as to zero-out any platform-specific fields of the `tm`. + +The `weekday` encoding of this library is the same encoding used in C, and so that conversion is straight forward, going from `local_days`, to `weekday`, to `unsigned`, and finally to `int`. + +The computation for days-since-Jan 1 is found by simply subtracting the expression for New Years day for the current year from the already stored `local_days` value. + +Finally we can set `tm_isdst` to 1 if the `save` member of `sys_info` is not `0min`, and to 0 otherwise. The `sys_info` can be obtained from the `zoned_seconds`, and contains all kinds of useful information (including the UTC offset should you want to install that into your platform-specific `tm`). + +If we want to convert from a `sys_seconds` to a `tm`, that is quite easy to do using the conversion function above: + +```c++ +std::tm +to_tm(date::sys_seconds tp) +{ + return to_tm(date::zoned_seconds{tp}); +} +``` + +This creates a `zoned_time`, and defaults the `time_zone` to "UTC", then passes that `zoned_time` to `to_tm`. If desired, one could repeat the code from `zoned_seconds` instead of reuse it. This would save a small amount of processing time involved in looking up "UTC" in the database. And in this event you would always set `t.tm_isdst` to 0. One would also use `sys_days` in place of `local_days` in this alternative. + +One can also create a `tm` from `local_seconds`: + +```c++ +std::tm +to_tm(date::local_seconds tp) +{ + auto tm = to_tm(date::sys_seconds{tp.time_since_epoch()}); + tm.tm_isdst = -1; + return tm; +} +``` + +In this variant, the `time_zone` is unknown, and thus `-1` is the proper value for `tm.tm_isdst`. + +#### Converting from a `timespec` + +`timespec` is new in the latest C specification. It contains at least the following members in any order: + +```c++ +struct timespec +{ + time_t tv_sec; // whole seconds -- >= 0 + long tv_nsec; // nanoseconds -- [0, 999999999] +}; +``` + +C uses `timespec` as both a time point, and as a time duration. So we should be able to convert to both `nanoseconds` (a `duration`), and `sys_time` (a `time_point`). Both are easy. First to convert to a duration: + +```c++ +std::chrono::nanoseconds +to_nanoseconds(timespec const& ts) +{ + using namespace std::chrono; + return seconds{ts.tv_sec} + nanoseconds{ts.tv_nsec}; +} +``` + +One just converts the integrals to their proper `duration` types and adds them. The result has type `nanoseconds`. + +We can resume the above the above function to convert to a `time_point`: + +```c++ +date::sys_time +to_time_point(timespec const& ts) +{ + return date::sys_time{to_nanoseconds(ts)}; +} +``` + +Just get the duration and explicitly convert it to the proper `time_point`. + +#### Converting to a `timespec` + +The reverse conversions are only slightly more complex: + +```c++ +timespec +to_timespec(std::chrono::nanoseconds const& d) +{ + using namespace std::chrono; + timespec ts; + seconds s = duration_cast(d); + ts.tv_sec = s.count(); + ts.tv_nsec = (d - s).count(); + return ts; +} +``` + +First truncate the nanoseconds-precision duration to seconds-precision. That truncated value can be placed into `ts.tv_sec`. Now the difference between the original nanoseconds-precision duration and the seconds-precsion duration is the amount of a nanoseconds left over, and is assigned to `ts.tv_nsec`. + +The conversion to a `time_point` follow exactly the same logic, with the syntax being slightly modified to account for the fact that we're working with `time_point`s instead of `duration`s: + +```c++ +timespec +to_timespec(date::sys_time const& tp) +{ + using namespace std::chrono; + timespec ts; + auto tps = time_point_cast(tp); + ts.tv_sec = tps.time_since_epoch().count(); + ts.tv_nsec = (tp - tps).count(); + return ts; +} +``` + ### Get milliseconds since the local midnight (by [Howard Hinnant](https://github.com/HowardHinnant)) @@ -1955,4 +2152,4 @@ Where some_local_time and some_sys_time are template instantiations of local_tim *** -![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/)._ \ No newline at end of file