I want to convert a UTC year, month, day to a std::chrono::time_point
(and also display it).
#include <chrono>
using namespace std::chrono;
void DisplayDate(int y, int m, int d) {//e.g. 2024, 11, 22
time_point<system_clock> tp;
//tp = system_clock::now(); // this works as expected.
tp = system_clock::time_point(years(y-1970) + months(m-1) + days(d-1));// this doesn't
std::wstring s = std::format(L"{:%Y-%m-%d %H:%M:%S}", zoned_time{current_zone(), tp});
wprintf(s.c_str());
}
Here DisplayDate(2024,11,22)
yields 2024-11-21 04:07:48.0000000
, but I would expect 2024-11-22 00:00:00.0000000
and I am at a loss (especially) where the 04:07:48
comes from and how to fix it. Especially the strange offset of 7:48
puzzles me, even after hours of looking into this.
I could use the std::tm
detour from How to get chrono time_point from year, month, day, hour, minute, second, millisecond? but I want to understand how to do this directly.
I want to convert a UTC year, month, day to a std::chrono::time_point
(and also display it).
#include <chrono>
using namespace std::chrono;
void DisplayDate(int y, int m, int d) {//e.g. 2024, 11, 22
time_point<system_clock> tp;
//tp = system_clock::now(); // this works as expected.
tp = system_clock::time_point(years(y-1970) + months(m-1) + days(d-1));// this doesn't
std::wstring s = std::format(L"{:%Y-%m-%d %H:%M:%S}", zoned_time{current_zone(), tp});
wprintf(s.c_str());
}
Here DisplayDate(2024,11,22)
yields 2024-11-21 04:07:48.0000000
, but I would expect 2024-11-22 00:00:00.0000000
and I am at a loss (especially) where the 04:07:48
comes from and how to fix it. Especially the strange offset of 7:48
puzzles me, even after hours of looking into this.
I could use the std::tm
detour from How to get chrono time_point from year, month, day, hour, minute, second, millisecond? but I want to understand how to do this directly.
1 Answer
Reset to default 5time_point<system_clock> tp;
The above is correct and fine. But just fyi, here is another way to say the same thing:
system_clock::time_point tp;
Which you choose is just a matter of style and readability for you.
tp = system_clock::time_point(years(y-1970) + months(m-1) + days(d-1));
This doesn't do what you think it does.
years
(note plural) is a duration
with the length of the average civil year: 365.2425 days.
months
(note plural) is a duration
with the length of the average civil month: 30.436875 days. This is exactly 1/12 of years
.
What you intend is to convert the {y, m, d}
triple to a year_month_day
data structure, and then convert that to a days-precision time_point
based on system_clock
, which will subsequently implicitly convert to a system_clock::time_point
. Sounds complicated. It is not:
tp = sys_days{year{y}/m/d};
The subexpression year{y}
creates a type std::chrono::year
from y
. The offset is year 0. So 2024 means year 2024.
The subexpression year{y}/m/d
creates a type year_month_day
which does no computation but merely stores y
, m
, and d
. For each of the fields the offset is 0. 11
for month means November
. 22
for day means the day 22 in the indicated month.
A more verbose but equivalent statement would be: year{m}/month{m}/day{d}
. The 2 latter types are implied by specifying the first type year
. Note that all of these type names are singular, not plural. These singular types are "calendrical specifiers". Plural types are duration
s.
sys_days
is just a type alias for a time_point
based on system_clock
with a precision of days
. And there is an implicit conversion from year_month_day
to sys_days
. Here I'm using explicit conversion syntax just because it is convenient to do so.
std::wstring s = std::format(L"{:%Y-%m-%d %H:%M:%S}\n", zoned_time{current_zone(), tp});
time_point
s based on system_clock
are UTC. If what you want is UTC, there is no need to involve any time_zone
, including your computer's currently set time_zone
returned by current_zone()
. You can just format
tp
directly:
std::wstring s = std::format(L"{:%Y-%m-%d %H:%M:%S}\n", tp);
Furthermore, there exists "shortcuts" for both %Y-%m-%d
and %H:%M:%S
if you prefer. They are %F
and %T
respectively. They offer no advantages or disadvantages besides what you may perceive as readability (either less or more, your choice). But the above line could be rewritten as:
std::wstring s = std::format(L"{:%F %T}\n", tp);
In either case the subsequent wprintf
will output:
2024-11-22 00:00:00.0000000
Involving zoned_time
and current_zone
in the format
statement causes the format
to convert the sys_time
(UTC) to local_time
according to your computer's locally set time_zone
obtained via current_zone()
.
From the comments:
What would be the format if I wanted millisecond precision, like for example 00:00:00.123.
The easiest way to control the precision for formatting is to control the precision for your entire use case. That is, traffic in millisecond precision from the point of creation of your time_point
s, not just at format-time.
In this example that looks like:
sys_time<milliseconds> tp = sys_days{year{y}/m/d};
And the output is now:
2024-11-22 00:00:00.000
sys_time
is just a template type alias for time_point<system_clock, D>
where D
is whatever precision you want. %T
will print out the full precision of whatever it is given.
Another way to achieve the same thing is:
auto tp = sys_days{year{y}/m/d} + 0ms;
Now the type of tp
is deduced as sys_time<milliseconds>
from the precision of the initialization expression.
The same technique is used to specify a time of day (UTC):
#include <chrono>
#include <format>
#include <iostream>
void
DisplayDateTime(int y, int m, int d, int h, int M, int s, int ms)
{
using namespace std::chrono;
auto tp = sys_days{year{y}/m/d} + hours{h} + minutes{M} + seconds{s}
+ milliseconds{ms};
std::wstring str = std::format(L"{:%F %T}\n", tp);
wprintf(str.c_str());
}
int
main()
{
DisplayDateTime(2024, 11, 22, 16, 19, 13, 127);
}
Output:
2024-11-22 16:19:13.127