Easy way to convert a struct tm (expressed in UTC) to time_t type

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



Easy way to convert a struct tm (expressed in UTC) to time_t type



How do I do the above? There is mktime function but that treats the input as expressed in local time but how do i perform the conversion if my input tm variable happens to be in UTC.




9 Answers
9



Use timegm() instead of mktime()





Good answer - only demerit is that it is a non-standard (as in, not in POSIX or C standard) function.
– Jonathan Leffler
Nov 12 '08 at 22:09





I've seen other answers on the net that talk about getting the timezone offset using something like (note this is pseudo code - the parameters aren't right): difftime( mktime( gmtime( time())), mktime( localtime( time()))). But no one ever says how you apply this offset to your time_t variable.
– Michael Burr
Nov 13 '08 at 0:09





Does this work in windows too?
– user24560
Nov 13 '08 at 0:18





Look at my answer for a more portable version.
– liberforce
Mar 8 '13 at 19:08





Down vote because you don't explain why one should use timegm() over mktime(); without the explanation I'm included to favour Dana's argument.
– Jamie
Oct 28 '15 at 18:45



timegm()


mktime()



for those on windows, the below function is available:


_mkgmtime



link for more info: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/mkgmtime-mkgmtime32-mkgmtime64



Here is a solution I use (Can't recall where I found it) when it isn't a windows platform


time_t _mkgmtime(const struct tm *tm)

// Month-to-day offset for non-leap-years.
static const int month_day[12] =
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334;

// Most of the calculation is easy; leap years are the main difficulty.
int month = tm->tm_mon % 12;
int year = tm->tm_year + tm->tm_mon / 12;
if (month < 0) // Negative values % 12 are still negative.
month += 12;
--year;


// This is the number of Februaries since 1900.
const int year_for_leap = (month > 1) ? year + 1 : year;

time_t rt = tm->tm_sec // Seconds
+ 60 * (tm->tm_min // Minute = 60 seconds
+ 60 * (tm->tm_hour // Hour = 60 minutes
+ 24 * (month_day[month] + tm->tm_mday - 1 // Day = 24 hours
+ 365 * (year - 70) // Year = 365 days
+ (year_for_leap - 69) / 4 // Every 4 years is leap...
- (year_for_leap - 1) / 100 // Except centuries...
+ (year_for_leap + 299) / 400))); // Except 400s.
return rt < 0 ? -1 : rt;





This solution can have integer overflows, thus security issues. You may need to bound the year and use larger types (see Mutt bug 3880 as an example).
– vinc17
Oct 25 '16 at 12:10






I used this method and for some reason 31-01-2018 23:59:58 had more seconds than 01-02-2018 00:00:01. I ended up writing my variant without taking leap years into account.
– RelativeGames
Feb 6 at 10:07



timegm() works, but is not present on all systems.


timegm()



Here's a version that only uses ANSI C. (EDIT: not strictly ANSI C! I'm doing math on time_t, assuming that the units are in seconds since the epoch. AFAIK, the standard does not define the units of time_t.) Note, it makes use of a hack, so-to-speak, to determine the machine's time zone and then adjusts the result from mktime accordingly.



/*
returns the utc timezone offset
(e.g. -8 hours for PST)
*/
int get_utc_offset()

time_t zero = 24*60*60L;
struct tm * timeptr;
int gmtime_hours;

/* get the local time for Jan 2, 1900 00:00 UTC */
timeptr = localtime( &zero );
gmtime_hours = timeptr->tm_hour;

/* if the local time is the "day before" the UTC, subtract 24 hours
from the hours to get the UTC offset */
if( timeptr->tm_mday < 2 )
gmtime_hours -= 24;

return gmtime_hours;



/*
the utc analogue of mktime,
(much like timegm on some systems)
*/
time_t tm_to_time_t_utc( struct tm * timeptr )

/* gets the epoch time relative to the local time zone,
and then adds the appropriate number of seconds to make it UTC */
return mktime( timeptr ) + get_utc_offset() * 3600;






Won't this fail if the time offset for the timeptr is different than the time offset at zero? In other words, I don't see how it properly accounts for a daylight savings time changeover.
– T.E.D.
Sep 6 '13 at 18:42



timeptr


zero





@T.E.D. you could be right; I have not tested that.
– Tom
Sep 23 '13 at 22:14





Anyway, no portable and correct implementation of UTC to time_t conversion exists
– jfs
Sep 6 '14 at 15:10





@T.E.D the question states that the timeptr in this case is UTC, not sure what your point is.
– Arran Cudbard-Bell
Mar 8 '17 at 18:15





This code won't work unless your time zone UTC offset is in whole hours. There are 12 time zones where this is not true. Ref. en.wikipedia.org/wiki/Time_zone#List_of_UTC_offsets
– Steve Hollasch
Feb 25 at 8:30



The following implementation of timegm(1) works swimmingly on Android, and probably works on other Unix variants as well:


timegm(1)


time_t timegm( struct tm *tm )
time_t t = mktime( tm );
return t + localtime( &t )->tm_gmtoff;





Wish I could give this more than one upvote. This allows you to pull the DST delta from a UTC.
– Dana
Jun 26 '14 at 20:48






@Dana: both mktime() and localtime() may introduce an error because a wrong UTC offset is used.
– jfs
Sep 6 '14 at 17:04


mktime()


localtime()





be careful with daylight saving time though... tm->tm_isdst must be 0 for this to work!
– Infinite
Feb 4 '15 at 8:40





As Leo says, this is not portable as tm_gmtoff isn't POSIX. It is available on BSDs and in the GNU C library however.
– Arran Cudbard-Bell
Mar 7 '17 at 19:02





Let the correct return value be time_t y. This answer fails in corner cases as the tm_gmtoff value used is based on t and not on y. So if tm_gmtoff differs from t and y, this code generates the wrong answer.
– chux
Aug 7 at 21:11


time_t y


tm_gmtoff


t


y


tm_gmtoff


t


y



The answer of Loki Astari was a good start, timegm is one of the possible solutions. However, the man page of timegm gives a portable version of it, as timegm is not POSIX-compliant. Here it is:


timegm


timegm


timegm


#include <time.h>
#include <stdlib.h>

time_t
my_timegm(struct tm *tm)

time_t ret;
char *tz;

tz = getenv("TZ");
if (tz)
tz = strdup(tz);
setenv("TZ", "", 1);
tzset();
ret = mktime(tm);
if (tz)
setenv("TZ", tz, 1);
free(tz);
else
unsetenv("TZ");
tzset();
return ret;





It also isn't thread-safe, but then again neither is mktime() nessecarily.
– T.E.D.
Sep 6 '13 at 18:59





if it's a c function, it's better to have a return type
– Haiyuan Zhang
Nov 6 '13 at 4:01





Nice catch! Yeah, that was a copy/paste error. This is fixed now.
– liberforce
Nov 7 '13 at 12:57





it probably won't work on Windows.
– jfs
Sep 6 '14 at 15:11





@T.E.D This is guaranteed to be thread-unsafe whereas mktime() usually is.
– Arran Cudbard-Bell
Mar 8 '17 at 18:34



This is really a comment with code to address the answer by Leo Accend:
Try the following:


#include <time.h>
#include <stdio.h>
#include <stdlib.h>

/*
* A bit of a hack that lets you pull DST from your Linux box
*/

time_t timegm( struct tm *tm ) // From Leo's post, above
time_t t = mktime( tm );
return t + localtime( &t )->tm_gmtoff;

main()

struct timespec tspec = 0;
struct tm tm_struct = 0;

if (gettimeofday(&tspec, NULL) == 0) // clock_gettime() is better but not always avail

tzset(); // Not guaranteed to be called during gmtime_r; acquire timezone info
if (gmtime_r(&(tspec.tv_sec), &tm_struct) == &tm_struct)

printf("time represented by original utc time_t: %sn", asctime(&tm_struct));
// Go backwards from the tm_struct to a time, to pull DST offset.
time_t newtime = timegm (&tm_struct);
if (newtime != tspec.tv_sec) // DST offset detected

printf("time represented by new time_t: %sn", asctime(&tm_struct));

double diff = difftime(newtime, tspec.tv_sec);
printf("DST offset is %g (%f hours)n", diff, diff / 3600);
time_t intdiff = (time_t) diff;
printf("This amounts to %sn", asctime(gmtime(&intdiff)));



exit(0);



POSIX page for tzset, describes global variable extern long timezone which contains the local timezone as an offset of seconds from UTC. This will be present on all POSIX compliant systems.


extern long timezone



In order for timezone to contain the correct value, you will likely need to call tzset() during your program's initialization.


tzset()



You can then just add timezone to the output of mktime to get the output in UTC.


timezone


mktime


#include <stdio.h>
#include <stdlib.h>
#include <time.h>

time_t utc_mktime(struct tm *t)

return mktime(t) + timezone;


int main(int argc, char **argv)

struct tm t = 0 ;

tzset();
utc_mktime(&t);



Note: Technically tzset() and mktime() aren't guaranteed to be threadsafe.


tzset()


mktime()



If a thread accesses tzname, [XSI] [Option Start] daylight, or timezone [Option End] directly while another thread is in a call to tzset(), or to any function that is required or allowed to set timezone information as if by calling tzset(), the behavior is undefined.



...but the majority of implementations are. GNU C uses mutexes in tzset() to avoid concurrent modifications to the global variables it sets, and mktime() sees very wide use in threaded programs without synchronization. I suspect if one were to encounter side effects, it would be from using setenv() to alter the value of TZ as done in the answer from @liberforce.


tzset()


mktime()


setenv()


TZ



I was troubled by the issue of mktime() as well. My solution is the following


time_t myTimegm(std::tm * utcTime)

static std::tm tmv0 = 0, 0, 0, 1, 0, 80, 0, 0, 0; //1 Jan 1980
static time_t utcDiff = std::mktime(&tmv0) - 315532801;

return std::mktime(utcTime) - utcDiff;



The idea is to get the time difference by calling std::mktime() with a known time (in this case 1980/01/01) and subtract its timestamp (315532801). Hope it helps.





This assumes that time_t represents seconds, which isn't necessarily the case (though it usually is)
– villapx
Mar 27 at 21:02


time_t






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

make 2 or more post in bootsrap

Store custom data using WC_Cart add_to_cart() method in Woocommerce 3

Firebase Auth - with Email and Password - Check user already registered