embedded/mx/DateTime/DateTime.py
changeset 0 b97547f5f1fa
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 """ Python part of the low-level DateTime[Delta] type implementation.
       
     2 
       
     3     Copyright (c) 1998-2001, Marc-Andre Lemburg; mailto:mal@lemburg.com
       
     4     Copyright (c) 2000-2007, eGenix.com Software GmbH; mailto:info@egenix.com
       
     5     See the documentation for further information on copyrights,
       
     6     or contact the author. All Rights Reserved.
       
     7 """
       
     8 # Import the python implementation module
       
     9 from mxDateTime_python import *
       
    10 from mxDateTime_python import __version__
       
    11 
       
    12 # Singletons
       
    13 oneSecond = DateTimeDelta(0,0,0,1)
       
    14 oneMinute = DateTimeDelta(0,0,1)
       
    15 oneHour = DateTimeDelta(0,1)
       
    16 oneDay = DateTimeDelta(1)
       
    17 oneWeek = DateTimeDelta(7)
       
    18 Epoch = DateTimeFromAbsDateTime(1,0)
       
    19 
       
    20 # Shortcuts for pickle; for backward compatibility only (they are now
       
    21 # defined in __init__.py to further reduce the pickles length)
       
    22 def _DT(absdate,abstime):
       
    23     return DateTimeFromAbsDateTime(absdate,abstime)
       
    24 def _DTD(seconds):
       
    25     return DateTimeDeltaFromSeconds(seconds)
       
    26 
       
    27 # Module init
       
    28 class modinit:
       
    29 
       
    30     global _time,_string,_math,_types
       
    31     import time,string,math,types
       
    32     _time = time
       
    33     _string = string
       
    34     _math = math
       
    35     _types = types
       
    36 
       
    37 del modinit
       
    38 
       
    39 ### Helpers
       
    40 
       
    41 def _isstring(arg,
       
    42 
       
    43               isinstance=isinstance, types=_types):
       
    44     
       
    45     if isinstance(arg, types.StringType):
       
    46         return 1
       
    47     try:
       
    48         if isinstance(arg, types.UnicodeType):
       
    49             return 1
       
    50     except AttributeError:
       
    51         pass
       
    52     return 0
       
    53 
       
    54 ### Compatibility APIs
       
    55 
       
    56 # Aliases and functions to make 'from mx.DateTime import *' work much
       
    57 # like 'from time import *'
       
    58 
       
    59 def localtime(ticks=None,
       
    60               # Locals:
       
    61               time=_time.time,float=float,localtime=_time.localtime,
       
    62               round=round,int=int,DateTime=DateTime,floor=_math.floor):
       
    63 
       
    64     """localtime(ticks=None)
       
    65 
       
    66        Construct a DateTime instance using local time from ticks.  If
       
    67        ticks are not given, it defaults to the current time.  The
       
    68        result is similar to time.localtime(). Fractions of a second
       
    69        are rounded to the nearest micro-second.
       
    70 
       
    71     """
       
    72     if ticks is None:
       
    73         ticks = time()
       
    74     else:
       
    75         ticks = float(ticks)
       
    76     ticks = round(ticks, 6)
       
    77     fticks = floor(ticks)
       
    78     Y,M,D,h,m,s = localtime(fticks)[:6]
       
    79     s = s + (ticks - fticks)
       
    80     return DateTime(Y,M,D,h,m,s)
       
    81 
       
    82 def gmtime(ticks=None,
       
    83            # Locals:
       
    84            time=_time.time,float=float,gmtime=_time.gmtime,
       
    85            round=round,int=int,DateTime=DateTime,floor=_math.floor):
       
    86 
       
    87     """gmtime(ticks=None)
       
    88 
       
    89        Construct a DateTime instance using UTC time from ticks.  If
       
    90        ticks are not given, it defaults to the current time.  The
       
    91        result is similar to time.gmtime(). Fractions of a second are
       
    92        rounded to the nearest micro-second.
       
    93 
       
    94     """
       
    95     if ticks is None:
       
    96         ticks = time()
       
    97     else:
       
    98         ticks = float(ticks)
       
    99     ticks = round(ticks, 6)
       
   100     fticks = floor(ticks)
       
   101     Y,M,D,h,m,s = gmtime(ticks)[:6]
       
   102     s = s + (ticks - fticks)
       
   103     return DateTime(Y,M,D,h,m,s)
       
   104 
       
   105 def mktime((year,month,day,hour,minute,second,dow,doy,dst),
       
   106            # Locals:
       
   107            DateTime=DateTime):
       
   108 
       
   109     """mktime((year,month,day,hour,minute,second,dow,doy,dst))
       
   110 
       
   111        Same as the DateTime() constructor accept that the interface
       
   112        used is compatible to the similar time.mktime() API.
       
   113 
       
   114        Note that the tuple elements dow, doy and dst are not used in
       
   115        any way.
       
   116       
       
   117     """
       
   118     return DateTime(year,month,day,hour,minute,second)
       
   119 
       
   120 def ctime(datetime):
       
   121 
       
   122     """ctime(datetime)
       
   123 
       
   124        Returns a string representation of the given DateTime instance
       
   125        using the current locale's default settings.
       
   126 
       
   127     """
       
   128     return datetime.strftime('%c')
       
   129 
       
   130 def today(hour=0,minute=0,second=0.0,
       
   131           # Locals:
       
   132           localtime=_time.localtime,time=_time.time,DateTime=DateTime):
       
   133 
       
   134     """today(hour=0,minute=0,second=0.0)
       
   135 
       
   136        Returns a DateTime instance for today (in local time) at the
       
   137        given time (defaults to midnight).
       
   138 
       
   139     """
       
   140     Y,M,D = localtime(time())[:3]
       
   141     return DateTime(Y,M,D,hour,minute,second)
       
   142 
       
   143 def TimeDelta(hours=0.0,minutes=0.0,seconds=0.0,
       
   144               # Locals:
       
   145               DateTimeDelta=DateTimeDelta):
       
   146 
       
   147     """TimeDelta(hours=0.0,minutes=0.0,seconds=0.0)
       
   148 
       
   149        Returns a DateTimeDelta-object reflecting the given time
       
   150        delta. Seconds can be given as float to indicate fractions.
       
   151 
       
   152     """
       
   153     return DateTimeDelta(0,hours,minutes,seconds)
       
   154 
       
   155 def gm2local(datetime):
       
   156 
       
   157     """ gm2local(datetime)
       
   158 
       
   159         Convert a DateTime instance holding UTC time to a DateTime
       
   160         instance using local time.
       
   161 
       
   162     """
       
   163     return localtime(datetime.gmticks())
       
   164 
       
   165 def local2gm(datetime):
       
   166 
       
   167     """ local2gm(datetime)
       
   168 
       
   169         Convert a DateTime instance holding local time to a DateTime
       
   170         instance using UTC time.
       
   171 
       
   172     """
       
   173     return gmtime(datetime.ticks())
       
   174 
       
   175 # Alias
       
   176 gmt = utc
       
   177 
       
   178 # Default value for DateTimeFromTJD's tjd_myriad parameter
       
   179 current_myriad = localtime().tjd_myriad
       
   180 
       
   181 def DateTimeFromTJD(tjd,tjd_myriad=current_myriad):
       
   182 
       
   183     """ DateTimeFromTJD(tjd[,myriad])
       
   184 
       
   185         Return a DateTime instance for the given Truncated Julian Day.
       
   186         myriad defaults to the TJD myriad current at package import
       
   187         time.
       
   188 
       
   189         Note that this version of Truncated Julian Day number does
       
   190         real truncation of important information. It's use is
       
   191         discouraged and unsupported.
       
   192 
       
   193     """
       
   194     return DateTimeFromAbsDays(tjd + tjd_myriad * 10000.0 - 1721425.0)
       
   195 
       
   196 def DateTimeFromJDN(jdn):
       
   197 
       
   198     """ DateTimeFromJDN(jdn)
       
   199 
       
   200         Return a DateTime instance for the given Julian Day Number.
       
   201 
       
   202         References:
       
   203         -----------
       
   204         Gregorian 2000-01-01 12:00:00 corresponds to JDN 2451545.0.
       
   205         Gregorian 1858-11-17 00:00:00.00 corresponds to JDN 2400000.5; MJD 0.0.
       
   206         Julian -4712-01-01 12:00:00.00 corresponds to JDN 0.0.
       
   207         Gregorian -4713-11-24 12:00:00.00 corresponds to JDN 0.0.
       
   208 
       
   209     """
       
   210     return DateTimeFromAbsDays(jdn - 1721425.5)
       
   211 
       
   212 def DateTimeFromMJD(mjd):
       
   213 
       
   214     """ DateTimeFromMJD(mjd)
       
   215 
       
   216         Return a DateTime instance for the given Modified Julian Day
       
   217         (MJD). The MJD is calculated the same way as the JDN except
       
   218         that 1858-11-17 00:00:00.00 is taken as origin of the scale.
       
   219 
       
   220     """
       
   221     return DateTimeFromAbsDays(mjd + 678575.0)
       
   222 
       
   223 def DateTimeFrom(*args, **kws):
       
   224 
       
   225     """ DateTimeFrom(*args, **kws)
       
   226 
       
   227         Generic DateTime instance constructor. Can handle parsing
       
   228         strings, numbers and keywords.
       
   229 
       
   230         XXX Add support for Unicode.
       
   231 
       
   232     """
       
   233     if len(args) == 1:
       
   234         # Single argument
       
   235         arg = args[0]
       
   236         argtype = type(arg)
       
   237         if _isstring(arg):
       
   238             import Parser
       
   239             return apply(Parser.DateTimeFromString, args, kws)
       
   240         elif argtype is DateTimeType:
       
   241             return arg
       
   242         elif argtype is DateTimeDeltaType:
       
   243             raise TypeError,'cannot convert DateTimeDelta to DateTime'
       
   244         else:
       
   245             try:
       
   246                 value = float(arg)
       
   247             except (TypeError, ValueError):
       
   248                 value = int(arg)
       
   249             assert not kws
       
   250             return DateTimeFromTicks(value)
       
   251 
       
   252     elif len(args) > 1:
       
   253         # More than one argument
       
   254         if len(args) == 2 and _isstring(args[0]) and _isstring(args[1]):
       
   255             # interpret as date and time string
       
   256             import Parser
       
   257             return apply(Parser.DateTimeFromString,
       
   258                          (args[0] + ' ' + args[1],),
       
   259                          kws)
       
   260 
       
   261         # Assume the arguments are the same as for DateTime()
       
   262         return apply(DateTime, args, kws)
       
   263 
       
   264     elif len(kws) > 0:
       
   265         # Keyword arguments; add defaults... today at 0:00:00
       
   266         hour = kws.get('hour',0)
       
   267         minute = kws.get('minute',0)
       
   268         second = kws.get('second',0)
       
   269         today = now()
       
   270         day = kws.get('day',today.day)
       
   271         month = kws.get('month',today.month)
       
   272         year = kws.get('year',today.year)
       
   273         return DateTime(year,month,day,hour,minute,second)
       
   274 
       
   275     else:
       
   276         raise TypeError,'cannot convert arguments to DateTime'
       
   277 
       
   278 def DateTimeDeltaFrom(*args, **kws):
       
   279 
       
   280     """ DateTimeDeltaFrom(*args, **kws)
       
   281 
       
   282         Generic DateTimeDelta instance constructor. Can handle parsing
       
   283         strings, numbers and keywords.
       
   284 
       
   285         XXX Add support for Unicode.
       
   286 
       
   287     """
       
   288     if len(args) == 1:
       
   289         # Single argument
       
   290         arg = args[0]
       
   291         if _isstring(arg):
       
   292             import Parser
       
   293             return apply(Parser.DateTimeDeltaFromString, args, kws)
       
   294         elif type(arg) is DateTimeDeltaType:
       
   295             return arg
       
   296         elif type(arg) is DateTimeType:
       
   297             raise TypeError,'cannot convert DateTime to DateTimeDelta'
       
   298         else:
       
   299             try:
       
   300                 value = float(arg)
       
   301             except TypeError:
       
   302                 value = int(arg)
       
   303             assert not kws
       
   304             return DateTimeDeltaFromSeconds(value)
       
   305 
       
   306     elif len(args) > 1:
       
   307         # Assume the arguments are the same as for DateTimeDelta()
       
   308         return apply(DateTimeDelta, args, kws)
       
   309 
       
   310     elif len(kws) > 0:
       
   311         # Keyword arguments; default: 00:00:00:00.00
       
   312         hours = kws.get('hours',0)
       
   313         minutes = kws.get('minutes',0)
       
   314         seconds = kws.get('seconds',0.0)
       
   315         days = kws.get('days',0)
       
   316         return DateTimeDelta(days,hours,minutes,seconds)
       
   317 
       
   318     else:
       
   319         raise TypeError,'cannot convert arguments to DateTimeDelta'
       
   320 
       
   321 def TimeDeltaFrom(*args, **kws):
       
   322 
       
   323     """ TimeDeltaFrom(*args, **kws)
       
   324 
       
   325         Generic TimeDelta instance constructor. Can handle parsing
       
   326         strings, numbers and keywords.
       
   327 
       
   328         XXX Add support for Unicode.
       
   329 
       
   330     """
       
   331     if len(args) > 1:
       
   332         # Assume the arguments are the same as for TimeDelta(): without
       
   333         # days part !
       
   334         return apply(DateTimeDelta, (0,)+args, kws)
       
   335     else:
       
   336         # Otherwise treat the arguments just like for DateTimeDelta
       
   337         # instances.
       
   338         return apply(DateTimeDeltaFrom, args, kws)
       
   339 
       
   340 def DateFromTicks(ticks,
       
   341                   # Locals:
       
   342                   DateTime=DateTime,localtime=_time.localtime):
       
   343 
       
   344     """ DateFromTicks(ticks)
       
   345 
       
   346         Constructs a DateTime instance pointing to the local time date
       
   347         at 00:00:00.00 (midnight) indicated by the given ticks value.
       
   348         The time part is ignored.
       
   349 
       
   350     """
       
   351     return apply(DateTime, localtime(ticks)[:3])
       
   352 
       
   353 def TimestampFromTicks(ticks,
       
   354                        # Locals:
       
   355                        DateTime=DateTime,localtime=_time.localtime):
       
   356 
       
   357     """ TimestampFromTicks(ticks)
       
   358 
       
   359         Constructs a DateTime instance pointing to the local date and
       
   360         time indicated by the given ticks value.
       
   361 
       
   362     """
       
   363     return apply(DateTime, localtime(ticks)[:6])
       
   364 
       
   365 def TimeFromTicks(ticks,
       
   366                   # Locals:
       
   367                   DateTimeDelta=DateTimeDelta,localtime=_time.localtime):
       
   368 
       
   369     """ TimeFromTicks(ticks)
       
   370 
       
   371         Constructs a DateTimeDelta instance pointing to the local time
       
   372         indicated by the given ticks value. The date part is ignored.
       
   373 
       
   374     """
       
   375     return apply(DateTimeDelta, (0,) + localtime(ticks)[3:6])
       
   376 
       
   377 # Aliases
       
   378 utctime = gmtime
       
   379 utc2local = gm2local
       
   380 local2utc = local2gm
       
   381 DateTimeFromTicks = localtime
       
   382 Date = DateTime
       
   383 Time = TimeDelta
       
   384 Timestamp = DateTime
       
   385 DateFrom = DateTimeFrom # XXX should only parse the date part !
       
   386 TimeFrom = TimeDeltaFrom
       
   387 TimestampFrom = DateTimeFrom
       
   388 GregorianDateTime = DateTime
       
   389 GregorianDate = Date
       
   390 JulianDate = JulianDateTime
       
   391 
       
   392 
       
   393 ### For backward compatibility (these are depreciated):
       
   394 
       
   395 def gmticks(datetime):
       
   396 
       
   397     """gmticks(datetime)
       
   398 
       
   399        [DEPRECIATED: use the .gmticks() method]
       
   400     
       
   401        Returns a ticks value based on the values stored in
       
   402        datetime under the assumption that they are given in UTC,
       
   403        rather than local time.
       
   404 
       
   405     """
       
   406     return datetime.gmticks()
       
   407 
       
   408 # Alias
       
   409 utcticks = gmticks
       
   410 
       
   411 def tz_offset(datetime,
       
   412               # Locals:
       
   413               oneSecond=oneSecond):
       
   414 
       
   415     """tz_offset(datetime)
       
   416 
       
   417        [DEPRECIATED: use the .gmtoffset() method]
       
   418     
       
   419        Returns a DateTimeDelta instance representing the UTC
       
   420        offset for datetime assuming that the stored values refer
       
   421        to local time. If you subtract this value from datetime,
       
   422        you'll get UTC time.
       
   423 
       
   424     """
       
   425     return datetime.gmtoffset()
       
   426 
       
   427 ### Constants (only English; see Locale.py for other languages)
       
   428 
       
   429 # Weekdays
       
   430 Monday =        0
       
   431 Tuesday =       1
       
   432 Wednesday =     2
       
   433 Thursday =      3
       
   434 Friday =        4
       
   435 Saturday =      5
       
   436 Sunday =        6
       
   437 # as mapping
       
   438 Weekday = {'Saturday': 5, 6: 'Sunday', 'Sunday': 6, 'Thursday': 3,
       
   439            'Wednesday': 2, 'Friday': 4, 'Tuesday': 1, 'Monday': 0,
       
   440            5: 'Saturday', 4: 'Friday', 3: 'Thursday', 2: 'Wednesday',
       
   441            1: 'Tuesday', 0: 'Monday'}
       
   442 
       
   443 # Months
       
   444 January =       1
       
   445 February =      2
       
   446 March =         3
       
   447 April =         4
       
   448 May =           5
       
   449 June =          6
       
   450 July =          7
       
   451 August =        8 
       
   452 September =     9
       
   453 October =       10
       
   454 November =      11
       
   455 December =      12
       
   456 # as mapping
       
   457 Month = {2: 'February', 3: 'March', None: 0, 'July': 7, 11: 'November',
       
   458     'December': 12, 'June': 6, 'January': 1, 'September': 9, 'August':
       
   459     8, 'March': 3, 'November': 11, 'April': 4, 12: 'December', 'May':
       
   460     5, 10: 'October', 9: 'September', 8: 'August', 7: 'July', 6:
       
   461     'June', 5: 'May', 4: 'April', 'October': 10, 'February': 2, 1:
       
   462     'January', 0: None}
       
   463 
       
   464 # Limits (see also the range checks in mxDateTime.c)
       
   465 MaxDateTime = DateTime(5867440,12,31) 
       
   466 MinDateTime = DateTime(-5851455,1,1)
       
   467 MaxDateTimeDelta = DateTimeDeltaFromSeconds(2147483647 * 86400.0)
       
   468 MinDateTimeDelta = -MaxDateTimeDelta
       
   469 
       
   470 ###
       
   471 
       
   472 class RelativeDateTime:
       
   473 
       
   474     """RelativeDateTime(years=0,months=0,days=0,
       
   475                   hours=0,minutes=0,seconds=0,
       
   476                   year=0,month=0,day=0,
       
   477                   hour=None,minute=None,second=None,
       
   478                   weekday=None,weeks=None)
       
   479 
       
   480        Returns a RelativeDateTime instance for the specified relative
       
   481        time. The constructor handles keywords, so you'll only have to
       
   482        give those parameters which should be changed when you add the
       
   483        relative to an absolute DateTime instance.
       
   484 
       
   485        Adding RelativeDateTime instances is supported with the
       
   486        following rules: deltas will be added together, right side
       
   487        absolute values override left side ones.
       
   488 
       
   489        Adding RelativeDateTime instances to DateTime instances will
       
   490        return DateTime instances with the appropriate calculations
       
   491        applied, e.g. to get a DateTime instance for the first of next
       
   492        month, you'd call now() + RelativeDateTime(months=+1,day=1).
       
   493 
       
   494     """
       
   495     years = 0
       
   496     months = 0
       
   497     days = 0
       
   498     year = None
       
   499     month = 0
       
   500     day = 0
       
   501     hours = 0
       
   502     minutes = 0
       
   503     seconds = 0
       
   504     hour = None
       
   505     minute = None
       
   506     second = None
       
   507     weekday = None
       
   508 
       
   509     # cached hash value
       
   510     _hash = None
       
   511 
       
   512     # For Zope security:
       
   513     __roles__ = None
       
   514     __allow_access_to_unprotected_subobjects__ = 1
       
   515 
       
   516     def __init__(self,
       
   517                  years=0,months=0,days=0,
       
   518                  hours=0,minutes=0,seconds=0,
       
   519                  year=None,month=None,day=None,
       
   520                  hour=None,minute=None,second=None,
       
   521                  weekday=None,weeks=0):
       
   522         
       
   523         self.years = years
       
   524         self.months = months
       
   525         self.days = days + weeks*7
       
   526         self.year = year
       
   527         self.month = month
       
   528         self.day = day
       
   529         self.hours = hours
       
   530         self.minutes = minutes
       
   531         self.seconds = seconds
       
   532         self.hour = hour
       
   533         self.minute = minute
       
   534         self.second = second
       
   535         if weekday is not None:
       
   536             #  Make sure we've got a 2-tuple
       
   537             assert len(weekday) == 2
       
   538             self.weekday = weekday
       
   539 
       
   540     def __add__(self,other,
       
   541                 # Locals:
       
   542                 isinstance=isinstance):
       
   543 
       
   544         if isinstance(other,RelativeDateTime):
       
   545             # RelativeDateTime (self) + RelativeDateTime (other)
       
   546 
       
   547             r = RelativeDateTime()
       
   548             # date deltas
       
   549             r.years = self.years + other.years
       
   550             r.months = self.months + other.months
       
   551             r.days = self.days + other.days
       
   552             # absolute entries of other override those in self, if given
       
   553             r.year = other.year or self.year
       
   554             r.month = other.month or self.month
       
   555             r.day = other.day or self.day
       
   556             r.weekday = other.weekday or self.weekday
       
   557             # time deltas
       
   558             r.hours = self.hours + other.hours
       
   559             r.minutes = self.minutes + other.minutes
       
   560             r.seconds = self.seconds + other.seconds
       
   561             # absolute entries of other override those in self, if given
       
   562             r.hour = other.hour or self.hour
       
   563             r.minute = other.minute or self.minute
       
   564             r.second = other.second or self.second
       
   565             return r
       
   566 
       
   567         else:
       
   568             raise TypeError,"can't add the two types"
       
   569 
       
   570     def __radd__(self,other,
       
   571                  # Locals:
       
   572                  isinstance=isinstance,DateTimeType=DateTimeType,
       
   573                  DateTime=DateTime,DateTimeDelta=DateTimeDelta):
       
   574 
       
   575         if isinstance(other,DateTimeType):
       
   576             # DateTime (other) + RelativeDateTime (self)
       
   577 
       
   578             # date
       
   579             if self.year is None:
       
   580                 year = other.year + self.years
       
   581             else:
       
   582                 year = self.year + self.years
       
   583             if self.month is None:
       
   584                 month = other.month + self.months
       
   585             else:
       
   586                 month = self.month + self.months
       
   587             if self.day is None:
       
   588                 day = other.day
       
   589             else:
       
   590                 day = self.day
       
   591             if day < 0:
       
   592                 # fix negative day values
       
   593                 month = month + 1
       
   594                 day = day + 1
       
   595             day = day + self.days
       
   596             # time
       
   597             if self.hour is None:
       
   598                 hour = other.hour + self.hours
       
   599             else:
       
   600                 hour = self.hour + self.hours
       
   601             if self.minute is None:
       
   602                 minute = other.minute + self.minutes
       
   603             else:
       
   604                 minute = self.minute + self.minutes
       
   605             if self.second is None:
       
   606                 second = other.second + self.seconds
       
   607             else:
       
   608                 second = self.second + self.seconds
       
   609 
       
   610             # Refit into proper ranges:
       
   611             if month < 1 or month > 12:
       
   612                 month = month - 1
       
   613                 yeardelta, monthdelta = divmod(month, 12)
       
   614                 year = year + yeardelta
       
   615                 month = monthdelta + 1
       
   616 
       
   617             # Make sure we have integers
       
   618             year = int(year)
       
   619             month = int(month)
       
   620             day = int(day)
       
   621 
       
   622             if self.weekday is None:
       
   623                 return DateTime(year, month, 1) + \
       
   624                        DateTimeDelta(day-1,hour,minute,second)
       
   625             
       
   626             # Adjust to the correct weekday
       
   627             day_of_week,index = self.weekday
       
   628             d = DateTime(year, month, 1) + \
       
   629                 DateTimeDelta(day-1,hour,minute,second)
       
   630             if index == 0:
       
   631                 # 0 index: next weekday if no match
       
   632                 return d + (day_of_week - d.day_of_week)
       
   633             elif index > 0:
       
   634                 # positive index (1 == first weekday of month)
       
   635                 first = d - (d.day - 1)
       
   636                 diff = day_of_week - first.day_of_week
       
   637                 if diff >= 0:
       
   638                     return first + (diff + (index-1) * 7)
       
   639                 else:
       
   640                     return first + (diff + index * 7)
       
   641             else:
       
   642                 # negative index (-1 == last weekday of month)
       
   643                 last = d + (d.days_in_month - d.day)
       
   644                 diff = day_of_week - last.day_of_week
       
   645                 if diff <= 0:
       
   646                     return last + (diff + (index+1) * 7)
       
   647                 else:
       
   648                     return last + (diff + index * 7)
       
   649             
       
   650         else:
       
   651             raise TypeError,"can't add the two types"
       
   652 
       
   653     def __sub__(self,other):
       
   654 
       
   655         if isinstance(other,RelativeDateTime):
       
   656             # RelativeDateTime (self) - RelativeDateTime (other)
       
   657 
       
   658             r = RelativeDateTime()
       
   659             # date deltas
       
   660             r.years = self.years - other.years
       
   661             r.months = self.months - other.months
       
   662             r.days = self.days - other.days
       
   663             # absolute entries of other override those in self, if given
       
   664             r.year = other.year or self.year
       
   665             r.month = other.month or self.month
       
   666             r.day = other.day or self.day
       
   667             r.weekday = other.weekday or self.weekday
       
   668             # time deltas
       
   669             r.hours = self.hours - other.hours
       
   670             r.minutes = self.minutes - other.minutes
       
   671             r.seconds = self.seconds - other.seconds
       
   672             # absolute entries of other override those in self, if given
       
   673             r.hour = other.hour or self.hour
       
   674             r.minute = other.minute or self.minute
       
   675             r.second = other.second or self.second
       
   676 
       
   677             return r
       
   678 
       
   679         else:
       
   680             raise TypeError,"can't subtract the two types"
       
   681 
       
   682     def __rsub__(self,other,
       
   683                  # Locals:
       
   684                  isinstance=isinstance,DateTimeType=DateTimeType):
       
   685 
       
   686         if isinstance(other,DateTimeType):
       
   687             # DateTime (other) - RelativeDateTime (self)
       
   688             return other + self.__neg__()
       
   689 
       
   690         else:
       
   691             raise TypeError,"can't subtract the two types"
       
   692 
       
   693     def __neg__(self):
       
   694 
       
   695         # - RelativeDateTime(self)
       
   696 
       
   697         r = RelativeDateTime()
       
   698         # negate date deltas
       
   699         r.years = - self.years
       
   700         r.months = - self.months
       
   701         r.days = - self.days
       
   702         # absolute entries don't change
       
   703         r.year = self.year
       
   704         r.month = self.month
       
   705         r.day = self.day
       
   706         r.weekday = self.weekday
       
   707         # negate time deltas
       
   708         r.hours = - self.hours
       
   709         r.minutes = - self.minutes
       
   710         r.seconds = - self.seconds
       
   711         # absolute entries don't change
       
   712         r.hour = self.hour
       
   713         r.minute = self.minute
       
   714         r.second = self.second
       
   715 
       
   716         return r
       
   717 
       
   718     def __nonzero__(self):
       
   719 
       
   720         # RelativeDateTime instances are considered false in case
       
   721         # they do not define any alterations
       
   722         if (self.year is None and
       
   723             self.years == 0 and
       
   724             self.month is None and
       
   725             self.months == 0 and
       
   726             self.day is None and
       
   727             self.weekday is None and
       
   728             self.days == 0 and
       
   729             self.hour is None and
       
   730             self.hours == 0 and
       
   731             self.minute is None and
       
   732             self.minutes == 0 and
       
   733             self.second is None and
       
   734             self.seconds == 0):
       
   735             return 0
       
   736         else:
       
   737             return 1
       
   738 
       
   739     def __mul__(self,other):
       
   740 
       
   741         # RelativeDateTime (self) * Number (other)
       
   742         factor = float(other)
       
   743 
       
   744         r = RelativeDateTime()
       
   745         # date deltas
       
   746         r.years = factor * self.years
       
   747         r.months = factor * self.months
       
   748         r.days = factor * self.days
       
   749         # time deltas
       
   750         r.hours = factor * self.hours
       
   751         r.minutes = factor * self.minutes
       
   752         r.seconds = factor * self.seconds
       
   753         return r
       
   754 
       
   755     __rmul__ = __mul__
       
   756 
       
   757     def __div__(self,other):
       
   758 
       
   759         # RelativeDateTime (self) / Number (other)
       
   760         return self.__mul__(1/float(other))
       
   761 
       
   762     def __eq__(self, other):
       
   763 
       
   764         if isinstance(self, RelativeDateTime) and \
       
   765            isinstance(other, RelativeDateTime):
       
   766             # RelativeDateTime (self) == RelativeDateTime (other)
       
   767             if (self.years == other.years and
       
   768                 self.months == other.months and
       
   769                 self.days == other.days and
       
   770                 self.year == other.year and
       
   771                 self.day == other.day and
       
   772                 self.hours == other.hours and
       
   773                 self.minutes == other.minutes and
       
   774                 self.seconds == other.seconds and
       
   775                 self.hour == other.hour and
       
   776                 self.minute == other.minute and
       
   777                 self.second == other.second and
       
   778                 self.weekday == other.weekday):
       
   779                 return 1
       
   780             else:
       
   781                 return 0
       
   782         else:
       
   783             raise TypeError,"can't compare the two types"
       
   784 
       
   785     def __hash__(self):
       
   786 
       
   787         if self._hash is not None:
       
   788             return self._hash
       
   789         x = 1234
       
   790         for value in (self.years, self.months, self.days,
       
   791                       self.year, self.day,
       
   792                       self.hours, self.minutes, self.seconds,
       
   793                       self.hour, self.minute, self.second,
       
   794                       self.weekday):
       
   795             if value is None:
       
   796                 x = 135051820 ^ x
       
   797             else:
       
   798                 x = hash(value) ^ x
       
   799         self._hash = x
       
   800         return x
       
   801 
       
   802     def __str__(self,
       
   803 
       
   804                 join=_string.join):
       
   805 
       
   806         l = []
       
   807         append = l.append
       
   808 
       
   809         # Format date part
       
   810         if self.year is not None:
       
   811             append('%04i-' % self.year)
       
   812         elif self.years:
       
   813             append('(%0+5i)-' % self.years)
       
   814         else:
       
   815             append('YYYY-')
       
   816         if self.month is not None:
       
   817             append('%02i-' % self.month)
       
   818         elif self.months:
       
   819             append('(%0+3i)-' % self.months)
       
   820         else:
       
   821             append('MM-')
       
   822         if self.day is not None:
       
   823             append('%02i' % self.day)
       
   824         elif self.days:
       
   825             append('(%0+3i)' % self.days)
       
   826         else:
       
   827             append('DD')
       
   828         if self.weekday:
       
   829             append(' %s:%i' % (Weekday[self.weekday[0]][:3],self.weekday[1]))
       
   830         append(' ')
       
   831         
       
   832         # Normalize relative time values to avoid fractions
       
   833         hours = self.hours
       
   834         minutes = self.minutes
       
   835         seconds = self.seconds
       
   836         hours_fraction = hours - int(hours)
       
   837         minutes = minutes + hours_fraction * 60.0
       
   838         minutes_fraction = minutes - int(minutes)
       
   839         seconds = seconds + minutes_fraction * 6.0
       
   840         seconds_fraction = seconds - int(seconds)
       
   841 
       
   842         if 0:
       
   843             # Normalize to standard time ranges
       
   844             if seconds > 60.0:
       
   845                 extra_minutes, seconds = divmod(seconds, 60.0)
       
   846                 minutes = minutes + extra_minutes
       
   847             elif seconds < -60.0:
       
   848                 extra_minutes, seconds = divmod(seconds, -60.0)
       
   849                 minutes = minutes - extra_minutes
       
   850             if minutes >= 60.0:
       
   851                 extra_hours, minutes = divmod(minutes, 60.0)
       
   852                 hours = hours + extra_hours
       
   853             elif minutes <= -60.0:
       
   854                 extra_hours, minutes = divmod(minutes, -60.0)
       
   855                 hours = hours - extra_hours
       
   856 
       
   857         # Format time part
       
   858         if self.hour is not None:
       
   859             append('%02i:' % self.hour)
       
   860         elif hours:
       
   861             append('(%0+3i):' % hours)
       
   862         else:
       
   863             append('HH:')
       
   864         if self.minute is not None:
       
   865             append('%02i:' % self.minute)
       
   866         elif minutes:
       
   867             append('(%0+3i):' % minutes)
       
   868         else:
       
   869             append('MM:')
       
   870         if self.second is not None:
       
   871             append('%02i' % self.second)
       
   872         elif seconds:
       
   873             append('(%0+3i)' % seconds)
       
   874         else:
       
   875             append('SS')
       
   876             
       
   877         return join(l,'')
       
   878 
       
   879     def __repr__(self):
       
   880 
       
   881         return "<%s instance for '%s' at 0x%x>" % ( 
       
   882             self.__class__.__name__, 
       
   883             self.__str__(), 
       
   884             id(self))
       
   885 
       
   886 # Alias
       
   887 RelativeDate = RelativeDateTime
       
   888 
       
   889 def RelativeDateTimeFrom(*args, **kws):
       
   890 
       
   891     """ RelativeDateTimeFrom(*args, **kws)
       
   892 
       
   893         Generic RelativeDateTime instance constructor. Can handle
       
   894         parsing strings and keywords.
       
   895 
       
   896     """
       
   897     if len(args) == 1:
       
   898         # Single argument
       
   899         arg = args[0]
       
   900         if _isstring(arg):
       
   901             import Parser
       
   902             return apply(Parser.RelativeDateTimeFromString, args, kws)
       
   903         elif isinstance(arg, RelativeDateTime):
       
   904             return arg
       
   905         else:
       
   906             raise TypeError,\
       
   907                   'cannot convert argument to RelativeDateTime'
       
   908 
       
   909     else:
       
   910         return apply(RelativeDateTime,args,kws)
       
   911 
       
   912 def RelativeDateTimeDiff(date1,date2,
       
   913 
       
   914                          floor=_math.floor,int=int,divmod=divmod,
       
   915                          RelativeDateTime=RelativeDateTime):
       
   916 
       
   917     """ RelativeDateTimeDiff(date1,date2)
       
   918 
       
   919         Returns a RelativeDateTime instance representing the difference
       
   920         between date1 and date2 in relative terms.
       
   921 
       
   922         The following should hold: 
       
   923         
       
   924         date2 + RelativeDateDiff(date1,date2) == date1 
       
   925 
       
   926         for all dates date1 and date2.
       
   927 
       
   928         Note that due to the algorithm used by this function, not the
       
   929         whole range of DateTime instances is supported; there could
       
   930         also be a loss of precision.
       
   931 
       
   932         XXX There are still some problems left (thanks to Carel
       
   933         Fellinger for pointing these out):
       
   934 
       
   935         29 1 1901 ->  1 3 1901 = 1 month
       
   936         29 1 1901 ->  1 3 1900 = -10 month and -28 days, but
       
   937         29 1 1901 -> 28 2 1900 = -11 month and -1 day
       
   938 
       
   939         and even worse:
       
   940 
       
   941         >>> print RelativeDateDiff(Date(1900,3,1),Date(1901,2,1))
       
   942         YYYY-(-11)-DD HH:MM:SS
       
   943 
       
   944         with:
       
   945 
       
   946         >>> print Date(1901,1,29) + RelativeDateTime(months=-11)
       
   947         1900-03-01 00:00:00.00
       
   948         >>> print Date(1901,2,1) + RelativeDateTime(months=-11)
       
   949         1900-03-01 00:00:00.00
       
   950 
       
   951     """
       
   952     diff = date1 - date2
       
   953     if diff.days == 0:
       
   954         return RelativeDateTime()
       
   955     date1months = date1.year * 12 + (date1.month - 1)
       
   956     date2months = date2.year * 12 + (date2.month - 1)
       
   957     #print 'months',date1months,date2months
       
   958 
       
   959     # Calculate the months difference
       
   960     diffmonths = date1months - date2months
       
   961     #print 'diffmonths',diffmonths
       
   962     if diff.days > 0:
       
   963         years,months = divmod(diffmonths,12)
       
   964     else:
       
   965         years,months = divmod(diffmonths,-12)
       
   966         years = -years
       
   967     date3 = date2 + RelativeDateTime(years=years,months=months)
       
   968     diff3 = date1 - date3
       
   969     days = date1.absdays - date3.absdays
       
   970     #print 'date3',date3,'diff3',diff3,'days',days
       
   971 
       
   972     # Correction to ensure that all relative parts have the same sign
       
   973     while days * diff.days < 0:
       
   974         if diff.days > 0:
       
   975             diffmonths = diffmonths - 1
       
   976             years,months = divmod(diffmonths,12)
       
   977         else:
       
   978             diffmonths = diffmonths + 1
       
   979             years,months = divmod(diffmonths,-12)
       
   980             years = -years
       
   981         #print 'diffmonths',diffmonths
       
   982         date3 = date2 + RelativeDateTime(years=years,months=months)
       
   983         diff3 = date1 - date3
       
   984         days = date1.absdays - date3.absdays
       
   985         #print 'date3',date3,'diff3',diff3,'days',days
       
   986 
       
   987     # Drop the fraction part of days
       
   988     if days > 0:
       
   989         days = int(floor(days))
       
   990     else:
       
   991         days = int(-floor(-days))
       
   992 
       
   993     return RelativeDateTime(years=years,
       
   994                             months=months,
       
   995                             days=days,
       
   996                             hours=diff3.hour,
       
   997                             minutes=diff3.minute,
       
   998                             seconds=diff3.second)
       
   999 
       
  1000 # Aliases
       
  1001 RelativeDateDiff = RelativeDateTimeDiff
       
  1002 Age = RelativeDateTimeDiff
       
  1003 
       
  1004 ###
       
  1005 
       
  1006 _current_year = now().year
       
  1007 _current_century, _current_year_in_century = divmod(_current_year, 100)
       
  1008 _current_century = _current_century * 100
       
  1009 
       
  1010 def add_century(year,
       
  1011 
       
  1012                 current_year=_current_year,
       
  1013                 current_century=_current_century):
       
  1014     
       
  1015     """ Sliding window approach to the Y2K problem: adds a suitable
       
  1016         century to the given year and returns it as integer.
       
  1017 
       
  1018         The window used depends on the current year (at import time).
       
  1019         If adding the current century to the given year gives a year
       
  1020         within the range current_year-70...current_year+30 [both
       
  1021         inclusive], then the current century is added. Otherwise the
       
  1022         century (current + 1 or - 1) producing the least difference is
       
  1023         chosen.
       
  1024 
       
  1025     """
       
  1026     if year > 99:
       
  1027         # Take it as-is
       
  1028         return year
       
  1029     year = year + current_century
       
  1030     diff = year - current_year
       
  1031     if diff >= -70 and diff <= 30:
       
  1032         return year
       
  1033     elif diff < -70:
       
  1034         return year + 100
       
  1035     else:
       
  1036         return year - 100
       
  1037 
       
  1038 # Reference formulas for JDN taken from the Calendar FAQ:
       
  1039 
       
  1040 def gregorian_jdn(year,month,day):
       
  1041 
       
  1042     # XXX These require proper integer division.
       
  1043     a = (14-month)/12
       
  1044     y = year+4800-a
       
  1045     m = month + 12*a - 3
       
  1046     return day + (306*m+5)/10 + y*365 + y/4 - y/100 + y/400 - 32045
       
  1047 
       
  1048 def julian_jdn(year,month,day):
       
  1049 
       
  1050     # XXX These require proper integer division.
       
  1051     a = (14-month)/12
       
  1052     y = year+4800-a
       
  1053     m = month + 12*a - 3
       
  1054     return day + (306*m+5)/10 + y*365 + y/4 - 32083