Linux-x64 glibc: Why does Feb 1 come before Jan 31? - c++

When you call mktime(), Feb 1 seems to come before Jan 31. Why is this? Am I doing something wrong or is this a bug in glibc?
Here's the code:
struct tm tm;
time_t tt;
memset(&tm, 0, sizeof(tm));
tm.tm_year = 2011;
tm.tm_mon = 1;
tm.tm_mday = 31;
tm.tm_hour = 11;
tm.tm_min = 41;
tm.tm_sec = 28;
tm.tm_isdst = 0;
tt = mktime(&tm);
printf("Time now %d-%d-%d %d:%d:%d (%s) = %lu\n",
tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_zone, tt);
memset(&tm, 0, sizeof(tm));
tm.tm_year = 2011;
tm.tm_mon = 2;
tm.tm_mday = 1;
tm.tm_hour = 1;
tm.tm_min = 1;
tm.tm_sec = 1;
tm.tm_isdst = 0;
tt = mktime(&tm);
printf("Time now %d-%d-%d %d:%d:%d (%s) = %lu\n",
tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_zone, tt);
And here's the output:
Time now 2011-2-3 11:41:28 (PST) = 61257325288
Time now 2011-2-1 1:1:1 (PST) = 61257114061
Note that the original intention was to compare two time_t's. This issue causes the first date/time to appear to be later than the second, which is obviously a bit of a problem.
This is just compiled with "gcc test.c" and run with "./a.out" on Ubuntu 9.10, gcc version 4.4.1 (Ubuntu 4.4.1-4ubuntu8), libc-2.10.1-0ubuntu15
On a 32-bit system the results are as expected - i.e. completely different to the 64 bit result!
Would anyone care to confirm/refute this result and/or give some insight into what I may be doing wrong?

tm_mon is zero-based, so you attempted to set February 31st, which got normalized. Here's a link to the definition of mktime().

Why does Feb 1 come before Jan 31?
Incorrect assignments to struct tm members.
.tm_mon is zero based
.tm_mon is zero based#Jim Garrison.
// tm.tm_mon = 1;
tm.tm_mon = 1 - 1; // For January
.tm.tm_year is 1900 based
// tm.tm_year = 2011;
tm.tm_year = 2011 - 1900;
time_t print specifier
"%ld" is not certainly the matching specifier for time_t, thus incurring undefined behavior (UB). time_t might not not even be an integer type. Recommend a cast to a wide signed type or this. Note that tt = mktime(&tm) returns a -1 value on error, so useful to see -1 and not an unsigned value.
// printf("%lu\n", tt);
printf("%lld\n", (long long) tt);
.tm_isdst
mktime() operates on local time. tm.tm_isdst = 0; asserts the time stamp is standard time (reasonable for PST about January). Had this code been run in a timezone with daylight time in January (e.g. Wellington), the reported tm.tm_hour may differ from expectations. Usually best to let mktime() deduce .tm_isdst. Otherwise finding the difference between timestamps (OP's higher goal) may shift an hour unexpectedly when spanning a DST change.
// tm.tm_isdst = 0;
tm.tm_isdst = -1; // DST information is not available, mktime will adjust.
tt = mktime(&tm)

Related

tm_wday returns a large integer outside 0-6 range

In using the tm structure in c++, i get values of tm_wday that are large integers (e.g. 4199040, much larger than the expected 0-6 range: ). Why would this happen? All the other values such as the year, month etc. are correct.
I have seen previous questions where the weekday seems to be calculated wrongly,i.e. is a different value within the 0-6 range than expected due to time zone differences etc. but I am baffled as to why i would get such a big number instead? It doesn't seem to be a memory location either (not a hex format number).
#include <stdio.h>
#include <iostream>
#include <time.h>
struct tm get_time(std::string timestamp_string = "2019.08.16D11:00:00"){
struct tm tm;
int hh, mm;
int MM, DD, YY;
float ss;
const char * timestamp = timestamp_string.c_str();
if (sscanf(timestamp,"%d.%d.%dD%d:%d:%f", &YY,&MM, &DD,&hh, &mm,&ss) != 6) std::cout<<"oops";
tm.tm_year = YY - 1900; // Years from 1900
tm.tm_mon = MM - 1; // Months form January
tm.tm_mday = DD;
tm.tm_hour = hh;
tm.tm_min = mm;
tm.tm_sec = ss;
tm.tm_isdst = 0;
return tm;
}
int main(){
struct tm tm = get_time("2019.08.16D11:00:00");
std::cout<<"Year is: "<<tm.tm_year<<std::endl; //119 - is correct
std::cout<<"Month since Jan is: "<<tm.tm_mon<<std::endl; //7 - is correct
std::cout<<"Weekday is: "<<tm.tm_wday<<std::endl;//4199040- why is this so large?
return 0;
}
Looking around some more, it is expected that after a tm structure is defined, you run the function mktime() with a reference to the instance in order to update derived values such as tm_wday. So the fixed main() function should be:
int main(){
struct tm tm = get_time("2019.08.16D11:00:00");
mktime(&tm); //needs to be called to update derived values such as tm_wday
std::cout<<"Year is: "<<tm.tm_year<<std::endl; //119 - is correct
std::cout<<"Month since Jan is: "<<tm.tm_mon<<std::endl; //7 - is correct
std::cout<<"Weekday is: "<<tm.tm_wday<<std::endl;//shows 5 now - is correct
return 0;
}
Why should it be any other value?
You never set it to anything.
So, it retains its initial value, which is unspecified since you never initialised the tm.
Therefore, it's not just "a large integer": your whole program has undefined behaviour as a result of attempting to read this unspecified value.
If you expected it to be automatically set to the appropriate weekday index for the values you entered, that's not how it works. The tm is just a collection of values, not a function.
However, you can call mktime to do what you need:
The mktime() function modifies the fields of the tm structure as follows: tm_wday and tm_yday are set to values determined from the contents of the other fields; if structure members are outside their valid interval, they will be normalized (so that, for example, 40 October is changed into 9 November); tm_isdst is set (regardless of its initial value) to a positive value or to 0, respectively, to indicate whether DST is or is not in effect at the specified time. Calling mktime() also sets the external variable tzname with information about the current timezone.
(ref)

Calculate seconds between NSDates in C

I'm writing a Arduino-script, which is C/C++. One of the key functions of the script, is reading a date from an API and calculate how many seconds are left until that datetime.
A typical value is 2016-04-10T02:36:00+02:00 which seems like a NSDate. I've found lots of solutions to accomplish what I want in Objective-C, but not in C/C++.
Any help would be highly appreciated.
To calculate the the difference in seconds in C, use difftime(), it returns the difference expressed in seconds.
double difftime(time_t time1, time_t time0);
If needed, to get the present time, use time(). It determines the current calendar time.
time_t time(time_t *timer);
So all that is left is how to take strings like "2016-04-10T02:36:00+02:00" into time_t.
Covert the string into numeric parts.
if (8 == sscanf(buf, "%d-%d-%dT%d:%d:%d%d:%d", &y, &m, ..., &tzh, &tzm)) Success();
Convert the y,m,d ... into struct tm
struct tm tm = {0}; // Important to first zero fill
tm.tm_year = y - 1900;
tm.tm_mon = m - 1;
tm.tm_mday = d;
...
tm.tm_hour = H + tzh;
tm.tm_min = M + tzm*sign(tzh);
tm.tm_sec = S;
Convert struct tm (in universal time) to time_t. This step is hard as C provides no clear standard way to do it. This this a reasonable portable method. See the rest of the post for alternate approaches.
Notice no assumption is made (or needed) about time_t being seconds since 1970 nor that struct tm has only 9 fields.

How to calculate time differences in C++ with time_t before the epoch?

What I would like to do with my simple program is to calculate a difference in seconds between two dates.
time_t referenceDate;
time_t dateNow = time(0);
struct tm referenceDateComponent = {0};
referenceDateComponent.tm_hour = 0;
referenceDateComponent.tm_min = 0;
referenceDateComponent.tm_sec = 0;
referenceDateComponent.tm_year = 89;
referenceDateComponent.tm_mon = 11;
referenceDateComponent.tm_mday = 31;
referenceDate = mktime(&referenceDateComponent);
long seconds = difftime(dateNow, referenceDate);
Whit the code above the application works fine, but if try to set tm.year negative (to build a date before 1900) the mktime() function return -1
I know that time_t type manage only dates starting from Jan 1, 1970 UTC according with the documentation:
For historical reasons, it is generally implemented as an integral value representing the number of seconds elapsed since 00:00 hours, Jan 1, 1970 UTC (i.e., a unix timestamp). Although libraries may implement this type using alternative time representations.
I know there are also the Boost libraries but is not a usable solution for me.
So my question would be, there is any way to get difference in seconds from dates starting before 1970?
I recommend using the C++11 std::chrono namespace and <chrono> standard headers and the standard functions and classes inside them.
You might also consider difftime from the C standard and localtime & mktime
And there are a lot of good other reasons to upgrade to C++11 at least (or C++14 if you can). Several good recent free software compilers GCC and Clang/LLVM support that standard (compile with -std=c++11 or -std=gnu++14 if you want GNU extensions & C++14)
BTW, your question is much more complex than you believe. Calendars has changed. Julian/Gregorian calendar transition happened in the XXth century in Russia. My late mother was born in 1919, during emigration and the civil war, in a Russian village whose legal system was disputed at that time (Russian revolution did not occur instantly). She had some papers mentioning 13th december 1919, and other papers mentioning 26th december 1919, referring to the same date in two different calendars. How would your software deal with that? I'm not even sure that timezone is enough!
BTW, I'm not sure that Boost or C++11 <chrono> can reliably deal with such calendar issues.
nwp mentioned in a comment a very good computerphile video: Problem with Time & Timezones.
You've already answered this question. time_t represents a number of seconds since the UNIX epoch, not number of seconds since some arbitrary time before that. What you are trying to do fundamentally makes no sense.
If you're stuck on C++03, regardless of your ambiguous claims about what you can and cannot use, you will have to use Boost.DateTime.
Otherwise, the standard library has some nice modern timekeeping features in <chrono>.
When you're trying to do calendar arithmetic using time_t, you naturally have to worry about the type and representation of time_t, which is of course implementation-defined. It's almost always a signed, integral type. On Unix and modern MacOS systems it's seconds since 1970, and for compatibility I think it might be used that way on Windows, too. It tends to be 32 bits. Putting that all together, it can typically represent dates between December 13, 1901 and January 18, 2038.
And indeed when I changed the tm_year line in your code to
referenceDateComponent.tm_year = 60;
the code worked and printed 1721200226, which is about 19921 days or 54.5 years, which is exactly the difference between December 31, 1960 and today.
But if you set tm_year to be negative, you'd be asking for a date before 1900, and that's not going to work using the typical definition of time_t we've been discussing.
(It's true there are other possibilities for time_t. It could be floating point. It could be unsigned instead of signed. It could be a 64-bit type, meaning it'd have a range of almost 600,000,000,000 years, which is incidentally more than a 32-bit tm_year can hold.)
So although there are several naysayers here telling you not to, and although there are certainly plenty of obscure difficulties having to do with time zones and leap seconds and calendars other than Gregorian, you can usually get away with using time_t to do basic calendar math for dates in the 20th century and in this century up until 2038, when the infamous "Y2.038K problem" is going to hit. (It will, I fear, be somewhat worse than the not-so-infamous Y2K problem, but that's another story.)
As I said, your code worked for me for dates before 1970. Here's what I'd recommend using for simple time_t-based date calculations (and with caveats as already mentioned):
time_t makedate(int year, int month, int day)
{
struct tm tm = {0};
tm.tm_hour = 12;
tm.tm_min = tm.tm_sec = 0;
tm.tm_year = year - 1900;
tm.tm_mon = month - 1;
tm.tm_mday = day;
tm.tm_isdst = -1;
return mktime(&tm);
}
int main()
{
long int d = difftime(makedate(2015, 7, 17), makedate(2015, 6, 1));
printf("%ld sec = %ld days = %.2f years\n", d, d/86400, d/86400./365.25);
d = difftime(makedate(2015, 7, 17), makedate(2014, 7, 17));
printf("%ld sec = %ld days = %.2f years\n", d, d/86400, d/86400./365.25);
d = difftime(makedate(1975, 7, 17), makedate(1965, 7, 17));
printf("%ld sec = %ld days = %.2f years\n", d, d/86400, d/86400./365.25);
d = difftime(makedate(1950, 1, 11), makedate(1925, 1, 1));
printf("%ld sec = %ld days = %.2f years\n", d, d/86400, d/86400./365.25);
d = difftime(makedate(2030, 12, 31), makedate(2025, 12, 31));
printf("%ld sec = %ld days = %.2f years\n", d, d/86400, d/86400./365.25);
}
Just like your code, this leverages the surprisingly powerful mktime function, and can do everything it can do. It handles leap years, no problem. It does not handle leap seconds or calendar changes.
And if, as you say, you're interested in dates before 1900, I'm afraid you're out of luck. time_t simply cannot represent those dates on most systems, so you're going to have to pursue some other solution.
You can use the Gregorian calendar's 400-year periodicity to work with dates before the Epoch. (Of course, you need to be careful about the start of the Gregorian calendar in your country of interest).
Add 400 years to both dates you're comparing so that they are beyond 1970 (the Epoch), normalize with mktime(), and compute the difference with difftime():
#include <time.h>
#include <stdio.h>
double compare_dates (const struct tm *date_1, const struct tm *date_2)
{
struct tm normalized_1 = *date_1, normalized_2 = *date_2;
normalized_1.tm_year += 400, normalized_2.tm_year += 400;
time_t t1 = mktime(&normalized_1);
time_t t2 = mktime(&normalized_2);
return difftime(t1, t2);
}
int main (void)
{
// how many seconds between June 1st 1880 and 15th September 1892 ?
struct tm june_1st_1880 = { .tm_mon = 6 - 1, .tm_mday = 1, .tm_year = 1880 - 1900 };
struct tm september_15th_1892 = { .tm_mon = 9 - 1, .tm_mday = 15, .tm_year = 1892 - 1900 };
printf("%f seconds", compare_dates(&june_1st_1880, &september_15th_1892));
}

Difference of two dates using C++

I am trying to take the difference of two dates by first reading the local time saving the tm structure and going to sleep for 5 seconds and read another local time and saving to another tm structure. I was hoping once I take the differences of the two dates to get a value 5 or greater. However, I am getting 0.
I get the correct result if comment out the following lines:
oldyear.tm_year = oldyear.tm_year + 1900;
oldyear.tm_mon = oldyear.tm_mon + 1;
newyear.tm_year = newyear.tm_year + 1900;
newyear.tm_mon = newyear.tm_mon + 1;
My code:
void timeTest()
{
time_t now;
struct tm newyear, oldyear;
double seconds;
time(&now); /* get current time; same as: now = time(NULL) */
oldyear = *localtime(&now);
oldyear.tm_year = oldyear.tm_year + 1900;
oldyear.tm_mon = oldyear.tm_mon + 1;
int epoch1 = mktime(&oldyear);
sleep(5);
time(&now); /* get current time; same as: now = time(NULL) */
newyear = *localtime(&now);
newyear.tm_year = newyear.tm_year + 1900;
newyear.tm_mon = newyear.tm_mon + 1;
int epoch2 = mktime(&newyear);
seconds = difftime(mktime(&newyear),mktime(&oldyear));
printf ("%.f seconds since new year in the current timezone.\n", seconds);
}
If I compile this on Linux, a 64-bit system, I get the output
5 seconds since new year in the current timezone.
However, if I compile for 32-bit system,
% gcc -m32 test2.c
% ./a.out
0 seconds since new year in the current timezone.
Note that mktime expects that the year is 1900-based and month 0-based, so the adjustment you do is incorrect and might cause overflow on 32-bit computers. What your code does is calculate the difference of 2 points of time on date 3914-08-28 - on 32-bit systems the time_t usually is 32 bits, and the largest date representable is 03:14:07 UTC on Tuesday, 19 January 2038 aka Y2K38 jf signed time_t is used.
On errors -1 is returned:
If the specified broken-down time cannot be represented as calendar
time (seconds since the Epoch), mktime() returns (time_t) -1 and does
not alter the members of the broken-down time structure.
Thus if you print out epoch and epoch2 I could bet you get -1 for both these timestamps.

std::mktime and timezone info

I'm trying to convert a time info I reveive as a UTC string to a timestamp using std::mktime in C++. My problem is that in <ctime> / <time.h> there is no function to convert to UTC; mktime will only return the timestamp as local time.
So I need to figure out the timezone offset and take it into account, but I can't find a platform-independent way that doesn't involve porting the whole code to boost::date_time. Is there some easy solution which I have overlooked?
timestamp = mktime(&tm) - _timezone;
or platform independent way:
timestamp = mktime(&tm) - timezone;
If you look in the source of mktime() on line 00117, the time is converted to local time:
seconds += _timezone;
mktime() uses tzname for detecting timezone. tzset() initializes the tzname variable from the TZ enviroment variable. If the TZ variable appears in the enviroment but its value is empty or its value cannot be correctly interpreted, UTC is used.
A portable (not threadsafe) version according to the timegm manpage
#include <time.h>
#include <stdlib.h>
time_t
my_timegm(struct tm *tm)
{
time_t ret;
char *tz;
tz = getenv("TZ");
setenv("TZ", "", 1);
tzset();
ret = mktime(tm);
if (tz)
setenv("TZ", tz, 1);
else
unsetenv("TZ");
tzset();
return ret;
}
Eric S Raymond has a threadsafe version published in his article Time, Clock, and Calendar Programming In C
time_t my_timegm(register struct tm * t)
/* struct tm to seconds since Unix epoch */
{
register long year;
register time_t result;
#define MONTHSPERYEAR 12 /* months per calendar year */
static const int cumdays[MONTHSPERYEAR] =
{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
/*# +matchanyintegral #*/
year = 1900 + t->tm_year + t->tm_mon / MONTHSPERYEAR;
result = (year - 1970) * 365 + cumdays[t->tm_mon % MONTHSPERYEAR];
result += (year - 1968) / 4;
result -= (year - 1900) / 100;
result += (year - 1600) / 400;
if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0) &&
(t->tm_mon % MONTHSPERYEAR) < 2)
result--;
result += t->tm_mday - 1;
result *= 24;
result += t->tm_hour;
result *= 60;
result += t->tm_min;
result *= 60;
result += t->tm_sec;
if (t->tm_isdst == 1)
result -= 3600;
/*# -matchanyintegral #*/
return (result);
}
I have this same problem yesterday and searching man mktime:
The functions mktime() and timegm() convert the broken-out time (in the structure pointed to by *timeptr) into a time value with the same encoding as that of the values returned by the time(3) function (that is, seconds from the Epoch, UTC). The mktime() function interprets the input structure according to the current timezone setting (see tzset(3)). The timegm() function interprets the input structure as representing Universal Coordinated Time (UTC).
In short:
You should use timegm(), instead of using mktime().
mktime assumes that the date value is in the local time zone. Thus you can change the timezone environment variable beforehand (setenv) and get the UTC timezone.
Windows tzset
Can also try looking at various home-made utc-mktimes, mktime-utcs, etc.
If you are trying to do this in a multithreaded program and don't want to deal with locking and unlocking mutexes (if you use the environment variable method you'd have to), there is a function called timegm that does this. It isn't portable, so here is the source:
http://trac.rtmpd.com/browser/trunk/sources/common/src/platform/windows/timegm.cpp
int is_leap(unsigned y) {
y += 1900;
return (y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0);
}
time_t timegm (struct tm *tm)
{
static const unsigned ndays[2][12] = {
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
time_t res = 0;
int i;
for (i = 70; i < tm->tm_year; ++i)
res += is_leap(i) ? 366 : 365;
for (i = 0; i < tm->tm_mon; ++i)
res += ndays[is_leap(tm->tm_year)][i];
res += tm->tm_mday - 1;
res *= 24;
res += tm->tm_hour;
res *= 60;
res += tm->tm_min;
res *= 60;
res += tm->tm_sec;
return res;
}
Use _mkgmtime, it takes care of everything.
Here is a simple, tested, hopefully portable piece of code converting from struct tm to seconds since the beginning of an adjustable UTC year, without temporary change of time zone.
// Conversion from UTC date to second, signed 64-bit adjustable epoch version.
// Written by François Grieu, 2015-07-21; public domain.
#include <time.h> // needed for struct tm
#include <stdint.h> // needed for int_least64_t
#define MY_EPOCH 1970 // epoch year, changeable
typedef int_least64_t my_time_t; // type for seconds since MY_EPOCH
// my_mktime converts from struct tm UTC to non-leap seconds since
// 00:00:00 on the first UTC day of year MY_EPOCH (adjustable).
// It works since 1582 (start of Gregorian calendar), assuming an
// apocryphal extension of Coordinated Universal Time, until some
// event (like celestial impact) deeply messes with Earth.
// It strive to be strictly C99-conformant.
//
// input: Pointer to a struct tm with field tm_year, tm_mon, tm_mday,
// tm_hour, tm_min, tm_sec set per mktime convention; thus
// - tm_year is year minus 1900;
// - tm_mon is [0..11] for January to December, but [-2..14]
// works for November of previous year to February of next year;
// - tm_mday, tm_hour, tm_min, tm_sec similarly can be offset to
// the full range [-32767 to 32767].
// output: Number of non-leap seconds since beginning of the first UTC
// day of year MY_EPOCH, as a signed at-least-64-bit integer.
// The input is not changed (in particular, fields tm_wday,
// tm_yday, and tm_isdst are unchanged and ignored).
my_time_t my_mktime(const struct tm * ptm) {
int m, y = ptm->tm_year+2000;
if ((m = ptm->tm_mon)<2) { m += 12; --y; }
// compute number of days within constant, assuming appropriate origin
#define MY_MKTIME(Y,M,D) ((my_time_t)Y*365+Y/4-Y/100*3/4+(M+2)*153/5+D)
return ((( MY_MKTIME( y , m, ptm->tm_mday)
-MY_MKTIME((MY_EPOCH+99), 12, 1 )
)*24+ptm->tm_hour)*60+ptm->tm_min)*60+ptm->tm_sec;
#undef MY_MKTIME // this macro is private
}
Key observations allowing great simplification compared to the code in this and that answers:
numbering months from March, all months except the one before that origin repeat with a cycle of 5 months totaling 153 days alternating 31 and 30 days, so that, for any month, and without consideration for leap years, the number of days since the previous February can be computed (within a constant) using addition of an appropriate constant, multiplication by 153 and integer division by 5;
the correction in days accounting for the rule for leap year on years multiple-of-100 (which by exception to the multiple-of-4 rules are non-leap except if multiple of 400) can be computed (within a constant) by addition of an appropriate constant, integer division by 100, multiplication by 3, and integer division by 4;
we can compute correction for any epoch using the same formula we use in the main computation, and can do this with a macro so that this correction is computed at compilation time.
Here is another version not requiring 64-bit support, locked to 1970 origin.
// Conversion from UTC date to second, unsigned 32-bit Unix epoch version.
// Written by François Grieu, 2015-07-21; public domain.
#include <time.h> // needed for struct tm
#include <limits.h> // needed for UINT_MAX
#if UINT_MAX>=0xFFFFFFFF // unsigned is at least 32-bit
typedef unsigned my_time_t; // type for seconds since 1970
#else
typedef unsigned long my_time_t; // type for seconds since 1970
#endif
// my_mktime converts from struct tm UTC to non-leap seconds since
// 00:00:00 on the first UTC day of year 1970 (fixed).
// It works from 1970 to 2105 inclusive. It strives to be compatible
// with C compilers supporting // comments and claiming C89 conformance.
//
// input: Pointer to a struct tm with field tm_year, tm_mon, tm_mday,
// tm_hour, tm_min, tm_sec set per mktime convention; thus
// - tm_year is year minus 1900
// - tm_mon is [0..11] for January to December, but [-2..14]
// works for November of previous year to February of next year
// - tm_mday, tm_hour, tm_min, tm_sec similarly can be offset to
// the full range [-32767 to 32768], as long as the combination
// with tm_year gives a result within years [1970..2105], and
// tm_year>0.
// output: Number of non-leap seconds since beginning of the first UTC
// day of year 1970, as an unsigned at-least-32-bit integer.
// The input is not changed (in particular, fields tm_wday,
// tm_yday, and tm_isdst are unchanged and ignored).
my_time_t my_mktime(const struct tm * ptm) {
int m, y = ptm->tm_year;
if ((m = ptm->tm_mon)<2) { m += 12; --y; }
return ((( (my_time_t)(y-69)*365u+y/4-y/100*3/4+(m+2)*153/5-446+
ptm->tm_mday)*24u+ptm->tm_hour)*60u+ptm->tm_min)*60u+ptm->tm_sec;
}
A solution with little coding and portable, as it only uses mktime:
The parsed time has to be in struct tm tm.
if you use c++11, you might want to use std::get_time for parsing. It parses most time strings!
Before calling mktime() be sure tm.tm_isdst is set to zero, then mktime does not adjust for daylight savings,
// find the time_t of epoch, it is 0 on UTC, but timezone elsewhere
// If you newer change timezone while program is running, you only need to do this once
// if your compiler(VS2013) rejects line below, zero out tm yourself (use memset or "=0" on all members)
struct std::tm epoch = {};
epoch.tm_mday = 2; // to workaround new handling in VC, add a day
epoch.tm_year = 70;
time_t offset = mktime(&epoch) - 60*60*24; // and subtract it again
// Now we are ready to convert tm to time_t in UTC.
// as mktime adds timezone, subtracting offset(=timezone) gives us the right result
result = mktime(&tm)-offset
Edit based on comment from #Tom
As other answers note, mktime() (infuriatingly) assumes the tm struct is in the local timezone (even on platforms where tm has a tm_gmtoff field), and there is no standard, cross platform way to interpret your tm as GMT.
The following, though, is reasonably cross platform—it works on macOS, Windows (at least under MSVC), Linux, iOS, and Android.
tm some_time{};
... // Fill my_time
const time_t utc_timestamp =
#if defined(_WIN32)
_mkgmtime(&some_time)
#else // Assume POSIX
timegm(&some_time)
#endif
;
The tm structure used by mktime has a timezone field.
What happens if you put 'UTC' into the timzone field?
http://www.delorie.com/gnu/docs/glibc/libc_435.html
I've just been trying to figure out how to do this. I'm not convinced this solution is perfect (it depends on how accurately the runtime library calculates Daylight Savings), but it's working pretty well for my problem.
Initially I thought I could just calculate the difference between gmtime and localtime, and add that on to my converted timestamp, but that doesn't work because the difference will change according to the time of year that the code is run, and if your source time is in the other half of the year you'll be out by an hour.
So, the trick is to get the runtime library to calculate the difference between UTC and local time for the time you're trying to convert.
So what I'm doing is calculating my input time and then modifying that calculated time by plugging it back into localtime and gmtime and adding the difference of those two functions:
std::tm tm;
// Fill out tm with your input time.
std::time_t basetime = std::mktime( &tm );
std::time_t diff;
tm = *std::localtime( &basetime );
tm.tm_isdst = -1;
diff = std::mktime( &tm );
tm = *std::gmtime( &basetime );
tm.tm_isdst = -1;
diff -= std::mktime( &tm );
std::time_t finaltime = basetime + diff;
It's a bit of a roundabout way to calculate this, but I couldn't find any other way without resorting to helper libraries or writing my own conversion function.
The easy platform-independent way to convert UTC time from string to a timestamp is to use your own timegm.
Using mktime and manipulating timezone environment variables depends on correctly installed and configured TZ database. In one case some timezone links were incorrectly configured (likely side effect of trying different time server packages) which caused mktime-based algorithm to fail on that machine depending on the selected timezone and the time.
Trying to solve this problem with mktime without changing timezone is a dead end because string time (treated as local time) cannot be correctly resolved around the time when your local clock is set back one hour to turn off DST - the same string will match two points in time.
// Algorithm: http://howardhinnant.github.io/date_algorithms.html
inline int days_from_civil(int y, int m, int d) noexcept
{
y -= m <= 2;
int era = y / 400;
int yoe = y - era * 400; // [0, 399]
int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; // [0, 365]
int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]
return era * 146097 + doe - 719468;
}
// Converts a broken-down time structure with UTC time to a simple time representation.
// It does not modify broken-down time structure as BSD timegm() does.
time_t timegm_const(std::tm const* t)
{
int year = t->tm_year + 1900;
int month = t->tm_mon; // 0-11
if (month > 11)
{
year += month / 12;
month %= 12;
}
else if (month < 0)
{
int years_diff = (11 - month) / 12;
year -= years_diff;
month += 12 * years_diff;
}
int days_since_epoch = days_from_civil(year, month + 1, t->tm_mday);
return 60 * (60 * (24L * days_since_1970 + t->tm_hour) + t->tm_min) + t->tm_sec;
}
This solution is free from external dependencies, threadsafe, portable and fast. Let me know if you can find any issues with the code.