I'd like to manipulate local time dates and then compute the time span between them. Questions:
what is the best way to go from a zoned_time to his components (y,m,d,hh,mm,ss, etc...) and back (taking into account dst)?
what is the correct way to find the elapsing time between two zoned_times?
What I tried so far:
I found that I can get the now from clock and then compute a zoned_time. Manipulating this info is becoming a nightmare though.
I found on this answer that, Extract year/month/day etc. from std::chrono::time_point in C++
#include <chrono>
int
main()
{
using namespace std::chrono;
auto tp = zoned_time{current_zone(), system_clock::now()}.get_local_time();
auto dp = floor<days>(tp);
year_month_day ymd{dp};
hh_mm_ss time{floor<milliseconds>(tp-dp)};
auto y = ymd.year();
auto m = ymd.month();
auto d = ymd.day();
auto h = time.hours();
auto M = time.minutes();
auto s = time.seconds();
auto ms = time.subseconds();
}
this would allow for example to take the current date, and be able to set for example:
h = 5;
M = 0;
s = 0;
ms = 0;
and have the 5am of the current date, but how to convert this information back into a zoned_time? Obviously this doesn't work:
auto rec = zoned_time(current_zone(), y + m + d + h + M + s + ms);
This seems a good approximation:
auto rec = zoned_time(current_zone(), std::chrono::local_days{y / m / d} + h + M + s + ms);
if (tp_s == rec) std::cout << "equal" << std::endl;
else {
std::cout << "different" << std::endl;
std::cout << tp << "\t" << rec << std::endl;
}
but it prints:
different 2023-03-21 10:54:30.0128147 2023-03-21 10:54:30.012 GMT
aside the small difference in time, what is perplexing is the std::chrono::local_days{y / m / d} + h + M + s + ms
. It's not really clear if the addition means shifting by a time interval or setting the actual fields. It's quite different to add 5h if there is a DST in the middle, or setting the hours field to 5.
Finally, I'd like to find the true time interval between the two local times, and i think it's done by:
auto diff = duration_cast<chr::minutes>(tp_s.get_sys_time() - rec.get_sys_time());
so this should return the true time passed between the two points regardless the DST, while i'm not really understanding what this really represents:
auto diff = duration_cast<chr::minutes>(tp_s.get_local_time() - rec.get_local_time());
- what is the best way to go from a zoned_time to his components (y,m,d,hh,mm,ss, etc...) and back (taking into account dst)?
You've done it right, except that you go too far. A good rule of thumb in chrono is:
This means try not to convert to integral types.
auto zt = zoned_time{current_zone(), system_clock::now()};
auto tp = zt.get_local_time();
auto dp = floor<days>(tp);
year_month_day ymd{dp};
hh_mm_ss time{floor<milliseconds>(tp-dp)};
You now have one structure that holds the local {y, m, d}
and another structure that holds the local {hh, mm, ss, subseconds}
. And as you've seen there are getters on each of those fields, and if you really have to you can turn those results into integral values, but don't.
Now if you want to turn this into local 05:00:00 of that day, it is simply:
tp = dp + 5h;
Or if you prefer:
tp = local_days{ymd} + 5h;
To get it back into the zoned_time
:
zt = tp;
Note that an important difference between this and calling current_zone()
again is that each call to current_zone()
looks up the current current_zone()
. If you're worried about mobile devices, keep in mind that the return of current_zone()
can change at any time. And if you want to ensure that you get the same value during a computation, only call it once.
different 2023-03-21 10:54:30.0128147 2023-03-21 10:54:30.012 GMT
This is because tp
has type local_time<decimicroseconds>
(the native precision of your system_clock
on Windows) and rec
has precision milliseconds
given from this line:
hh_mm_ss time{floor<milliseconds>(tp-dp)};
Without the floor<milliseconds>
this would've yielded the native system_clock
precision.
In the local time addition:
tp = local_days{ymd} + 5h;
there is no implicit shifting under the hood. This is just straight forward arithmetic under the hood. It adds the number of days since 1970-01-01 to 5 hours, taking care of the conversion of days to hours under the hood. This is exactly the same arithmetic as if you had used sys_days
and done the arithmetic in Unix Time.
The UTC offset comes into play when you assign this back to a zoned_time
:
zt = tp;
The assignment recognizes the local_time
type and thus knows how to apply the UTC offset to get sys_time
(and vice-versa when assigned from sys_time
).
Also note that at this point in the computation, there are no longer any fields. Once you move away from year_month_day
and hh_mm_ss
, you are dealing in chronological types represented by a single value under the hood (count of ticks since the epoch).
Finally, I'd like to find the true time interval between the two local times, and i think it's done by:
auto diff = duration_cast<chr::minutes>(tp_s.get_sys_time() - rec.get_sys_time());
That is exactly correct. If there is a UTC offset change between these two instants, that change will be correctly accounted for.
while i'm not really understanding what this really represents:
auto diff = duration_cast<chr::minutes>(tp_s.get_local_time() - rec.get_local_time());
This is called "local time arithmetic" and is useful when you want to "preserve" parts of the local time throughout the arithmetic, effectively ignoring UTC offset changes.
A classic example is: I want to have a meeting at 9am local time every morning. This morning's meeting is at:
zoned_time zt{current_zone(), local_days{2023y/March/21} + 9h};
Tomorrow's meeting is at:
zt = zt.get_local_time() + days{1};
Remember, there are no fields here. Just a count of ticks from the epoch. You add exactly 1 day (24 hours, or 86400 seconds) to the local time. This will get you 9am tomorrow, whether or not there is a UTC offset change tonight.
If there is a UTC offset change between today and tomorrow, then if you retrieved the sys_time
before and after the addition of days{1}
, then that difference would not be equal to days{1}
.
And if you wanted the next meeting to be exactly 24 hours later in physical time, one would do the arithmetic in sys_time
instead of in local_time
.