I get the epoch time in ms
from my API and would like to extract the year, month, etc. from it (also compare two dates, add hours/days/months, etc.).
I have watched Howard Hinnant "A chrono Tutorial" and tried cppreference, but I can't quite get it to work.
Here is all I have so far:
enum class DatePrecision { YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, MILLI };
long long et = 1693322440062;
std::chrono::milliseconds ms{ et };
std::chrono::time_point<std::chrono::system_clock> tp(ms);
int getTimeUnit(const std::chrono::time_point tp, const DatePrecision dp) {
/* .?. */
}
Where do I go from here? tp
only offers me time_since_epoch
as a function on it and as I said, I can't get the example from cppreference to work, especially when using utc_time
instead of system_time
. Extracting yeah/month/day would be a great start, but I would prefer
Thank you in advance for your help!
So this is pretty easy to do, but I don't recommend it. The reason I don't recommend it is that it is best to stay within the chrono type system so that the compiler can help you find any logic errors at compile-time. Returning these values as an int
erases the type safety that chrono provides you.
Ok, enough with the lecture. Here's how you can do it in C++20 (assuming your vendor has implemented all of the chrono parts of C++20):
#include <chrono>
#include <iostream>
enum class DatePrecision { YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, MILLI };
int
getTimeUnit(const std::chrono::system_clock::time_point tp, const DatePrecision dp)
{
using namespace std;
using namespace std::chrono;
auto tp_days = floor<days>(tp);
year_month_day ymd = tp_days;
hh_mm_ss hms{floor<milliseconds>(tp - tp_days)};
switch (dp)
{
case DatePrecision::YEAR:
return int{ymd.year()};
case DatePrecision::MONTH:
return unsigned{ymd.month()};
case DatePrecision::DAY:
return unsigned{ymd.day()};
case DatePrecision::HOUR:
return hms.hours().count();
case DatePrecision::MINUTE:
return hms.minutes().count();
case DatePrecision::SECOND:
return hms.seconds().count();
case DatePrecision::MILLI:
return hms.subseconds().count();
}
}
int main()
{
using namespace std;
using namespace std::chrono;
long long et = 1693322440062;
std::chrono::milliseconds ms{ et };
std::chrono::time_point<std::chrono::system_clock> tp(ms);
cout << tp << '\n';
cout << getTimeUnit(tp, DatePrecision::YEAR) << '\n';
cout << getTimeUnit(tp, DatePrecision::MONTH) << '\n';
cout << getTimeUnit(tp, DatePrecision::DAY) << '\n';
cout << getTimeUnit(tp, DatePrecision::HOUR) << '\n';
cout << getTimeUnit(tp, DatePrecision::MINUTE) << '\n';
cout << getTimeUnit(tp, DatePrecision::SECOND) << '\n';
cout << getTimeUnit(tp, DatePrecision::MILLI) << '\n';
}
Output:
2023-08-29 15:20:40.062000
2023
8
29
15
20
40
62
Explanation:
You need to switch on each desired field for the proper syntax to extract it from the time_point
. Note that std::chrono::time_point
is a template, not a type. I presumed you meant std::chrono::system_clock::time_point
(or equivalently std::chrono::time_point<std::chrono::system_clock>
) in the getTimeUnit
signature.
Also note that time_point
s based on system_clock
have the semantics of UTC, not local time.
For the "date fields", first truncate the time_point
to days
precision with floor<days>
, and then convert that to a year_month_day
. The year_month_day
type has getters for year
, month
and day
, which subsequently have explicit conversions to integral types.
For the "time of day fields", subtract the day-precision time_point
from the original to get the time of day as a duration. Then the type hh_mm_ss
transforms that duration
into a {hours, minutes, seconds, subseconds}
data structure with getters for each field.
If you don't have the C++20 chrono bits available to you, you can still use this code by taking advantage of my free, open-source, header-only C++20 chrono preview library.
Just add #include "date/date.h"
and using namespace date;
in appropriate places, and the rest of the syntax is the same. This will obviously easily port to C++20 when you are able to migrate to it.
Disclaimer: I've thrown this together, and it can probably be refactored to be a little bit neater. But hopefully this will get you started.
Using this a bit more, it is indeed problematic that this not aware of time zones (or is using the wrong one, I'm UTC+1). Is there any quick fix to consider these?
There's a few ways to interpret this, but I think I know what you mean. :-)
The third is trivial, and won't change the results. Though to be consistent, the input should use local_time
instead of sys_time
as the input (e.g. std::chrono::local_time<std::chrono::milliseconds>
as the first parameter).
The second would be somewhat unusual, but is certainly easy. It would be only a minor modification of the first choice (which I'm assuming you want).
I'm assuming you mean the first. Here is a slight modification of my original answer that outputs the units in a time zone of your choosing, and defaults to your computer's local time zone:
int
getTimeUnit(const std::chrono::system_clock::time_point tp_utc,
const DatePrecision dp,
const std::chrono::time_zone* tz = std::chrono::current_zone())
{
using namespace std;
using namespace std::chrono;
auto tp_local = tz->to_local(tp_utc);
auto tp_days = floor<days>(tp_local);
year_month_day ymd{tp_days};
hh_mm_ss hms{floor<milliseconds>(tp_local - tp_days)};
switch (dp)
{
case DatePrecision::YEAR:
return int{ymd.year()};
case DatePrecision::MONTH:
return unsigned{ymd.month()};
case DatePrecision::DAY:
return unsigned{ymd.day()};
case DatePrecision::HOUR:
return hms.hours().count();
case DatePrecision::MINUTE:
return hms.minutes().count();
case DatePrecision::SECOND:
return hms.seconds().count();
case DatePrecision::MILLI:
return hms.subseconds().count();
}
}
Besides some variable renaming (for clarity), there's really only two changes:
You first have to convert the input UTC time point to a local time point with auto tp_local = tz->to_local(tp_utc);
.
The conversion from the days
-precision time_point
to year_month_day
is now an explicit conversion as opposed to an implicit one: year_month_day ymd{tp_days};
. Explicit conversion syntax would have also worked in the original code.
Everything else is exactly the same, even including how you use it:
int h = getTimeUnit(tp, DatePrecision::HOUR);
However if you would like to specify the time zone instead of get the local one, that can be done like this:
int h = getTimeUnit(tp, DatePrecision::HOUR,
std::chrono::locate_zone("America/New_York"));