[openfirmware] [commit] r2792 - ofw/fs

repository service svn at openfirmware.info
Sat Dec 31 21:02:00 CET 2011


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 



More information about the openfirmware mailing list