Problem parsing date/time with timezone name using Howard Hinnant's library - c++

I'm writing a method to parse date/time strings in a variety of formats.
std::chrono::system_clock::time_point toTimePoint(const std::string str) {
... a bunch of code that determines the format of the input string
std::string formatStr = string{"%Y-%m-%d"}
+ " " // Delimeter between date and time.
+ "%H:%M:%S"
+ "%t%Z"
;
// The %t should be 0 or 1 whitespace
// The %Z should be a timezone name
std::chrono::system_clock::time_point retVal;
std::istringstream in{str};
in >> date::parse(formatStr, retVal);
return retVal;
}
I then test it with a variety of inputs. The other formats work. I can do these:
2022-04-01 12:17:00.1234
2022-04-01 12:17:00.1234-0600
2022-04-01 12:17:00.1234-06:00
The latter two are for US Mountain Daylight Time. It does all the right things. The first one shows as 12:17:00 UST. The other two are 18:17:00 UST. Working great. I've omitted all that code for brevity. What does not work is this:
2022-04-01 12:17:00.1234 US/Central
I've tried a variety of timezone names after writing a different program to dump the ones known by Howard's library. None of them matter. I get a UST-time value with no time zone offset.
Luckily, what I need right now is the -06:00 format, so I can move forward. But I'd like to fix the code, as we have other places that use timezone names, and I'd like to get this working properly.
I'm not sure what I'm doing wrong.

When reading an offset with %z (e.g. -0600), combined with a sys_time type such as system_clock::time_point, the parse time point is interpreted as a local time, and the offset is applied to get the sys_time, as desired in your first two examples.
However this is not the case when reading a time zone name or abbreviation with %Z (note the change from lower case z to upper case Z).
%Z parses a time zone abbreviation or name, which is just a string. The common case is for this to just parse an abbreviation, e.g. CST. And in general, there is no unique mapping from an abbreviation to an offset. And so the offset can not be internally applied. Thus the parsed value should always be interpreted as a local time.
However all is not lost. You can parse the time zone name with %Z into a string, and then look up the time_zone with that name and use it to convert the parse local_time into a sys_time. This could look like:
#include "date/tz.h"
#include <chrono>
#include <iostream>
#include <sstream>
int
main()
{
using namespace date;
using namespace std;
using namespace std::chrono;
istringstream in{"2022-04-01 12:17:00.1234 US/Central"};
string tz_name;
local_time<microseconds> local_tp;
in >> parse("%F %T%t%Z", local_tp, tz_name);
system_clock::time_point tp = locate_zone(tz_name)->to_sys(local_tp);
cout << tp << '\n';
}
Just add a string as the third argument in your parse call, and make sure the first argument is a local_time instead of a sys_time. Then use locate_zone to get a time_zone const* and call to_sys with that, passing in the parsed local_time.
The above program outputs:
2022-04-01 17:17:00.123400
This is an hour off from the -6h offset because US/Central goes to daylight saving on 2022-03-13 (-5h offset).

Related

C++ String to DateTime Functions

Coming from C# I'm a bit lost with the datetime functionality in C++. I am simply looking to convert from a string in the format 2023-01-12T07:00:00+08:00 to the number of seconds since 1-1-2023 UTC.
And the reverse, i.e. an int of the number of seconds since the start of 2023 to a string in the format "%Y-%m-%dT%H:%M:%S%z". Any code or pointers in the right direction would be greatly appreciated.
Have tried various options using chrono and time_t which seems to work:
std::time_t getTime(const std::string& dateTime) {
std::chrono::sys_time<std::chrono::seconds> tTime;
std::istringstream stream(dateTime);
std::chrono::from_stream(stream, "%Y-%m-%dT%H:%M:%S%z", tTime);
return std::chrono::system_clock::to_time_t(tTime);
}
const time_t EPOCH_2023 = getTime("2023-01-01T00:00:00+00:00");
int stringToIntTime(const std::string& dateTime) {
return static_cast<int>(getTime(dateTime) - EPOCH_2023);
}
to get the int.
But I haven't a clue on doing the reverse.
Here is what I recommend:
#include <chrono>
#include <format>
#include <iostream>
#include <sstream>
using namespace std::chrono_literals;
constexpr std::chrono::sys_seconds EPOCH_2023 = std::chrono::sys_days{2023y/01/01};
int
stringToIntTime(const std::string& dateTime)
{
using namespace std;
using namespace std::chrono;
sys_seconds tTime;
istringstream stream(dateTime);
stream >> parse("%FT%T%Ez", tTime);
return (tTime - EPOCH_2023)/1s;
}
std::string
intToStringTime(int i)
{
using namespace std;
using namespace std::chrono;
sys_seconds t = EPOCH_2023 + seconds{i};
return format("{:%FT%T%Ez}", zoned_time{"Etc/GMT-8", t});
}
int
main()
{
using namespace std;
int i = stringToIntTime("2023-01-12T07:00:00+08:00");
string s = intToStringTime(i);
cout << i << '\n';
cout << s << '\n';
}
Which should output:
946800
2023-01-12T07:00:00+08:00
I've taken the liberty of simplifying your stringToIntTime somewhat:
Your EPOCH_2023 constant can be made more efficient by storing it in a sys_seconds type as opposed to a string, and making it constexpr. In the object code this will just be a integral literal which is the count of seconds between your epoch and the system_clock epoch of 1970-01-01.
stringToIntTime is correct, but I've simplified it down to one function and used parse in place of from_stream just for slightly cleaner syntax. parse is a slightly higher level API.
Also note the use of %Ez in place of %z. The former includes the : separator between the hours and minutes of the UTC offset.
There's no need to go through the C API with time_t. One can just subtract the parsed UTC time tTime from your epoch. This results in seconds since your epoch. To convert that to int, just divide by 1 second.
intToStringTime starts with converting the int to seconds and adding that to your epoch. This gives t the type sys_seconds and the value of a time_point with seconds since the system_clock epoch.
Finally just format t, using a time zone with the +08:00 UTC offset, using the desired format. Note the use of -8 in the name to give +8 for the offset. This is simply POSIX weirdness that IANA inherits. If some other time zone is desired, just sub that in for "Etc/GMT-8".
Note the use of %T which is a shortcut for %H:%M:%S and %F which is a shortcut for %Y-%m-%d.
The best way is probably to use sscanf_s (stdio.h since C11) or strptime (POSIX standard) to convert the string into either individual values or a tm type (time.h), respectively. From there you can use mktime (time.h) to get back a time_t type. Then just subtract them. How to convert a string variable containing time to time_t type in c++?

Create std::chrono::time_point from string

A program like this
int
main()
{
using namespace date;
std::cout << std::chrono::system_clock::now() << '\n';
}
prints something like 2017-09-15 13:11:34.356648.
Assume I have a string literal "2017-09-15 13:11:34.356648" in my code.
What is the right way to create std::chrono::time_point from it in C++20?
Just to be clear, there is no namespace date in C++20. So the code in the question should look like:
#include <chrono>
#include <iostream>
int
main()
{
std::cout << std::chrono::system_clock::now() << '\n';
}
The inverse of this is std::chrono::parse which operates on streams. You can also use std::chrono::from_stream if desired. parse is a stream manipulator that makes the syntax a little nicer.
istringstream in{"2017-09-15 13:11:34.356648"};
system_clock::time_point tp;
in >> parse("%F %T", tp);
(I've dropped the namespaces just to keep the verbosity down)
The locale used is the global locale in effect at the time the istringstream is constructed. If you prefer another locale use the imbue member function to set the desired locale. The locale will only impact the decimal point character in this example.
The %T will read up to whatever precision the input time_point has (which varies with platform from microseconds to nanoseconds). If you want to be sure you can parse nanoseconds even if system_clock::time_point is coarser than that, then you can parse into a sys_time<nanoseconds> which is a type alias for time_point<system_clock, nanoseconds>.
sys_time<nanoseconds> tp;
in >> parse("%F %T", tp);
If the input stream has precision less than the input time_point, there is no problem. What the stream has will be read, and no more. If the input stream has precision finer than the input time_point, then the parse stops at the precision of the time_point, and the remaining digits are left unparsed in the stream.
Other strptime-like parsing flags are supported.

Convert ISO datetime to local datetime and extract time in c++

I have an iso date return from REST api:
2018-05-07T06:46:24.763Z
And I want to convert it to local datetime, let say in Philippines, it's +8. So should be
2018-05-07T14:46:24.763Z
And I want to extract the time only:
14:46
And convert it to std::string.
How can I do it?
Do I need to specify the local timezone or is there an automatic getting timezone just like how javascript work in browser?
Thank you.
If you would like to use Howard Hinnant's free, open-source date/time library, here is what it could look like:
#include "date/tz.h"
#include <iostream>
#include <sstream>
#include <string>
std::string
time_in_philippines(const std::string& utc)
{
using namespace std;
using namespace std::chrono;
using namespace date;
istringstream in{utc};
sys_time<milliseconds> tp;
in >> parse("%FT%TZ", tp);
auto zt = make_zoned("Asia/Manila", tp);
return format("%H:%M", zt);
}
int
main()
{
std::cout << time_in_philippines("2018-05-07T06:46:24.763Z") << '\n';
}
This program outputs:
14:46
sys_time<milliseconds> is just a chrono::time_point based on chrono::system_clock, but with milliseconds precision. The parse function will parse the time_point out of the istream using the indicated parsing flags.
The time_point is implicitly UTC. To convert it to a zoned_time<milliseconds> one pairs the UTC time_point with a time_zone ("Asia/Manila" in this example). If your computer's current local time zone is already "Asia/Manila", one could also pick up the current time zone with:
auto zt = make_zoned(current_zone(), tp);
Next one just formats the zoned_time with the desired flags, "%H:%M" in this case. format returns a std::string.
Some installation is required for working with the time zone library.

c++ convert time_t to string and back again

I want to convert a time_t to a string and back again.
I'd like to convert the time to a string using ctime().
I can't seem to find anything on google or the time.h header file, any ideas?
Basically what I'm trying to do is store a date in a file, and then read it back so I can use it as a time_t again.
Also, no library references outside of std,mfc.
One more note, this will have to function on Windows xp and above and that's it.
Edit
All I want to do is convert a time_t into a string(I don't care if it's human readable) and then convert it back to a time_t. I'm basically just trying to store the time_t into a file and read it again(but I don't want any code for that, as there will be more info in the file besides a time_t).
You'll have to write your own function to do that. These functions convert any primitive type (or any type which overloads operator<< and/or operator>>) to a string, and viceversa:
template<typename T>
std::string StringUtils::toString(const T &t) {
std::ostringstream oss;
oss << t;
return oss.str();
}
template<typename T>
T StringUtils::fromString( const std::string& s ) {
std::istringstream stream( s );
T t;
stream >> t;
return t;
}
ctime() returns a pointer to a character buffer that uses a specific formatting. You could use sprintf() to parse such a string into its individual portions, store them in a struct tm, and use mktime() to convert that to a time_t.
The time_t Wikipedia article article sheds some light on this. The bottom line is that the type of time_t is not guaranteed in the C specification. Here is an example of what you can try:
Try stringstream.
#include <string>
#include <sstream>
time_t seconds;
time(&seconds);
std::stringstream ss;
ss << seconds;
std::string ts = ss.str();
A nice wrapper around the above technique is Boost's lexical_cast:
#include <boost/lexical_cast.hpp>
#include <string>
time_t t;
time(&t);
std::string ts = boost::lexical_cast<std::string>(seconds);
Wikipedia on time_t:
The time_t datatype is a data type in
the ISO C library defined for storing
system time values. Such values are
returned from the standard time()
library function. This type is a
typedef defined in the standard
header. ISO C defines
time_t as an arithmetic type, but does
not specify any particular type,
range, resolution, or encoding for it.
Also unspecified are the meanings of
arithmetic operations applied to time
values.
Unix and POSIX-compliant systems implement the time_t type as a signed
integer (typically 32 or 64 bits wide)
which represents the number of seconds
since the start of the Unix epoch:
midnight UTC of January 1, 1970 (not
counting leap seconds). Some systems
correctly handle negative time values,
while others do not. Systems using a
32-bit time_t type are susceptible to
the Year 2038 problem.
Convert the time_t to struct tm using gmtime(), then convert the struct tm to plain text (preferably ISO 8601 format) using strftime(). The result will be portable, human readable, and machine readable.
To get back to the time_t, you just parse the string back into a struct tm and use mktime().

Getting the current time as a YYYY-MM-DD-HH-MM-SS string

I'm trying to get the current time as a "YYYY-MM-DD-HH-MM-SS" formatted string in an elegant way. I can take the current time in ISO format from Boost's "Date Time" library, but it has other delimiting strings which won't work for me (I'm using this in a filename).
Of course I can just replace the delimiting strings, but have a feeling that there's a nicer way to do this with date-time's formatting options. Is there such a way, and if so, how can I use it?
Use std::strftime, it is standard C++.
#include <cstdio>
#include <ctime>
int main ()
{
std::time_t rawtime;
std::tm* timeinfo;
char buffer [80];
std::time(&rawtime);
timeinfo = std::localtime(&rawtime);
std::strftime(buffer,80,"%Y-%m-%d-%H-%M-%S",timeinfo);
std::puts(buffer);
return 0;
}
The answer depends on what you mean by get and take. If you are trying to output a formatted time string, use strftime(). If you are trying to parse a text string into a binary format, use strptime().