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.
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).
timegm()
over mktime()
; without the explanation I'm included to favour Dana's argument. –
Killoran time_t timegm(struct tm *timeptr);
to the standard library, consider adding that info to your answer. –
Ogrady 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
_mkgmtime()
. –
Ogrady 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;
}
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;
}
unsetenv()
is not part of the standard C library. Still a good effort. –
Ogrady 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;
}
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. –
Rutherford 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;
}
mktime()
and localtime()
may introduce an error because a wrong UTC offset is used. –
Maurita 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. –
Ogrady 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
.
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? –
Exploitation std::chrono
has overloads for operator/
. 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). –
Togoland 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.
mktime(t) - timezone
. Also, mktime
converts with respect to DST at t
which is not account for at all. –
Kosher 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.
time_t
represents seconds, which isn't necessarily the case (though it usually is) –
Olecranon 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.
TZ
setting is constant, the non-daylight time UTC offset may changed in those few hours. Example Moscow in one day in 2014. Many other modern examples exist and may happen the future rendering this approach brittle. –
Ogrady 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);
}
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.)
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;
}
.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. –
Uncomfortable 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(). –
Oloroso mktime()
anymore, let's just copy the source code from timegm(0)
, it calculates absolutely, I updated the answer. –
Oloroso © 2022 - 2024 — McMap. All rights reserved.