Author: wmb Date: Sat Dec 31 21:01:59 2011 New Revision: 2792 URL: http://tracker.coreboot.org/trac/openfirmware/changeset/2792
Log: OLPC trac #11563 - fixed longstanding problem with Unix timestamp conversion routines - leap year adjustment was badly broken.
Modified: ofw/fs/unixtime.fth
Modified: ofw/fs/unixtime.fth ============================================================================== --- ofw/fs/unixtime.fth Sat Dec 31 21:01:56 2011 (r2791) +++ ofw/fs/unixtime.fth Sat Dec 31 21:01:59 2011 (r2792) @@ -2,39 +2,112 @@ purpose: Convert Unix seconds to time and date
decimal -\ date&time is number of seconds since 1970 +\ February is given 29 days so the loop in >d/m will exit at the "unloop". +\ The array begins at March so that the leap day falls at the end. create days/month -\ Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec - 31 c, 28 c, 31 c, 30 c, 31 c, 30 c, 31 c, 31 c, 30 c, 31 c, 30 c, 31 c, +\ Mar Apr May Jun Jul Aug Sep Oct Nov Dec Jan Feb + 31 c, 30 c, 31 c, 30 c, 31 c, 31 c, 30 c, 31 c, 30 c, 31 c, 31 c, 29 c,
-: >d/m ( day-in-year -- day month ) - 12 0 do - days/month i ca+ c@ 2dup < if - drop 1+ i 1+ leave - then - - - loop +\ In >d/m and d/m>, the yearly period starts on March 1. day-in-year is +\ relative to March 1, and month-index 0 is March, 9 is December, 10 is January, +\ 11 is February. This representation simplifies the calculations by +\ putting the optional leap day at the end, i.e. day-of-year=365. + +\ Convert day-in-year to day-of-month and month-index. + +: >d/m ( day-in-year0..365 -- day1..31 month-index0..11 ) + d# 12 0 do ( days-left ) + days/month i ca+ c@ 2dup < if ( days-left ) + drop 1+ i unloop exit ( -- day1..31 month-index0..11 ) + then ( days-left ) + - ( days-left' ) + loop ( days-left ) + \ This is reached only if the argument is >365 + 1+ d# 12 ( day1..31 month-index0..11 ) ; -: unix-seconds> ( seconds -- s m h d m y ) - 60 u/mod 60 u/mod 24 u/mod ( s m h days ) - [ 365 4 * 1+ ] literal /mod >r ( s m h day-in-cycle ) ( r: cycles ) - dup [ 365 365 + 31 + 29 + ] literal - 2dup = if \ exactly leap year Feb 29 - 3drop 2 29 2 ( s m h year-in-cycle d m ) - else - > if 1- then \ after leap year - 365 u/mod ( s m h day-in-year year-in-cycle ) - swap >d/m ( s m h year-in-cycle d m ) - then - rot r> 4 * + 1970 + ( s m h d m y ) + +\ Convert day-of-month and month-index to day-in-year. + +: d/m> ( day1..31 month-index0..11 -- day-in-year0..365 ) + swap 1- swap 0 ?do i days/month + c@ + loop ( day-in-year ) ; -: >unix-seconds ( s m h d m y -- seconds ) \ since 1970 - d# 1970 - 4 /mod [ d# 365 4 * 1+ ] literal * ( s m h d m yrs days ) - swap d# 365 * + ( s m h d m days ) - swap 1 max d# 12 min ( s m h d days m' ) - 1- 0 ?do i days/month + c@ + loop ( s m h d days ) - + 1- ( s m h days ) - d# 24 * + d# 60 * + d# 60 * + + +\ +d# 365 constant d/y +d/y d# 30 * \ Years from 1970 to 2000 +d# 7 + \ Leap days from 1970 to 2000 +d# 31 + \ Days in January 2000 +d# 28 + \ Days in February 2000 (not a leap year) +constant days-to-break + +: unix-seconds> ( seconds -- s m h d m y ) + \ Changing the 3 /mod's below to u/mod's would "fix" the year 2038 problem + \ at the expense of breaking dates before 1970. + d# 60 /mod d# 60 /mod d# 24 /mod ( s m h days ) + + \ Rotate the number space so that day 0 is March 1, 2000 + \ That's convenient because it begins a 4 year + 1 day leap cycle + days-to-break - + + \ Adjust days before day 0 for the fact that 2000, unlike other + \ 0mod4 years, is not a leap year + dup 0< if 1- then ( s m h days' ) + + \ Reduce modulo the number of days in a 4-year leap cycle + \ This depends on floored division + [ d/y 4 * 1+ ] literal /mod >r ( s m h day-in-cycle r: cycles ) + + \ Reduce by the number of days in a normal year + d/y /mod ( s m h day-in-year year-in-cycle r: cycles ) + + \ If year-in-cycle is 4, it's Feb 29 + dup 4 = if ( s m h day-in-year year-in-cycle r: cycles ) + \ Leap day Feb 29 at end of cycle + swap d/y + swap 1- ( s m h day-in-year' year-in-cycle' r: cycles ) + then ( s m h day-in-year year-in-cycle r: cycles ) + r> 4 * + >r ( s m h day-in-year r: year ) + + >d/m ( s m h day-in-month month-index r: year ) + + \ Adjust the month number - at this point March is 0 and we want it to be 3 + 3 + ( s m h d month' r: year ) + + \ Months 13 and 14 are January and February of the following year + dup d# 13 >= if ( s m h d month r: year ) + d# 12 - r> 1+ >r ( s m h d month' r: year' ) + then ( s m h d month r: year ) + + r> d# 2000 + ( s m h d m y ) +; + +: >unix-seconds ( s m h d m y -- seconds ) \ since 1970 + d# 2000 - >r ( s m h d m r: y' ) + + \ Move January and February to the end so the leap day is day number 365 + dup 3 < if ( s m h d month' r: y ) + d# 12 + r> 1- >r ( s m h d month' r: y' ) + then ( s m h d month r: y ) + + \ Convert month numbers 3..14 to 0..11 + 3 - ( s m h d month-index r: y ) + + \ Convert day and month to day in year + d/m> ( s m h day-in-year r: y ) + + r@ 4 / ( s m h day-in-year leap-years r: y ) + r> d/y * + ( s m h day-in-year year-days ) + + ( s m h days ) + + \ Adjust for the missing leap day in 2000 + dup 0< if 1+ then ( s m h days' ) + + \ Adjust to 1970 + days-to-break + ( s m h days' ) + + + \ Changing the 3 *'s below to u*'s would "fix" the year 2038 problem + \ at the expense of breaking dates before 1970. + d# 24 * + d# 60 * + d# 60 * + ( seconds ) ;
\ e.g. time&date >unix-seconds
openfirmware@openfirmware.info