Corner case date issue in daylight savings time.

Started by ghfischer, March 30, 2008, 10:11:14 AM

Previous topic - Next topic

ghfischer

I'm working on adding some date syntactic sugar for newlisp.  Inspired by the ease of use of certain date manipulation in Ruby.



During my tests I noticed what i think is an issue in some of the date routines related to offset or possibly daylight savings time.  I tried to implement a newlisp version of http://snippets.dzone.com/posts/show/2099">days_in_month.



(Note: I'm in Austin, TX CDT which means my GMT offset is -0500 ==> 300 offset)



(Also note that if I use (date-value) without any options I don't need to specify an offset to get the correct datetime)


newLISP v.9.3.5 on OSX IPv4 UTF-8, execute 'newlisp -h' for more info.

> (date (date-value 2008 1 1) 300 "%Y %m %d")
"2007 12 31"
> (date (date-value) 0 "%Y %m %d"))
"2008 03 30"


Playing around a bit I found this


> (date (date-value) 300 "%z")
"-0500"
> (date (date-value 2008 3 1 0 0 0) 300 "%d")
"29"
> (date (date-value 2008 3 1 0 0 1) 300 "%d")
"29"
> (date (date-value 2008 3 1 1 0 0 ) 300 "%d")
"01"


It looks like there's some issue that can be solved by adding an hour.

Upon further investigation I found this


> (date (date-value 2008 4 1) 300 "%Y %m %d")
"2008 04 01"


That made me think - Daylight Savings Time issue (March 9th)


> (date (date-value 2008 3 8) 300 "%Y %m %d")
"2008 03 07"
> (date (date-value 2008 3 9) 300 "%Y %m %d")
"2008 03 08"
> (date (date-value 2008 3 10) 300 "%Y %m %d")
"2008 03 10"
> (date (date-value 2008 3 9 1 0 0) 300 "%Y %m %d")
"2008 03 09"
> (date (date-value 2008 3 8 1 0 0) 300 "%Y %m %d")
"2008 03 08"


It looks like doing historical date conversion doesn't take into consideration daylight savings time.  Also notice that I can't get access to what the timezone was before March 9th and adjust the offset accordingly.



My hackish workaround is to take the time at 3am.  

But I'm hoping there's a better way.  

So finally ... here's a newlisp days-in-month
(define (days-in-month y m)
  (set 'tmp (date (date-value) 0 "%z"))
  (set 'offset (+ (* (int (1 2 tmp)) 60) (int (3 2 tmp))))
  (if (= "+" (0 1 tmp)) (set 'offset (* -1 offset)))
  (int (date (date-value y (+ m 1) 0 3 0 0) offset "%d"))
)
> (days-in-month 2008 2)
29
> (days-in-month 2007 2)
28
> (days-in-month 2008 3)
31
> (days-in-month 2008 4)
30

ghfischer

#1

> (date (date-value) 0 "%Y %m %d %H %M %S")
"2008 03 30 12 32 58"
> (date (date-value 2008 3 30 12 32 58) 0 "%Y %m %d %H %M %S")
"2008 03 30 07 32 58"


To me these two functions should return the same value.



Lutz - Perhaps you could add another option onto (date-value)



syntax: (date-value int-year int-month int-day [int-hour int-min int-sec])
syntax: (date-value int-year int-month int-day [int-hour int-min int-sec] [int-offset])


If int-offset is NULL just do the conversion based upon timezone like it's being done in (date-value).  If int-offset is non-null then the programmer is asking for the raw GMT value.

Lutz

#2
good to hear from you Gordon :), this is the story:



Unfortunately on some Unix, including Mac OS X, the daylight savings flag (last number returned by the function 'now') is not updated correctly and on some Solaris versions the offset value is completely random. This is a libc problem.



One trick to use, is the difference from local time to UTC/GMT which is smaller during daylight savings time.



As an example: in Florida I have now 4 hours of difference between what (date) and (now) report, but the offset value from (now) is 5 hours (300 minutes west of UTC), so I know I am in daylights savings time (my local hour is 1 hour forward).



Also this seems work during the whole year:


> (= (date (apply date-value (now))) (date))
true
>


which means that the OS gets the daylight-savings flag from some internal table, which is the reason that some computers / cell phones didn't hget the DST change this year, when it moved a week.



Regarding your last post, yes we can add an additional, optional offset value to date-value, which is added to the result.

Lutz

#3
CORRECTION:



Regarding your last post for an additional offset parameter in date-value: this would break (apply date-value (now)). Because now has 5 additional fields which must be ignored in the 'date-value' application. So we can not do it.



Just add the offset yourself ;-)

ghfischer

#4
Good point on the apply consequence.



Imagine you have a scheduled report to run at 7am every day.  Since you're in a timezone that does daylight savings for a portion of the year the report should run at 7am GMT and for another portion at 8am GMT.



But as a user, I don't care about any of that - I want my report at 7am every day and I want the computer to figure out when exactly that is in GMT.



It looks like this will be a bit harder problem to solve since detecting whether I'm in daylight savings time differs per platform.