C++ - compare two dates - c++

I am writing a application which needs the possibility to compare two dates. This is what I have so far:
struct entry {
string text;
string date; // format: dd.mm.yyyy
bool finished;
};
string addNulls(int number, int cols) {
string num = to_string(number);
if (num.size() < cols) {
int count = cols - num.size();
for (int i = 0; i < count; i++) {
num = "0" + num;
}
}
return num;
}
// [...]
entry e = {"here is some text", "21.03.2019", false};
int day2 = atoi(e.date.substr(0, 2).c_str());
int month2 = atoi(e.date.substr(3, 2).c_str());
int year2 = atoi(e.date.substr(6, 4).c_str());
time_t t = time(0);
struct tm * now = localtime(&t);
string date1 = e.date.substr(6, 4) + "-" + e.date.substr(3, 2) + "-" + e.date.substr(0, 2) + " 00:00:00";
string date2 = addNulls(now->tm_year, 4) + "-" + addNulls(now->tm_mon, 2) + "-" + addNulls(now->tm_mday, 2) + " 00:00:00";
if(date2 > date1) {
// do something
}
the code gets an "entry" struct which contains a date. Than the code compares the date with the actual time. The problem is, it does not work! I run some tests with some example content, but the result (date2 > date1) returns false.
Why?
I read this: C++ compare to string dates

I'm not actually answering your question. However I am offering you a solution. Have you considered a date/time library? Boost datetime is very popular.
If you are compiling in C++11 or later, I recommend this date time library, as it is header-only (eliminating the need to link to a library such as boost), and in my opinion, it has cleaner syntax (that is a very subjective and biased viewpoint).
This latter library builds on the C++11 <chrono> library. Here is your example code using this library:
#include "date.h"
#include <iostream>
#include <string>
struct entry {
std::string text;
date::year_month_day date;
bool finished;
};
int
main()
{
entry e = {"here is some text", date::day(21)/3/2019, false};
auto day2 = e.date.day();
auto month2 = e.date.month();
auto year2 = e.date.year();
auto t = std::chrono::system_clock::now();
auto date1 = date::sys_days{e.date};
auto date2 = t;
if (date2 > date1)
std::cout << "It is past " << e.date << '\n';
else
std::cout << "It is not past " << e.date << '\n';
}
Which currently outputs:
It is not past 2019-03-21
In C++14, the chrono literals make specifying literal times very compact:
using namespace std::literals;
auto date1 = date::sys_days{e.date} + 0h + 0min + 0s;
Also on the subject of literals, you can make the construction of entry slightly more compact if you drop in a using namespace date;:
entry e = {"here is some text", 21_d/3/2019, false};
Reusing a date or datetime class, or even creating your own, is easier than trying to use a string to hold a date. Additionally you get the type-safety of not accidentally adding a string to a date, when you meant to add a time duration to a time point.

Why don't you use strptime to parse your date strings, convert them to epoch times and then compare?
#include <time.h>
char *
strptime(const char *restrict buf, const char *restrict format,
struct tm *restrict tm);

Related

Rounding converting from string to double/float

I am trying to extract a number from a string and convert it into a double or float so I can do some numerical operations on it. I am able to isolate the variable I need so the string consists only of the number, but when I try to convert it to a float or double it rounds the value, ie from 160430.6 to 160431.
//Helper Function to Extract Value of Interest
//Based on column of final digit of numbers being same across various FLOPS output files
double findValue(string &line, int &refN){
setprecision(100);
string output;
//go to end column and work backwards to get value string
while(line[refN] != ' '){
output = line[refN] + output;
refN = refN - 1;
}
const char* outputx = output.c_str();
double out = atof(outputx);
//removing the const char* line and replacing atof with stod(output) runs into the same issue
return out;
}
int main()
{
string name;
cin >> name;
ifstream file(name);
//opens file
if(!file.is_open()){"error while opening the file";
}else{
//Temporary Reference Definitions
string ref = "TOGW";
int refN = 25;
string line = findLine(file,ref);
double MTOGW = findValue(line, refN);
cout << MTOGW;
}
return 0;
}
I initially tried using stof() to convert, but that rounded. I have also tried using stod() and stold(), and last tried converting to a const char* and using atof(). I have messed with the setprecision() value, but also have not been able to solve it that way.
I cannot use Boost
You were almost there. The rounding was occurring on output, so that's where you need to use setprecision. That and always use double instead of float to ensure you have enough precision in your variables.
#include <vector>
#include <ranges>
#include <iomanip>
#include <iostream>
#include <string>
using std::string;
double findValue(string &line, int &refN){
//setprecision(100);
string output;
//go to end column and work backwards to get value string
while(line[refN] != ' '){
output = line[refN] + output;
refN = refN - 1;
}
const char* outputx = output.c_str();
double out = strtod(outputx, NULL);
return out;
}
int main()
{
string s = " 160430.6";
int n = s.size() - 1;
std::cout << std::setprecision(10) << findValue(s, n) << '\n';
}
See it in action on the Godbolt compiler.

Combining common code in functions as individual function in C++?

I'm currently working on an application in C++, and a small part of that involves appending the date and time in array values, so I've created a couple of functions that get these values and make them look more user-friendly. There's a common chunk of code between the two functions, and I can't remember for the life of me how to stick it in it's own function and have it work as it does currently. The full code is below:
// GetDateAndTime.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
#include <ctime>
#include <sstream>
using namespace std;
string getTime();
string getDate();
int main()
{
string theTime = getTime();
string theDate = getDate();
cout << "It is " << theTime << " on " << theDate << endl;
return 0;
}
string getTime()
{
// define variables
string strHour;
string strMin;
// get local time
time_t now;
struct tm nowLocal;
now = time(NULL);
nowLocal = *localtime(&now);
int hour = nowLocal.tm_hour;
int min = nowLocal.tm_min;
// send hour value to string
stringstream ssHour;
ssHour << hour;
if (hour >= 0 && hour <= 9)
{
strHour = "0" + ssHour.str();
}
else
{
strHour = ssHour.str();
}
// send min value to string
stringstream ssMin;
ssMin << min;
if (min >= 0 && min <= 9)
{
strMin = "0" + ssMin.str();
}
else
{
strMin = ssMin.str();
}
string curTime = strHour + ":" + strMin;
return curTime;
}
string getDate()
{
// define variables
string strDay;
string strMonth;
string strYear;
// get local time
time_t now;
struct tm nowLocal;
now = time(NULL);
nowLocal = *localtime(&now);
int day = nowLocal.tm_mday;
int month = nowLocal.tm_mon + 1;
int year = nowLocal.tm_year + 1900;
// send day value to string
stringstream ssDay;
ssDay << day;
if (day >= 1 && day <= 9)
{
strDay = "0" + ssDay.str();
}
else
{
strDay = ssDay.str();
}
// send month value to string
stringstream ssMonth;
ssMonth << month;
if (month >= 1 && month <= 9)
{
strMonth = "0" + ssMonth.str();
}
else
{
strMonth = ssMonth.str();
}
// send year value to string
stringstream ssYear;
ssYear << year;
strYear = ssYear.str();
string curDate = strDay + "/" + strMonth + "/" + strYear;
return curDate;
}
The code block in question is the one commented as "get local time". I'd appreciate any help at all! Thank you!
You can define a function called getLocalTime. This function will do what these 4 lines contains. In both getTime and getDate you'll call it.
I removed the include of stdafx.h because I couldn't compile with it, and it worked fine without it.
using namespace std;
string getTime();
string getDate();
string prepareValueForAddingToString(int dateThing) {
string str;
stringstream ssDateThing;
ssDateThing *2 left arrows* dateThing;
if (dateThing >= 1 && dateThing <= 9)
{
str = "0" + ssDateThing.str();
}
else {
str = ssDateThing.str();
}
return str;
}
int main()
{
string theTime = getTime();
string theDate = getDate();
cout << "It is " << theTime << " on " << theDate << endl;
return 0;
}
string getTime()
{
// define variables
string strHour;
string strMin;
// get local time
time_t now;
struct tm nowLocal;
now = time(NULL);
nowLocal = *localtime(&now);
int hour = nowLocal.tm_hour;
int min = nowLocal.tm_min;
// send hour value to string
string curTime;
curTime += prepareValueForAddingToString(hour);
curTime += ":";
curTime += prepareValueForAddingToString(min);
return curTime;
}
string getDate()
{
// define variables
string strDay;
string strMonth;
string strYear;
// get local time
time_t now;
struct tm nowLocal;
now = time(NULL);
nowLocal = *localtime(&now);
int day = nowLocal.tm_mday;
int month = nowLocal.tm_mon + 1;
int year = nowLocal.tm_year + 1900;
// send day value to string
string curDate;
curDate += prepareValueForAddingToString(day);
curDate += "/";
curDate += prepareValueForAddingToString(month);
curDate += "/";
curDate += prepareValueForAddingToString(year);
return curDate;
}
An example output: It is 15:26 on 07/04/2017

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().

How to convert formatted string HH:MM:SS to seconds in C++

I want to convert a string time stamp formatted in HH:MM:SS to seconds only, and then compare it with a number. I have written a prime version of my code in Java, however I separately ask from Scanner as opposed to having a string time. I'm not much familiar with C++ libraries, as I'm a Java guy. Wondering how I can do it in C++?
Make it brief, String s = "1:01:01"; and String s2= "3600"; I need to know if (s>s2)
import java.util.*;
public class Test {
public static void main(String[] args) {
Scanner console = new Scanner(System.in);
int hours;
int mins;
int secs;
System.out.println("Enter Hours: ");
hours = console.nextInt();
System.out.println("Enter Minutes: ");
mins = console.nextInt();
System.out.println("Enter Seconds: ");
secs = console.nextInt();
int showSecs = (hours * 3600) + (mins * 60) + secs;
System.out.println(hours + ":" + mins + ":" + secs + " in secs are "
+ showSecs);
}
}
I'll risk the downvotes and remind you that we still have sscanf in our toolbox.
int h, m, s= 0;
std::string time ="10:40:03"
if (sscanf(time.c_str(), "%d:%d:%d", &h, &m, &s) >= 2)
{
int secs = h *3600 + m*60 + s;
}
As #ghostofstandardspast suggested you can use the std::get_time() I/O manipulator to read a specific time format from a std::istream
#include <iostream>
#include <sstream>
#include <locale>
#include <iomanip>
#include <ctime>
int main() {
std::tm t;
std::istringstream ss("1:01:01");
ss >> std::get_time(&t, "%H:%M:%S");
std::cout << "Total seconds: "
<< t.tm_hour * 3600 + t.tm_min * 60 + t.tm_sec
<< std::endl;
}
Here's a fully working sample using clang. Unfortunately I couldn't get this sample running using GCC 4.8.x, I'd guess it's not complete in this implementation. May be GCC 4.9.x supports this correctly.
As looking for an alternative (if you can't use a compiler supporting the complete current c++11 standard actually), you may either consider to use std::sscanf() as #Roddy suggested, or split the string using ':' as delimiter character, and simply convert the split out parts to integer values, using the e.g. the atoi() method.
Here's the alternative
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
std::vector<std::string> &split
( const std::string &s
, char delim
, std::vector<std::string> &elems)
{
std::istringstream ss(s);
std::string item;
while (std::getline(ss, item, delim)) {
elems.push_back(item);
}
return elems;
}
int main() {
std::vector<std::string> parts;
split("1:01:01",':',parts);
if(parts.size() == 3) {
int hour = std::atoi(parts[0].c_str());
int min = std::atoi(parts[1].c_str());
int sec = std::atoi(parts[2].c_str());
std::cout << "Total seconds: "
<< hour * 3600 + min * 60 + sec
<< std::endl;
}
return 0;
}
Let's do a simple automaton that parse your string:
#include <string>
int string2sec(const std::string& str) {
int i = 0;
int res = -1;
int tmp = 0;
int state = 0;
while(str[i] != '\0') {
// If we got a digit
if(str[i] >= '0' && str[i] <= '9') {
tmp = tmp * 10 + (str[i] - '0');
}
// Or if we got a colon
else if(str[i] == ':') {
// If we were reading the hours
if(state == 0) {
res = 3600 * tmp;
}
// Or if we were reading the minutes
else if(state == 1) {
if(tmp > 60) {
return -1;
}
res += 60 * tmp;
}
// Or we got an extra colon
else {
return -1;
}
state++;
tmp = 0;
}
// Or we got something wrong
else {
return -1;
}
i++;
}
// If we were reading the seconds when we reached the end
if(state == 2 && tmp < 60) {
return res + tmp;
}
// Or if we were not, something is wrong in the given string
else {
return -1;
}
}
This has not been tested so there is no warranties.
EDIT: I still can not promise anything but I ran quick tests and it seems to work properly as expected.
#include <iostream>
int main(int argc, char** argv) {
(void) argc;
(void) argv;
std::cout << string2sec("0:2:0") << std::endl;
std::cout << string2sec("1:0:0") << std::endl;
std::cout << string2sec("1:2:0") << std::endl;
std::cout << string2sec("10:10:10") << std::endl;
return 0;
}
Output:
120
3600
3720
36610