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;
}





Reply via email to