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.
13 Answers
Use timegm()
instead of mktime()
Worth noting as pointed out by @chux - Reinstate Monica below is that time_t timegm(struct tm *timeptr)
is considered adding to the C23 standard (and thus by inclusion into the C++ standard).

- 257,169
- 86
- 333
- 562
-
7Good 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
-
1I'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 00:09
-
1Look at my answer for a more portable version. – liberforce Mar 08 '13 at 19:08
-
@Dana: What is your source for the deprication (I can find no reference to it in the man pages of any of my systems)? I will give you that it is a non standard GNU function. But it is commonly found on BSD Linux systems. But the fact that the OP accepted the answer means it was useful to them. – Martin York Jun 26 '14 at 22:26
-
Not meaning to say it is not useful. But it is a feature test macro, not a standard function. See http://linux.die.net/man/3/timegm - says it is to be avoided. If you include that in your answer I can remove my downvote. – Dana Jun 27 '14 at 23:31
-
5Down 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
-
1I just wrote an article on this subject: https://blog.reverberate.org/2020/05/12/optimizing-date-algorithms.html – Josh Haberman May 13 '20 at 22:51
-
Martin York, as C23 is adding `time_t timegm(struct tm *timeptr);` to the standard library, consider adding that info to your answer. – chux - Reinstate Monica Jun 08 '23 at 22:18
-
@chux-ReinstateMonica I don't see it in [N4950](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/n4950.pdf) – Martin York Jun 09 '23 at 00:18
-
@MartinYork post is tagged C and C++. My comment was about C23. N4950 is about C++. Such is the woes of dual language tagging. – chux - Reinstate Monica Jun 09 '23 at 01:59
-
@chux-ReinstateMonica Done – Martin York Jun 09 '23 at 17:42
for those on windows, the below function is available:
_mkgmtime
link for more info: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/mkgmtime-mkgmtime32-mkgmtime64

- 669
- 13
- 22
-
"for those on windows," --> is more like "for those using _visual studio_. It is a function available via the compiler, not the OS. Other compilers on widows may not have `_mkgmtime()`. – chux - Reinstate Monica Jun 07 '23 at 13:02
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;
}

- 91
- 1
- 1
-
2This solution can have integer overflows, thus security issues. You may need to bound the year and use larger types (see [Mutt bug 3880](https://dev.mutt.org/trac/ticket/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 06 '18 at 10:07
-
This can be made even simpler if you just use the value of tm->tm_yday instead of messing around with the tm_mday and the number of ydays prior to tm_mon. – karora Jun 16 '19 at 16:43
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:
#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;
}

- 11,189
- 37
- 48
-
It also isn't thread-safe, but then again neither is mktime() nessecarily. – T.E.D. Sep 06 '13 at 18:59
-
1
-
-
@T.E.D This is guaranteed to be thread-unsafe whereas mktime() usually is. – Arran Cudbard-Bell Mar 08 '17 at 18:34
-
@liberforce i noticed your copy/paste is missing some code from the man pages, can you comment on why you skipped it? if (tz) tz = strdup(tz); – Syler Oct 09 '17 at 20:57
-
That was 4 years ago and now I have no Idea why :). But you're right, so I'm changing. Thanks for pointing this out. – liberforce Oct 10 '17 at 07:53
-
I think at that time I failed to understand that tz could be a statically allocated buffer, and didn't see the point in copying it... That was a really really bad idea, I can't think of a good reason why one would change an example from a man page. – liberforce Oct 10 '17 at 07:58
-
"portable version"? --> `unsetenv()` is not part of the standard C library. Still a good effort. – chux - Reinstate Monica Jun 07 '23 at 11:36
The following implementation of timegm(1)
works swimmingly on Android, and probably works on other Unix variants as well:
time_t timegm( struct tm *tm ) {
time_t t = mktime( tm );
return t + localtime( &t )->tm_gmtoff;
}

- 321
- 2
- 10
-
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
-
1@Dana: both `mktime()` and `localtime()` may introduce an error because a wrong UTC offset is used. – jfs Sep 06 '14 at 17:04
-
1be careful with daylight saving time though... tm->tm_isdst must be 0 for this to work! – Infinite Feb 04 '15 at 08:40
-
1As 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 07 '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 - Reinstate Monica Aug 07 '18 at 21:11
-
1This function will fail when called around the time when DST is turned of and failure will depend on the selected timezone of the machine making a call. – Sergey D Jan 18 '20 at 03:37
timegm()
works, but is not present on all systems.
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;
}

- 18,685
- 15
- 71
- 81
-
1Won'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 06 '13 at 18:42
-
[Anyway, no portable and correct implementation of UTC to time_t conversion exists](http://www.opensource.apple.com/source/lukemftp/lukemftp-3/lukemftp/libukem/timegm.c) – jfs Sep 06 '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 08 '17 at 18:15
-
2This 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. https://en.wikipedia.org/wiki/Time_zone#List_of_UTC_offsets – Steve Hollasch Feb 25 '18 at 08:30
New answer for old question because C++20 chrono makes this operation very nearly trivial, and very efficient.
- Threadsafe.
- Does not involve the local UTC offset.
- No iteration, not even within the chrono implementation.
#include <chrono>
#include <ctime>
std::time_t
my_timegm(std::tm const& t)
{
using namespace std::chrono;
return system_clock::to_time_t(
sys_days{year{t.tm_year+1900}/(t.tm_mon+1)/t.tm_mday} +
hours{t.tm_hour} + minutes{t.tm_min} + seconds{t.tm_sec});
}
<chrono>
is designed so that you never have to deal with the C timing API again. But even when you do have to deal with it, <chrono>
can make that easier too.
Update:
In response to the first comment below:
The subexpression year{t.tm_year+1900}/(t.tm_mon+1)/t.tm_mday
creates a {year, month, day}
structure called year_month_day
. I.e. no computation is done to construct the year_month_day
, it simply stores the three fields.
Then the year_month_day
is converted to an equivalent date class called sys_days
. This is a time_point
based on system_clock
with a precision of days
. This holds a count of days since the Unix Time epoch of 1970-01-01. This conversion uses the algorithm days_from_civil
described in detail at the link. Note that the algorithm contains no loops, and a good optimizer can get rid of the branches too (it does using clang at -O3).
Finally the time-of-day is added to the date, with chrono supplying all of the necessary conversion factors (multiply the day count by 86400, the hour count by 3600, etc.).
The result is a time_point
based on system_clock
with a precision of seconds
. For all implementations of chrono I'm aware of, the system_clock::to_time_t
function will simply unwrap the count of seconds
so it can be stored in a time_t
.

- 206,506
- 52
- 449
- 577
-
Could you explain that a bit more? In particular, the `sys_days` construction intuits differently than what is actually happening. It looks like a string-like expression `year{2023}/3/16` but it's actually arithmetic? – jwm Mar 16 '23 at 22:58
-
-
2@jwm The trick is that `std::chrono` has [overloads for `operator/`](https://en.cppreference.com/w/cpp/chrono/operator_slash). So an expression like `year{2023}/3/16` looks like two divisions, but those divisions are actually used to create a date. Specifically, the first one takes a `year` and an `int`, and it returns a `year_month` (it's listed as overload #2 at that link), and the second one takes the resulting `year_month` and an `int` and returns a `year_month_day` (overload #21). – Fabio says Reinstate Monica Mar 17 '23 at 01:07
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.
In order for timezone to contain the correct value, you will likely need to call tzset()
during your program's initialization.
You can then just subtract timezone
from the output of mktime
to get the output in UTC.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
time_t utc_mktime(struct tm *t)
{
return (mktime(t) - timezone) - ((t->tm_isdst > 0) * 3600);
}
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.
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.

- 5,912
- 2
- 26
- 48
-
This was helpful to me, but I have found I need to add an additional check that t.tm_isdst > 0 before adding timezone. – karora Jun 15 '19 at 22:47
-
It should be `mktime(t) - timezone`. Also, `mktime` converts with respect to DST at `t` which is not account for at all. – beroal May 10 '21 at 10:13
-
1One caveat is that this approach presumes that time zones are a fixed thing. When in a time zone that has seen a change in its offset between the date to convert and today, the result will be off. – Christoph Lipka Aug 12 '21 at 16:36
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.

- 277
- 2
- 6
-
1This assumes that `time_t` represents seconds, which isn't necessarily the case (though it usually is) – villapx Mar 27 '18 at 21:02
-
Isn't the time_t value here off by 1? Seems like it should be 315532800. – TrentP Sep 08 '18 at 21:56
-
This is not reliable, as Howard Hinnant pointed out in a comment here: https://stackoverflow.com/a/60954178/4083309 As a practical example, Russia experimented with "permanent DST" between 2011 and 2014. This means a date like 2012-01-01 in MSK (Moskau Standard Time) would be converted according to UTC+04, but the correction for 1.1.1980 would be according to UTC+03 assuming a historically accurate implementation. – Arne Vogel Jun 16 '20 at 11:27
Here's my take, which is based exclusively on time_t
/tm
conversion functions, and the only presumption it makes about time_t
is that it is linear:
- Pretending against better knowledge the
tm
structure holds local time (non-DST if anyone asks; it doesn't matter, but must be consistent with step 3), convert it totime_t
. - Convert the date back into a
tm
structure, but this time in UTC representation. - Pretending against better knowledge that
tm
structure to also hold local (non-DST if anyone asks, but more importantly consistent with step 1), and convert it totime_t
once more. - From the two
time_t
results I can now compute the difference between local time (non-DST if anyone asks) and UTC intime_t
units. - Adding that difference to the first
time_t
result gives me the proper time in UTC.
Note that computation of the difference can conceivably be done once, and then applied later to as many dates as desired; this might be a way to solve issues arising from the lack of thread-safety in gmtime
.
(Edit: Then again, this might cause issues if the time zone is changed between the date used to compute the offset, and the date to be converted.)
tm tt;
// populate tt here
tt.tm_isdst = 0;
time_t tLoc = mktime(&tt);
tt = *gmtime(&tLoc);
tt.tm_isdst = 0;
time_t tRev = mktime(&tt);
time_t tDiff = tLoc - tRev;
time_t tUTC = tLoc + tDiff;
Caveat: If the system uses a TAI-based time_t
(or anything else that does respect leap seconds), the resulting time may be off by 1 second if applied to a point in time close to a leap second insertion.

- 652
- 4
- 15
-
"this might cause issues if the time zone is changed between the date used to compute the offset, and the date to be converted" --> Not only that, but even if the `TZ` setting is constant, the non-daylight time UTC offset may changed in those few hours. Example [Moscow](https://en.wikipedia.org/wiki/Moscow_Time) in one day in 2014. Many other modern examples exist and may happen the future rendering this approach [brittle](https://en.wikipedia.org/wiki/Software_brittleness). – chux - Reinstate Monica Sep 27 '22 at 18:35
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: %s\n", 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: %s\n", 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 %s\n", asctime(gmtime(&intdiff)));
}
}
}
exit(0);
}

- 354
- 2
- 9
For all timezones and at all times would be exceedingly difficult if not impossible. You would need an accurate record of all the various arbitrary timezone and daylight savings time (DST) decrees. Sometimes, it is not clear who the local authority is, never mind what was decreed and when. Most systems, for example, are off by one second for uptime (time system has been up) or boottime (timestamp system booted), if a leap second was spanned. A good test would be a date that was once in DST but now is not (or vis versa). (It was not too long ago in the US that it changed.)

- 1
- 4
- 19
Souce code copied from timegm()
:
https://sources.debian.org/src/tdb/1.2.1-2/libreplace/timegm.c/
static int is_leap(unsigned y)
{
y += 1900;
return (y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0);
}
time_t rep_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;
unsigned i;
if (tm->tm_mon > 12 ||
tm->tm_mon < 0 ||
tm->tm_mday > 31 ||
tm->tm_min > 60 ||
tm->tm_sec > 60 ||
tm->tm_hour > 24) {
/* invalid tm structure */
return 0;
}
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;
}
Test by switching the timezone
int main()
{
struct tm utc = {};
utc.tm_year = 1972 - 1900;
utc.tm_mon = 1 - 1;
utc.tm_mday = 1;
time_t calendar = rep_timegm(&utc);
printf("is_leap: %d\n", is_leap(utc.tm_year));
printf("timegm: %ld\n", calendar);
assert(calendar == 63072000);
return 0;
}

- 1,803
- 17
- 12
-
timezone offsets never change with time? I have to change *my* local clocks twice a year. I wish I lived in your timezone... – Howard Hinnant Mar 02 '23 at 20:14
-
mktime(...) depends on the system local timezone, it will change with your timezone, just test it, no need to live in my era. mktime("1970-1-1") will give you an opposite timezone offset, it depends on your system. – Sunding Wei Mar 03 '23 at 02:18
-
I tested it & learned something new, thanks. Your code implies `.tm_idst = 0` which means Daylight Saving Time is not in effect. Your code is correct for every time zone for which the standard UTC offset for 1970-01-01 is the same as the standard offset for the `utc` argument. For my time zone "America/New_York", this is correct: UTC standard offset = -5h always. This negates the Daylight Saving effect I anticipated. However your code still fails for time zones where the standard UTC offset changed since 1970. In "Pacific/Apia" after 2011 your code is off by 1 day. Haven't checked other zones. – Howard Hinnant Mar 03 '23 at 03:47
-
Interesting discussion for a 14 years old question. Seconds since "January 1, 1970" is the unix time definition, it maybe not precise to UTC due to leap seconds, I hate the leap seconds. So the `mktime("January 1, 1970")` will always give you a system correct opposite timezone offset in Unix time, if not exploring the universe, I am confident to use the Unix/Linux mktime(). – Sunding Wei Mar 05 '23 at 10:01
-
When I test your code with my machine set to Europe/London for the UTC date 1972-01-01 00:00:00 it gives 63075600. Is that what it gives for you? The correct answer is 63072000 == 365*2*86400. – Howard Hinnant Mar 05 '23 at 13:48
-
OK, it seems the leap seconds issue, thanks for pointing out, now I am not confident with the system `mktime()` anymore, let's just copy the source code from `timegm(0)`, it calculates absolutely, I updated the answer. – Sunding Wei Mar 06 '23 at 02:30