Date/time conversion: string representation to time_t [closed] - c++

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
How do I convert a date string, formatted as "MM-DD-YY HH:MM:SS", to a time_t value in either C or C++?

Use strptime() to parse the time into a struct tm, then use mktime() to convert to a time_t.

In the absence of strptime you could use sscanf to parse the data into a struct tm and then call mktime. Not the most elegant solution but it would work.

Boost's date time library should help; in particular you might want to look at http://www.boost.org/doc/libs/1_37_0/doc/html/date_time/date_time_io.html

Note that strptime mentioned in accepted answer is not portable. Here's handy C++11 code I use to convert string to std::time_t :
static std::time_t to_time_t(const std::string& str, bool is_dst = false, const std::string& format = "%Y-%b-%d %H:%M:%S")
{
std::tm t = {0};
t.tm_isdst = is_dst ? 1 : 0;
std::istringstream ss(str);
ss >> std::get_time(&t, format.c_str());
return mktime(&t);
}
You can call it like this:
std::time_t t = to_time_t("2018-February-12 23:12:34");
You can find string format parameters here.

I'm afraid there isn't any in Standard C / C++ . There is the POSIX function strptime which can convert to struct tm, which can then be converted to time_t using mktime.
If you are aiming for cross platform compatibility, better use boost::date_time, which has sophisticated functions for this.

best way to convert a date string, formatted as "MM-DD-YY HH:MM:SS", to a time_t
Restricting code to standard C library functions is looking for the inverse of strftime(). To expand #Rob general idea, uses sscanf().
Use "%n" to detect completed scan
time_t date_string_to_time(const char *date) {
struct tm tm = { 0 }; // Important, initialize all members
int n = 0;
sscanf(date, "%d-%d-%d %d:%d:%d %n", &tm.tm_mon, &tm.tm_mday, &tm.tm_year,
&tm.tm_hour, &tm.tm_min, &tm.tm_sec, &n);
// If scan did not completely succeed or extra junk
if (n == 0 || date[n]) {
return (time_t) -1;
}
tm.tm_isdst = -1; // Assume local daylight setting per date/time
tm.tm_mon--; // Months since January
// Assume 2 digit year if in the range 2000-2099, else assume year as given
if (tm.tm_year >= 0 && tm.tm_year < 100) {
tm.tm_year += 2000;
}
tm.tm_year -= 1900; // Years since 1900
time_t t = mktime(&tm);
return t;
}
Additional code could be used to insure only 2 digit timestamp parts, positive values, spacing, etc.
Note: this assume the "MM-DD-YY HH:MM:SS" is a local time.

static time_t MKTimestamp(int year, int month, int day, int hour, int min, int sec)
{
time_t rawtime;
struct tm * timeinfo;
time ( &rawtime );
timeinfo = gmtime ( &rawtime );
timeinfo->tm_year = year-1900 ;
timeinfo->tm_mon = month-1;
timeinfo->tm_mday = day;
timeinfo->tm_hour = hour;
timeinfo->tm_min = min;
timeinfo->tm_sec = sec;
timeinfo->tm_isdst = 0; // disable daylight saving time
time_t ret = mktime ( timeinfo );
return ret;
}
static time_t GetDateTime(const std::string pstr)
{
try
{
// yyyy-mm-dd
int m, d, y, h, min;
std::istringstream istr (pstr);
istr >> y;
istr.ignore();
istr >> m;
istr.ignore();
istr >> d;
istr.ignore();
istr >> h;
istr.ignore();
istr >> min;
time_t t;
t=MKTimestamp(y,m,d,h-1,min,0);
return t;
}
catch(...)
{
}
}

Related

how to use localtime() correctly?

In one of my utility programs, localtime() is used to covert unix timestamps to human readable date time.
The following code used to work in VS2010 while it fails to work in VS2019:
std::string sec = "1234123456";
int nsec = atoi(sec.c_str());
tm* t = localtime((time_t*)&nsec); // return null pointer
If I change the code in the following way, it will work also in VS2019:
std::string sec = "1234123456";
int nsec = atoi(sec.c_str());
time_t tt = nsec;
tm* t = localtime(&tt); // works
I have no idea why the additional int to time_t conversion is needed, any suggestion would be appreciated.
On most (if not all) modern compilers time_t is now a 64-bit integer. (time_t*)&nsec is therefore undefined behaviour as you are casting from one pointer type to a different one.
You fixed version is well defined but you will run into the reason that time_t is now 64-bit as 32-bit numbers will only work for times up to 2038 (assuming time_t is using the Unix epoch).
Unfortunately c++ doesn't provide a simple method for converting a string to time_t, to do it properly you'd need something like this:
#include <iostream>
#include <charconv>
time_t str_to_time_t(const std::string& str)
{
auto begin = str.c_str();
auto end = begin + str.size();
time_t time;
auto result = std::from_chars(begin, end, time);
if (result.ec != std::errc())
{
throw std::system_error(std::make_error_code(result.ec));
}
if (result.ptr != end)
{
throw std::invalid_argument("invalid time_t string");
}
return time;
}
int main()
{
std::string sec = "1234123456";
auto nsec = str_to_time_t(sec);
tm* t = localtime((time_t*)&nsec);
if (t)
{
std::cout << "parsed OK\n";
}
}

C++ Convert a string timestamp to std::chrono::system_clock::time_point

I am trying to convert a string timestamp expressed in the following format: "28.08.2017 03:59:55.0007" to a std::chrono::system_clock::time_point by preserving the microseconds precision.
Is there any way to achieve this by using the standard library or boost?
Thanks.
I'd make use of Howard Hinnant's date library https://howardhinnant.github.io/date/date.html
e.g.:
std::stringstream str( "28.08.2017 03:59:55.0007" );
str.imbue( std::locale() );
std::chrono::time_point< std::chrono::system_clock, std::chrono::microseconds > result;
date::from_stream( str, "%d.%m.%Y %H:%M:%S", result );
std::cout << result.time_since_epoch().count();
Thought I'd add an answer since there isn't one available that exclusively uses the standard.
Given the input: istringstream timestamp("28.08.2017 03:59:55.0007"), this could be converted to a tm via get_time, but for the fractional seconds. The fractional seconds would need to be converted manually (this could be done by constructing chrono::microseconds from the rounded remainder divided by the micro ratio.) All this could be combined into something like this:
tm tmb;
double r;
timestamp >> get_time(&tmb, "%d.%m.%Y %T") >> r;
const auto output = chrono::time_point_cast<chrono::microseconds>(chrono::system_clock::from_time_t(mktime(&tmb))) + chrono::microseconds(lround(r * micro::den));
Live Example
One implementation can be:
#include <ctime>
#include <cmath>
#include <chrono>
#include <string>
#include <cstdint>
#include <stdexcept>
std::chrono::system_clock::time_point parse_my_timestamp(std::string const& timestamp) {
auto error = [&timestamp]() { throw std::invalid_argument("Invalid timestamp: " + timestamp); };
std::tm tm;
auto fraction = ::strptime(timestamp.c_str(), "%d.%m.%Y %H:%M:%S", &tm);
if(!fraction)
error();
std::chrono::nanoseconds ns(0);
if('.' == *fraction) {
++fraction;
char* fraction_end = 0;
std::chrono::nanoseconds fraction_value(std::strtoul(fraction, &fraction_end, 10));
if(fraction_end != timestamp.data() + timestamp.size())
error();
auto fraction_len = fraction_end - fraction;
if(fraction_len > 9)
error();
ns = fraction_value * static_cast<std::int32_t>(std::pow(10, 9 - fraction_len));
}
else if(fraction != timestamp.data() + timestamp.size())
error();
auto seconds_since_epoch = std::mktime(&tm); // Assumes timestamp is in localtime. For UTC use timegm.
auto timepoint_ns = std::chrono::system_clock::from_time_t(seconds_since_epoch) + ns;
return std::chrono::time_point_cast<std::chrono::system_clock::duration>(timepoint_ns);
}

C++ Converting A String to a time_t variable

I'm working on a C++ function that is supposed to figure out if a specified event happened between two time points. The event name, start datetime, and end datetime are all passed in from Lua as strings. Because of that, I need to parse my datetime strings into time_t variables. Based on what I've seen on StackOverflow and other forums, this code should work:
time_t tStart;
int yy, month, dd, hh, mm, ss;
struct tm whenStart = {0};
const char *zStart = startTime.c_str();
sscanf(zStart, "%d/%d/%d %d:%d:%d", &yy, &month, &dd, &hh, &mm, &ss);
whenStart.tm_year = yy - 1900;
whenStart.tm_mon = month - 1;
whenStart.tm_mday = dd;
whenStart.tm_hour = hh;
whenStart.tm_min = mm;
whenStart.tm_sec = ss;
whenStart.tm_isdst = -1;
tStart = mktime(&whenStart);
However, tStart appears to be assigned the value of -1 here. If I use strftime to reconstruct a string from whenStart, that tm structure appears to have been made completely correctly. Somehow mktime() is not liking the structure, I think. What is wrong with this code?
Also, before you answer, know that I already tried using a strptime() call. For reasons that are unclear to me, this function gets rejected with an "undefined reference to 'strptime'" error. The various descriptions I've found for how to fix this problem only serve to destroy the rest of the code base I'm working with, so I would rather avoid messing with _XOPEN_SOURCE or similar redefinitions.
Thanks for your help!
The code you posted is correct.
This leads me to believe that your input string (startTime) is not in the format you are expecting, and therefore sscanf cannot parse out the values.
Example:
#include <iostream>
int main()
{
std::string startTime = "2016/05/18 13:10:00";
time_t tStart;
int yy, month, dd, hh, mm, ss;
struct tm whenStart;
const char *zStart = startTime.c_str();
sscanf(zStart, "%d/%d/%d %d:%d:%d", &yy, &month, &dd, &hh, &mm, &ss);
whenStart.tm_year = yy - 1900;
whenStart.tm_mon = month - 1;
whenStart.tm_mday = dd;
whenStart.tm_hour = hh;
whenStart.tm_min = mm;
whenStart.tm_sec = ss;
whenStart.tm_isdst = -1;
tStart = mktime(&whenStart);
std::cout << tStart << std::endl;
}
Output:
1463595000
Have you sanity checked your inputs?
Please note that you can check the return value of sscanf to verify if it worked as you expected.
Return value
Number of receiving arguments successfully assigned, or EOF if read failure occurs before the first receiving argument was assigned.
If the return value is not 6, then the input string is incorrect.
int num_args = sscanf(zStart, "%d/%d/%d %d:%d:%d", &yy, &month, &dd, &hh, &mm, &ss);
if (num_args != 6)
{
std::cout << "error in format string " << startTime << '\n';
return 1;
}
As a rule of thumb you shouldn't ever assume that your inputs will be correct. As such, defensive programming is a good habit to get into.

C++ Compare 2 dates and check the minutes difference

Here's the thing:
A datetime access is created with C# using DateTime.Now. This datetime is passed through JSON to a C++ method. I'm using JsonCpp to handle the Json data, but I'm not sure how to handle when the data is a datetime.
I want to compare this datetime that I received with the actual datetime and check the minutes difference between this two (if the difference is on a interval that was defined).
If I convert the Json datetime to a string using JsonCpp I have this format:
2015-06-08T11:17:23.746389-03:00
So what I'm trying to do is something like this:
var d1 = oldAccess["Date"].ToString(); //Json datetime converted to a string
var d2 = actualAccess["Date"].ToString()
if((d2 - d1) < 20) { //Difference between the two dates needs to be less than 20 minutes
return true;
} else return false;
I'm new in C++, even looking for I don't discovered how to do this.
Well, I got it. Not the best way neither the pretty one, but it works since I know that the two dates were set on the same server and always comes in the same format \"2015-01-01T23:40:00.000000-03:00\"
Here's what I did:
int convertToInt(std::string number_str){
int number;
std::istringstream ss(number_str);
ss.imbue(std::locale::classic());
ss >> number;
return number;
}
time_t convertDatetime(std::string date_str) {
time_t rawtime;
struct tm date;
int year, month, day, hour, min, sec;
date_str.erase(std::remove_if(date_str.begin(), date_str.end(), isspace), date_str.end());
year = convertToInt(date_str.substr(1, 4));
month = convertToInt(date_str.substr(6, 2));
day = convertToInt(date_str.substr(9, 2));
hour = convertToInt(date_str.substr(12, 2));
min = convertToInt(date_str.substr(15, 2));
sec = convertToInt(date_str.substr(18, 2));
time(&rawtime);
localtime_s(&date, &rawtime);
date.tm_year = year - 1900;
date.tm_mon = month - 1;
date.tm_mday = day;
date.tm_hour = hour;
date.tm_min = min;
date.tm_sec = sec;
return mktime(&date);
}
bool isValidIntervalDatetime(std::string actualDatetime_str, std::string oldDatetime_str, int maxMinutesInterval) {
double maxSecondsInterval = 60 * maxMinutesInterval;
time_t actualDatetime = convertDatetime(actualDatetime_str);
time_t oldDatetime = convertDatetime(oldDatetime_str);
double secondsDiff = difftime(actualDatetime, oldDatetime);
return secondsDiff <= maxSecondsInterval;
}
int main(int argc, char* argv[])
{
auto maxMinutesInterval = 20;
auto actuaDatetime = JsonConverter::toString(actualAccess["Date"]); // \"2015-01-02T00:00:00.000000-03:00\"
auto oldDatetime = JsonConverter::toString(oldAccess["Date"]); // \"2015-01-01T23:40:00.000000-03:00\"
if (isValidIntervalDatetime(actuaDatetime, oldDatetime, maxMinutesInterval){
//do something
}
}

how do I parse an iso 8601 date (with optional milliseconds) to a struct tm in C++?

I have a string which should specify a date and time in ISO 8601 format, which may or may not have milliseconds in it, and I am wanting to get a struct tm from it as well as any millisecond value that may have been specified (which can be assumed to be zero if not present in the string).
What would be involved in detecting whether the string is in the correct format, as well as converting a user-specified string into the struct tm and millisecond values?
If it weren't for the millisconds issue, I could probably just use the C function strptime(), but I do not know what the defined behavior of that function is supposed to be when the seconds contain a decimal point.
As one final caveat, if it is at all possible, I would greatly prefer a solution that does not have any dependency on functions that are only found in Boost (but I'm happy to accept C++11 as a prerequisite).
The input is going to look something like:
2014-11-12T19:12:14.505Z
or
2014-11-12T12:12:14.505-5:00
Z, in this case, indicates UTC, but any time zone might be used, and will be expressed as a + or - hours/minutes offset from GMT. The decimal portion of the seconds field is optional, but the fact that it may be there at all is why I cannot simply use strptime() or std::get_time(), which do not describe any particular defined behavior if such a character is found in the seconds portion of the string.
New answer for old question. Rationale: updated tools.
Using this free, open source library, one can parse into a std::chrono::time_point<system_clock, milliseconds>, which has the advantage over a tm of being able to hold millisecond precision. And if you really need to, you can continue on to the C API via system_clock::to_time_t (losing the milliseconds along the way).
#include "date.h"
#include <iostream>
#include <sstream>
date::sys_time<std::chrono::milliseconds>
parse8601(std::istream&& is)
{
std::string save;
is >> save;
std::istringstream in{save};
date::sys_time<std::chrono::milliseconds> tp;
in >> date::parse("%FT%TZ", tp);
if (in.fail())
{
in.clear();
in.exceptions(std::ios::failbit);
in.str(save);
in >> date::parse("%FT%T%Ez", tp);
}
return tp;
}
int
main()
{
using namespace date;
using namespace std;
cout << parse8601(istringstream{"2014-11-12T19:12:14.505Z"}) << '\n';
cout << parse8601(istringstream{"2014-11-12T12:12:14.505-5:00"}) << '\n';
}
This outputs:
2014-11-12 19:12:14.505
2014-11-12 17:12:14.505
Note that both outputs are UTC. The parse converted the local time to UTC using the -5:00 offset. If you actually want local time, there is also a way to parse into a type called date::local_time<milliseconds> which would then parse but ignore the offset. One can even parse the offset into a chrono::minutes if desired (using a parse overload taking minutes&).
The precision of the parse is controlled by the precision of the chrono::time_point you pass in, instead of by flags in the format string. And the offset can either be of the style +/-hhmm with %z, or +/-[h]h:mm with %Ez.
You can use C's sscanf (http://www.cplusplus.com/reference/cstdio/sscanf/) to parse it:
const char *dateStr = "2014-11-12T19:12:14.505Z";
int y,M,d,h,m;
float s;
sscanf(dateStr, "%d-%d-%dT%d:%d:%fZ", &y, &M, &d, &h, &m, &s);
If you have std::string it can be called like this (http://www.cplusplus.com/reference/string/string/c_str/):
std::string dateStr = "2014-11-12T19:12:14.505Z";
sscanf(dateStr.c_str(), "%d-%d-%dT%d:%d:%fZ", &y, &M, &d, &h, &m, &s);
If it should handle different timezones you need to use sscanf return value - number of parsed arguments:
int tzh = 0, tzm = 0;
if (6 < sscanf(dateStr.c_str(), "%d-%d-%dT%d:%d:%f%d:%dZ", &y, &M, &d, &h, &m, &s, &tzh, &tzm)) {
if (tzh < 0) {
tzm = -tzm; // Fix the sign on minutes.
}
}
And then you can fill tm (http://www.cplusplus.com/reference/ctime/tm/) struct:
tm time = { 0 };
time.tm_year = y - 1900; // Year since 1900
time.tm_mon = M - 1; // 0-11
time.tm_mday = d; // 1-31
time.tm_hour = h; // 0-23
time.tm_min = m; // 0-59
time.tm_sec = (int)s; // 0-61 (0-60 in C++11)
It also can be done with std::get_time (http://en.cppreference.com/w/cpp/io/manip/get_time) since C++11 as #Barry mentioned in comment how do I parse an iso 8601 date (with optional milliseconds) to a struct tm in C++?
Modern C++ version of parse ISO 8601* function
* - this code supports only subset of ISO 8601. The only supported forms are "2020-09-19T05:12:32Z" and "2020-09-19T05:12:32.123Z". Milliseconds can be 3 digit length or no milliseconds part at all, no timezone except Z, no other more rare features.
#include <cstdlib>
#include <ctime>
#include <string>
#ifdef _WIN32
#define timegm _mkgmtime
#endif
inline int ParseInt(const char* value)
{
return std::strtol(value, nullptr, 10);
}
// ParseISO8601 returns milliseconds since 1970
std::time_t ParseISO8601(const std::string& input)
{
constexpr const size_t expectedLength = sizeof("1234-12-12T12:12:12Z") - 1;
static_assert(expectedLength == 20, "Unexpected ISO 8601 date/time length");
if (input.length() < expectedLength)
{
return 0;
}
std::tm time = { 0 };
time.tm_year = ParseInt(&input[0]) - 1900;
time.tm_mon = ParseInt(&input[5]) - 1;
time.tm_mday = ParseInt(&input[8]);
time.tm_hour = ParseInt(&input[11]);
time.tm_min = ParseInt(&input[14]);
time.tm_sec = ParseInt(&input[17]);
time.tm_isdst = 0;
const int millis = input.length() > 20 ? ParseInt(&input[20]) : 0;
return timegm(&time) * 1000 + millis;
}
Old question, and I have some old code to contribute ;). I was using the date library mentioned here. While it works great, it comes at a performance cost. For most common cases this would be not really relevant. However, if you have for example a service parsing data like I do, it really does matter.
I was profiling my server application for performance optimization, and found that parsing an ISO timestamp using the date library was 3 times slower compared to parsing the whole (roughly 500 bytes) json document. In total parsing the timestamp accounted for about 4.8% of total CPU time.
On my quest to optimize this part, I did not find much with C++ that I would consider for a living product. And the code which I did consider further mostly had some dependencies (e.g. the ISO parser in CEPH looks ok and seems well tested).
In the end, I turned to good old C and stripped out some code from the SQLite date.c to make it work standalone. The difference:
date: 872ms
SQLite date.c: 54ms
(Profiled function weight of real life service application)
Here it is (all credits to SQLite):
The header file date_util.h
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
// Calculates time since epoch including milliseconds
uint64_t ParseTimeToEpochMillis(const char *str, bool *error);
// Creates an ISO timestamp with milliseconds from epoch with millis.
// The buffer size (resultLen) for result must be at least 100 bytes.
void TimeFromEpochMillis(uint64_t epochMillis, char *result, int resultLen, bool *error);
#ifdef __cplusplus
}
#endif
This is the C file date_util.c:
#include "_date.h"
#include <ctype.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdarg.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
/*
** A structure for holding a single date and time.
*/
typedef struct DateTime DateTime;
struct DateTime {
int64_t iJD; /* The julian day number times 86400000 */
int Y, M, D; /* Year, month, and day */
int h, m; /* Hour and minutes */
int tz; /* Timezone offset in minutes */
double s; /* Seconds */
char validJD; /* True (1) if iJD is valid */
char rawS; /* Raw numeric value stored in s */
char validYMD; /* True (1) if Y,M,D are valid */
char validHMS; /* True (1) if h,m,s are valid */
char validTZ; /* True (1) if tz is valid */
char tzSet; /* Timezone was set explicitly */
char isError; /* An overflow has occurred */
};
/*
** Convert zDate into one or more integers according to the conversion
** specifier zFormat.
**
** zFormat[] contains 4 characters for each integer converted, except for
** the last integer which is specified by three characters. The meaning
** of a four-character format specifiers ABCD is:
**
** A: number of digits to convert. Always "2" or "4".
** B: minimum value. Always "0" or "1".
** C: maximum value, decoded as:
** a: 12
** b: 14
** c: 24
** d: 31
** e: 59
** f: 9999
** D: the separator character, or \000 to indicate this is the
** last number to convert.
**
** Example: To translate an ISO-8601 date YYYY-MM-DD, the format would
** be "40f-21a-20c". The "40f-" indicates the 4-digit year followed by "-".
** The "21a-" indicates the 2-digit month followed by "-". The "20c" indicates
** the 2-digit day which is the last integer in the set.
**
** The function returns the number of successful conversions.
*/
static int GetDigits(const char *zDate, const char *zFormat, ...){
/* The aMx[] array translates the 3rd character of each format
** spec into a max size: a b c d e f */
static const uint16_t aMx[] = { 12, 14, 24, 31, 59, 9999 };
va_list ap;
int cnt = 0;
char nextC;
va_start(ap, zFormat);
do{
char N = zFormat[0] - '0';
char min = zFormat[1] - '0';
int val = 0;
uint16_t max;
assert( zFormat[2]>='a' && zFormat[2]<='f' );
max = aMx[zFormat[2] - 'a'];
nextC = zFormat[3];
val = 0;
while( N-- ){
if( !isdigit(*zDate) ){
goto end_getDigits;
}
val = val*10 + *zDate - '0';
zDate++;
}
if( val<(int)min || val>(int)max || (nextC!=0 && nextC!=*zDate) ){
goto end_getDigits;
}
*va_arg(ap,int*) = val;
zDate++;
cnt++;
zFormat += 4;
}while( nextC );
end_getDigits:
va_end(ap);
return cnt;
}
/*
** Parse a timezone extension on the end of a date-time.
** The extension is of the form:
**
** (+/-)HH:MM
**
** Or the "zulu" notation:
**
** Z
**
** If the parse is successful, write the number of minutes
** of change in p->tz and return 0. If a parser error occurs,
** return non-zero.
**
** A missing specifier is not considered an error.
*/
static int ParseTimezone(const char *zDate, DateTime *p){
int sgn = 0;
int nHr, nMn;
int c;
while( isspace(*zDate) ){ zDate++; }
p->tz = 0;
c = *zDate;
if( c=='-' ){
sgn = -1;
}else if( c=='+' ){
sgn = +1;
}else if( c=='Z' || c=='z' ){
zDate++;
goto zulu_time;
}else{
return c!=0;
}
zDate++;
if( GetDigits(zDate, "20b:20e", &nHr, &nMn)!=2 ){
return 1;
}
zDate += 5;
p->tz = sgn*(nMn + nHr*60);
zulu_time:
while( isspace(*zDate) ){ zDate++; }
p->tzSet = 1;
return *zDate!=0;
}
/*
** Parse times of the form HH:MM or HH:MM:SS or HH:MM:SS.FFFF.
** The HH, MM, and SS must each be exactly 2 digits. The
** fractional seconds FFFF can be one or more digits.
**
** Return 1 if there is a parsing error and 0 on success.
*/
static int ParseHhMmSs(const char *zDate, DateTime *p){
int h, m, s;
double ms = 0.0;
if( GetDigits(zDate, "20c:20e", &h, &m)!=2 ){
return 1;
}
zDate += 5;
if( *zDate==':' ){
zDate++;
if( GetDigits(zDate, "20e", &s)!=1 ){
return 1;
}
zDate += 2;
if( *zDate=='.' && isdigit(zDate[1]) ){
double rScale = 1.0;
zDate++;
while( isdigit(*zDate) ){
ms = ms*10.0 + *zDate - '0';
rScale *= 10.0;
zDate++;
}
ms /= rScale;
}
}else{
s = 0;
}
p->validJD = 0;
p->rawS = 0;
p->validHMS = 1;
p->h = h;
p->m = m;
p->s = s + ms;
if( ParseTimezone(zDate, p) ) return 1;
p->validTZ = (p->tz!=0)?1:0;
return 0;
}
/*
** Put the DateTime object into its error state.
*/
static void DatetimeError(DateTime *p){
memset(p, 0, sizeof(*p));
p->isError = 1;
}
/*
** Convert from YYYY-MM-DD HH:MM:SS to julian day. We always assume
** that the YYYY-MM-DD is according to the Gregorian calendar.
**
** Reference: Meeus page 61
*/
static void ComputeJD(DateTime *p){
int Y, M, D, A, B, X1, X2;
if( p->validJD ) return;
if( p->validYMD ){
Y = p->Y;
M = p->M;
D = p->D;
}else{
Y = 2000; /* If no YMD specified, assume 2000-Jan-01 */
M = 1;
D = 1;
}
if( Y<-4713 || Y>9999 || p->rawS ){
DatetimeError(p);
return;
}
if( M<=2 ){
Y--;
M += 12;
}
A = Y/100;
B = 2 - A + (A/4);
X1 = 36525*(Y+4716)/100;
X2 = 306001*(M+1)/10000;
p->iJD = (int64_t)((X1 + X2 + D + B - 1524.5 ) * 86400000);
p->validJD = 1;
if( p->validHMS ){
p->iJD += p->h*3600000 + p->m*60000 + (int64_t)(p->s*1000);
if( p->validTZ ){
p->iJD -= p->tz*60000;
p->validYMD = 0;
p->validHMS = 0;
p->validTZ = 0;
}
}
}
/*
** Parse dates of the form
**
** YYYY-MM-DD HH:MM:SS.FFF
** YYYY-MM-DD HH:MM:SS
** YYYY-MM-DD HH:MM
** YYYY-MM-DD
**
** Write the result into the DateTime structure and return 0
** on success and 1 if the input string is not a well-formed
** date.
*/
static int ParseYyyyMmDd(const char *zDate, DateTime *p){
int Y, M, D, neg;
if( zDate[0]=='-' ){
zDate++;
neg = 1;
}else{
neg = 0;
}
if( GetDigits(zDate, "40f-21a-21d", &Y, &M, &D)!=3 ){
return 1;
}
zDate += 10;
while( isspace(*zDate) || 'T'==*(uint8_t*)zDate ){ zDate++; }
if( ParseHhMmSs(zDate, p)==0 ){
/* We got the time */
}else if( *zDate==0 ){
p->validHMS = 0;
}else{
return 1;
}
p->validJD = 0;
p->validYMD = 1;
p->Y = neg ? -Y : Y;
p->M = M;
p->D = D;
if( p->validTZ ){
ComputeJD(p);
}
return 0;
}
/* The julian day number for 9999-12-31 23:59:59.999 is 5373484.4999999.
** Multiplying this by 86400000 gives 464269060799999 as the maximum value
** for DateTime.iJD.
**
** But some older compilers (ex: gcc 4.2.1 on older Macs) cannot deal with
** such a large integer literal, so we have to encode it.
*/
#define INT_464269060799999 ((((int64_t)0x1a640)<<32)|0x1072fdff)
/*
** Return TRUE if the given julian day number is within range.
**
** The input is the JulianDay times 86400000.
*/
static int ValidJulianDay(int64_t iJD){
return iJD>=0 && iJD<=INT_464269060799999;
}
/*
** Compute the Year, Month, and Day from the julian day number.
*/
static void ComputeYMD(DateTime *p){
int Z, A, B, C, D, E, X1;
if( p->validYMD ) return;
if( !p->validJD ){
p->Y = 2000;
p->M = 1;
p->D = 1;
}else if( !ValidJulianDay(p->iJD) ){
DatetimeError(p);
return;
}else{
Z = (int)((p->iJD + 43200000)/86400000);
A = (int)((Z - 1867216.25)/36524.25);
A = Z + 1 + A - (A/4);
B = A + 1524;
C = (int)((B - 122.1)/365.25);
D = (36525*(C&32767))/100;
E = (int)((B-D)/30.6001);
X1 = (int)(30.6001*E);
p->D = B - D - X1;
p->M = E<14 ? E-1 : E-13;
p->Y = p->M>2 ? C - 4716 : C - 4715;
}
p->validYMD = 1;
}
/*
** Compute the Hour, Minute, and Seconds from the julian day number.
*/
static void ComputeHMS(DateTime *p){
int s;
if( p->validHMS ) return;
ComputeJD(p);
s = (int)((p->iJD + 43200000) % 86400000);
p->s = s/1000.0;
s = (int)p->s;
p->s -= s;
p->h = s/3600;
s -= p->h*3600;
p->m = s/60;
p->s += s - p->m*60;
p->rawS = 0;
p->validHMS = 1;
}
/*
** Compute both YMD and HMS
*/
static void ComputeYMD_HMS(DateTime *p){
ComputeYMD(p);
ComputeHMS(p);
}
/*
** Input "r" is a numeric quantity which might be a julian day number,
** or the number of seconds since 1970. If the value if r is within
** range of a julian day number, install it as such and set validJD.
** If the value is a valid unix timestamp, put it in p->s and set p->rawS.
*/
static void SetRawDateNumber(DateTime *p, double r){
p->s = r;
p->rawS = 1;
if( r>=0.0 && r<5373484.5 ){
p->iJD = (int64_t)(r*86400000.0 + 0.5);
p->validJD = 1;
}
}
/*
** Clear the YMD and HMS and the TZ
*/
static void ClearYMD_HMS_TZ(DateTime *p){
p->validYMD = 0;
p->validHMS = 0;
p->validTZ = 0;
}
// modified methods to only calculate for and back between epoch and iso timestamp with millis
uint64_t ParseTimeToEpochMillis(const char *str, bool *error) {
assert(str);
assert(error);
*error = false;
DateTime dateTime;
int res = ParseYyyyMmDd(str, &dateTime);
if (res) {
*error = true;
return 0;
}
ComputeJD(&dateTime);
ComputeYMD_HMS(&dateTime);
// get fraction (millis of a full second): 24.355 => 355
int millis = (dateTime.s - (int)(dateTime.s)) * 1000;
uint64_t epoch = (int64_t)(dateTime.iJD/1000 - 21086676*(int64_t)10000) * 1000 + millis;
return epoch;
}
void TimeFromEpochMillis(uint64_t epochMillis, char *result, int resultLen, bool *error) {
assert(resultLen >= 100);
assert(result);
assert(error);
int64_t seconds = epochMillis / 1000;
int millis = epochMillis - seconds * 1000;
DateTime x;
*error = false;
memset(&x, 0, sizeof(x));
SetRawDateNumber(&x, seconds);
/*
** unixepoch
**
** Treat the current value of p->s as the number of
** seconds since 1970. Convert to a real julian day number.
*/
{
double r = x.s*1000.0 + 210866760000000.0;
if( r>=0.0 && r<464269060800000.0 ){
ClearYMD_HMS_TZ(&x);
x.iJD = (int64_t)r;
x.validJD = 1;
x.rawS = 0;
}
ComputeJD(&x);
if( x.isError || !ValidJulianDay(x.iJD) ) {
*error = true;
}
}
ComputeYMD_HMS(&x);
snprintf(result, resultLen, "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
x.Y, x.M, x.D, x.h, x.m, (int)(x.s), millis);
}
These two helper methods simply convert to and from a timestamp in with milliseconds. Setting a tm struct from the DateTime should be obvious.
Example usage:
// Calculate milliseconds since epoch
std::string timeStamp = "2019-09-02T22:02:24.355Z";
bool error;
uint64_t time = ParseTimeToEpochMillis(timeStamp.c_str(), &error);
// Get ISO timestamp with milliseconds component from epoch in milliseconds.
// Multiple by 1000 in case you have a standard epoch in seconds)
uint64_t epochMillis = 1567461744355; // == "2019-09-02T22:02:24.355Z"
char result[100] = {0};
TimeFromEpochMillis(epochMillis, result, sizeof(result), &error);
std::string resultStr(result); // == "2019-09-02T22:02:24.355Z"
There is a from_iso_string and from_iso_extended_string in Boost::DateTime library:
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::posix_time;
// signature
ptime from_iso_string(std::string)
ptime from_iso_extended_string(std::string)
// examples
std::string ts("20020131T235959");
ptime t1(from_iso_string(ts))
std::string ts("2020-01-31T23:59:59.123");
ptime t2(from_iso_extended_string(ts))
I used strptime():
const chrono::time_point<chrono::system_clock, chrono::seconds> iSO8601StringToTimePoint(const string& iso8601) {
std::tm t = {};
// F: Equivalent to %Y-%m-%d, the ISO 8601 date format.
// T: ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S
// z: ISO 8601 offset from UTC in timezone (1 minute=1, 1 hour=100). If timezone cannot be determined, no characters
strptime(iso8601.c_str(), "%FT%T%z", &t);
return chrono::system_clock::from_time_t(mktime(&t));
}
While I went the sscanf() path at first, after switching my IDE to CLion, it suggested the use of std::strtol() function to replace sscanf().
Remember that this is just an example of achieving the same result as the sscanf() version. It's not meant to be shorter, universal and correct in every way, but to point everyone in the "pure C++ solution" direction. It's based on the timestamp strings I receive from an API and is not yet universal (my case needs handling the YYYY-MM-DDTHH:mm:ss.sssZ format), it could be easily modified to handle different ones.
Before posting the code, there's one thing that needs to be done before using std::strtol(): cleaning up the string itself, so removing any non-digit markers ("-", ":", "T", "Z", "."), because without it std::strtol() will parse the numbers the wrong way (you might end up with negative month or day values without it).
This little snippet takes a ISO-8601 string (the format I needed, as mentioned above) and converts it into a std::time_t result, representing the epoch time in milliseconds. From here it's quite easy to go into std::chrono-type objects.
std::time_t parseISO8601(const std::string &input)
{
// prepare the data output placeholders
struct std::tm time = {0};
int millis;
// string cleaning for strtol() - this could be made cleaner, but for the sake of the example itself...
std::string cleanInput = input
.replace(4, 1, 1, ' ')
.replace(7, 1, 1, ' ')
.replace(10, 1, 1, ' ')
.replace(13, 1, 1, ' ')
.replace(16, 1, 1, ' ')
.replace(19, 1, 1, ' ');
// pointers for std::strtol()
const char* timestamp = cleanInput.c_str();
// last parsing end position - it's where strtol finished parsing the last number found
char* endPointer;
// the casts aren't necessary, but I just wanted CLion to be quiet ;)
// first parse - start with the timestamp string, give endPointer the position after the found number
time.tm_year = (int) std::strtol(timestamp, &endPointer, 10) - 1900;
// next parses - use endPointer instead of timestamp (skip the part, that's already parsed)
time.tm_mon = (int) std::strtol(endPointer, &endPointer, 10) - 1;
time.tm_mday = (int) std::strtol(endPointer, &endPointer, 10);
time.tm_hour = (int) std::strtol(endPointer, &endPointer, 10);
time.tm_min = (int) std::strtol(endPointer, &endPointer, 10);
time.tm_sec = (int) std::strtol(endPointer, &endPointer, 10);
millis = (int) std::strtol(endPointer, &endPointer, 10);
// convert the tm struct into time_t and then from seconds to milliseconds
return std::mktime(&time) * 1000 + millis;
}
Not the cleanest and most universal, but gets the job done without resorting to C-style functions like sscanf().