struct tm tm_isdst disagrees with BST - c

I'm in the UK. I'm using C++ builder 10.2 with the clang compiler. The following code
#include <stdio.h>
#include <conio.h>
#include <time.h>
#ifdef _WIN32
#include <tchar.h>
#else
typedef char _TCHAR;
#define _tmain main
#endif
int _tmain()
{
printf("TZ set = %s\r\n",putenv("TZ=Europe/London")==0 ? "true" : "false");
printf("TZ=%s\r\n",getenv("TZ"));
for (int dst = 0, year = 2017; year <= 2023; year++)
for (int mon = 1; mon <= 12; mon++)
for (int mday = 1; mday <= 31; mday++)
{
struct tm st = { 0, 0, 12, mday, mon - 1, year - 1900 };
st.tm_isdst=-1;
time_t tt = mktime(&st); // this sets the tm_isdst to 1 or 0
if (st.tm_isdst != dst)
{
dst = st.tm_isdst;
printf("%02d/%02d/%d (%ld) ", mday - !dst, mon, year, tt-(!dst)*24*60*60);
if (!dst) printf("\r\n");
}
}
getch();
}
produces the following output
12/03/2017 (1489316400) 04/11/2017 (1509796800)
11/03/2018 (1520766000) 03/11/2018 (1541246400)
10/03/2019 (1552215600) 02/11/2019 (1572696000)
08/03/2020 (1583665200) 00/11/2020 (1604145600)
14/03/2021 (1615719600) 06/11/2021 (1636200000)
13/03/2022 (1647169200) 05/11/2022 (1667649600)
12/03/2023 (1678618800) 04/11/2023 (1699099200)
(The 00/11/2020 should be 30/10/2020 but I don't see the point of complicating the code to correct it).
The problem is the above dates are totally at odds with British Summer Time as listed by wiki -
2017 26 March 29 October
2018 25 March 28 October
2019 31 March 27 October
2020 29 March 25 October
2021 28 March 31 October
2022 27 March 30 October
2023 26 March 29 October
The BST starting dates provided by my code (left hand side) return unix timestamps that are 3600 secs (1 hour) out. From comments below it seems the output would be all correct if my TZ was set to Canadian-American but it's set to London.
EDIT: I'm rephrasing the question. HITF do you get the code above to use the time zone as set in the Windows 10 settings? No matter what I set the time zone to it still comes up with similar dates. The only time I get a correct answer is if I specifically make the time zone (UTC-8.00) Pacific Time (US & Canada). It seems to use that time zone regardless of the one selected in settings. It's been bad enough waking up during this lockdown and not knowing what day it is. Now I don't even know what time zone it is.
EDIT2: I added the lines
printf("TZ set = %s\r\n",putenv("TZ=Europe/London")==0 ? "true" : "false");
printf("TZ=%s\r\n",getenv("TZ"));
to the code and while they printed
TZ set = true
TZ=Europe/London
nothing changed.

Those dates are the first and last date of Canadian-American DST. Check what time zone is specified by the TZ environment variable.
Other issues:
You assume the order of fields in struct tm, but the order isn't specified by the language.
You don't initialize the tm_isdst field correctly. -1 should be used for if it's unknown whether DST is being used or not. The value is presumably used to handle the overlapped ("fall back") hours in a change out of DST.
Your code assumes the switch to DST happens earlier in the year than the switch from DST, but it would be the opposite in the southern hemisphere.
Program with these issues fixed:
#include <stdio.h>
#include <time.h>
int main(void) {
int dst = -1, dst_syear, dst_smon, dst_smday;
for (int year=2017; year<=2023; ++year) {
for (int mon=1; mon<=12; ++mon) {
for (int mday=1; mday<=31; ++mday) {
// Note that using .tm_isdst = -1 instead of the proper value
// will cause mktime to fail during one of the overlapped hours
// of a "fall back" change from DST.
struct tm st = {
.tm_year = year-1900,
.tm_mon = mon-1,
.tm_mday = mday,
.tm_hour = 12,
.tm_isdst = -1,
};
mktime(&st); // This sets the tm_isdst to 1 or 0
if (dst == -1) {
if (st.tm_isdst == 0) {
dst = 0;
}
} else {
if (st.tm_isdst != dst) {
dst = st.tm_isdst;
if (st.tm_isdst) {
dst_syear = year;
dst_smon = mon;
dst_smday = mday;
} else {
printf("%d-%02d-%02d %d-%02d-%02d\n",
dst_syear, dst_smon, dst_smday,
year, mon, mday);
}
}
}
}
}
}
return 0;
}
Output:
$ TZ=Europe/London ./a
2017-03-26 2017-10-29
2018-03-25 2018-10-28
2019-03-31 2019-10-27
2020-03-29 2020-10-25
2021-03-28 2021-10-31
2022-03-27 2022-10-30
2023-03-26 2023-10-29
$ TZ=America/Toronto ./a
2017-03-12 2017-11-05
2018-03-11 2018-11-04
2019-03-10 2019-11-03
2020-03-08 2020-11-01
2021-03-14 2021-11-07
2022-03-13 2022-11-06
2023-03-12 2023-11-05
$ TZ=Australia/Sydney ./a
2017-09-31 2018-04-01
2018-10-07 2019-04-07
2019-10-06 2020-04-05
2020-10-04 2021-04-04
2021-10-03 2022-04-03
2022-10-02 2023-04-02

Related

Why does mktime give me an hour less?

I would like to see if at 00:00:00 on January 1, 1970 it actually corresponds to 0 seconds, and I wrote the following:
#include <stdio.h>
#include <time.h>
int main(void) {
int year = 1970;
struct tm t = {0};
t.tm_mday = 1; // January
t.tm_year = year - 1900;
t.tm_hour = 0;
t.tm_isdst = -1;
printf("%ld\n", mktime(&t));
return 0;
}
it gives me a value of -3600. Where am I wrong?
PS: tested with GCC v.10.1. I tried with another compiler under another architecture and it gives me back the correct value.
The time info you provide to mktime() is in local time, so the timezone matters even if summer time / daylight savings time does not.
You can fool your program by telling it you're in UTC:
$ gcc mytime.c -o mytime
$ ./mytime
28800 <-- Pacific time in the US
$ TZ=GMT0 ./mytime
0
The mktime function takes a time in local time. Apparently, 00:00:00 at your local time was one hour before the epoch. Launch the program with TZ set to UTC.
I would like to see if at 00:00:00 on January 1, 1970 it actually corresponds to 0 seconds, and I wrote the following:
00:00:00 on January 1, 1970 GMT, UTC corresponds to 0 seconds.
00:00:00 on January 1, 1970 Italia time corresponds to -3600 seconds.
Set timezone to UTC and then call mktime(). Unfortunately C does not have a portable way to do this, so the suggested code is only illustrative.
setenv("TZ", "UTC", 1);
tzset();
....
mktime(&t)
time_t does not necessarily match long. Recommend casting to a wide type.
// printf("%ld\n", mktime(&t));
printf("%lld\n", (long long) mktime(&t));
t.tm_mday = 1; // January misleads. .tm_mday is the day of the month, not January.
.tm_mon is the months since January so the initialization to 0 matches January.
Concerns about DST apply here only if the local time was using DST in January.
As other answers indicate, mktime works in your local time zone. However, many operating systems offer a related function timegm that works in UTC. This slight modification of your program prints 0, as expected, on my computer:
#include <stdio.h>
#include <time.h>
int main(void)
{
int year = 1970;
struct tm t = {0};
t.tm_mday = 1; // January
t.tm_year = year - 1900;
t.tm_hour = 0;
t.tm_isdst = -1;
printf("%ld\n", timegm(&t));
return 0;
}
Regrettably, this function is not standardized. You may have to define a special "feature selection macro" to get your time.h to declare it.

Strange behavior of mktime() when using passing parameter created by malloc

Below I have four functions.
first() and second() initialize only the year, mon & mday of structure tm.
first_p() and second_p allocate memory using malloc, then assign year, mon & mday.
All functions call mktime() at the end.
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void first()
{
int year1 = 2020, month1 = 4, day1 = 23;
struct tm date1 = {.tm_year = year1-1900, .tm_mon = month1-1, .tm_mday = day1};
mktime(&date1);
printf("%s", asctime(&date1));
}
void second()
{
int year2 = 2021, month2 = 5, day2 = 24;
struct tm date2 = {.tm_year = year2-1900, .tm_mon = month2-1, .tm_mday = day2};
mktime(&date2);
printf("%s", asctime(&date2));
}
void first_p()
{
int year1 = 2020, month1 = 4, day1 = 23;
struct tm *date1 = (struct tm *) malloc (sizeof(struct tm));
date1->tm_year = year1 - 1900;
date1->tm_mon = month1 -1;
date1->tm_mday = day1;
mktime(date1);
printf("%s", asctime(date1));
}
void second_p()
{
int year2 = 2021, month2 = 5, day2 = 24;
struct tm *date2 = (struct tm *) malloc (sizeof(struct tm));
date2->tm_year = year2 - 1900;
date2->tm_mon = month2 - 1;
date2->tm_mday = day2;
mktime(date2);
printf("%s", asctime(date2));
}
I tried different permutation when calling these functions in main():
1) first_p() and second_p() show random date and time.
int main()
{
first();
second();
first_p();
second_p();
return 0;
}
Thu Apr 23 00:00:00 2020
Mon May 24 00:00:00 2021
Thu Sep 30 23:09:20 66488
Wed Aug 31 14:44:48 66489
2) only second_p() shows random date and time.
int main()
{
first_p();
second_p();
first();
second();
return 0;
}
Thu Apr 23 00:00:00 2020
Sun Dec 8 01:26:16 -103880
Thu Apr 23 00:00:00 2020
Mon May 24 00:00:00 2021
3) Only second_p() shows random date and time.
int main()
{
first_p();
first();
second();
second_p();
return 0;
}
Thu Apr 23 00:00:00 2020
Thu Apr 23 00:00:00 2020
Mon May 24 00:00:00 2021
Thu Oct 9 04:53:52 60110
4) first_p() and second_p() show random date and time.
int main()
{
first();
first_p();
second_p();
second();
return 0;
}
Thu Apr 23 00:00:00 2020
Sat Sep 25 12:05:36 182934
Fri Aug 26 03:41:04 182935
Mon May 24 00:00:00 2021
What I observe is:
Directly initializing the structure and then passing it to mktime() anywhere has no strange behavior.
Allocating memory using malloc, then assigning the values and later passing it to mktime() has two behaviours:
If mktime() is being called for the first time, then there is no strange behavior.
else it shows shows some random date and time. Sometimes years are negative!
Am I missing something about mktime() that's responsible for this behavior?
Edit:
I added free(date1); and free(date2); at the end of first_p() and second_p(). Now calling *_p() functions one after another doesn't display random date and time. However calling first() or second() function before them shows random date and time for the first *_p() function while the functions the come after it doesn't, i.e, for cases 1) , 3) and 4) listed above.
However, can't free it as I need them somewhere else (why I had to use malloc in the first place). Is there a way to achieve it?
You've got quasi-random (indeterminate) values in the hours, minutes, seconds and daylight saving flag in the malloc() data, so you get indeterminate results back from mktime().
Use calloc() instead of malloc(), or zero the fields yourself.
Also, consider setting the tm_isdst member to -1 to let the system determine whether daylight saving or standard time was appropriate for the dates.

ISO 8601 week number in C

I am trying to get the ISO8601 week number with C. MinGW is installed on my PC. GCC version is 5.3.0. You can see my code below. strftime doesn't work for specifier "%V". But it works fine with the specifier "%W". But that's not what I want. I need the week number of year in ISO 8601 format.
I have tried my code with 2 different online C compilers and they both worked fine. I doubt that the compiler on my PC is not well configured. Can anyone tell me what am I doing wrong? Any help would be appreciated.
Here is my code:
#include <stdio.h>
#include <time.h>
#include <string.h>
int main ()
{
time_t timep;
struct tm * time_inf;
char buff [80];
time ( &timep );
time_inf = localtime ( &timep );
time_inf->tm_year = 2008 - 1900;
time_inf->tm_mon = 11;
time_inf->tm_mday = 31;
mktime ( time_inf );
strftime (buff, sizeof(buff), "%V", time_inf) ;
puts (buff); //prints nothing
printf("%d", strlen(buff)); //prints 0
return 0;
}
to get the ISO8601 week number with C
When "%V" with strftime() is not available or problematic, code can directivity calculate the ISO 8601 week.
ISO 8601 weeks of the year begins on Mondays.
When one want to find the ISO 8601 week of the year, often the corresponding "year" is needed too.
The first week of the year, week #1, is the first week, starting on Monday, that has at least 4 days in January - or as code below uses, the first Thursday of the year is in week 1.
Is is possible that Dec 31 is in week 1 of the next year.
Is is possible that Jan 1 is in week 52/53 of the previous year.
#include <time.h>
// return 1 on failure, 0 on success
int tm_YearWeek(const struct tm *tmptr, int *year, int *week) {
// work with local copy
struct tm tm = *tmptr;
// fully populate the yday and wday fields.
if (mktime(&tm) == -1) {
return 1;
}
// Find day-of-the-week: 0 to 6.
// Week starts on Monday per ISO 8601
// 0 <= DayOfTheWeek <= 6, Monday, Tuesday ... Sunday
int DayOfTheWeek = (tm.tm_wday + (7 - 1)) % 7;
// Offset the month day to the Monday of the week.
tm.tm_mday -= DayOfTheWeek;
// Offset the month day to the mid-week (Thursday) of the week, 3 days later.
tm.tm_mday += 3;
// Re-evaluate tm_year and tm_yday (local time)
if (mktime(&tm) == -1) {
return 1;
}
*year = tm.tm_year + 1900;
// Convert yday to week of the year, stating with 1.
*week = tm.tm_yday / 7 + 1;
return 0;
}
Example
int main() {
struct tm tm = { 0 };
tm.tm_year = 2008 - 1900;
tm.tm_mon = 12 - 1;
tm.tm_mday = 31;
tm.tm_isdst = -1;
int y = 0, w = 0;
int err = tm_YearWeek(&tm, &y, &w);
printf("Err:%d Year:%d Week:%d %02d%02d\n", err, y, w, y%100, w);
return 0;
}
Output is week 1 of 2009 for Dec 31, 2008 or 0901. This is expected per the discussion above and may explain OP's unstated concern with OP's code.
Err:0 Year:2009 Week:1 0901
MinGW doesn't provide its own strftime, but links in MSVCRT's definition, which doesn't provide %V.
Either implement what you're missing yourself or use an alternate implementation, e.g. here's BSD's strftime.

Detect DST flag of future date in C

I have a system where I am provided with date and time in the form of a string, for example "2011-03-13 03:05:00". I may receive this string at "2011-03-13 01:59:00" and I need to know the length of time between now and the time in the string (6 minutes due to DST change).
I have code that parses the string and creates a tm struct which is then converted to a time_t with mktime. The problem is that I have to set the tm_isdst flag manually when I parse the time and so I'm looking for a way to detect whether tm_isdst should be set. Any ideas?
I have some ideas for how to deal with the case where there are 2, 2AMs that would be specific to my application, but I still need a way to say "If this time was the current system time, would DST be in effect?"
Edit: Idea based on Pete's suggestion. What if I:
Check if the time received and current system time have a different hour:
If same hour but in the past, add an hour to current time and see if DST flag changed. (If same hour and in future, assume same DST flag as current time (this is most of the year))
If different hour, we add 1 hour to current system time and see if DST flag changed
Thoughts?
According to man mktime (on Linux, emphasis mine):
The value specified in the tm_isdst
field informs mktime() whether or not
daylight saving time (DST) is in
effect for the time supplied in the tm
structure: a positive value means DST
is in effect; zero means that DST is
not in effect; and a negative value
means that mktime() should (use
timezone information and system
databases to) attempt to determine
whether DST is in effect at the
specified time.
Have you tried that?
(It's "attempt to determine" because some times are fundamentally ambiguous.)
Something you could try for the truly ambiguous times is to see if mktime "corrects" your dst flag or not. I'd wager this is non-portable though. Example code, transition set on 31/10/2010, 3am rolls back to 2am in my timezone (Europe/Paris):
#include <time.h>
#include <stdio.h>
#include <string.h>
void printit(int hour, int isdst)
{
struct tm when;
memset(&when, 0, sizeof(when));
when.tm_sec = 0;
when.tm_min = 30;
when.tm_hour = hour;
when.tm_mday = 31;
when.tm_mon = 9;
when.tm_year = 110;
when.tm_isdst = isdst;
time_t secs = mktime(&when);
fprintf(stdout, "%2d %ld %d %s", isdst, secs, when.tm_isdst, asctime(&when));
}
int main(int argc, char **argv)
{
for (int i=1; i<4; i++) {
fprintf(stdout, "At %dam\n", i);
printit(i, 1);
printit(i, 0);
printit(i, -1);
}
}
Output is:
At 1am
1 1288481400 1 Sun Oct 31 01:30:00 2010
0 1288485000 1 Sun Oct 31 02:30:00 2010
-1 1288481400 1 Sun Oct 31 01:30:00 2010
At 2am
1 1288485000 1 Sun Oct 31 02:30:00 2010
0 1288488600 0 Sun Oct 31 02:30:00 2010
-1 1288488600 0 Sun Oct 31 02:30:00 2010
At 3am
1 1288488600 0 Sun Oct 31 02:30:00 2010
0 1288492200 0 Sun Oct 31 03:30:00 2010
-1 1288492200 0 Sun Oct 31 03:30:00 2010
As you can see, when the time is non-ambiguous, mktime corrects it by setting the right tm_isdst and offsetting the time.
When it is ambiguous, tm_isdst is not changed.
Count on me always for the brute-force way. This might work:
completely parse the time string and put it into a tm structure
call mktime( ) to get a time_t val on that tm struct
add 6 minutes to that new time_t val (using what value for 6 minutes?)
call asctime( ) or similar to get a tm struct (i.e., a "broken-down time") from that time_t val
in this tm struct you just got back from asctime( ), what does tm_isdst tell you ?
-- Pete
Yes, you are right: the whole mess around time is very badly (even maliciously, one at times believes) designed to trip up programmers and to provide perfect closed-book quiz questions for your whoreson lazy / pedantic / idiotic CS instructors.
I have the same question as you, I don't know whether this solution can fix it?
struct tm *local_tm;
time_t t;
t = time(NULL);
local_tm = localtime(&t);
local_tm->tm_year = 2012-1900;
local_tm->tm_mon = 9 - 1;
local_tm->tm_mday = 13;
time_t utc_time = mktime(local_tm);
I think if it you get tm from the system, the tm_isdst field is already set, so the things left is change other fields except tm_isdst, I am not sure whether it is right, but I will try it, if it works for me,I will give further feedback.

Converting string containing localtime into UTC in C

I have a string containing a local date/time and I need to convert it to a time_t value (in UTC) - I've been trying this:
char* date = "2009/09/01/00";
struct tm cal = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL};
strptime(date, "%Y/%m/%d/%H", &cal);
time_t t = mktime(&cal);
but the time_t value I get back is the value that I would expect if the string was being parsed as UTC not local time. Maybe I have misunderstood what strptime is supposed to do, but in my timezone (UK) on the 1st September we are using BST (ie UTC + 1 hour) so I would expect the value I end up with to be 1 hour ahead of UTC.
Is there a way to interpret the string as localtime, automatically taking into account the UTC offset that would have been in effect on that date? Note that I need the time_t value not a struct tm, in the example above I want the time_t value to correspond to 2009-09-01 01:00:00 GMT
You can use mktime to interpret a struct tm in the local timezone. When you do so, be careful to set the tm_isdst flag. It's 0 for summertime, 1 for wintertime, and to -1 to have mktime figure it out. Here's some example code:
void main()
{
char* date = "2009/09/01/00";
struct tm cal = {};
// Read string into struct tm
strptime(date, "%Y/%m/%d/%H", &cal);
// Tell mktime to figure out the daylight saving time
cal.tm_isdst = -1;
printf("%20s: %s", "Before mktime", asctime(&cal));
// Convert struct tm to time_t
time_t t = mktime(&cal);
// Convert time_t to localtime
struct tm localcal = *localtime(&t);
printf("%20s: %s", "Local time", asctime(&localcal));
printf("%20s: %i\n", "Local DST", localcal.tm_isdst);
// Convert time_t to GMT
struct tm gmcal = *gmtime(&t);
printf("%20s: %s", "GM time", asctime(&gmcal));
printf("%20s: %i\n", "GM DST", gmcal.tm_isdst);
}
This prints (I live in GMT+1, and it's wintertime now):
Before mktime: Tue Sep 1 00:00:00 2009
Local time: Tue Sep 1 00:00:00 2009
Local DST: 1
GM time: Mon Aug 31 22:00:00 2009
GM DST: 0
It looks like mktime converts a date in September based on the current daylight savings time. It's November now, so it's actually one hour off. I haven't found a way to correct that.
Here's my version, using tm_gmtoff. Hopefully, the library takes care of daylight savings time ...
#define _BSD_SOURCE
#define _XOPEN_SOURCE
#include <stdio.h>
#include <time.h>
int gmtoffset(void) {
struct tm *dummy;
time_t t = 0;
dummy = localtime(&t);
return dummy->tm_gmtoff; /* _BSD_SOURCE */
}
int main(void) {
int off;
const char *date = "2009/09/01/00";
struct tm cal = {0};
time_t t;
off = gmtoffset();
strptime(date, "%Y/%m/%d/%H", &cal); /* _XOPEN_SOURCE */
t = mktime(&cal);
printf("t --> %s", ctime(&t)); /* ctime includes a final '\n' */
t -= off;
printf("t-off --> %s", ctime(&t));
return 0;
}
$ /usr/bin/gcc ptime.c
$ ./a.out
t --> Tue Sep 1 01:00:00 2009
t-off --> Tue Sep 1 00:00:00 2009
I think I've cracked it now, thanks to Andomar - this code does what I need and appears to work regardless of the current DST status (I changed the clock on my PC to check this):
#include <time.h>
#include <assert.h>
time_t parseLocalDate(char* date){
struct tm cal = {0, 0, 0, 0, 0, 0, 0, 0, -1, 0, NULL};
strptime(date, "%Y/%m/%d/%H", &cal);
return mktime(&cal);
}
int main(int argc, char *argv[]){
// DST is effect, Local Time = GMT+1
assert(1251759600 == parseLocalDate("2009/09/01/00")); // Mon, 31 Aug 2009 23:00:00 GMT
assert(1254351600 == parseLocalDate("2009/10/01/00")); // Wed, 30 Sep 2009 23:00:00 GMT
// DST not in effect, Local Time = GMT
assert(1257033600 == parseLocalDate("2009/11/01/00")); // Sun, 01 Nov 2009 00:00:00 GMT
}

Resources