Difference between mktime and timelocal - c

What is the difference between these two functions? It was my understanding that those should be the same: http://www.gnu.org/software/libc/manual/html_node/Broken_002ddown-Time.html.
I wrote this code to test the conversion (the Qt part is only for comparison):
#include <QCoreApplication>
#include <QDateTime>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QDateTime datetime(QDate(2012, 3, 25), QTime(5, 15));
qDebug("Timestamp: %lld.", datetime.toMSecsSinceEpoch()/1000L);
time_t timestamp;
tm* timeinfo = localtime(&timestamp);
timeinfo->tm_hour = 5;
timeinfo->tm_mday = 25;
timeinfo->tm_min = 15;
timeinfo->tm_mon = 2;
timeinfo->tm_year = 112;
timeinfo->tm_sec = 0;
qDebug("Timestamp: %ld.", timelocal(timeinfo));
return 0;
}
and found out that the output is:
Timestamp: 1332645300.
Timestamp: 1332645300.
which is what I'd expect. Then I replaced timelocal with mktime and found out that this was the output:
Timestamp: 1332645300.
Timestamp: 1332648900.
It seems like an hour was added (consider that my current timezone is GMT+2:00 and my locale is set to Italy). Why? What is the difference between the two and why mktime adds 1 hour to the date I set?
EDIT: I tested again and it seems that on Mac OS X (and iOS) timelocal is returning the same hour placed in the timeinfo structure, while mktime is actually adding an hour both in the returned time_t value and in the structure tm.
On Linux Kubuntu instead, with both functions I get that an hour is added to both the tm structure and the returned value.
Anyone who can explain why?

The man of OpenBSD's timelocal states:
timelocal is a deprecated interface that is equivalent to calling
mktime() with a negative value for tm_isdst
A negative value for tm_isdst means that timelocal doesn't take daylight saving time (DST) into account. It seems that QDateTime has troubles with DST as well.
mktime on the other hand, handles DST and this might explain the 1-hour difference on OS X.
Since timelocal is deprecated, it might have newer imlementations that deal with DST properly.

Related

mktime() for non-local timezone

In C the function mktime() returns the epoch time according to the local timezone (the input struct is locally formatted).
The function timegm() returns the epoch time according to the UTC time (the input struct is formatted based off of UTC time).
The function localtime_r () takes in an epoch time and returns a local timezone formatted struct.
The function gmtime_r () takes in an epoch time and returns a UTC formatted struct.
I need to find out if a non-local timezone is currently daylight savings time or not, which would work with the localtime_r() function if it were local, but what if it were not local?
The gmtime_r() function always sets the tm_isdst field to zero, which won't work here.
Maybe there's some other function I am not aware of. Not sure.
If you (a) don't want to muck around with a global environment variable and (b) have the "NetBSD inspired" time functions available to you, there's an additional possibility: mktime_z() and localtime_rz(), which let you explicitly specify the zone you want to use. So you're not limited to your default local zone, or UTC.
Here's an example:
int main(int argc, char **argv)
{
timezone_t tzp = tzalloc(argv[1]);
if(tzp == NULL) return 1;
time_t now = time(NULL);
struct tm tm;
struct tm *tmp = localtime_rz(tzp, &now, &tm);
char tmpbuf[20];
strftime(tmpbuf, sizeof(tmpbuf), "%H:%M:%S", tmp);
printf("right now in zone %s is %s\n", argv[1], tmpbuf);
tm.tm_year = 1976 - 1900;
tm.tm_mon = 7 - 1;
tm.tm_mday = 4;
tm.tm_hour = 12;
tm.tm_min = tm.tm_sec = 0;
tm.tm_isdst = -1;
time_t t = mktime_z(tzp, &tm);
printf("in zone %s, %d-%02d-%02d %d:%02d was %ld\n", argv[1],
1900+tm.tm_year, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, t);
}
When I invoke tzt America/New_York I see
right now in zone America/New_York is 11:58:23
in zone America/New_York, 1976-07-04 12:00 was 205344000
and when I invoke tzt America/Los_Angeles I see
right now in zone America/Los_Angeles is 08:58:49
in zone America/Los_Angeles, 1976-07-04 12:00 was 205354800
Now, with that said, two further comments, tied to my opening "if"s:
a. If you don't want to muck around with a global environment variable, I don't blame you one tiny bit. I positively hate mucking around with global variables (let alone environment variables) to affect how a function like mktime or localtime behaves. Unfortunately, however, this is the recommended way, in C, in this situation — see this question's other answers 1, 2 for details.
b. Chances are unfortunately quite good that you don't, in fact, "have the NetBSD inspired time functions available to you". They're nonstandard and not even very popular. I was able to compile the test program above only because I had a copy of the IANA tz database and its code handy, which includes those functions if you also define NETBSD_INSPIRED. (That's why I broke the rules and didn't show a complete example, with all #include lines, since mine were weird and idiosyncratic.)
I need to find out if a non-local timezone is currently daylight savings time or not
Get the current time as a time_t from time.
Set env var TZ to the target time zone using putenv.
Call tzset. (I don't know if this is required, but there's surely no harm in calling it.)
Use localtime to convert the time_t into a struct tm according to the (modified) local time zone.
Check the tm_isdst field of that struct tm.
Here's some code I wrote a decade ago that does what ikegami suggests in their answer:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
static void time_convert(time_t t0, char const *tz_value)
{
char old_tz[64] = "-none-";
char *tz = getenv("TZ");
if (tz != 0)
strcpy(old_tz, tz);
setenv("TZ", tz_value, 1);
tzset();
char new_tz[64];
strcpy(new_tz, getenv("TZ"));
char buffer[64];
struct tm *lt = localtime(&t0);
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", lt);
if (strcmp(old_tz, "-none-") == 0)
unsetenv("TZ");
else
setenv("TZ", old_tz, 1);
tzset();
printf("%lld = %s (TZ=%s, DST = %d)\n",
(long long)t0, buffer, new_tz, lt->tm_isdst);
}
int main(void)
{
time_t t0 = time(0);
char *tz = getenv("TZ");
if (tz != 0)
time_convert(t0, tz);
time_convert(t0, "UTC0");
time_convert(t0, "IST-5:30");
time_convert(t0, "EST5");
time_convert(t0, "EST5EDT");
time_convert(t0, "PST8");
time_convert(t0, "PST8PDT");
}
The output I get is:
1650647033 = 2022-04-22 17:03:53 (TZ=UTC0, DST = 0)
1650647033 = 2022-04-22 22:33:53 (TZ=IST-5:30, DST = 0)
1650647033 = 2022-04-22 12:03:53 (TZ=EST5, DST = 0)
1650647033 = 2022-04-22 13:03:53 (TZ=EST5EDT, DST = 1)
1650647033 = 2022-04-22 09:03:53 (TZ=PST8, DST = 0)
1650647033 = 2022-04-22 10:03:53 (TZ=PST8PDT, DST = 1)
Note that some of the time zones are specified without a daylight saving time, and the code reports DST = 0 for those zones.
Be wary of changing the time zone like this in multi-threaded applications. And be cautious about resetting the environment in case you fork() and exec() other programs with an unexpected value for the TZ environment variable.
Note: I've modified the code to:
Use long long instead of long for printing the time_t value. One of the annoying things is that there is no standard format string for printing the time_t type as an integer (indeed, the C standard doesn't even guarantee that the type is an integer, but it actually is on most systems, and POSIX requires it to be an integer type — see <sys/types.h>). This change should allow 32-bit systems to work in 2038. That assumes that time_t is a 64-bit value even though it is a 32-bit system; if the system still uses a 32-bit time_t, it is terminally broken when time_t wraps around to -231 — but that shouldn't still be my problem then.
Print lt->tm_isdst which is the information wanted in the question.

year not displaying on linux

Hope someone can help. I am fixing a problem in someone’s’ C code that was written a long time ago and he has since moved on.
The piece of code outputs the timestamp of a particular file. The code works fine when run on windows but when it is run on Linux it displays the Year incorrectly. The year is not displaying on linux, it shows 35222. Does anyone have any idea what is the problem here?
Thanks
Windows output:
Source file: test.dtl, Created: Mon, 27 May, 2013 at 16:13:20
Linux output:
Source file: test.dtl, Created: Mon, 27 May, 35222 at 16:13:20
The function in C code:
void SummaryReport ( report_t *report, char *dtlName)
{
LogEntry(L"SummaryReport entry\n");
int i;
wchar_t *rootStrType,*localStr,timeStr[48];
wchar_t fileBuff[64];
struct tm *timeVals;
timeVals = localtime (&logHdr.date);
wcsftime (timeStr,47,L"%a, %#d %b, %Y at %X",timeVals);
/* Print the header information */
DisplayReportFile (report);
ReportEntry (report,L" Filesystem Audit Summary Report\n\n");
ReportEntry (report,L"Source file: %s, Created: %ls\n\n",dtlName,timeStr);
ReportEntry (report,L"Server: %ls",srvrName);
…
}
Verified on a minimal example and it "works-for-me". Does this show the right time?
#include <wchar.h>
#include <time.h>
int main() {
wchar_t timeStr[48];
struct tm *timeVals;
time_t now = time(NULL);
timeVals = localtime(&now);
wcsftime(timeStr, 47, L"%a, %#d %b, %Y at %X", timeVals);
wprintf(timeStr);
return 0;
}
If yes, check the file itself - if you're sharing the filesystem, maybe there's some weird issue with the file timestamp itself? (or with understanding the fs metadata)
In case wcsftime() itself calls localtime(), insure the results of your call are not corrupted.
struct tm timeVals;
timeVals = *localtime (&logHdr.date);
wcsftime (timeStr,47,L"%a, %#d %b, %Y at %X", &timeVals);
localtime() saves its results in a static struct tm somewhere. The address (pointer) to that location is returned. Subsequent calls to localtime() or gmtime() alter the struct tm. I suspect the call to wcsftime() indirectly does that in Linux.
BTW: localtime() could return NULL, so safer code would check the localtime() return value.
Your may want to look into the localtime_s()

GMT confusion with ctime functions [duplicate]

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()
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
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;
}
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;
}
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;
}
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.
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.
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 to time_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 to time_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 in time_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.
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.)

UTC to Time of the day in ANSI C?

how to convert the utc time to local time of the day?
You must use a mix of tzset() with time/gmtime/localtime/mktime functions.
Try this:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
time_t makelocal(struct tm *tm, char *zone)
{
time_t ret;
char *tz;
tz = getenv("TZ");
setenv("TZ", zone, 1);
tzset();
ret = mktime(tm);
if(tz)
setenv("TZ", tz, 1);
else
unsetenv("TZ");
tzset();
return ret;
}
int main(void)
{
time_t gmt_time;
time_t local_time;
struct tm *gmt_tm;
gmt_time = time(NULL);
gmt_tm = gmtime(&gmt_time);
local_time = makelocal(gmt_tm, "CET");
printf("gmt: %s", ctime(&gmt_time));
printf("cet: %s", ctime(&local_time));
return 0;
}
Basically, this program takes the current computer day as GMT (time(NULL)), and convert it to CET:
$ ./tolocal
gmt: Tue Feb 16 09:37:30 2010
cet: Tue Feb 16 08:37:30 2010
M. MARIE's answer does not in fact work for the question as posed: tzset() is POSIX, but not ANSI C as the title of the original question asked. There is no mention of it in either C90 or C99 (from searching the draft standards; I have no access to the final standards).
OP's question is perhaps a little vague as it is not clear what he means by "utc time", but presumably he means broken-down components, let's say filled into a struct tm.
It is possible in C99 to determine local TZ's offset from UTC by parsing the output of strftime("%z",...) (make sure that you call it with your own date values, as this offset will change over time); but this format-code is not available in C90, so AFAIK you're out of luck if you must conform to C90, unless you want to try to parse the output of strftime("%Z",...), but that's going to be fundamentally non-portable.
You then could convert your UTC components to time_t using mktime(), although they will be interpreted as in the local timezone; then apply the offset, and convert back to broken-down components using localtime(). You may run into edge cases around the time when your local timezone switches to and from DST (or when changes to your timezone's offset where effected), but this can be easily avoided by moving to a locale that does not use DST, or ameliorated by setting tm_dst to 0 when calling both strftime() and mktime().
Alternatively, don't restrict yourself to ANSI C.

Converting between timezones in C

I need to convert time between timezones in C (on linux, so anything specific would do too).
I know my current time, local and UTC, I have the offset of the target time. I am trying to use mktime, gmtime, localtime and similar set of functions but still can't figure it out.
Thanks in advance.
As comments do not allow posting the code, posting as a separate answer.. If you know "local" time and "UTC" time, you can calculate the offset of the "other" time from your "local" time. Then you convert the struct tm into calendar time, add the desired number of seconds (being the offset of the target time), and convert it back to struct tm:
(edited to account for another scenario to use mktime's normalization)
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
int main(int argc, char *argv) {
struct timeval tv_utc;
struct tm *local_tm, *other_tm;
/* 'synthetic' time_t to convert to struct tm for the other time */
time_t other_t_synt;
/* Other time is 1 hour ahead of local time */
int other_local_delta = 1*3600;
/* the below two lines are just to set local_tm to something */
gettimeofday(&tv_utc, NULL);
local_tm = localtime(&tv_utc.tv_sec);
printf("Local time: %s", asctime(local_tm));
#ifdef DO_NOT_WRITE_TO_LOCAL_TM
other_t_synt = mktime(local_tm) + other_local_delta;
#else
local_tm->tm_sec += other_local_delta;
/* mktime will normalize the seconds to a correct calendar date */
other_t_synt = mktime(local_tm);
#endif
other_tm = localtime(&other_t_synt);
printf("Other time: %s", asctime(other_tm));
exit(0);
}
You can use gmtime() and the tm structure to directly set this, provided you know the offsets.
If you know your local time and UTC, you know your local offset. Provided you also know the target offset, it's just a matter of setting tm_hour appropriate (and potentially flipping the day, too, if you go <0 or >23).
For some sample code, see this gmtime reference page. It shows offsetting based off time zone offsets.
Edit:
In response to the comments - you can also let mktime handle the shifting for you, which allows you to simplify this by converting back to a time_t. You can use something like:
time_t currentTime;
tm * ptm;
time ( &currentTime );
ptm = gmtime ( &rawtime );
ptm->tm_hour += hours_to_shift;
ptm->tm_minutes += minutes_to_shift; // Handle .5 hr timezones this way
time_t shiftedTime = mktime( ptm );
// If you want to go back to a tm structure:
tm * pShiftedTm = gmtime( &shiftedTime );
In all likelyhood, your operating system provides some support for this.
In unix derived OSs you might want to look at the man pages for asctime, asctime_r, ctime, ctime_r, difftime, gmtime, gmtime_r, localtime, localtime_r, mktime, timegm.

Resources