c++ converting a string containing UTC date & time to a tm struct - c++

I have the following function which takes a UTC? formatted string and should convert it into a tm struct.
tm utc_string_to_dt(std::wstring sTime)
{
static const std::wstring dateTimeFormat{ L"%Y-%m-%dT%H:%M:%S" };
std::wstringstream ss{ sTime };
std::tm dt;
ss >> std::get_time(&dt, dateTimeFormat.c_str());
// corrections to the returned values. Brain dead year counts from 1900
dt.tm_year = dt.tm_year + 1900; // year counted from 1900
dt.tm_mon = dt.tm_mon + 1; // month starts from 0 (not 1)
return dt;
}
If I give it the string '2011-09-30T19:46:01.000Z' I get a date and time back of 30th Sept 2011 19:46:01 however if it gets a string "2011-08-30T10:00:00+01:00 I get back 30th August 2011 00:00:00 - the time part is being set to midnight. How can I convert the latter string accurately. I am using VS2013 on windows.

How are you outputting it/checking what the value is, and what is the time zone set to on your computer?
I'm not seeing what you're seeing. Given this code, slightly modified from yours:
const std::wstring dateTimeFormat{ L"%Y-%m-%dT%H:%M:%S" };
tm utc_string_to_dt(std::wstring sTime)
{
std::wstringstream ss{ sTime };
std::tm dt;
ss >> std::get_time( &dt, dateTimeFormat.c_str() );
return dt;
}
int main()
{
tm x = utc_string_to_dt( L"2011-08-30T10:00:00+01:00" );
std::cout << x.tm_year + 1900 << '/' << x.tm_mon + 1 << '/' << x.tm_mday << "\n"
<< x.tm_hour << ':' << x.tm_min << ':' << x.tm_sec << "\n";
std::wcout << std::put_time( &x, dateTimeFormat.c_str() );
return 0;
}
This is the output:
2011/8/30
10:0:0
2011-08-30T10:00:00
I suspect that the method of output is adjusting the time.

Related

Getting Current Date inside a C++ process running 24*7

I have a backend process running 24*7 mostly built using C++ and I need to validate if an input date (in format YYYYMMDD) belongs in a set of next 5 business days. The input date is not a clear indicator of the current date so I am using the following function to get the current date and then calculating the next 5 business days from it.
const std::string& CurrentDateStr() {
static const std::string sDate = []() {
time_t currTime = time(NULL);
struct tm timeinfo;
localtime_r(&currTime, &timeinfo);
char buffer[16]="";
strftime(buffer, sizeof(buffer), "%Y%m%d", &timeinfo);
return std::string(buffer);
} ();
return sDate;
}
This function returns me the correct current date if the process was started today but if the process continues running till tomorrow then it will return me yesterday's date as current date due to which calculation of next 5 business days from current date goes for a toss.
Is this expected ? Is there some workaround for it or is there a better way to implement the requirement using standard C++
Your issue is the static variable. You should read up on that, because you're going to encounter it a lot. This is what the comments were trying to get you to do. You can fix your issue by just removing it:
const std::string& CurrentDateStr() {
time_t currTime = time(NULL);
struct tm timeinfo;
localtime_r(&currTime, &timeinfo);
char buffer[16]="";
strftime(buffer, sizeof(buffer), "%Y%m%d", &timeinfo);
return std::string(buffer);
}
For a more modern solution, as suggested in the comments as well, read up on chrono. Especially system_clock::now().
one way to do it using chrono:
#include <iostream>
#include <ctime>
#include <chrono>
#include <thread>
int main()
{
while (true)
{
theTime currentTime = time(nullptr);
tm* date = gmtime(&currentTime);
// Print the date and time
std::cout << "Current date and time: " << date->theDay << "/" << date->theMon + 1 << "/" << date->theYear + 1900;
std::cout << " " << date->theHour << ":" << date->theMmin << ":" << date->theSec << std::endl;
// Wait for 1 minute
std::this_thread::sleep_for(std::chrono::minutes(1));
}
}
OR Use the sleep method.
#include <iostream>
#include <ctime>
#include <unistd.h>
int main()
{
while (true)
{
time_t currentTime = time(nullptr);
tm* date = gmtime(&currentTime);
std::cout << "Current date and time: " << date->tm_mday << "/" << date->tm_mon + 1 << "/" << date->tm_year + 1900;
std::cout << " " << date->tm_hour << ":" << date->tm_min << std::endl;
// Wait for 1 minute (60 seconds)
sleep(60);
}
}

From xs:dateTime to std::chrono::timepoint [duplicate]

Consider a historic date string of format:
Thu Jan 9 12:35:34 2014
I want to parse such a string into some kind of C++ date representation, then calculate the amount of time that has passed since then.
From the resulting duration I need access to the numbers of seconds, minutes, hours and days.
Can this be done with the new C++11 std::chrono namespace? If not, how should I go about this today?
I'm using g++-4.8.1 though presumably an answer should just target the C++11 spec.
std::tm tm = {};
std::stringstream ss("Jan 9 2014 12:35:34");
ss >> std::get_time(&tm, "%b %d %Y %H:%M:%S");
auto tp = std::chrono::system_clock::from_time_t(std::mktime(&tm));
GCC prior to version 5 doesn't implement std::get_time. You should also be able to write:
std::tm tm = {};
strptime("Thu Jan 9 2014 12:35:34", "%a %b %d %Y %H:%M:%S", &tm);
auto tp = std::chrono::system_clock::from_time_t(std::mktime(&tm));
New answer for old question. Rationale for the new answer: The question was edited from its original form because tools at the time would not handle exactly what was being asked. And the resulting accepted answer gives a subtly different behavior than what the original question asked for.
I'm not trying to put down the accepted answer. It's a good answer. It's just that the C API is so confusing that it is inevitable that mistakes like this will happen.
The original question was to parse "Thu, 9 Jan 2014 12:35:34 +0000". So clearly the intent was to parse a timestamp representing a UTC time. But strptime (which isn't standard C or C++, but is POSIX) does not parse the trailing UTC offset indicating this is a UTC timestamp (it will format it with %z, but not parse it).
The question was then edited to ask about "Thu Jan 9 12:35:34 2014". But the question was not edited to clarify if this was a UTC timestamp, or a timestamp in the computer's current local timezone. The accepted answer implicitly assumes the timestamp represents the computer's current local timezone because of the use of std::mktime.
std::mktime not only transforms the field type tm to the serial type time_t, it also performs an offset adjustment from the computer's local time zone to UTC.
But what if we want to parse a UTC timestamp as the original (unedited) question asked?
That can be done today using this newer, free open-source library.
#include "date/date.h"
#include <iostream>
#include <sstream>
int
main()
{
using namespace std;
using namespace date;
istringstream in{"Thu, 9 Jan 2014 12:35:34 +0000"};
sys_seconds tp;
in >> parse("%a, %d %b %Y %T %z", tp);
}
This library can parse %z. And date::sys_seconds is just a typedef for:
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>
The question also asks:
From the resulting duration I need access to the numbers of seconds, minutes, hours and days.
That part has remained unanswered. Here's how you do it with this library.
#include "date/date.h"
#include <chrono>
#include <iostream>
#include <sstream>
int
main()
{
using namespace std;
using namespace date;
istringstream in{"Thu, 9 Jan 2014 12:35:34 +0000"};
sys_seconds tp;
in >> parse("%a, %d %b %Y %T %z", tp);
auto tp_days = floor<days>(tp);
auto hms = hh_mm_ss<seconds>{tp - tp_days};
std::cout << "Number of days = " << tp_days.time_since_epoch() << '\n';
std::cout << "Number of hours = " << hms.hours() << '\n';
std::cout << "Number of minutes = " << hms.minutes() << '\n';
std::cout << "Number of seconds = " << hms.seconds() << '\n';
}
floor<days> truncates the seconds-precision time_point to a days-precision time_point. If you subtract the days-precision time_point from tp, you're left with a duration that represents the time since midnight (UTC).
The type hh_mm_ss<seconds> takes any duration convertible to seconds (in this case time since midnight) and creates a {hours, minutes, seconds} field type with getters for each field. If the duration has precision finer than seconds this field type will also have a getter for the subseconds. Prior to C++17, one has to specify that finer duration as the template parameter. In C++17 and later it can be deduced:
auto hms = hh_mm_ss{tp - tp_days};
Finally, one can just print out all of these durations. This example outputs:
Number of days = 16079d
Number of hours = 12h
Number of minutes = 35min
Number of seconds = 34s
So 2014-01-09 is 16079 days after 1970-01-01.
Here is the full example but at milliseconds precision:
#include "date/date.h"
#include <chrono>
#include <iostream>
#include <sstream>
int
main()
{
using namespace std;
using namespace std::chrono;
using namespace date;
istringstream in{"Thu, 9 Jan 2014 12:35:34.123 +0000"};
sys_time<milliseconds> tp;
in >> parse("%a, %d %b %Y %T %z", tp);
auto tp_days = floor<days>(tp);
hh_mm_ss hms{tp - tp_days};
std::cout << tp << '\n';
std::cout << "Number of days = " << tp_days.time_since_epoch() << '\n';
std::cout << "Number of hours = " << hms.hours() << '\n';
std::cout << "Number of minutes = " << hms.minutes() << '\n';
std::cout << "Number of seconds = " << hms.seconds() << '\n';
std::cout << "Number of milliseconds = " << hms.subseconds() << '\n';
}
Output:
2014-01-09 12:35:34.123
Number of days = 16079d
Number of hours = 12h
Number of minutes = 35min
Number of seconds = 34s
Number of milliseconds = 123ms
This library is now part of C++20, but is in namespace std::chrono and found in the header <chrono>.
This is rather C-ish and not as elegant of a solution as Simple's answer, but I think it might work. This answer is probably wrong but I'll leave it up so someone can post corrections.
#include <iostream>
#include <ctime>
int main ()
{
struct tm timeinfo;
std::string buffer = "Thu, 9 Jan 2014 12:35:00";
if (!strptime(buffer.c_str(), "%a, %d %b %Y %T", &timeinfo))
std::cout << "Error.";
time_t now;
struct tm timeinfo2;
time(&now);
timeinfo2 = *gmtime(&now);
time_t seconds = difftime(mktime(&timeinfo2), mktime(&timeinfo));
time(&seconds);
struct tm result;
result = *gmtime ( &seconds );
std::cout << result.tm_sec << " " << result.tm_min << " "
<< result.tm_hour << " " << result.tm_mday;
return 0;
}
Cases covered (code is below):
since a give date until now
long int min0 = getMinutesSince( "2005-02-19 12:35:00" );
since the epoch until now
long int min1 = getMinutesSince1970( );
between two date+hours (since the epoch until a given date)
long int min0 = getMinutesSince1970Until( "2019-01-18 14:23:00" );
long int min1 = getMinutesSince1970Until( "2019-01-18 14:27:00" );
cout << min1 - min0 << endl;
Complete code:
#include <iostream>
#include <chrono>
#include <sstream>
#include <string>
#include <iomanip>
using namespace std;
// ------------------------------------------------
// ------------------------------------------------
long int getMinutesSince1970Until( string dateAndHour ) {
tm tm = {};
stringstream ss( dateAndHour );
ss >> get_time(&tm, "%Y-%m-%d %H:%M:%S");
chrono::system_clock::time_point tp = chrono::system_clock::from_time_t(mktime(&tm));
return
chrono::duration_cast<chrono::minutes>(
tp.time_since_epoch()).count();
} // ()
// ------------------------------------------------
// ------------------------------------------------
long int getMinutesSince1970() {
chrono::system_clock::time_point now = chrono::system_clock::now();
return
chrono::duration_cast<chrono::minutes>( now.time_since_epoch() ).count();
} // ()
// ------------------------------------------------
// ------------------------------------------------
long int getMinutesSince( string dateAndHour ) {
tm tm = {};
stringstream ss( dateAndHour );
ss >> get_time(&tm, "%Y-%m-%d %H:%M:%S");
chrono::system_clock::time_point then =
chrono::system_clock::from_time_t(mktime(&tm));
chrono::system_clock::time_point now = chrono::system_clock::now();
return
chrono::duration_cast<chrono::minutes>(
now.time_since_epoch()-
then.time_since_epoch()
).count();
} // ()
// ------------------------------------------------
// ------------------------------------------------
int main () {
long int min = getMinutesSince1970Until( "1970-01-01 01:01:00" );
cout << min << endl;
long int min0 = getMinutesSince1970Until( "2019-01-18 14:23:00" );
long int min1 = getMinutesSince1970Until( "2019-01-18 14:27:00" );
if ( (min1 - min0) != 4 ) {
cout << " something is wrong " << endl;
} else {
cout << " it appears to work !" << endl;
}
min0 = getMinutesSince( "1970-01-01 01:00:00" );
min1 = getMinutesSince1970( );
if ( (min1 - min0) != 0 ) {
cout << " something is wrong " << endl;
} else {
cout << " it appears to work !" << endl;
}
} // ()

How to append system time to a string variable

#include<iostream>
#include<ctime>
#include<string>
using namespace std;
int main()
{
string NowTime;
time_t now;
struct tm nowLocal;
now=time(NULL); // get the time from the OS
nowLocal=*localtime(&now);
NowTime = nowLocal.tm_hour + ':' + nowLocal.tm_min + ':' + nowLocal.tm_sec;
cout<< NowTime;
}
When I run the program, it display nothing, can someone help me? I am totally new in programming.
If you try
cout << nowLocal.tm_hour + ':' + nowLocal.tm_min + ':' + nowLocal.tm_sec;
you'll see an integer, not anything resembling a time.
This is because it's the sum of five integers - the characters are promoted to integers, and then it's all added up.
The simplest fix is to not build a string at all but to output the parts individually:
cout << nowLocal.tm_hour << ':' << nowLocal.tm_min << ':' << nowLocal.tm_sec;
Otherwise, you need to convert those numbers to strings:
NowTime = std::to_string(nowLocal.tm_hour) + ':' + std::to_string(nowLocal.tm_min) + ':' + std::to_string(nowLocal.tm_sec);
or, you can use a std::ostringstream, which works just like std::cout and other streams, but writes to a std::string:
std::ostringstream ss;
ss << nowLocal.tm_hour << ':' << nowLocal.tm_min << ':' << nowLocal.tm_sec;
NowTime = ss.str();
The line:
NowTime = nowLocal.tm_hour + ':' + nowLocal.tm_min + ':' + nowLocal.tm_sec;
is adding the hour, minute, and second to the numeric value of the colon symbol, since char is implicitly coerced to an int. That value is then being interpreted as a char in the string assignment operator.
Instead, you can simply output the values directly to cout. They will be formatted appropriately by cout's stream insertion operator <<.
#include<iostream>
#include<ctime>
#include<string>
using namespace std;
int main()
{
string NowTime;
time_t now;
tm nowLocal;
now=time(NULL); // get the time from the OS
nowLocal=*localtime(&now);
cout << nowLocal.tm_hour << ':' << nowLocal.tm_min << ':' << nowLocal.tm_sec;
return 0;
}
If you would instead want to store them in a string, read up about stringstreams. They have a similar syntax to cout and can make formatting strings much easier.
Instead of having to put the result in a variable, you could output it like this:
cout << nowLocal.tm_hour << ':' << nowLocal.tm_min << ':' << nowLocal.tm_sec;
Live Example
Also, if you want to keep the variable, do this:
NowTime = std::to_string(nowLocal.tm_hour) + ':' + std::to_string(nowLocal.tm_min) + ':' + std::to_string(nowLocal.tm_sec);
cout << NowTime;
Try this:
#include<iostream>
#include<ctime>
#include<string>
#include <sstream>
using namespace std;
int main()
{
string NowTime;
time_t now;
struct tm nowLocal;
now=time(NULL); // get the time from the OS
nowLocal=*localtime(&now);
stringstream s;
s<<nowLocal.tm_hour;
s<<":";
s<<nowLocal.tm_min;
s<<":";
s<<nowLocal.tm_sec;
NowTime = s.str();
cout<< NowTime;
}
You cannot cas directly from int to string and you need put values into stream and then to string.
What about using iostringstream to build the string you want?
#include<iostream>
#include<ctime>
#include<string>
#include <sstream>
using namespace std;
int main()
{
ostringstream NowTime;
time_t now;
struct tm nowLocal;
now=time(NULL); // get the time from the OS
nowLocal=*localtime(&now);
NowTime << nowLocal.tm_hour << ":" << nowLocal.tm_min << ":" << nowLocal.tm_sec;
cout<< NowTime.str() << endl;
}
Or for the purposes of your program you could simple use std::cout which also happens to be an output stream.
cout << nowLocal.tm_hour << ":" << nowLocal.tm_min << ":" << nowLocal.tm_sec << endl;
Since it looks like you're pre-c++11 and can't use std::to_string. Here's a C-like way of doing it, sticking to the includes you're currently using.
#include<iostream>
#include<ctime>
#include<string>
using namespace std;
#define STR_LEN 128
int main()
{
string nowTime;
time_t now;
struct tm nowLocal;
now = time( NULL ); // get the time from the OS
nowLocal = *localtime( &now );
char hour[ STR_LEN ], min[ STR_LEN ], sec[ STR_LEN ];
sprintf( hour, "%d", nowLocal.tm_hour );
sprintf( min, "%d", nowLocal.tm_min );
sprintf( sec, "%d", nowLocal.tm_sec );
nowTime = string( hour ) + ':' + string( min ) + ':' + string( sec );
cout << nowTime << endl;
}
How append system time to a string?
My answer is to build a convenience function.
If you really only need hour, minute, second, then you need not use the relatively slow localtime(). (on the other hand, if you do need more, I think you should prefer localtime_r() for the conversion).
For an embedded system several contracts back, I found this conversion to be a relatively slow function and chose to avoid it. The algorithms to handle leap days, centuries, etc. appear simple enough. I suspect I considered it slow simply because it calculates more than I needed in that application that was trying to do the conversion many times per second.
There exists a simpler (and probably still faster) approach involving modular arithmetic. It starts the same - with a time(0) (and thus I suspect what I am doing here is 'hidden' in the localtime_r() function). Side note 1 - on my older Dell running Ubuntu 15.10, time(0) is simply the fastest access to the wall clock, measuring about 6 or 7 ns 'typical' duration. Side note 2 - time_t may change someday. "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."
The code I currently use to conveniently generate a time stamp string:
std::string hhCmmCssGet(time_t tt = 0) // tt has default value
{
time_t now = ( tt ? tt : time(0) );
static time_t prev = 0;
static char hhmmss[] = "hh:mm:ss";
if (prev != now)
{
prev = now;
const int32_t CST = -6;
int64_t hr = ((now / 3600) % 24) + CST; // hr of day, CST offset
if (hr < 0) hr += 24;
uint64_t min = ((now / 60) % 60); // min of day
uint64_t sec = (now % 60); // sec of day
std::stringstream ss;
ss << std::dec
<< std::setfill('0') << std::setw(2) << hr << ":"
<< std::setfill('0') << std::setw(2) << min << ":"
<< std::setfill('0') << std::setw(2) << sec;
for (size_t i=0; i<8; i++) // transfer new value
hhmmss[i] = ss.str()[i]; // into static hhmmss
}
std::string retVal(hhmmss);
return(retVal);
}
The static items and "if (prev != now)" clause, allow this function to be invoked thousands of times per second ... with much reduced effort. The second, after all, only updates 1ce per second. And note that the std::stringstream stuff and modular arithmetic operations only run 1ce per second.

Valid Date function using <ctime> mktime

I wanted a function that would take three inputs of day, month, year and tell me whether it is valid or not. Then using the example on http://www.cplusplus.com/reference/ctime/mktime/
I tried to implement my function:
bool ValidDate(int d, int m, int y)
{
struct tm *timeinfo;
time_t rawtime;
time (&rawtime);
timeinfo = localtime(&rawtime);
timeinfo->tm_year = y - 1900;
timeinfo->tm_mon = m - 1;
timeinfo->tm_mday = d;
if (mktime(timeinfo) == -1 )
return false;
else return true;
}
The problem is that the function is returning not as i want it to.
e.g im checking like
if (ValidDate(4,13,2010)) // out put is valid
std::cout << "valid\n";
else std::cout << "Invalid\n";
ValidDate(4,22,2010) // valid
ValidDate(344,13,2010) //valid
ValidDate(4,133,2010) //valid
ValidDate(31,12, 1920) //invalid
ValidDate(31,9,2010) //valid
ValidDate(4,9,2010) //valid
Why? thanks.
EDIT:
all dates entered were invalid except 31,12,1920 and 4,9,2010 and non of the outputs were correct.
mktime return is as follow :
Time since epoch as a std::time_t object on success or -1 if time cannot be represented as a std::time_t object.
std::time_t is defined as follow :
Arithmetic type capable of representing times.
Although not defined, this is almost always a integral value holding the number of seconds (not counting leap seconds) since 00:00, Jan 1 1970 UTC, corresponding to POSIX time.
So 31/12/1920 cannot be represented into a std::time_t as it is before the epoch.
As for the other invalid dates that are reported as valid, mktime also states :
The values in [the parameter] are permitted to be outside their normal ranges.
Here is the example taken from cppreference :
#include <iostream>
#include <iomanip>
#include <ctime>
int main()
{
std::time_t t = std::time(NULL);
std::tm tm = *std::localtime(&t);
std::cout << "Today is " << std::put_time(&tm, "%c %Z") <<'\n';
tm.tm_mon -= 100; // tm_mon is now outside its normal range
std::mktime(&tm);
std::cout << "100 months ago was " << std::put_time(&tm, "%c %Z") << '\n';
}
Output is :
Today is Wed Dec 28 09:56:10 2011 EST
100 months ago was Thu Aug 28 10:56:10 2003 EDT

C++: mktime bug under MinGW on Windows Vista?

So I'm trying to convert dates in the format "2000-01-01" into integers representing the number of days since some arbitrary origin (e.g. 1900/01/01) so I can treat them as integer indices. To do this I wrote a conversion function which works fine on MinGW under Windows XP but not under Vista. I've added some logging code:
int dateStrToInt(string date) {
int ymd[3];
tm tm1, tm0;
istringstream iss(date);
string s;
for (int i = 3; i; --i) {
getline(iss, s, '-');
ymd[3-i] = str2<int>(s);
}
cout << ymd[0] << ' ' << ymd[1] << ' ' << ymd[2] << ' ' << endl;
tm1.tm_year = ymd[0] - 1900;
tm1.tm_mon = ymd[1] - 1;
tm1.tm_mday = ymd[2];
time_t t1 = mktime(&tm1);
tm0.tm_year = 0;
tm0.tm_mon = 0;
tm0.tm_mday = 0;
time_t t0 = mktime(&tm0);
//cout << "times: " << mktime(&origin) << ' ' << mktime(&time) << endl;
cout << "times: " << t0 << ' ' << t1 << endl;
cout << "difftime: " << difftime(t1, t0) << endl;
return difftime(mktime(&tm1), mktime(&tm0)) / (60*60*24);
}
int i = dateStrToInt("2000-01-01");
and the output I get from that is
2000 1 1
times: -1 -1
difftime: 0
which seems clearly wrong. What can I do about this?
EDIT: as the answer below says, there seems to be a problem with years prior to 1970. To avoid this I've handrolled my own day-counting function:
int dateStrToInt(string date) {
int ymd[3];
istringstream iss(date);
string s;
for (int i = 0; i < 3; ++i) {
getline(iss, s, '-');
ymd[i] = str2<int>(s);
}
const static int cum_m_days[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
int year = ymd[0]+10000, month = ymd[1], day = ymd[2];
int days = year*365 + cum_m_days[month-1] + day;
// handle leap years
if (month <= 2)
--year;
days = days + (year/4) - (year/100) + (year/400);
return days;
}
It's not necessarily a good idea leaving all of those other struct tm fields at their default (random in this case) values.
The standard is not overly explicit about what fields need to be set before calling mktime but it does say that it sets tm_wday and tm_yday based on the other fields, and that those other fields are not restricted to being valid.
One thing the standard does show is example code which sets all fields except those two mentioned above so that's what I'd be aiming for.
Try to change the segment that calculates the times from:
tm1.tm_year = ymd[0] - 1900;
tm1.tm_mon = ymd[1] - 1;
tm1.tm_mday = ymd[2];
time_t t1 = mktime(&tm1);
tm0.tm_year = 0;
tm0.tm_mon = 0;
tm0.tm_mday = 0;
time_t t0 = mktime(&tm0);
to something like:
// Quick and dirty way to get decent values for all fields.
time_t filled_in;
time (&filled_in);
memcpy (&tm1, localtime ( &filled_in ), sizeof (tm1));
memcpy (&tm0, &tm1, sizeof (tm0));
// Now do the modifications to relevant fields, and calculations.
tm1.tm_year = ymd[0] - 1900;
tm1.tm_mon = ymd[1] - 1;
tm1.tm_mday = ymd[2];
time_t t1 = mktime(&tm1);
tm0.tm_year = 0;
tm0.tm_mon = 0;
tm0.tm_mday = 0;
time_t t0 = mktime(&tm0);
In addition, some experimentation with CygWin under XP results in mktime alway seeming to return -1 for struct tm structures where the tm_year is less than two. Whether that's an actual bug or not is questionable since I've often found that implementations don't always support dates before the epoch (Jan 1, 1970).
Some UNIXes did allow you to specify tm_year values less than 70 and they could often use these "negative" values of time_t to access years back to 1970.
But, since the standard doesn't really go into that, it's left to the implementation. The relevant bit of the C99 standard (and probably earlier iterations), which carries forward to C++, is found in 7.23.1/4:
The range and precision of times representable in clock_t and time_t are implementation-defined.
The safest bet would be to use a date after the start of the epoch as the baseline date. This is shown in the following code:
#include <iostream>
#include <sstream>
#include <string>
#include <ctime>
#include <cstring>
#include <cstdlib>
int dateStrToInt(std::string date) {
int ymd[3];
tm tm1, tm0;
std::istringstream iss(date);
std::string s;
// Test code.
ymd[0] = 2000; ymd[1] = 1; ymd[2] = 1;
std::cout << ymd[0] << ' ' << ymd[1] << ' ' << ymd[2] << ' ' << std::endl;
time_t filled_in;
time (&filled_in);
std::memcpy (&tm0, localtime ( &filled_in ), sizeof (tm0));
std::memcpy (&tm1, &tm0, sizeof (tm1));
tm1.tm_year = ymd[0] - 1900;
tm1.tm_mon = ymd[1] - 1;
tm1.tm_mday = ymd[2];
time_t t1 = mktime(&tm1);
tm0.tm_year = 1970 - 1900; // Use epoch as base date.
tm0.tm_mon = 0;
tm0.tm_mday = 1;
time_t t0 = mktime(&tm0);
std::cout << "times: " << t0 << ' ' << t1 << std::endl;
std::cout << "difftime: " << difftime(t1, t0) << std::endl;
return difftime(mktime(&tm1), mktime(&tm0)) / (60*60*24);
}
int main (void) {
int i = dateStrToInt("2000-01-01");
double d = i; d /= 365.25;
std::cout << i << " days, about " << d << " years." << std::endl;
return 0;
}
This outputs the expected results:
2000 1 1
times: 31331 946716131
difftime: 9.46685e+08
10957 days, about 29.9986 years.
As an addendum, POSIX has this to say:
4.14 Seconds Since the Epoch
A value that approximates the number of seconds that have elapsed since the Epoch. A Coordinated Universal Time name (specified in terms of seconds (tm_sec), minutes (tm_min), hours (tm_hour), days since January 1 of the year (tm_yday), and calendar year minus 1900, (tm_year)) is related to a time represented as seconds since the Epoch, according to the expression below.
If the year is <1970 or the value is negative, the relationship is undefined. If the year is >=1970 and the value is non-negative, the value is related to a Coordinated Universal Time name according to the C-language expression, where tm_sec, tm_min, tm_hour, tm_yday, and tm_year are all integer types:
tm_sec + tm_min*60 + tm_hour*3600 + tm_yday*86400 +
(tm_year-70)*31536000 + ((tm_year-69)/4)*86400 -
((tm_year-1)/100)*86400 + ((tm_year+299)/400)*86400
The relationship between the actual time of day and the current value for seconds since the Epoch is unspecified.
How any changes to the value of seconds since the Epoch are made to align to a desired relationship with the current actual time is implementation-defined. As represented in seconds since the Epoch, each and every day shall be accounted for by exactly 86400 seconds.
Note: The last three terms of the expression add in a day for each year that follows a leap year starting with the first leap year since the Epoch. The first term adds a day every 4 years starting in 1973, the second subtracts a day back out every 100 years starting in 2001, and the third adds a day back in every 400 years starting in 2001. The divisions in the formula are integer divisions; that is, the remainder is discarded leaving only the integer quotient.
In other words (see "If the year is <1970 or the value is negative, the relationship is undefined"), use dates before 1970 at your own risk.