[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: NSCalendar bug (mktime related)
From: |
Fred Kiefer |
Subject: |
Re: NSCalendar bug (mktime related) |
Date: |
Sat, 25 Apr 2020 15:57:44 +0200 |
Hi Andreas,
I checked the code of NSCalendar, which you could have done yourself, this is
free software, and we are not using mtkime there. The problem is a lot worse.
We just ignore the handed in time zone of the NSDateComponents. This could be
repaired but I am no expert on NSCalendar so if anybody else wants to look into
this feel free. It would be great to have a few more tests for NSCalendar.
Anybody willing to provide a few test cases?
Fred
> Am 23.04.2020 um 10:51 schrieb Andreas Fink <list@fink.org>:
>
> Hello all,
>
> I have run into a very weird thing in conversion from NSStrings to NSDate.
> The result is we are always off by 1h under LInux.
> Under MacOS X I have the same problem but only with mktime() not with
> NSCalendar.
> I am suspecting Gnustep implementation probably uses mktime() in the back and
> thus inherits this issue also for NSCalendar.
>
> What I try to do is to convert aNSString with a timestamp which is always in
> UTC into a date.
> So the timestamp I supply has timezone GMT+0 and no daylight savings time.
> If the current system currently experiences daylight savings time, the result
> by mktime is off by 1h even if I specify timezone to be UTC in struct tm.
>
> Here is a test programm for this:
>
>
> #define _BSD_SOURCE
> #include <time.h>
> #include <string.h>
> #include <stdio.h>
> time_t test(void)
> {
> struct tm tm;
> memset(&tm,0, sizeof(tm));
>
> int year = 1970;
> int month = 1;
> int day = 1;
> int hour = 0;
> int minute = 0;
> int second = 0;
>
> tm.tm_year = year -1900;
> tm.tm_mon = month -1,
> tm.tm_mday = day;
> tm.tm_hour = hour;
> tm.tm_min = minute;
> tm.tm_sec = second;
> tm.tm_zone = "UTC";
> tm.tm_isdst = -1;
> tm.tm_gmtoff = 0;
>
> time_t t = mktime(&tm);
> return t;
> }
>
>
> int main(int argc, const char * argv[])
> {
> time_t t;
>
> setenv("TZ","UTC",1);
> t = test();
> printf("TZ=UTC: t=%d\n",t);
>
> setenv("TZ","CET",1);
> t = test();
> printf("TZ=CET: t=%d\n",t);
>
> setenv("TZ","CEST",1);
> t = test();
> printf("TZ=CEST: t=%d\n",t);
>
> setenv("TZ","Europe/Zurich",1);
> t = test();
> printf("TZ=Europe/Zurich: t=%d\n",t);
>
> }
>
>
> So the timestamp I supply has timezone GMT+0 and no daylight savings time.
> So the time_t value returned should always be 0 but its off by -1h if the
> envirnment has the timezone set to Europe/Zurich or CET. Interestingly CEST
> is correct (which is the current timezone in Zurich CET + Daylight savings
> time)
>
> TZ=UTC: t=0
> TZ=CET: t=-3600
> TZ=CEST: t=0
> TZ=Europe/Zurich: t=-3600
>
> The output of this is indicating that the daylight savings yes/no from the
> supplied timezone is ignore as well as the timezone. It always bases the date
> on the current environmental variable even though the current timezone might
> not be the one in effect at that date.
> So it assumes that on 1.11970 we had daylight savings time (because TZ says
> we have now) despite being in January and despite it wasn't in use in that
> year even.
>
> Now lets say this is a bug of mktime or maybe a wanted feature. Be is at it
> is.
>
> The problem is NSCalendar fails for the same issue.
>
>
> This piece of code works under MacOS X but fails under Linux.
> Under NSCalendar I specify the timezone explicitly and TZ environment
> variable should not be relevant. But if NSCalendar implementation uses
> mktime, it inherits above strange behaviour.
>
> NSDate *dateFromStringNSCalendar(NSString *str, const char *ctimezone_str) /*
> expects YYYY-MM-DD hh.mm.ss.SSSSSS TZ timestamps */
> {
> int year;
> int month;
> int day;
> int hour;
> int minute;
> int seconds;
> double subsecond = 0;
> const char *cdate_str;
> const char *ctime_str;
>
> NSArray *components = [str componentsSeparatedByString:@" "];
> if(components.count >0)
> {
> NSString *s = components[0];
> cdate_str = s.UTF8String;
> }
> if(components.count > 1)
> {
> NSString *s = components[1];
> ctime_str = s.UTF8String;
> }
> if(components.count > 2)
> {
> NSMutableArray *arr = [components mutableCopy];
> [arr removeObjectsInRange:NSMakeRange(0,2)];
> NSString *s = [arr componentsJoinedByString:@" "];
> ctimezone_str = s.UTF8String;
> }
>
> /* parsing date */
> sscanf(cdate_str,"%04d-%02d-%02d",
> &year,
> &month,
> &day);
> if(strlen(ctime_str) ==8 ) /* HH:mm:ss.SSSSSS */
> {
> sscanf(ctime_str,"%02d:%02d:%02d",
> &hour,
> &minute,
> &seconds);
> }
> else if(strlen(ctime_str) >=9 ) /* HH:mm:ss.SSSSSS */
> {
> sscanf(ctime_str,"%02d:%02d:%lf",
> &hour,
> &minute,
> &subsecond);
> seconds = (int)subsecond;
> subsecond = subsecond - (double)seconds;
> }
> else
> {
> return NULL;
> }
> NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
> dateComponents.day = day;
> dateComponents.month = month;
> dateComponents.year = year;
> dateComponents.hour = hour;
> dateComponents.minute = minute;
> dateComponents.second = seconds;
> #ifdef __APPLE__
> dateComponents.nanosecond = subsecond * 1000000000;
> #endif
> if(ctimezone_str!=NULL)
> {
> NSTimeZone *tz = [NSTimeZone timeZoneWithName:@(ctimezone_str)];
> dateComponents.timeZone = tz;
> }
> NSCalendar *gregorianCalendar = [[NSCalendar alloc]
> initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
> NSDate *date = [gregorianCalendar dateFromComponents:dateComponents];
> return date;
> }
>
>
>
>
>