Changeset 2792


Ignore:
Timestamp:
Dec 31, 2011, 9:01:59 PM (3 years ago)
Author:
wmb
Message:

OLPC trac #11563 - fixed longstanding problem with Unix timestamp conversion routines - leap year adjustment was badly broken.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • ofw/fs/unixtime.fth

    r752 r2792  
    33 
    44decimal 
    5 \ date&time is number of seconds since 1970 
     5\ February is given 29 days so the loop in >d/m will exit at the "unloop". 
     6\ The array begins at March so that the leap day falls at the end. 
    67create days/month 
    7 \ Jan   Feb   Mar   Apr   May   Jun   Jul   Aug   Sep   Oct   Nov   Dec 
    8   31 c, 28 c, 31 c, 30 c, 31 c, 30 c, 31 c, 31 c, 30 c, 31 c, 30 c, 31 c, 
     8\ Mar   Apr   May   Jun   Jul   Aug   Sep   Oct   Nov   Dec   Jan   Feb 
     9  31 c, 30 c, 31 c, 30 c, 31 c, 31 c, 30 c, 31 c, 30 c, 31 c, 31 c, 29 c, 
    910 
    10 : >d/m  ( day-in-year -- day month ) 
    11    12 0  do 
    12       days/month i ca+ c@  2dup <  if 
    13          drop 1+  i 1+  leave 
    14       then 
    15       - 
    16    loop 
     11\ In >d/m and d/m>, the yearly period starts on March 1. day-in-year is 
     12\ relative to March 1, and month-index 0 is March, 9 is December, 10 is January, 
     13\ 11 is February.  This representation simplifies the calculations by  
     14\ putting the optional leap day at the end, i.e. day-of-year=365. 
     15 
     16\ Convert day-in-year to day-of-month and month-index. 
     17 
     18: >d/m  ( day-in-year0..365 -- day1..31 month-index0..11 ) 
     19   d# 12 0  do                           ( days-left ) 
     20      days/month i ca+ c@  2dup <  if    ( days-left ) 
     21         drop 1+  i  unloop exit         ( -- day1..31 month-index0..11 ) 
     22      then                               ( days-left ) 
     23      -                                  ( days-left' ) 
     24   loop                                  ( days-left ) 
     25   \ This is reached only if the argument is >365 
     26   1+  d# 12                             ( day1..31 month-index0..11 ) 
    1727; 
    18 : unix-seconds>  ( seconds -- s m h d m y ) 
    19    60 u/mod  60 u/mod  24 u/mod         ( s m h days ) 
    20    [ 365 4 * 1+ ] literal /mod >r       ( s m h day-in-cycle )  ( r: cycles ) 
    21    dup [ 365 365 + 31 + 29 + ] literal 
    22    2dup =  if           \ exactly leap year Feb 29 
    23       3drop 2 29 2                      ( s m h year-in-cycle d m ) 
    24    else 
    25       >  if  1-  then   \ after leap year 
    26       365 u/mod                         ( s m h day-in-year year-in-cycle ) 
    27       swap >d/m                         ( s m h year-in-cycle d m ) 
    28    then 
    29    rot r> 4 * + 1970 +                  ( s m h d m y ) 
     28 
     29\ Convert day-of-month and month-index to day-in-year. 
     30 
     31: d/m>  ( day1..31 month-index0..11 -- day-in-year0..365 ) 
     32   swap 1-  swap 0  ?do  i days/month + c@ +  loop      ( day-in-year )    
    3033; 
    31 : >unix-seconds   ( s m h d m y -- seconds )    \ since 1970 
    32    d# 1970 - 4 /mod [ d# 365 4 * 1+ ] literal *         ( s m h d m yrs days ) 
    33    swap d# 365 * +                                      ( s m h d m days ) 
    34    swap  1 max  d# 12 min                               ( s m h d days m' ) 
    35    1- 0 ?do  i days/month + c@ + loop                   ( s m h d days ) 
    36    + 1-                                                 ( s m h days ) 
    37    d# 24 * +   d# 60 * +   d# 60 * + 
     34 
     35\  
     36d# 365 constant d/y 
     37d/y d# 30 * \ Years from 1970 to 2000 
     38d#  7 +     \ Leap days from 1970 to 2000 
     39d# 31 +     \ Days in January 2000 
     40d# 28 +     \ Days in February 2000 (not a leap year) 
     41constant days-to-break 
     42 
     43: unix-seconds>   ( seconds -- s m h d m y ) 
     44   \ Changing the 3 /mod's below to u/mod's would "fix" the year 2038 problem 
     45   \ at the expense of breaking dates before 1970. 
     46   d# 60 /mod  d# 60 /mod  d# 24 /mod   ( s m h days ) 
     47 
     48   \ Rotate the number space so that day 0 is March 1, 2000 
     49   \ That's convenient because it begins a 4 year + 1 day leap cycle 
     50   days-to-break - 
     51 
     52   \ Adjust days before day 0 for the fact that 2000, unlike other 
     53   \ 0mod4 years, is not a leap year 
     54   dup 0<  if  1-  then ( s m h days' ) 
     55 
     56   \ Reduce modulo the number of days in a 4-year leap cycle 
     57   \ This depends on floored division 
     58   [ d/y 4 * 1+ ] literal /mod >r       ( s m h day-in-cycle r: cycles ) 
     59 
     60   \ Reduce by the number of days in a normal year 
     61   d/y /mod                             ( s m h day-in-year year-in-cycle r: cycles ) 
     62 
     63   \ If year-in-cycle is 4, it's Feb 29 
     64   dup 4 =  if                          ( s m h day-in-year year-in-cycle r: cycles ) 
     65      \ Leap day Feb 29 at end of cycle 
     66      swap d/y +  swap 1-               ( s m h day-in-year' year-in-cycle' r: cycles ) 
     67   then                                 ( s m h day-in-year year-in-cycle r: cycles ) 
     68   r> 4 * + >r                          ( s m h day-in-year r: year ) 
     69 
     70   >d/m                                 ( s m h day-in-month month-index r: year ) 
     71 
     72   \ Adjust the month number - at this point March is 0 and we want it to be 3 
     73   3 +                                  ( s m h d month' r: year ) 
     74 
     75   \ Months 13 and 14 are January and February of the following year 
     76   dup d# 13 >=  if                     ( s m h d month r: year ) 
     77      d# 12 -  r> 1+ >r                 ( s m h d month' r: year' ) 
     78   then                                 ( s m h d month r: year ) 
     79 
     80   r> d# 2000 +                         ( s m h d m y ) 
     81; 
     82 
     83: >unix-seconds  ( s m h d m y -- seconds )     \ since 1970 
     84   d# 2000 - >r                                         ( s m h d m  r: y' ) 
     85 
     86   \ Move January and February to the end so the leap day is day number 365 
     87   dup 3 <  if                                          ( s m h d month' r: y ) 
     88      d# 12 +  r> 1- >r                                 ( s m h d month' r: y' ) 
     89   then                                                 ( s m h d month  r: y ) 
     90 
     91   \ Convert month numbers 3..14 to 0..11 
     92   3 -                                                  ( s m h d month-index  r: y ) 
     93 
     94   \ Convert day and month to day in year 
     95   d/m>                                                 ( s m h day-in-year  r: y ) 
     96 
     97   r@ 4 /                                               ( s m h day-in-year leap-years  r: y ) 
     98   r> d/y * +                                           ( s m h day-in-year year-days ) 
     99   +                                                    ( s m h days ) 
     100 
     101   \ Adjust for the missing leap day in 2000 
     102   dup 0<  if  1+  then                                 ( s m h days' ) 
     103 
     104   \ Adjust to 1970 
     105   days-to-break +                                      ( s m h days' ) 
     106 
     107 
     108   \ Changing the 3 *'s below to u*'s would "fix" the year 2038 problem 
     109   \ at the expense of breaking dates before 1970. 
     110   d# 24 * +   d# 60 * +   d# 60 * +                    ( seconds ) 
    38111; 
    39112 
Note: See TracChangeset for help on using the changeset viewer.