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 <[email protected]>: > > 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; > } > > > > >
