Changeset 2792


Ignore:
Timestamp:
Dec 31, 2011, 9:01:59 PM (4 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.