discuss-gnustep
[Top][All Lists]
Advanced

[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: Sun, 26 Apr 2020 21:31:15 +0200

I changed a few things in NSCalendar:
First I completed NSDateComponents up to the recent Cocoa specification. That 
part should be fairly complete and correct. 
Next I tried to support more of these components in the existing NSCalendar 
methods. While doing so I noticed some oddities in that class and tried to 
clean those. And while doing that I did get even more confused about the 
interaction with NSLocale. 
Finally I tried to make NSCalendar respect the timezone of NSDateComponents. As 
Stefan already pointed out you have to be careful here not to change the state 
of the calendar itself, but the state currently also includes one ucal object, 
which means that NSCalendar is not multithread safe and that some operations 
may influence later ones. To work around this I am now using a local ucal 
object for -dateFromComponents:.

As I wrote, I am no expert in this area and my changes may have broken some 
stuff. At least the tests (plus my new one) still work. Please give this 
classes some more real testing and feel free to provide test code.

If these changes work out I am willing to implement some of the missing methods 
on NSCalendar.

@Richard:
In the header file NSCalendar.h there are some left over instance variables 
that according to the comments there could be removed in a binary compatibility 
breaking release of base. Are you planing to have the next release to be binary 
compatible or not?

Cheers,
Fred


> Am 25.04.2020 um 16:36 schrieb Stefan Bidigaray <stefanbidi@gmail.com>:
> 
> Hi Fred/Andreas,
> I wrote all that code, so I'm fairly familiar with it. The code is entirely 
> dependent on ICU's implementation of ucal, so the solution is to simply set 
> the Time Zone value in the ucal. The function to do this is: 
> ucal_setTimeZone() 
> (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/ucal_8h.html#ae5612988cb9dc282ccda82fda38602b2).
> 
> I no longer have a development machine with GNUstep installed, so I cannot 
> make the changes and test it. There are two methods where this needs to be 
> fixed:
> -dateByAddingComponents:toDate: and -dateFromComponents:.
> 
> The part I'm not totally sure of is if the fix should be done by calling 
> -setTimeZone: or ucal_setTimeZone(). Based on the method descriptions, I 
> believe a call to ucal_setTimeZone should be added to lines NSCalendar.m:546 
> and NSCalendar.m:611 (first a UChar timezone name must be retrieved from the 
> NSTimeZone object) since -setTimeZone: will permanently modify the NSCalendar 
> object.
> 
> Regards
> Stefan
> 
> On Sat, Apr 25, 2020 at 9:58 AM Fred Kiefer <fredkiefer@gmx.de> wrote:
> 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;
> > }
> > 
> > 
> > 
> > 
> > 
> 
> 




reply via email to

[Prev in Thread] Current Thread [Next in Thread]