Working on an Ubuntu 12.04.3 LTS box, I just noticed that localtime() and localtime_r() behave differently when the system's timezone changes during the lifetime of a process: localtime() picks up the timezone change immediately, whereas localtime_r() does not, it seems to stick to what was the timezone at the launch of the process. Is this expected behavior? I haven't seen this covered anywhere.
More precisely, when I use the following code ...
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
int main() {
while (1) {
time_t t = time(NULL);
struct tm *tm = localtime(&t);
printf("localtime:%02d/%02d/%02d-%02d:%02d:%02d\n",
tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900,
tm->tm_hour, tm->tm_min, tm->tm_sec);
sleep(1);
}
return 0;
}
... and change the timezone from UTC via ...
# echo 'Europe/Berlin' > /etc/timezone
# sudo dpkg-reconfigure --frontend noninteractive tzdata
... then the code produces the following, ...
localtime:10/04/2013-01:11:33
localtime:10/04/2013-01:11:34
localtime:10/04/2013-01:11:35
localtime:10/03/2013-23:11:36
localtime:10/03/2013-23:11:37
localtime:10/03/2013-23:11:38
... but if I use:
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
int main() {
while (1) {
time_t t = time(NULL);
struct tm local_tm;
struct tm *tm = localtime_r(&t, &local_tm);
printf("localtime_r:%02d/%02d/%02d-%02d:%02d:%02d\n",
tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900,
tm->tm_hour, tm->tm_min, tm->tm_sec);
sleep(1);
}
return 0;
}
... then there's no change when doing a similar timezone change:
localtime_r:10/04/2013-01:15:37
localtime_r:10/04/2013-01:15:38
localtime_r:10/04/2013-01:15:39
localtime_r:10/04/2013-01:15:40
localtime_r:10/04/2013-01:15:41
localtime_r:10/04/2013-01:15:42
UPDATE: adding a call to tzset() before invoking localtime_r() produces the expected behavior. Whether that's clear from the spec/manpage or not (see discussion below) is a question for mentalhealth.stackexchange.com...
See this following documentation:
The localtime() function converts the calendar time timep to
broken-down time representation, expressed relative to the user's
specified timezone. The function acts as if it called tzset(3) and
sets the external variables tzname with information about the current
timezone, timezone with the difference between Coordinated Universal
Time (UTC) and local standard time in seconds, and daylight to a
nonzero value if daylight savings time rules apply during some part of
the year. The return value points to a statically allocated struct
which might be overwritten by subsequent calls to any of the date and
time functions. The localtime_r() function does the same, but stores
the data in a user-supplied struct. It need not set tzname, timezone,
and daylight.
From: http://linux.die.net/man/3/localtime_r
So as far as I can tell, it appears that the code is working as I'd expect.
Edited to add more from the same documentation:
According to POSIX.1-2004, localtime() is required to behave as though
tzset(3) was called, while localtime_r() does not have this
requirement. For portable code tzset(3) should be called before
localtime_r().
Related
I compile C using the C99 version, and I want to try and output the timezone of the given time.
The IDE I use gives GMT+0 as the timezone, but I want to somehow output it with struct tm.
So I followed the instructions from this answer and made this program:
#include <stdio.h>
#include <time.h>
int main()
{
time_t present = time(NULL);
struct tm now = *localtime(&present);
now.tm_mon += 1;
now.tm_year += 1900;
struct tm t = {0};
localtime_r(&present, &t);
printf("%i/%i/%i %i:%i:%i from %s\n", now.tm_mon, now.tm_mday, now.tm_year, now.tm_hour, now.tm_min, now.tm_sec, t.tm_zone);
}
And it seems like I got 2 errors here:
implicit declaration of function 'localtime_r' is invalid in C99
no member named 'tm_zone' in 'struct tm'
So I checked the IDE Manual, and find that localtime_r actually exists, and is part of the <time.h> library.
So now I'm wondering if the IDE's confused or something. I don't know how to fix it either.
This might get closed as it might "need debugging details", but read more.
Because of this whole situation, how can I get the timezone (maybe even the offset) in C99 and get it to be outputted with printf()?
First, localtime_r is not part of the standard library - it's an extension offered by some implementations, and by default its declaration is not exposed in those implementations. To make it available, you'll have to define the macro _POSIX_SOURCE before including time.h to make it available. An easy way to do that is on the command line, like so:
gcc -o tz -D_POSIX_SOURCE -std=c11 -pedantic -Wall -Werror tz.c
otherwise, just define it in your source before including time.h:
#define _POSIX_SOURCE
#include <stdio.h>
#include <time.h>
Secondly, if all you're interested in is the local time zone then there's an easier way to do this - get the current time:
time_t t = time( NULL );
then use both localtime and gmtime to get the broken down time for the current time zone and UTC:
struct tm *local = localtime( &t );
struct tm *zulu = gmtime( &t );
Then compute the difference between the tm_hour members of local and zulu, and that's your time zone.
int tz = zulu->tm_hour - local->tm_hour;
You'll want to check local->tm_isdst to account for daylight savings, but that should at least get you started.
I have the following file test.c:
#define _POSIX_THREAD_SAFE_FUNCTIONS
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
int main(int argc,char**argv) {
struct tm t1, t2, t3;
time_t w1, w2, w3;
memset(&t1,0,sizeof(struct tm));
memset(&t2,0,sizeof(struct tm));
memset(&t3,0,sizeof(struct tm));
w1 = 0;
errno = 0;
localtime_r(&w1,&t1);
printf("localtime_r: errno=%d\n",errno);
errno = 0;
w2 = mktime(&t1);
printf("mktime: errno=%d result=%" PRId64 "\n",errno,((int64_t)w2));
errno = 0;
localtime_r(&w2,&t2);
printf("localtime_r: errno=%d\n",errno);
errno = 0;
w3 = mktime(&t2);
printf("mktime: errno=%d result=%" PRId64 "\n",errno,((int64_t)w3));
errno = 0;
localtime_r(&w3,&t3);
printf("localtime_r: errno=%d\n",errno);
printf("sizeof(time_t)=%" PRId64 "\n", ((int64_t)sizeof(time_t)));
printf("W1=%" PRId64 " W2=%" PRId64 " W3=%" PRId64 "\n",((int64_t)w1),((int64_t)w2),((int64_t)w3));
printf("Y1=%d Y2=%d Y3=%d\n",t1.tm_year,t2.tm_year,t3.tm_year);
return 0;
}
I compile it like this:
i686-w64-mingw32-gcc -D__MINGW_USE_VC2005_COMPAT=1 -o test.exe test.c
Note, i686-w64-mingw32-gcc --version reports 8.3-win32 20190406
This is running in a Docker image of Ubuntu 19.04, using the MinGW version
that comes with Ubuntu 19.04 (it says version 6.0.0-3).
I have a Windows 10 VM (Version 1809 OS Build 17763.379).
By default, time zone is set to US Pacific Time (UTC-8).
I copy test.exe to this VM and run it there.
It prints:
localtime_r: errno=0
mktime: errno=0 result=0
localtime_r: errno=0
mktime: errno=0 result=0
localtime_r: errno=0
sizeof(time_t)=8
W1=0 W2=0 W3=0
Y1=69 Y2=69 Y3=69
That's the expected result. (At UTC midnight on 1 Jan 1970, it was still 1969 in UTC-8.)
I change the Windows time zone to UTC+10 (Canberra, Melbourne, Sydney).
Run it again. It prints:
localtime_r: errno=0
mktime: errno=0 result=47244640256
localtime_r: errno=22
mktime: errno=22 result=4294967295
localtime_r: errno=0
sizeof(time_t)=8
W1=0 W2=47244640256 W3=4294967295
Y1=70 Y2=-1 Y3=206
It seems the mktime() call is returning an invalid value in UTC+10 time zone, but returns the correct value of 0 in UTC-8 time zone.
Why does this code work in one timezone break in another?
Note, this is only a problem with -D__MINGW_USE_VC2005_COMPAT=1 to enable
64-bit time_t. If I leave that out, which means 32-bit time_t, then the code
works in both timezones. (But, 32-bit time_t is not a good idea, because it breaks in the year 2038, and that's less than twenty years away now.)
I worked out the cause of the problem. Sander De Dycker's suggestion, that mktime is returning a 32-bit value, is correct.
The problem is basically this: the MSVCRT defines three mktime functions: _mktime32 for 32-bit time_t, _mktime64 for 64-bit time_t, and _mktime which is a legacy alias for _mktime32.
_mingw.h does a #define _USE_32BIT_TIME_T in 32-bit code unless you #define __MINGW_USE_VC2005_COMPAT to disable that. Once you have #define __MINGW_USE_VC2005_COMPAT, then localtime_s is defined as an inline function which calls _localtime64_s. And #define _POSIX_THREAD_SAFE_FUNCTIONS defines localtime_r as an inline function which calls localtime_s. However, mktime is still 32-bit. To get 64-bit mktime, you need to also #define __MSVCRT_VERSION__ 0x1400 (or higher). Once you do that, mktime becomes an inline function which calls _mktime64. Before that, mktime is a normal function declaration which is linked to the legacy 32-bit mktime.
So #define __MINGW_USE_VC2005_COMPAT 1 without #define __MSVCRT_VERSION__ 0x1400 (or -D equivalent) gives you a localtime_r with 64-bit time_t, but a mktime with 32-bit time_t, which obviously won't work. Even worse than that, the actual implementation of the mktime symbol is returning a 32-bit time_t, but the function declaration is for a 64-bit time_t, which is what causes the junk in the upper 32-bits.
As to the difference behaviour in different time zones, I don't have a complete explanation for that, but I think the reason is likely as follows: when you have a function which actually returns a 32-bit value but is incorrectly being defined to return a 64-bit value, the upper 32-bits of the return value will hold random junk data left over from previous calculations. So, any difference in the previous calculations, or slightly different code paths, may result in different random junk. With a UTC-8 timezone, for whatever reason, the random junk is coincidentally zero, so the code (despite its incorrectness) actually works. With a UTC+10 timezone, the random junk turns out to be non-zero, which causes the rest of the code to stop working.
For a computer security assignment, I have to modify the time function in order to return a specific date. I need the time function to return a date between Jan 1st, 2016 and June 15th, 2018. I then use these commands to overload and hook into the time function:
gcc -Wall -fPIC -shared -o newtime.so newtime.c -ldl
export LD_PRELOAD=$PWD/newtime.so
Here is my modified version of the time function:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <time.h>
time_t time (time_t *t)
{
long int seconds = 1485907200;
time_t modifiedTime = (time_t) seconds;
return modifiedTime;
}
Whenever I run this implementation, it says that the date being returned is December 31, 1969 19:00:00. Am I just formatting the time since the Linux Epoch incorrectly or am I making a more serious mistake? I have tried using a regular int instead of a long int, and still experience the same issues. Some insight into my mistake would be very helpful.
You're not implementing the entire functionality of time(). The code your interposing on may use functionality that you have not implemented.
Per the C standard:
7.27.2.4 The time function (note the bolded part):
Synopsis
#include <time.h>
time_t time(time_t *timer);
Description
The time function determines the current calendar time. The encoding
of the value is unspecified.
Returns
The time function returns the implementation's best approximation to
the current calendar time. The value (time_t)(-1) is returned if the
calendar time is not available. If timer is not a null pointer, the
return value is also assigned to the object it points to.
A full implementation, based on your code:
time_t time (time_t *t)
{
long int seconds = 1485907200;
time_t modifiedTime = (time_t) seconds;
if ( t )
{
*t = modifiedTime;
}
return modifiedTime;
}
In fact, there is no problem in the code that you presented. I tested it with the following basic program:
#include <stdio.h>
#include <time.h>
int main(void)
{
printf("%ld\n", (long) time(NULL));
}
So I just run LD_PRELOAD=./newtime.so ./test and I get the expected result.
However, the date command doesn't make call to the time function. It calls instead int clock_gettime(clockid_t clk_id, struct timespec *tp). So you should better re-implement them both if you would like to cover this case.
May be a simple implementation like the following one (It works fine with date):
int clock_gettime(clockid_t clk_id, struct timespec *tp)
{
if(tp) {
tp->tv_sec = 1485907200;
tp->tv_nsec = 0;
}
return 0;
}
If you get a date different from your expectation, it could be related to your time zone.
Incomprehensible behavior of the function strptime():
#define _XOPEN_SOURCE
#include <stdio.h>
#include <time.h>
double getPeriod(char * dateStart, char * dateStop) {
struct tm tmStart, tmStop;
time_t timeStampStart, timeStampStop;
strptime(dateStart, "%Y-%m-%d %H:%M:%S", &tmStart);
strptime(dateStop, "%Y-%m-%d %H:%M:%S", &tmStop);
timeStampStart = mktime(&tmStart);
timeStampStop = mktime(&tmStop);
printf("%d\t%d\n", tmStart.tm_hour, tmStop.tm_hour);
}
int main()
{
getPeriod("2016-12-05 18:14:35", "2016-12-05 18:18:34");
return 0;
}
Output:
17 18
Why does this happen?
Compiler gcc (GCC) 6.2.1
OS Linux
tmStart and tmStop are not initialized, so some fields will be uninitialized when passed to mktime. Thus, the behavior is technically undefined.
From the strptime man page (note the first two sentences):
In principle, this function does not initialize tm but only stores the values specified. This means that tm should be initialized before the call. Details differ a bit between different UNIX systems. The glibc implementation does not touch those fields which are not explicitly specified, except that it recomputes the tm_wday and tm_yday field if any of the year, month, or day elements changed.
How to write current time in printf on Minix 3.2.1?
I try to use gmtime like below but it gives error on time(&nowtime).
#include <sys/time.h>
#include <time.h>
struct tm *now;
time_t nowtime;
time(&nowtime);
now=gmtime(&nowtime);
printf("TIME is NOW %s",now);
Moreover, I try to recall that in kernel (/usr/src/kernel/main.c) because I need that time on the booting of minix to say when the kernel process is finished and switch to user.
I take some errors on above code like when rebuild the kernel like below;
Not that familiar with minix, but it is similar to Unix & Linux, so maybe something from that platform may be present on minix... so A couple of approaches
Run a man on ctime
the man page on Linux's time() command contains this example code (which you may have to modify for minix, but it shows how to use asctime() localtime() and time() ):
#include <stdio.h>
#include <time.h>
int main(void)
{
time_t result;
result = time(NULL);
printf("%s%ju secs since the Epoch\n",
asctime(localtime(&result)),
(uintmax_t)result);
return(0);
}