[Python-checkins] CVS: python/nondist/sandbox/datetime datetime.py,1.10,1.11 test_datetime.py,1.6,1.7

Tim Peters tim_one@users.sourceforge.net
Sat, 02 Mar 2002 12:42:49 -0800


Update of /cvsroot/python/python/nondist/sandbox/datetime
In directory usw-pr-cvs1:/tmp/cvs-serv13899

Modified Files:
	datetime.py test_datetime.py 
Log Message:
Fiddle to compute day ordinals consistent with the "proleptic Gregorian"
calendar at the heart of Dershowitz and Reingold's "Calendrical
Calculations" book.  They're the experts, and they call 1 Jan 1 "day 1"
(the Dates.py code called 1 Jan 0 "day 1" -- same calendar, different
origin).

Their book gives algorithms for converting between proleptic Gregorian
ordinals and many other calendar systems, so supplying these ordinals
should make life much easier for people with other needs.

Note that we're going to need *some* form of ordinal conversion regardless,
in order to get around C limitations on timestamps and mktime().

Bummer:  while putting the origin at year 1 simplifies _days_before_year(),
it makes the details in _num2date significantly harder to understand; it's
a net loss in clarity.


Index: datetime.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/datetime.py,v
retrieving revision 1.10
retrieving revision 1.11
diff -C2 -d -r1.10 -r1.11
*** datetime.py	2 Mar 2002 13:59:32 -0000	1.10
--- datetime.py	2 Mar 2002 20:42:47 -0000	1.11
***************
*** 6,11 ****
  # Utility functions, adapted from Python's Demo/classes/Dates.py, which
  # also assumes the current Gregorian calendar indefinitely extended in
! # both directions.
! # XXX Why not borrow from calendar.py?
  
  _DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
--- 6,16 ----
  # Utility functions, adapted from Python's Demo/classes/Dates.py, which
  # also assumes the current Gregorian calendar indefinitely extended in
! # both directions.  Difference:  Dates.py calls January 1 of year 0 day
! # number 1.  The code here calls January 1 of year 1 day number 1.  This is
! # to match the definition of the "proleptic Gregorian" calendar in Dershowitz
! # and Reingold's "Calendrical Calculations", where it's the base calendar
! # for all computations.  See the book for algorithms for converting between
! # day ordinals in the proleptic Gregorian calendar and many other calendar
! # systems.
  
  _DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
***************
*** 28,32 ****
  def _days_before_year(year):
      "year -> number of days before January 1st of year."
!     return year*365 + (year+3)//4 - (year+99)//100 + (year+399)//400
  
  def _days_in_month(month, year):
--- 33,38 ----
  def _days_before_year(year):
      "year -> number of days before January 1st of year."
!     y = year - 1
!     return y*365 + y//4 - y//100 + y//400
  
  def _days_in_month(month, year):
***************
*** 48,52 ****
              date.day)
  
! _DI400Y = _days_before_year(400)    # number of days in 400 years
  
  def _num2date(n):
--- 54,58 ----
              date.day)
  
! _DI400Y = _days_before_year(401)    # number of days in 400 years
  
  def _num2date(n):
***************
*** 54,66 ****
  
      n400 = (n-1) // _DI400Y  # number of 400-year blocks preceding
!     year = n400 * 400
      n -= n400 * _DI400Y
  
      more = n // 365
!     dby = _days_before_year(more)
      if dby >= n:
-         more -= 1
          dby -= _days_in_year(more)
      year += more
      n -= dby
  
--- 60,73 ----
  
      n400 = (n-1) // _DI400Y  # number of 400-year blocks preceding
!     year = n400 * 400 + 1
      n -= n400 * _DI400Y
  
      more = n // 365
!     dby = _days_before_year(more + 1)
      if dby >= n:
          dby -= _days_in_year(more)
+         more -= 1
      year += more
+     assert n > dby
      n -= dby
  
***************
*** 70,74 ****
          month -= 1
          dbm -= _days_in_month(month, year)
! 
      return datetime(year, month, n-dbm)
  
--- 77,81 ----
          month -= 1
          dbm -= _days_in_month(month, year)
!     assert n > dbm
      return datetime(year, month, n-dbm)
  

Index: test_datetime.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/test_datetime.py,v
retrieving revision 1.6
retrieving revision 1.7
diff -C2 -d -r1.6 -r1.7
*** test_datetime.py	2 Mar 2002 01:10:35 -0000	1.6
--- test_datetime.py	2 Mar 2002 20:42:47 -0000	1.7
***************
*** 28,38 ****
  
      def test_ordinal_conversions(self):
!         import datetime as _datetime
!         for year in range(_datetime.MINYEAR, _datetime.MAXYEAR + 1):
!             for month, day in (1, 1), (12, 31), (6, 17):
!                 base = datetime(year, month, day)
!                 ordinal = _datetime._date2num(base)
!                 derived = _datetime._num2date(ordinal)
!                 self.assertEqual(base, derived)
  
      def test_bad_constructor_arguments(self):
--- 28,53 ----
  
      def test_ordinal_conversions(self):
!         from datetime import _date2num, _num2date, MINYEAR, MAXYEAR
! 
!         # Verify 1 Jan 1 has ordinal 1.
!         one = datetime.new(1, 1, 1)
!         self.assertEqual(_date2num(one), 1)
!         self.assertEqual(_num2date(1), one)
! 
!         # The first example in "Calendrical Calculations".
!         d = datetime.new(1945, 11, 12)
!         self.assertEqual(_date2num(d), 710347)
!         self.assertEqual(_num2date(710347), d)
! 
!         for year in range(MINYEAR, MAXYEAR + 1):
!             base = datetime(year, 1, 1)
!             ordinal = _date2num(base)
!             derived = _num2date(ordinal)
!             self.assertEqual(base, derived)
!             if year > MINYEAR:
!                 # Verify that moving back a day gets to the end of year-1.
!                 lastyear = datetime(year-1, 12, 31)
!                 derived = _num2date(ordinal - 1)
!                 self.assertEqual(lastyear, derived)
  
      def test_bad_constructor_arguments(self):