I'm trying to print in the console the current time in my local timezone (-0600), then printing the time at the +0100 timezone. Currently I'm using gmtime, and adding 1 to the tm_hour section.
However when using strftime, it still prints the: "... +0000".
How can I print it properly? How can I change my effective time zone, for instance?
On macOS Sierra 10.12.2 with GCC 6.3.0, the following code works:
#include "posixver.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#ifndef lint
extern const char jlss_id_settz_c[];
const char jlss_id_settz_c[] = "#(#)$Id: settz.c,v 1.2 2017/01/23 07:06:21 jleffler Exp $";
#endif
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("%ld = %s (TZ=%s)\n", (long)t0, buffer, new_tz);
}
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");
}
By default, TZ is not set in the environment — those ungainly tests for getenv("TZ") returning NULL are necessary to handle that. When run, the output is:
$ ./settz
1485155290 = 2017-01-23 07:08:10 (TZ=UTC0)
1485155290 = 2017-01-23 12:38:10 (TZ=IST-5:30)
1485155290 = 2017-01-23 02:08:10 (TZ=EST5)
1485155290 = 2017-01-23 02:08:10 (TZ=EST5EDT)
1485155290 = 2017-01-22 23:08:10 (TZ=PST8)
1485155290 = 2017-01-22 23:08:10 (TZ=PST8PDT)
$
With the environment set so TZ=US/Alaska, the output is:
$ TZ=US/Alaska ./settz
1485155395 = 2017-01-22 22:09:55 (TZ=US/Alaska)
1485155395 = 2017-01-23 07:09:55 (TZ=UTC0)
1485155395 = 2017-01-23 12:39:55 (TZ=IST-5:30)
1485155395 = 2017-01-23 02:09:55 (TZ=EST5)
1485155395 = 2017-01-23 02:09:55 (TZ=EST5EDT)
1485155395 = 2017-01-22 23:09:55 (TZ=PST8)
1485155395 = 2017-01-22 23:09:55 (TZ=PST8PDT)
$
This is ugly as a technique; it is also not fast. However, on some platforms, it does in fact work.
Related
How could I determine if daylight savings time is in effect for a specified timezone given only a time_t? I've read that "In POSIX systems, a user can specify the time zone by means of the TZ environment variable." I was thinking that I could save the current TZ value (if set), change it to the TZ I'm interested in, call localtime() and check tm_isdst, and change TZ back to it's original value. I'm just not sure how portable that would be.
Is there a portable way to determine DST for a timezone given a time_t in C?
This is as portable as I could make it. I'd be interested in any better solution. What I've done is calculate the time from epoch until the start and end of DST in the America/New_York time zone for a given year, and test if the given time_t falls in between. This is specific to the America/New_York time zone but I would imagine that it could easily be adapted for another time zone, or adapted for any/all time zones, with some effort.
If using the GNU C Library, timegm can be used in place of getenv, mktime, setenv, but according to GNU.org:
mktime is essentially universally available. timegm is rather rare.
For the most portable conversion from a UTC broken-down time to a
simple time, set the TZ environment variable to UTC, call mktime, then
set TZ back.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
/***********************************************\
* In America/New_York:
* DST begins: second Sunday in March 02:00 local, after which EDT == UTC-04:00
* DST ends: first Sunday in November 02:00 local, after which EST == UTC-05:00
\***********************************************/
//Return 1 if the year at UTC is greater than the year in America/New_York at
//the given time t. In other words, at time t, is it between 00:00:00 UTC
//(midnight) Jan 1 and 05:00:00 UTC Jan 1. Return 0 if the year at UTC is the
//same as America/New_York at time t.
int UTCyearIsGreater(time_t when) {
time_t begin, end;
struct tm* tm;
tm = gmtime(&when);
if (tm->tm_mon == 11 && tm->tm_mday == 31 &&
(tm->tm_hour >= 19 && tm->tm_hour < 5)) {
return 1;
}
return 0;
}
//Return number of seconds from epoch until DST begins/began in America/New_York, the second Sunday in March (ssim).
//for the given year.
time_t ssim(int year) {
time_t t, t2;
int sim = 0;
struct tm tm = {0};
tm.tm_year = year;
tm.tm_mon = 2;
tm.tm_mday = 1;
tm.tm_hour = 7;
char* env;
env = getenv("TZ");
setenv("TZ", "UTC", 1);
t = mktime(&tm);
tm = *gmtime(&t);
while (sim < 2) {
if (tm.tm_wday == 0) {
sim += 1;
if (sim == 2) { break; }
}
tm.tm_mday += 1;
tm.tm_wday = 0;
t = mktime(&tm);
tm = *gmtime(&t);
}
t = mktime(&tm);
if (env == NULL) {
unsetenv("TZ");
} else {
setenv("TZ", env, 1);
}
return t;
}
//Return number of seconds from epoch until DST ends/ended in America/New_York, the first Sunday in November (fsin).
//for the given year.
time_t fsin(int year) {
time_t t;
struct tm tm = {0};
tm.tm_year = year;
tm.tm_mon = 10;
tm.tm_mday = 1;
tm.tm_hour = 6;
char* env;
env = getenv("TZ");
setenv("TZ", "UTC", 1);
t = mktime(&tm);
tm = *gmtime(&t);
while (1) {
if (tm.tm_wday == 0) { break; }
tm.tm_mday += 1;
tm.tm_wday = 0;
t = mktime(&tm);
tm = *gmtime(&t);
}
t = mktime(&tm);
if (env == NULL) {
unsetenv("TZ");
} else {
setenv("TZ", env, 1);
}
return t;
}
//Return 1 if DST is in effect in America/New_York at time t, return 0 otherwise
int DSTinNYC(time_t t) {
time_t beginDST, endDST;
struct tm* tm_ptr;
tm_ptr = gmtime(&t);
if (UTCyearIsGreater(t)) {
tm_ptr->tm_year -= 1;
}
beginDST = ssim(tm_ptr->tm_year);
endDST = fsin(tm_ptr->tm_year);
return (t >= beginDST && t < endDST);
}
int main() {
//test it
if (DSTinNYC(1461179392)) {
printf("CORRECT 20 Apr 2016 15:09:52 EDT\n");
} else {
printf("FAILED 20 Apr 2016 15:09:52 EDT\n");
}
if (DSTinNYC(1455993975)) {
printf("FAILED 20 Feb 2016 13:46:15 EST\n");
} else {
printf("CORRECT 20 Feb 2016 13:46:15 EST\n");
}
if (DSTinNYC(1571179392)) {
printf("CORRECT 15 Oct 2019 18:43:12 EDT\n");
} else {
printf("FAILED 15 Oct 2019 18:43:12 EDT\n");
}
//results checked with http://www.epochconverter.com/
return 0;
}
I have the following code:
#include <stdio.h>
#include <string.h>
#include <time.h>
time_t make_time(char *input, char *hs) {
struct tm t;
char d[17];
strcpy(d, input);
strcat(d, hs);
d[16] = '\0';
strptime(d, "%m%d%Y%H:%M:%S", &t);
printf("data: %s", asctime(&t));
return mktime(&t);
}
void main(int argc, char *argv[]) {
char *s = argv[1];
char *e = argv[2];
time_t now = time(NULL);
time_t start = make_time(s, "00:00:00");
time_t end = make_time(e, "23:59:59");
int s1 = difftime(now, start);
int s2 = difftime(end, now);
int ret = 0;
if (s1 > 0 && s2 > 0) {
ret = 1;
}
printf("Result:\nInput: %s %s\nDiff: start: %d; end: %d\nret: %d\n", s, e, s1, s2, ret);
printf("\n");
}
2 versions were built. One is for use on debian linux & the other for an arm device.
On debian linux, the result looks right:
devbox#debian:~$ ./a.out 11182015 11302015
data: Wed Nov 18 00:00:00 2015
data: Mon Nov 30 23:59:59 2015
Result:
Input: 11182015 11302015
diff: start: 665291; end: 457908
ret: 1
But on armv7l env, strptime() produces wrong result:
# /tmp/a.out 11182015 11302015
data: ??? Jan 8 ??:??:?? 1900
data: ??? Jan 8 ??:??:?? 1900
Result:
Input: 11182015 11302015
diff: start: 1448437831; end: -1448437831
ret: 0
Is there something wrong with strptime on armv7l or is it not a standard GNU C method?
UPDATE
Here is the result of uname command:
# uname -a
Linux buildroot 3.1.0-xg3517-1.1 #1 Tue Mar 12 12:54:57 JST 2013 armv7l GNU/Linux
I finally know why & make it works. The problem is: strptime() seems to have no idea how to parse the following formats:
"%m%d%Y%H:%M:%S"
// or
"%m%d%Y %H:%M:%S"
// or
"%m/%d/%Y %H:%M:%S"
But, if I use this: "%m-%d-%Y %T"
strptime(d, "%m-%d-%Y %T", &t);
Then it works, what a shame !
I don't know why those formats are not accepted, since doc didn't say anything about those which are not being fully implemented. Anyway, if anyone knows why, please shed me some light.
I am editing time value using a variable of type struct tm (adding some seconds to tm->tm_sec), but I am getting wrong results after doing mktime(&t).
Doing so in Linux gets me proper results, but in AIX not. What could be the problem?
#include <stdio.h>
#include <time.h>
#include <langinfo.h>
#include <locale.h>
int main ()
{
struct tm tm;
struct tm *end;
time_t t;
char str[20] = {'\0'};
//if (strptime("7 Feb 2013 01:47:30", "%d %b %Y %H:%M:%S", &tm) == NULL)
if (strptime("2012-10-17-01-07-30", "%Y-%m-%d-%H-%M-%S", &tm) == NULL)
{printf("Error\n");
}
tm.tm_sec = (tm.tm_sec + 1200);
//tm.tm_sec = 12;
//t = mktime(&tm);
//t = t + 12;
//end =localtime(&t);
strftime(str,20,"%Y %m %d %H %M %S",&tm);
printf("str is %s\n",str);
return 0;
}
I believe the correct answer is to use time_t, which is a large number representing the time in seconds since midnight of 1 Jan 1970. Adding arbitrary number of seconds here becomes very trivial.
I expect that if you are just adding seconds to tm->tm_sec, it overflows, and that causes the result to be incorrect. If you are unlucky, you will need to ripple your change in seconds all the way through to year (adding 5 seconds to 31 Dec 2013 23:59:56 will take you to 01 Jan 2014 00:00:01). Which of course can be done, but instead of:
t =+ 5;
you get about a dozen steps along the line of
tm.tm_sec += 5;
if (tm.tm_sec >= 60)
{
tm.tm_sec -= 60;
tm.tm_min += 1;
if (tm.tm_min >= 60)
{
... And so on ...
}
}
It gets even more interesting if you overflow the days in a month, since you then have to take into account of the number of days in each month, 28, 29, 30 or 31 depending on which month [and if it's a leap-year or not].
This is effectively what Mats said:
#include <stdio.h>
#include <time.h>
#include <langinfo.h>
#include <locale.h>
int main ()
{
struct tm tm;
time_t t;
char str[20] = {'\0'};
if (strptime("2012-10-17-01-07-30", "%Y-%m-%d-%H-%M-%S", &tm) == NULL) {
printf("error\n");
}
t = mktime(&tm);
t += 1200;
tm = *localtime(&t);
strftime(str,20,"%Y %m %d %H %M %S",&tm);
printf("str is %s\n",str);
return 0;
}
Produces:
cc -o t t.c && ./t
str is 2012 10 17 02 27 30
I am trying to use strptime(buf, &pattern,&result) to convert char[] containing date into tm structure.
I am using function like this:
if(strptime(buf, &pattern,&result) == NULL)
{
printf("\nstrptime failed\n");
...
and everything works if my variables are defined like this:
char buf[] = "26/10/2011";
char pattern[] = "%d/%m/%y";
struct tm result;
but if I change them into:
char buf[] = "2011/26/10";
char pattern[] = "%y/%d/%m";
struct tm result;
I get "strptime failed". Notice, that I have only put year in the beginning (both in buf and pattern).
Help appreciated. My final target is to convert string in this format: 2011-10-26T08:39:21
It's because the lower case %y is for the two-digit year within the century. Try changing it to uppercase %Y and it will work okay. You can see this from the following program:
#include <stdio.h>
#include <time.h>
int main (void) {
char buf[] = "26/10/2011";
char pattern[] = "%d/%m/%y";
struct tm result;
if (strptime (buf, pattern, &result) == NULL) {
printf("strptime failed\n");
return -1;
}
printf ("%d\n", 1900 + result.tm_year);
return 0;
}
This outputs 2020, meaning that the year is being read as just the 20 portion of 2011, with the remainder being ignored. If you use upper-case %Y, it outputs the correct 2011 instead.
Code that generates the conversion error using the reversed format:
#include <stdio.h>
#include <time.h>
int main (void) {
char buf[] = "2011/10/26";
char pattern[] = "%y/%m/%d";
struct tm result;
if (strptime (buf, pattern, &result) == NULL) {
printf("strptime failed\n");
return -1;
}
printf ("%d\n", 1900 + result.tm_year);
return 0;
}
will work fine (ie, output 2011) when you change the pattern value to "%Y/%m/%d".
Using my own 'strptime' and 'timestamp' commands, I get:
$ strptime -T '%y/%d/%m' 2011/26/11
strptime: failed to convert <2011/26/11> using format <%y/%d/%m>
$ strptime -T '%Y/%d/%m' 2011/26/11
1322294400 = 2011/26/11
$ strptime -T '%d/%m/%y' 26/11/2011
1606377600 = 26/11/2011
$ timestamp 1322294400 1606377600
1322294400 = Sat Nov 26 00:00:00 2011
1606377600 = Thu Nov 26 00:00:00 2020
$
(Time zone here is US/Pacific, currently UTC-7.)
Note that the '%d/%m/%y' format generates a date in 2020, not in 2011.
the following code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
static const char * wday_abb_names[] =
{
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
"Sun",
};
static void mb_setenv(const char *name, const char *value)
{
#if !(defined _WIN32) || defined HAVE_SETENV
setenv(name, value, 1);
#else
int len = strlen(name)+1+strlen(value)+1;
char *str = malloc(len);
sprintf(str, "%s=%s", name, value);
putenv(str);
#endif
}
static void mb_unsetenv(const char *name)
{
#if !(defined _WIN32) || defined HAVE_SETENV
unsetenv(name);
#else
int len = strlen(name)+2;
char *str = malloc(len);
sprintf(str, "%s=", name);
putenv(str);
free(str);
#endif
}
time_t mb_timegm(struct tm *tm)
{
time_t ret;
char *tz;
tz = getenv("TZ");
mb_setenv("TZ", "");
tzset();
ret = mktime(tm);
if (tz)
{
mb_setenv("TZ", tz);
}
else
{
mb_unsetenv("TZ");
}
tzset();
return ret;
}
time_t get_test_time()
{
struct tm msg_time;
msg_time.tm_isdst = 0;
msg_time.tm_wday = 4;
msg_time.tm_mon = 5;
msg_time.tm_mday = 16;
msg_time.tm_hour = 4;
msg_time.tm_min = 53;
msg_time.tm_sec = 0;
msg_time.tm_year = 111; //2011 - 1900
time_t retval = mb_timegm(&msg_time);
printf("final msg_time = %ld\n", retval);
return retval;
}
void print_time(const char *msg, struct tm *t)
{
printf("%s %s, %02d.%02d.%2d %2d:%02d\n", msg,
wday_abb_names[t->tm_wday], t->tm_mday, t->tm_mon, t->tm_year,
t->tm_hour, t->tm_min);
}
int main()
{
printf( "=== ENVIRON ===\n");
printf("TZ = %s\n", getenv("TZ"));
time_t now;
struct tm l, g;
time(&now);
l = *localtime(&now);
g = *gmtime(&now);
print_time("Local time :", &l);
print_time("utc :", &g);
printf("=== END ENVIRON ===\n\n");
time_t tt = get_test_time();
printf("fix test (16.6.2011 04:53) --> %s\n", ctime(&tt));
printf("done.\n");
return 0;
}
running on GNU/Linux it produces:
=== ENVIRON ===
TZ = (null)
Local time : Sat, 24.05.111 14:20
utc : Sat, 24.05.111 12:20
=== END ENVIRON ===
final msg_time = 1308199980
fix test (16.6.2011 04:53) --> Thu Jun 16 06:53:00 2011
done.
running on Win7 it produces:
=== ENVIRON ===
TZ = (null)
Local time : Sat, 24.05.111 14:25
utc : Sat, 24.05.111 12:25
=== END ENVIRON ===
final msg_time = 1308196380
fix test (16.6.2011 04:53) --> Thu Jun 16 05:53:00 2011
done.
Both Systems have a Timezone of UTC+1 including DST (that makes UTC+2 in effect) and both systems are not having any time-problems at all - except for the difference displayed.
As you can see, the "final msg_time" is missing exactly 3600 seconds, so it is not a problem in ctime.
Can anybody explain to me why mktime seems to behave different on GNU/Linux and Windows - or how to correct that?
Edit:
Both systems (after calling tzset()) are reporting tzname[0] = CET, tzname[1] = CEST, daylight=1, timezone = -3600
My mb_timegm was based on the code stated in man 3 timegm and it stated
"set the TZ environment variable to UTC" to do this setenv("TZ", ""); is called.
However - this does not work on windows.
Using setenv("TZ", "UTC"); (or, in the above case mb_setenv) instead fixes the problem.
I'm assuming based on the info your provided where daylight savings time is in effect, that you would need to set msg_time.tm_isdst in get_test_time() to a value of 1 rather than 0. This may be the issue accounting for the missing hour. Either that, or you could set it to -1 and allow the system to attempt to figure out if you are in daylight savings time or not for the given input value.