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.
Related
I used std::mktime to set/get time in my program. The man page tells:
The values in time are permitted to be outside their normal ranges.
and also that it returns -1 if it can not represent the result as std::time_t
I tried everything in my unit tests to make this function fail, but without a success.
These are just some attempts:
#include <iostream>
#include <iomanip>
#include <ctime>
#include <stdlib.h>
int main()
{
setenv("TZ", "/usr/share/zoneinfo/America/New_York", 1); // POSIX-specific
std::time_t t = std::time(nullptr);
std::tm tm = *std::localtime(&t);
std::cout << "Today is " << std::put_time(&tm, "%c %Z")
<< " and DST is " << (tm.tm_isdst ? "in effect" : "not in effect") << '\n';
tm.tm_year = 550;
tm.tm_year = 123456789;
tm.tm_mon = 88;
tm.tm_mday = 200;
//tm.tm_mon -= 100; // tm_mon is now outside its normal range
std::mktime(&tm); // tm_dst is not set to -1; today's DST status is used
std::cout << "100 months ago was " << std::put_time(&tm, "%c %Z")
<< " and DST was " << (tm.tm_isdst ? "in effect" : "not in effect") << '\n';
}
So, how do I set parameters to make this function fails?
As suggested in comments, I tried INT_MAX and it still doesn't fail. So, this:
tm.tm_year = std::numeric_limits< decltype( tm.tm_year ) >::max();
if ( -1 == std::mktime(&tm) )
std::cout << "std::mktime() failed)" << '\n';
is still not going to make it fail.
$CXX --version
aarch64-pdm3-linux-g++ (GCC) 6.4.0
Your sample does fail for the suggested input std::numeric_limits< decltype( tm.tm_year ) >::max(). Your sample does not check for the -1 return value. If you add
auto err = std::mktime(&tm); // tm_dst is not set to -1, err is.
err is set to -1 for this input.
You can see the failure at https://ideone.com/GUcM3N
You can also see the failure at http://coliru.stacked-crooked.com/a/8288579ec8924d7e
Output is identical on both services:
Today is Thu Apr 19 09:07:40 2018 EDT and DST is in effect
100 months ago was Thu ? 200 09:07:40 -2147481749 EDT and DST was in effect
-1
mktime will fail to update a tm struct when an overflow occurs, e.g. when year is set to INT_MAX and more than 12 months are added (over a year). Like this:
std::tm tm;
tm.tm_year = INT_MAX;
tm.tm_mon = 13;
std::time_t rc = std::mktime(&tm);
It will also fail to update a tm struct if you set the year to INT_MIN, the month to zero and subtract one day (set tm_mon to 0 and tm_mday to -1):
std::tm tm;
tm.tm_year = INT_MIN;
tm.tm_mon = 0;
tm.tm_mday = -1;
std::time_t rc = std::mktime(&tm);
It will not fail when year is set to INT_MAX if the combination of month and tm_mday is still less than a year, since that is still representable!
However as MSalters mentions this won't always result in a -1 as return value. Sometimes mktime will return -1 when it cannot normalize the time-stamp to a valid tm struct. But depending on the implementation mktime could also only return a -1 when the time cannot be expressed in time_t (which could be never since time_t is "unspecified"). In the latter case it is actually dangerous to assume the tm struct is normalized to a valid value.
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.
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
I have a problem with using strptime() function in c++.
I found a piece of code in stackoverflow like below and I want to store string time information on struct tm. Although I should get year information on my tm tm_year variable, I always get a garbage.Is there anyone to help me ? Thanks in advance.
string s = dtime;
struct tm timeDate;
memset(&timeDate,0,sizeof(struct tm));
strptime(s.c_str(),"%Y-%m-%d %H:%M", &timeDate);
cout<<timeDate.tm_year<<endl; // in the example below it gives me 113
cout<<timeDate.tm_min<<endl; // it returns garbage
**string s will be like "2013-12-04 15:03"**
cout<<timeDate.tm_year<<endl; // in the example below it gives me 113
it is supposed to give you value decreased by 1900 so if it gives you 113 it means the year is 2013. Month will also be decreased by 1, i.e. if it gives you 1, it is actually February. Just add these values:
#include <iostream>
#include <sstream>
#include <ctime>
int main() {
struct tm tm{};
std::string s("2013-12-04 15:03");
if (strptime(s.c_str(), "%Y-%m-%d %H:%M", &tm)) {
int d = tm.tm_mday,
m = tm.tm_mon + 1,
y = tm.tm_year + 1900;
std::cout << y << "-" << m << "-" << d << " "
<< tm.tm_hour << ":" << tm.tm_min;
}
}
outputs 2013-12-4 15:3
I am trying to write a function that takes in a parameter which is the offset days, and returns the date those many offset days from now. I can easily get the current date from below
#include <ctime>
#include <iostream>
using namespace std;
int main() {
time_t t = time(0); // get time now
struct tm * now = localtime( & t );
cout << (now->tm_year + 1900) << '-'
<< (now->tm_mon + 1) << '-'
<< now->tm_mday
<< endl;
}
My question is if I change the now->tm_mday to now->tm_mday - offset, is it smart enough to do the month change or the year change as they may change.
No — (now->tm_year + 1900), (now->tm_mon + 1) and now->tm_mday are separate expressions and adding a new arithmetic operation to one will not affect the others.
Apply the offset to t instead, which is an integral value representing seconds since UNIX epoch. Then the change will carry through to the tm structure and, ultimately, each of your output expressions:
time_t t0 = time(0); // now
time_t t1 = time(0) - 5; // five seconds ago
time_t t2 = time(0) - 60*60*2; // two hours ago
time_t t3 = time(0) - 60*60*24*5; // five days ago
// (do try to avoid "magic numbers", though)