Is sqeak 2.3 beta ready for Y2K?

Alan Lovejoy sourcery at pacbell.net
Fri Jul 2 09:40:08 UTC 1999


Stephen Pair wrote:

>  How old will you be on that birthday?  :)
>
>  Interestingly, to verify those dates, you would need to rely on some
>  algorithm, unless you happen to have calendars for the next mellinium handy
>  (and a lot of spare time).  So you would need to verify the algorithm in
>  Squeak with the algorithm accepted as an international standard.
>
>  Assuming that the arithmetic and algorithms in the Date class are correct
>  and that you have a reliable source (your OS, accessing primitives, human
>  entry) for dates and times, you should be safe.  Storage of the dates should
>  also be a non issue as long as there are no flaws in the internal
>  representation and external conversions.

I decided to actually check the code, and believe I have found some
flaws:

Date class>fromDays: dayCount
  "Answer an instance of me which is dayCount days after January 1,
  1901."

  ^self
       newDay: 1 + (dayCount asInteger rem: 1461)
            "There are 1461 days in a 4-year cycle.
             2000 is a leap year, so no extra correction is necessary. "
       year: 1901 + ((dayCount asInteger quo: 1461) * 4)

The problem with the above is that the algorithm deals only with every-4-year
leap days, but not with every-100-year anti-leap days nor every-400 year
super-leap days. Of course, as the comment notes, this lapse just so happens
to give the correct result for the year 2000.  The algorithm above is valid
only for years in the range 1901 to 2099.

subtractDate: aDate
  "Answer the number of days between the receiver and aDate."

     year = aDate year
         ifTrue: [^day - aDate day]
         ifFalse: [^year - 1 // 4 - (aDate year // 4) + day
                         + aDate daysLeftInYear + (year - 1 - aDate year * 365)]

The same flaw is exhibited with the algorithm above: it only accounts for
the leap days defined by the Julian calendar, and fails to consider the
Gregorian leap days (which fail to occur in 3 out of every 4 century
years).

Date class>leapYear: yearInteger
  "Answer 1 if the year yearInteger is a leap year; answer 0 if it is not."

     (yearInteger \\ 4 ~= 0 or: [yearInteger \\ 100 = 0 and: 
[yearInteger \\ 400 ~= 0]])
         ifTrue: [^0]
         ifFalse: [^1]

And finally, the algorithm (above) that determines whether a year is 
a leap year
does not properly handle dates B.C.  This can easily be fixed with 
the following
version:

Date class>leapYear: yearInteger
  "Answer 1 if the year yearInteger is a leap year; answer 0 if it is not."

     | adjustedYear |
     adjustedYear := yearInteger > 0
         ifTrue: [yearInteger]
         ifFalse: [(yearInteger + 1) negated "There is no year 0!!"].
      (adjustedYear \\ 4 ~= 0 or: [adjustedYear \\ 100 = 0 and: 
[adjustedYear \\ 400 ~= 0]])
         ifTrue: [^0]
         ifFalse: [^1]

Below is an alogorithm that computes the Gregorian year, and the day of
the year, from the number of days since (or until) January 1 of the 
year 1 A.D.:

Date class>gregorianYearAndDaysInYearFromDays: days into: aTwoArgBlock

   | quadCentury centuryquadYear year daysInMonth value isInADEra |

      value := days.
      isInADEra := days >= 0.

      isInADEra
         ifTrue: [year := 0]
         ifFalse:
             [value := value abs.
             value >= DaysPerLeapYear
                 ifTrue:
                    [year := 1;
                     value := value - DaysPerLeapYear. "Subtract the 
year 1 B.C." ]
                 ifFalse: [year := 0]]

      quadCentury := value // DaysPerQuadCentury.
      value := value \\ DaysPerQuadCentury.
      century := value // DaysPerCentury.
      value := value \\ DaysPerCentury.
      quadYear := value // DaysPerQuadYear.
      value := value \\ DaysPerQuadYear.
      value >= DaysPerStandardYear
         ifTrue:
             ["e.g., 1 AD or 2 BC"
             value := value - DaysPerStandardYear.
             year := year + 1.
             value >= DaysPerStandardYear
                 ifTrue:
                     ["e.g., 2 AD or 3 BC"
                     value := value - DaysPerStandardYear.
                     year := year + 1.
                     value >= DaysPerStandardYear
                         ifTrue:
                             ["e.g., 3 AD or 4 BC"
                             value := value - DaysPerStandardYear.
                             year := year + 1.
                             value >= DaysPerLeapYear
                                 ifTrue:
                                     ["e.g., 4 AD or 5 BC (although 
this won't occur in the AD case)"
                                     value := value - DaysPerLeapYear.
                                     year := year + 1]]]].
      year := year + (quadCentury * YearsPerQuadCentury) +
          (century * YearsPerCentury) +
          (quadYear * YearsPerQuadYear) + 1;
      isInADEra
         ifTrue: [year := year + 1 "Adjust for fact that years are ordinals"]
         ifFalse:
             [year := year negated.
             value > 0
                 ifTrue:
                     [(Date leapYear: year) = 1
                         ifTrue: [value := DaysPerLeapYear - value]
                         ifFalse: [value := DaysPerStandardYear - value]]].
      ^aTwoArgBlock value: year value: value

And here is an algorithm that computes the number of days from (or until)
January 1 of the year 1 A.D. upto (or since) January 1 of a given year:

Date class>daysForYear: gregorianYear
     | days yearDelta quadCenturies centuries quadYears years isInADEra |

     days := 0.
     isInADEra := gregorianYear > 0.

     gregorianYear = 0 ifTrue: [gregorianYear = -1]. "There is no year 0"
     isInADEra
         ifTrue: [yearDelta := gregorianYear - 1]
         ifFalse: [yearDelta := (gregorianYear + 1) negated].
     quadCenturies := yearDelta // yearsPerQuadCentury.
     yearDelta := yearDelta rem: yearsPerQuadCentury.
     centuries := yearDelta // yearsPerCentury.
     yearDelta := yearDelta rem: yearsPerCentury.
     quadYears := yearDelta // yearsPerQuadYear.
     years := yearDelta rem: yearsPerQuadYear.
     days :=
         (quadCenturies * DaysPerQuadCentury) +
         (centuries * DaysPerCentury)  +
         (quadYears * DaysPerQuadYear) +
         (years * DaysPerStandardYear).
     isInADEra
         ifFalse:
             [days := days + DaysPerLeapYear.  "1 B.C. is a leap year"
             days := days negated].
     ^days

These algorithms depend on the following constants:

     DaysPerStandardYear := 365.
     DaysPerLeapYear := DaysPerStandardYear + 1.
     YearsPerQuadYear := 4.
     YearsPerCentury := 100.
     YearsPerQuadCentury := 400.
     QuadYearsPerCentury = 25.
     DaysPerQuadYear := DaysPerLeapYear + (3 * DaysPerStandardYear).
     DaysPerCentury := QuadYearsPerCentury * DaysPerQuadYear - 1.
     CenturiesPerQuadCentury := 4.
     DaysPerQuadCentury := CenturiesPerQuadCentury * DaysPerCentury + 1.

Some useful/interesting values:

     Number of days (Gregorian, not Julian) from 1/1/1 to 15 Oct 1582 (the date
     on which the Gregorian Calendar became the official calendar of the Roman
     Catholic world): 577735.

     Number of days (Gregorian, not Julian) from 1/1/1 to 1 Jan 1900: 693,595.

     Number of days (ditto) from 1/1/1 to 1 Jan 1901: 693,960 
(Squeak's & VW's base date).

     Number of days (ditto) from 1/1/1 to 1 Jan 1970: 719,162 
(Unix/VMS/MVS/VM base date).

     Number of days (ditto) from 1/1/1 to 1 Jan 1980: 722,814 
(WinDos/Mac base date).

     Number of days (ditto) from 1/1/1 to: 1 Jan 2000: 730,119 (a 
topical value).

--Alan ("Do I have too much ``time`` on my hands, or what?")


Content-Type: text/x-vcard; charset=us-ascii;
  name="sourcery.vcf"
Content-Transfer-Encoding: 7bit
Content-Description: Card for Alan Lovejoy
Content-Disposition: attachment;
  filename="sourcery.vcf"

Attachment converted: Anon:sourcery.vcf 4 (TEXT/R*ch) (0000AD6E)





More information about the Squeak-dev mailing list