C++ Convert Unix time (nanoseconds) to readable datetime in local timezone - c++

I have a Unix time (nanoseconds since epoch) and I would like to go back and forth between this and a readable string in a specifiable timezone (using TZ strings), with nanoseconds preserved. I am using C++17 but willing to migrate to C++20 if it would make things much easier.
Example:
uint64_t unix_time = 1669058870115719258;
std::string datetime = convert_unix_to_datetime(unix_time, "America/Detroit");
std::cout << datetime << "\n";
std::cout << convert_datetime_to_unix_time(datetime) << "\n";
Desired output:
2021-11-21 14:27:50.115719258
1669058870115719258
Apologies if this is a basic question--I have searched around the site and tried to implement solutions but keep getting confused by different C++ versions, whether the datetime is in UTC or local timezone, different types like chrono::time_point vs. time_t etc. Hoping to find a simple solution here.

Using C++20, this is very easy. Using C++11/14/17 it is harder but doable with a free, open-source time zone library.
Here is what it looks like with C++20:
#include <chrono>
#include <cstdint>
#include <format>
#include <iostream>
#include <sstream>
#include <string>
std::string
convert_unix_to_datetime(std::uint64_t unix_time, std::string const& tz)
{
using namespace std;
using namespace chrono;
sys_time<nanoseconds> tp{nanoseconds{unix_time}};
return format("{:%F %T} ", zoned_time{tz, tp}) + tz;
}
The first line converts the uint64_t, first into a nanoseconds chrono::duration, and then into a nanoseconds-precision chrono::time_point based on system_clock. chrono::system_clock uses Unix Time as its measure: http://eel.is/c++draft/time.clock.system#overview-1
The second line creates a chrono::zoned_time from the time zone name, and the sys_time timepoint. A zoned_time is a simple pairing of these two objects from which you can extract a local time. When formatted, it is the local time that is printed. Here I've used "%F %T" as the format which will output with the syntax: YYYY-MM-DD HH:MM:SS.fffffffff. Many formatting flags are available..
Finally I've added the time zone name to the timestamp. This is done so that the parsing function can recover the time zone name as it is not passed in as one of the parameters to convert_datetime_to_unix_time.
std::uint64_t
convert_datetime_to_unix_time(std::string s)
{
using namespace std;
using namespace chrono;
istringstream in{std::move(s)};
in.exceptions(ios::failbit);
std::string tz_name;
local_time<nanoseconds> tp;
in >> parse("%F %T %Z", tp, tz_name);
zoned_time zt{tz_name, tp};
return zt.get_sys_time().time_since_epoch().count();
}
The first line moves the string into a istringstream as the parsing must use a stream. I've set the stream to throw an exception if there is a syntax error in the input s. If you would rather check for failbit manually, remove the line that sets failbit to throw an exception.
Next arguments are default constructed which will be parsed into:
std::string tz_name;
local_time<nanoseconds> tp;
Here it is important to use the type local_time as opposed to sys_time because it is the local time that was formatted out. Use of the local_time type informs the library how to apply the UTC offset under the zoned_time constructor after parse.
The parse can pick up the time zone name with the %Z flag, and will place it in the last argument to parse.
Construct the zoned_time with the time zone and time point.
Finally, the sys_time (Unix Time) can be extracted from the zoned_time with the .get_sys_time() member function. This will have precision nanoseconds since the input local_time has precision nanoseconds. The underlying integral count of nanoseconds is extracted with .time_since_epoch().count().
Using your example driver, this will output:
2022-11-21 14:27:50.115719258 America/Detroit
1669058870115719258
As I write this, only the latest MSVC tools fully implement this part of C++20.
If you need to use the free, open-source time zone library, a few minor changes are needed:
Add using namespace date; to each function.
Change the format string from "{:%F %T} " to "%F %T ".
Add #include "date/tz.h".
Compile and link to tz.cpp using the instructions at https://howardhinnant.github.io/date/tz.html#Installation
This will by default download a copy of the IANA time zone database, though there are ways to avoid that on non-Windows platforms (described in the installation instructions).

Related

Converting ISO8601 string to milliseconds since epoch

Sample string:
2018-10-31T14:45:21.778952-07:00
I would like to convert it to int64_t representing milliseconds (or even microseconds) since epoch. The timezone can vary. The code will be executing on a linux box, but I only have access to std and folly (cannot use any arbitrary 3P libraries).
I searched for this and found a few different ways that do not work for me:
strptime() and std::get_time() lose the millisecond precision
More importantly, neither of those can deal with timezone offsets
Some other solutions depend on 3P libraries
Is there some easy way to do this?
From the comments above:
I am looking into using Howard's library. However, that it makes a web call gives me pause. I assume that if the call fails it will just use the locally stored timezone name data? We won't be dealing with timezone names, so I don't care about those. However, making a network call might be an issue.
Howard's library is layered:
A foundational layer that does not need the IANA time zone database and thus never makes networking calls. This is a single header, header-only library.
A time zone layer that is aware of the IANA time zone database. This layer can be configured to make network calls or not (depending on build flags), or even use your OS's time zone database (except on Windows).
Your application does not require the time zone layer as it only deals with UTC offsets, and not time zone names or rules.
Here is a function that converts a std::string with your sample format into a std::chrono::time_point<std::chrono::system_clock, std::chrono::microseconds>. That type is a big verbose mouthful for a system_clock::time_point except guaranteed to have microseconds precision. The foundational layer has a type alias for this type called date::sys_time<std::chrono::microseconds>.
#include "date/date.h"
#include <chrono>
#include <iostream>
#include <sstream>
auto
to_sys_time(std::string s)
{
using namespace date;
using namespace std;
using namespace std::chrono;
istringstream in{move(s)};
in.exceptions(ios::failbit);
sys_time<microseconds> tp;
in >> parse("%FT%T%z", tp);
return tp;
}
Put the string into a istringstream.
Optionally set the istringstream to throw an exception if it fails to parse something (you may choose to handle errors differently).
Declare your time_point with the desired precision.
Parse it with your desired format.
Return it.
You can exercise this function like so:
int
main()
{
auto tp = to_sys_time("2018-10-31T14:45:21.778952-07:00");
using date::operator<<;
std::cout << tp << " UTC\n";
std::cout << tp.time_since_epoch() << '\n';
}
Call to_sys_time with the desired input string.
Make the streaming operators in namespace date available.
Print out the time_point (this is a UTC time_point).
Extract and print out the duration of the time_point.
The output of this program is:
2018-10-31 21:45:21.778952 UTC
1541022321778952µs
This program will port to C++20 by removing #include "date/date.h", using namespace date; and using date::operator<<;.

does a C++ locale have an associated timezone? And if yes, how do you access it?

I've done a little research on this, and I have pretty convincing evidence for the
answer YES, and the answer NO. I'm not sure which side to believe.
First, the documentation I've found on cppreference.com, and
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf appear to say nothing about this.
I take that as evidence that locales do NOT support a timezone.
But https://en.cppreference.com/w/cpp/locale/time_get/get and https://en.cppreference.com/w/cpp/locale/time_put/put
both say:
%z writes offset from UTC in the ISO 8601 format (e.g. -0430), or no
characters if the time zone information is not available %Z writes
time zone name or abbreviation, or no characters if the time zone
information is not available (locale dependent)
which appears to suggest there is a timezone SOMETIMES associated with
a locale () object.
Now, if you take the locale en_US.utf8 (one of my favorites ;-)), there really isn't any sensible
timezone to associate (the United States contains at least 4 or more timezones).
So time to get empirical.
I ran the code:
#include <iostream>
#include <cstdlib>
#include <locale>
#include <sstream>
using namespace std;
int main ()
{
// locale l;
locale l = locale::classic ();
tm when{};
const time_put<char>& tmput = use_facet<time_put<char>> (l);
ostringstream oss;
oss.imbue (l);
static const string kTZOffsetPattern_{"%z"};
tmput.put (oss, oss, ' ', &when, kTZOffsetPattern_.c_str (), kTZOffsetPattern_.c_str () + kTZOffsetPattern_.length ());
cout << oss.str ();
return 0;
}
On Linux (ubuntu), this gave the answer I expected, +0000 (OK, I also wouldn't have been surprised with error or empty string).
But on Windows (visual studio.net 2k17 - 15.8.7) - this gives:
-0500
Yes, as you may have guessed, I'm testing this in the eastern timezone. But I still would have expected
0, or the empty string (especially for the locale::classic() case).
Direct Answer to your question
Does a C++ locale have an associated time zone?
No.
Nor will it in the future. As correctly noted in the question, for many locales it wouldn't make sense as the geographical area represented by the locale can have more than one time zone.
The C standard does say in the spec for strftime:
%Z is replaced by the locale’s time zone name or abbreviation, or by no characters if no
time zone is determinable. [tm_isdst]
But the C spec for struct lconv provides no such member to store that information. The spec does allow implementations to add such members, but in practice, implementations do not store that information with the C locale.
The C++ locale facets time_put and time_get define themselves in terms of the C spec for strftime, the POSIX spec for strptime, and some additions, which do not include a time zone name or abbreviation.
The POSIX spec for strftime is much more detailed than the C spec, and removes the association with "locale":
Z Replaced by the timezone name or abbreviation, or by no bytes if no timezone information exists. [ tm_isdst]
The POSIX spec for struct lconv is also much more detailed than the C spec, but still does not provide storage for a time zone name or abbreviation.
But the future does bring hope of more easily and effectively accessing information about time zones, at least in C++.
Prior to C++20, C++ has knowledge of:
A single time standard: UTC, which is closely modeled by Unix Time.
A single time zone: the "local time zone" set by the user or administrator of the computer. UTC can also be used as a local time zone.
As detailed above, the local time zone is not part of the C++ (or C) locale data. The locale data does include some calendrical data such as:
Full and abbreviated weekday names.
Full and abbreviated month names.
Local conventional formats for displaying date and time (e.g. year, month, day ordering).
The UTC offset (%z) and time zone abbreviation (%Z) may be available, but would be stored as part of the local time zone data, instead of with the current locale data, largely because there is not a good one-to-one mapping between time zones and locales.
Explanation of what happened with the code presented in the OP's question
In your example: tm when{}; zeroes all members of the tm, including tm_isdst. When tm_isdst is zero, this means Daylight Saving Time is known to not be in effect, for this particular tm.
tm is also allowed to have members not specified by the standard. A popular extension is to have a member tm_gmtoff which holds the UTC offset in seconds. If your Linux implementation has such a member, tm when{}; would have set it to 0 seconds. If your Windows implementation does not have such a member, the UTC offset of the local time zone would be stored elsewhere. This explains the differences you're seeing, and both implementations are conforming.
Helpful information about how to access time zones since C++ locales do not provide access
In the C++20 spec, there exists a new type called std::chrono::time_zone. One of the member functions of time_zone is:
template<class Duration> sys_info get_info(const sys_time<Duration>& st) const;
sys_time<Duration> is just a system_clock::time_point, but of any precision. So you give a time_zone a time_point, and you get back a sys_info which contains all kinds of useful information about that time_zone at that time_point:
struct sys_info
{
sys_seconds begin;
sys_seconds end;
seconds offset;
minutes save;
string abbrev;
};
The range [begin, end) tells you for what times this information is valid (these are UTC time points).
offset is the time_zone's current UTC offset in seconds.
If save != 0min, the time_zone is currently considered to be in daylight savings.
The time_zone's current abbreviation is stored in abbrev.
Additionally, there is a non-member function:
const time_zone* current_zone();
which returns a pointer to your current local time zone. Putting this all together, here is a C++20 program that prints out interesting information about your current local time zone:
#include <chrono>
#include <iostream>
int
main()
{
using namespace std::chrono;
std::cout << current_zone()->get_info(system_clock::now()) << '\n';
}
This just output for me:
2018-03-11 07:00:00
2018-11-04 06:00:00
-04:00:00
01:00
EDT
If you like, you can experiment with this part of C++20 using C++11, 14 or 17 by using Howard Hinnant's timezone library. This library puts everything in namespace date instead of std::chrono.
You can also get information on any IANA time zone, for example:
#include "date/tz.h"
#include <chrono>
#include <iostream>
int
main()
{
using namespace date;
using namespace std::chrono;
std::cout << locate_zone("Australia/Sydney")->get_info(system_clock::now()) << '\n';
}
which just output for me:
2018-10-06 16:00:00
2019-04-06 16:00:00
11:00:00
01:00
AEDT
Note though that even in C++20, time zones and locales are not coupled. It just doesn't make sense to do so.
does a C++ locale have an associated timezone?
All aspects of current timezone are implementation defined.
The exact wording of %Z specifier from C99 (C++ delegates C library function specification to the C standard) is:
is replaced by the locale’s time zone name or abbreviation, or by no characters if no time zone is determinable.
It seems a bit ambiguous. One interpretation is indeed that locale may affect the time zone. Another, which doesn't quite fit the wording as well, would be that the locale affects the name or abbreviation of the time zone. Regardless, there appears to be no guarantee that the time zone isn't affected by the locale, although I would not expect that it were.
how do you access it?
As far as I know, you can't using standard library utilities. Not directly anyway, and no way of modifying it.
A way to print the current timezone is to use the %z or %Z format specifiers of strftime/put_time/time_put as you've shown.
There is a way to get the zone difference as an integer as well. std::mktime unparses a std::tm structure into a timestamp according to the locale, while std::gmtime parses a timestamp into std::tm structure according to the UTC, so if you start with the epoch and combine those two, you will get the difference of the current locale time zone and the UTC in seconds.
std::time_t t = 0;
std::cout << -1 * std::mktime(std::gmtime(&t));
The relevant passage of the C standard (which the C++ standard relies upon) says
%z is replaced by the offset from UTC in the ISO 8601 format ‘‘−0430’’ (meaning 4 hours 30 minutes behind UTC, west of Greenwich), or by no characters if no time zone is determinable. [tm_isdst]
%Z is replaced by the locale’s time zone name or abbreviation, or by no characters if no time zone is determinable. [tm_isdst]
Note that time zone name is said to be locale dependent, but time zone offset is not.
Cppreference needs to fix their sloppy wording.

Using std::chrono / date::gps_clock for converting a double gps timestamp to utc/tai

I get a timestamp from a GPS device in a gps_data struct as a double.
I'd like to convert this GPS timestamp to UTC and TAI times, something simple as:
void handle_gps_timestamp(double timestamp)
{
double utc = utc_from_gps(timestamp);
double tai = tai_from_gps(timestamp);
do_stuff(gps, utc, tai);
}
Luckily I found Howard Hinnant's date and timezone library (proposed for C++20) that seems to provide this exact functionality. Unfortunately, at least from what I can see, the date/tz/chrono library has no convenient methods that allow this simple usage.
I must first somehow "transfer" my double into a known chrono/date type. But OK, since I understand the overall concept, namely that the timepoint is defined as a duration after (or before) the epoch of a clock, and I think that this is a beautiful model.
Assumption
I should be able to very easily translate that model to fit my problem, right?
In my case, I have a timestamp that is a point in time, specified as the duration since the gps epoch. Now, there should be a class type of a clock that abstracts and handles all of this for me, I'd like to think. And yes! There is a date::gps_clock and a date::gps_time, which surely should do the work.
Problem
I cannot make it work for me. I'm sure the solution is trivial.
Question
Can someone give me a helping hand, showing how I should use Howard's date library applied to my problem?
It is difficult to answer this question precisely because the input to the problem is underspecified:
I get a timestamp from a GPS device in a gps_data struct as a double ... specified as the duration since the gps epoch.
Therefore I'm going to make some assumptions. I'll state all of my assumptions, and hopefully it will be clear how to alter my answer for other guesses/facts about what that double represents.
Let's say that the double is a non-integral count of milliseconds since the gps epoch. Let's furthermore assume that I want to capture the precision of this input down to microseconds.
#include "date/tz.h"
#include <cstdint>
#include <iostream>
int
main()
{
double gps_input = 1e+12 + 1e-3;
using namespace date;
using namespace std::chrono;
using dms = duration<double, std::milli>;
gps_time<microseconds> gt{round<microseconds>(dms{gps_input})};
auto utc = clock_cast<utc_clock>(gt);
auto tai = clock_cast<tai_clock>(gt);
std::cout << gt << " GPS\n";
std::cout << utc << " UTC\n";
std::cout << tai << " TAI\n";
}
I've arbitrarily created an example input and stored it in gps_input.
Some using directives make the code a lot less verbose.
A custom chrono::duration type that exactly matches the documented specification for what the double represents makes things much simpler, and lessens the chance for errors. In this case I've made a chrono::duration that stores milliseconds in a double and named that type dms.
Now you simply convert the double to dms, and then using round, convert the dms to microseconds, and store those microseconds in a gps time point with precision microseconds or finer. One could use duration_cast in place of round, but when converting from floating point to integral, I usually prefer round, which means round-to-nearest-and-to-even-on-tie.
Now that you have a gps_time, one can use the clock_cast function to convert to other times such as utc_time and tai_time.
This program outputs:
2011-09-14 01:46:40.000001 GPS
2011-09-14 01:46:25.000001 UTC
2011-09-14 01:46:59.000001 TAI
Adjust the milliseconds and microseconds units above as needed. For example if the input represents seconds, the easiest thing to do is to default the second template argument on dms:
using dms = duration<double>;
This library works with C++11/14/17. And with minor modifications it is now part of the official C++20 specification.
The other answer isn't bad but it does require you to have c++17, curl, run cmake, and acquire some custom libraries.
Something that is much easier to drop in as a .h and .cpp would be http://www.leapsecond.com/tools/gpsdate.c.
That doesn't handle the TAI conversion but that might also be on that list.

How to get the local current time in seconds since epoch in C++ (MSVS)?

I need the local (with timezone offset) current time in seconds since epoch. The following code looks a bit clumzy because it creates an intermediate temporary structure tm which is superfluous. Why do I have to get time_t then convert it to tm in order to return to time_t? Is there a better way?
time_t ct = time(0);
tm lct = tm();
localtime_s(&lct, &ct);
ct = _mkgmtime(&lct);
If you want to get the local time (with time zone and DST applied) in portable C, then yes, it's generally a two-step procedure: starting with your time-since-the-epoch, first call localtime, then do something with the resulting broken-down struct tm. (Usually what I do next is call strftime.)
You can also call ctime to get a local time string directly.
The reason there are a lot of different function calls involved is that, unfortunately, there are several different time formats in use. (And the reason for that is that dates and times are complicated!) You can represent time as seconds-since-1970. You can represent it as a struct tm. You can represent it as a string (in one of several zillion formats). In Unix and Linux, you can represent it as a struct timeval or a struct timespec.
But one thing there isn't a straightforward or standard way to do, as you've discovered, is get local time as seconds-since-1970. But the reason for that is that it's not a very useful representation. In general, there are two things you might want to do with a date/time value: (1) perform computations on it or (2) display it to the user. If you want to display it to the user, you probably want to display it in local time, so there are lots of ways of converting to local time in human-readable format in any format you want. (As I said, the usual way is to call localtime, then strftime.) But if you want to perform computations, really the only way to do those is using seconds-since-1970 in UTC, because that makes all the other hairy problems go away. How many days are there in the month? Is it a leap year? What time zone are we in? Is daylight saving time in effect?
If you try to represent local time as seconds-since-1970, though, you're probably fibbing. For example, right now, the time is 1460383736, which is 14:08:56 UTC. Where I'm sitting, that's 10:08:56 EDT (U.S. Eastern time, DST in effect). So I suppose I could say that's 1460369336 seconds since 1970, local time. But, again where I'm sitting, 1460369336 seconds ago was not midnight on January 1, 1970 -- it was actually 11 pm on December 31, 1969. It's off by an hour, and the reason is that DST was not in effect on January 1, 1970.
So, bottom line, I would encourage you to rethink the way you're handling local times, because while it's possible to compute this "seconds-since-1970 as local time" value, it's an odd thing to do, and it's likely to cause you various problems which will be much harder to work around than if you used a more straightforward scheme.
But, if you really want to, here are two ways you might be able to determine the offset between UTC and local time, without calling gmtime or _mkgmtime:
Call localtime, and look at the tm_gmtoff field. (Unfortunately, this field is nonstandard, and not present on all systems.)
Call the obsolete ftime function, and look at the timezone field of struct timeb. (Here there are several gotchas: not only is ftime obsolete and nonstandard, but the timezone field is in minutes, and it's positive for zones west of Greenwich, while tm_gmtoff is negative.)
But, anyway, those would more or less directly give you the number to add to or subtract from your UTC seconds-since-1970 value to get "local" seconds-since-1970.
Here is a way to do this computation using the C++11/14 <chrono> library plus this free, open-source timezone library to do the conversion to local time.
#include "tz.h"
#include <iostream>
int
main()
{
using namespace date;
using namespace std;
using namespace std::chrono;
auto now = floor<seconds>(system_clock::now());
auto s = current_zone()->to_local(now) - local_days{1970_y/jan/1};
cout << s.count() << '\n';
}
You first discover your current IANA timezone with current_zone(). Then you get the current time with system_clock::now() and truncate it to seconds. Next you can convert that to your local time, and then subtract the result from any epoch you desire (1970-01-01 in this example).
The result is of type std::chrono::seconds.
All this being said, I share the same reservations about doing this as described in Steve Summit's answer.
If you instead decide to represent the timestamp as a string, that is also easily done:
auto now = make_zoned(current_zone(), floor<seconds>(system_clock::now()));
auto str = format("%F %T %z", now);
str has type std::string. This just output for me:
2016-04-11 11:42:50 -0400
which is my current local time (truncated to seconds), and my current local UTC offset.
If in the future you decide that seconds-precision is too coarse, you can easily change the above code to any other precision by just changing one line:
floor<milliseconds>(system_clock::now());
and now the contents of str would look like:
2016-04-11 11:42:50.368 -0400

Is there a way to determine if a date/time does not exist?

Fun fact that I'm sure most of us who get to play in the time realms know - there are date/times that can appear valid but actually do not exist, e.g. 2:30 AM on a daylight savings switching time.
Is there a way in C++ (standard or Windows) to figure out if a given date/time is valid in a given time zone specification?
Using Windows-specific functions, you can make a call to TzSpecificLocalTimeToSystemTime() followed by a call to SystemTimeToTzSpecificLocalTime() with the relevant time zone.
Upon completion, if the two differ you have an invalid time as TzSpecificLocalTimeToSystemTime converts the invalid time to a "real" time.
bool IsValidTime(TIME_ZONE_INFORMATION tz, SYSTEMTIME st)
{
SYSTEMTIME utcSystemTime, st2;
TzSpecificLocalTimeToSystemTime(&tz, &st, &utcSystemTime);
SystemTimeToTzSpecificLocalTime(&tz, &utcSystemTime, &st2);
return (st != st2);
}
Using this free, open-source library:
#include "tz.h"
#include <iostream>
int
main()
{
using namespace date;
using namespace std::chrono_literals;
try
{
auto zone = locate_zone("America/New_York");
zone->to_sys(local_days{mar/13/2016} + 2h + 30min);
}
catch (const std::exception& e)
{
std::cout << e.what() << '\n';
}
}
The output of this program is:
2016-03-13 02:30 is in a gap between
2016-03-13 02:00:00 EST and
2016-03-13 03:00:00 EDT which are both equivalent to
2016-03-13 07:00:00 UTC
In short, this program attempts to translate a locale date/time of 2016-03-13 02:30:00 to UTC using the timezone "America/New_York". The translation throws an exception because the local time doesn't exist. An exception is also thrown (with a different error message), if the local time is ambiguous, such as when setting the local clock back from daylight saving.
The library also provides syntax for avoiding these exceptions if that is the desire.
This works on VS-2013, VS-2015, clang, and gcc. It requires C++11, C++14 or C++1z.
The link above points to the documentation. Here is the github repository:
https://github.com/HowardHinnant/date
The time_t format is an integer value (seconds since 1/1/1970 00:00 UTC) so every possible value of time_t exists (within the limits of the integer type used of course).
On the other hand, not all possible values of "struct tm" (where you have members for day, month, year, hour, minute and second) exist (and this is probably what you mean). To check whether a "struct tm" exists, you have to do the following:
Set the member tm_isdst to -1. This indicates that you don't know whether its DST or not.
Convert the "struct tm" to a time_t using mktime().
Convert the time_t back to "struct tm" using localtime().
Compare the resulting "struct tm" with the initial "struct tm", ignore the tm_isdst field (or use it to see whether you are in DST or not).