Open CKD4XXH opened 4 years ago
ymd.day() = 20_d;
ymd = ymd.year()/ymd.month()/20;
or:
ymd = ymd.year()/ymd.month()/20_d;
ymd.day() += days{2};
ymd = ymd.year()/ymd.month()/(ymd.day() + days{2});
The above expression might create a day field that is beyond the end of the month for ymd.year()/ymd.month()
. If that happens, ymd.ok() == false
.
ymd.month() = x3;
ymd = ymd.year()/x3/ymd.day();
Ditto about the subsequent possibility that ymd.ok() == false
.
hms.hours() = x1;
hms = hh_mm_ss{hms.to_duration() - hms.hours() + x1};
hms.hours() += x2;
hms = hh_mm_ss{hms.to_duration() + x2};
In summary, year_month_day
is good for getting the year
, month
and day
fields. It is also good for year
and month
arithmetic. It isn't that good at day
arithmetic unless you know that you're not going to overflow the day
field. sys_days
is the right data structure for general purpose day
arithmetic. And it is also good at converting year
, month
and day
fields into a chrono::time_point
.
In a nutshell, year_month_day
is not your one-stop-shopping destination for all of your date needs. It is convenient only for those things that the underlying data structure is actually good at. Other data structures, such as sys_days
are more convenient for other things, which they happen to be efficient for. And only with the union of all of the calendrical and chronological types, you get complete functionality.
hh_mm_ss
is barely more than a formatting aid. It is a convenient way to break a duration
down into human readable hours
, minutes
, seconds
fields. Its most important contribution to functionality is taking an arbitrary precision duration
and figuring out how many decimal digits of seconds
will exactly represent that duration
, if it is possible at all (which is non-trivial but necessary as part of the formatting process). For example:
using quarters = duration<int, ratio<1, 4>>;
hh_mm_ss hms{quarters{17}};
cout << "hms.fractional_width = " << hms.fractional_width << '\n';
cout << "hms.to_duration() = " << hms.to_duration() << '\n';
cout << "hms = " << hms << '\n';
Output:
hms.fractional_width = 2
hms.to_duration() = 425cs
hms = 00:00:04.25
sys_days is the right data structure for general purpose day arithmetic
Even when the ymd
is "local"? I mean:
// 'zoned' is some zoned time
date:local_time<std::chrono::milliseconds> ltim = zoned.get_local_time();
date:year_month_day ymd{date:floor<date:days>(ltim)};
When to use sys_days
and when to use local_days
?
When I tried to test, did not found any difference. They give the same results:
auto ymd1 = date:year_month_day{date:sys_days{ymd}};
stdate:cout << ymd1 << (ymd1.ok() ? "\n" : " invalid date\n");
ymd = date:year_month_day{date:local_days{ymd}};
stdate:cout << ymd << (ymd.ok() ? "\n" : " invalid date\n");
(I do not thoroughly understand, why.)
local_days
is also a good data structure to represent a date. It is just a count of days since 1970-01-01, just like sys_days
. The only difference between sys_days
and local_days
is that sys_days
represents a time point in UTC, and local_days
represents a time point that has no associated time zone. Though a time zone can be paired with local_days
to give it a precise meaning.
When converting among year_month_day <-> local_days
, the exact same formulas are used as converting among year_month_day <-> sys_days
. It's just that local_days
and sys_days
have subtly different meanings.
Example:
auto ymd = March/12/2020;
local_days ld{ymd};
sys_days sd{ymd};
auto sydney_zone = locate_zone("Australia/Sydney");
zoned_time zt1{sydney_zone, ld};
zoned_time zt2{sydney_zone, sd};
cout << zt1.get_sys_time() << '\n';
cout << zt2.get_sys_time() << '\n';
Above we start with a year_month_day
. Then we convert that to both local_days
and sys_days
. If we were to peek under the hood of both ld
and sd
we would see the exact same value (same count of days).
Next we create two zoned_time
s, both using "Australia/Sydney", but one using the local_days
value and one using the sys_days
value. These two zoned_time
s represent different instances in time. To see that, the UTC equivalent of each of these times is printed out:
2020-03-11 13:00:00
2020-03-12 00:00:00
The first does not even have the same date. The second is actually the same time that we constructed with. Equivalently we could have printed out the local time of both:
cout << zt1 << '\n';
cout << zt2 << '\n';
Output:
2020-03-12 00:00:00 AEDT
2020-03-12 11:00:00 AEDT
Now the first output is the identity operation with respect to the constructed time.
So for casual calendrical operations that need to convert between the {y, m, d}
data structure, and the {count of days}
data structure, it really doesn't make a lot of difference if you use local_days
or sys_days
. Whichever you find more readable is the winner.
But when dealing with time zones, and in particular zoned_time
, it is critically important that you use local_days
(local_time<Duration>
in general) when you mean "local time", and sys_days
(sys_time<Duration>
in general) when you mean UTC.
Howard, sorry for chiming in on this old thread, but I have a question regarding your last comment. I noticed that the conversions between sys_days and year_month_day are implicit, but they are explicit between local_days and year_month_day. So I wonder what was the design decision, to make it like this and not the other way round. Since both year_month_day and local_days have no relation to a particular clock or time zone, I would have thought it would be logical to allow direct conversions between those data types.
PS: Thank you for providing this great library - not only is it very useful, it also has taught me a lot on better c++ API design!
This is a good question.
First, the reason that the conversions between year_month_day
and local_days
/sys_days
aren't both implicit is that when I tried that I got ambiguities in some inconvenient expressions. So only one of them can be implicit. And the reason sys_days
was chosen as the implicit one is largely historical, as opposed to a solid technical reason: sys_days
came first in the development cycle. local_days
was invented only during the later development of the time zone library.
It would not be a bad design choice to make local_days
the one with the implicit conversion. However I never came up with a sufficiently motivating use case to change the status quo.
Thank you for your answer!
In my application I had to do lots of date operations with no particular time zone, like finding the start/end of business quarters, calculating deadlines, etc. So I started my implementation using sys_days
.
But later, occasionally, I had to determine if a certain time point (on the system_clock
) is within a given day. There, I have to associate the date of the day with the current local time zone before I can do the actual comparison.
Using sys_days
here could lead to errors because it already implies UTC, and one could easily forget to convert it to the local time zone.
So I changed my implementation to use local_days
instead of sys_days
, which, I think, matches my use case much better.
But then, I hat to add explicit conversions between year_month_day
and local_days
all over my code, which makes it more verbose than it was before.
So, for my application it would definitely be beneficial if local_days
would be implicitly convertible. But I'm afraid that a change to the library at this point in time will no longer be so easy, since it would cause other programs depending on the current behavior to break.
Yes, that is a good use case. And you did exactly the right thing. Thanks for the feedback.
Given some year_month_day or hh_mm_ss variable,
How can i do things like the following pseudo-code?
Are the year_month_day and hh_mm_ss objects mutable?
(Regarding the
+=
method: does it mutates? Or somehow creates a new object? (What happens with the old object?))