1##############################################################################
2#
3# Copyright (c) 2002 Zope Foundation and Contributors.
4#
5# This software is subject to the provisions of the Zope Public License,
6# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
7# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10# FOR A PARTICULAR PURPOSE
11#
12##############################################################################
13
14import copy_reg
15import math
16import re
17from time import altzone
18from time import daylight
19from time import gmtime
20from time import localtime
21from time import time
22from time import timezone
23from time import tzname
24from datetime import datetime
25
26from pytz_support import PytzCache
27from zope.interface import implements
28
29from interfaces import IDateTime
30from interfaces import DateTimeError
31from interfaces import SyntaxError
32from interfaces import DateError
33from interfaces import TimeError
34
35default_datefmt = None
36
37
38def getDefaultDateFormat():
39    global default_datefmt
40    if default_datefmt is None:
41        try:
42            from App.config import getConfiguration
43            default_datefmt = getConfiguration().datetime_format
44            return default_datefmt
45        except Exception:
46            return 'us'
47    else:
48        return default_datefmt
49
50# To control rounding errors, we round system time to the nearest
51# microsecond.  Then delicate calculations can rely on that the
52# maximum precision that needs to be preserved is known.
53_system_time = time
54
55
56def time():
57    return round(_system_time(), 6)
58
59# Determine machine epoch
60tm= ((0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334),
61     (0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335))
62yr, mo, dy, hr, mn, sc = gmtime(0)[:6]
63i = int(yr - 1)
64to_year = int(i * 365 + i / 4 - i / 100 + i / 400 - 693960.0)
65to_month = tm[yr % 4 == 0 and (yr % 100 != 0 or yr % 400 == 0)][mo]
66EPOCH = ((to_year + to_month + dy +
67    (hr / 24.0 + mn / 1440.0 + sc / 86400.0)) * 86400)
68jd1901 = 2415385L
69
70_TZINFO = PytzCache()
71
72INT_PATTERN = re.compile(r'([0-9]+)')
73FLT_PATTERN = re.compile(r':([0-9]+\.[0-9]+)')
74NAME_PATTERN = re.compile(r'([a-zA-Z]+)', re.I)
75SPACE_CHARS =' \t\n'
76DELIMITERS = '-/.:,+'
77
78_MONTH_LEN = ((0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
79              (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31))
80_MONTHS = ('', 'January', 'February', 'March', 'April', 'May', 'June',
81           'July', 'August', 'September', 'October', 'November', 'December')
82_MONTHS_A = ('', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
83             'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')
84_MONTHS_P = ('', 'Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'June',
85             'July', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.')
86_MONTHMAP = {'january': 1, 'jan': 1,
87             'february': 2, 'feb': 2,
88             'march': 3, 'mar': 3,
89             'april': 4, 'apr': 4,
90             'may': 5,
91             'june': 6, 'jun': 6,
92             'july': 7, 'jul': 7,
93             'august': 8, 'aug': 8,
94             'september': 9, 'sep': 9, 'sept': 9,
95             'october': 10, 'oct': 10,
96             'november': 11, 'nov': 11,
97             'december': 12, 'dec': 12}
98_DAYS = ('Sunday', 'Monday', 'Tuesday', 'Wednesday',
99         'Thursday', 'Friday', 'Saturday')
100_DAYS_A = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat')
101_DAYS_P = ('Sun.', 'Mon.', 'Tue.', 'Wed.', 'Thu.', 'Fri.', 'Sat.')
102_DAYMAP = {'sunday': 1, 'sun': 1,
103           'monday': 2, 'mon': 2,
104           'tuesday': 3, 'tues': 3, 'tue': 3,
105           'wednesday': 4, 'wed': 4,
106           'thursday': 5, 'thurs': 5, 'thur': 5, 'thu': 5,
107           'friday': 6, 'fri': 6,
108           'saturday': 7, 'sat': 7}
109
110numericTimeZoneMatch = re.compile(r'[+-][0-9][0-9][0-9][0-9]').match
111iso8601Match = re.compile(r'''
112  (?P<year>\d\d\d\d)                # four digits year
113  (?:-?                             # one optional dash
114   (?:                              # followed by:
115    (?P<year_day>\d\d\d             #  three digits year day
116     (?!\d))                        #  when there is no fourth digit
117   |                                # or:
118    W                               #  one W
119    (?P<week>\d\d)                  #  two digits week
120    (?:-?                           #  one optional dash
121     (?P<week_day>\d)               #  one digit week day
122    )?                              #  week day is optional
123   |                                # or:
124    (?P<month>\d\d)?                #  two digits month
125    (?:-?                           #  one optional dash
126     (?P<day>\d\d)?                 #  two digits day
127    )?                              #  after day is optional
128   )                                #
129  )?                                # after year is optional
130  (?:[T ]                           # one T or one whitespace
131   (?P<hour>\d\d)                   # two digits hour
132   (?::?                            # one optional colon
133    (?P<minute>\d\d)?               # two digits minute
134    (?::?                           # one optional colon
135     (?P<second>\d\d)?              # two digits second
136     (?:[.,]                        # one dot or one comma
137      (?P<fraction>\d+)             # n digits fraction
138     )?                             # after second is optional
139    )?                              # after minute is optional
140   )?                               # after hour is optional
141   (?:                              # timezone:
142    (?P<Z>Z)                        #  one Z
143   |                                # or:
144    (?P<signal>[-+])                #  one plus or one minus as signal
145    (?P<hour_off>\d                 #  one digit for hour offset...
146     (?:\d(?!\d$)                   #  ...or two, if not the last two digits
147    )?)                             #  second hour offset digit is optional
148    (?::?                           #  one optional colon
149     (?P<min_off>\d\d)              #  two digits minute offset
150    )?                              #  after hour offset is optional
151   )?                               # timezone is optional
152  )?                                # time is optional
153  (?P<garbage>.*)                   # store the extra garbage
154''', re.VERBOSE).match
155
156
157def _findLocalTimeZoneName(isDST):
158    if not daylight:
159        # Daylight savings does not occur in this time zone.
160        isDST = 0
161    try:
162        # Get the name of the current time zone depending
163        # on DST.
164        _localzone = PytzCache._zmap[tzname[isDST].lower()]
165    except:
166        try:
167            # Generate a GMT-offset zone name.
168            if isDST:
169                localzone = altzone
170            else:
171                localzone = timezone
172            offset = (-localzone / (60 * 60.0))
173            majorOffset = int(offset)
174            if majorOffset != 0:
175                minorOffset = abs(int((offset % majorOffset) * 60.0))
176            else:
177                minorOffset = 0
178            m = majorOffset >= 0 and '+' or ''
179            lz = '%s%0.02d%0.02d' % (m, majorOffset, minorOffset)
180            _localzone = PytzCache._zmap[('GMT%s' % lz).lower()]
181        except:
182            _localzone = ''
183    return _localzone
184
185_localzone0 = _findLocalTimeZoneName(0)
186_localzone1 = _findLocalTimeZoneName(1)
187_multipleZones = (_localzone0 != _localzone1)
188
189# Some utility functions for calculating dates:
190
191def _calcSD(t):
192    # Returns timezone-independent days since epoch and the fractional
193    # part of the days.
194    dd = t + EPOCH - 86400.0
195    d = dd / 86400.0
196    s = d - math.floor(d)
197    return s, d
198
199
200def _calcDependentSecond(tz, t):
201    # Calculates the timezone-dependent second (integer part only)
202    # from the timezone-independent second.
203    fset = _tzoffset(tz, t)
204    return fset + long(math.floor(t)) + long(EPOCH) - 86400L
205
206
207def _calcDependentSecond2(yr, mo, dy, hr, mn, sc):
208    # Calculates the timezone-dependent second (integer part only)
209    # from the date given.
210    ss = int(hr) * 3600 + int(mn) * 60 + int(sc)
211    x = long(_julianday(yr, mo, dy) - jd1901) * 86400 + ss
212    return x
213
214
215def _calcIndependentSecondEtc(tz, x, ms):
216    # Derive the timezone-independent second from the timezone
217    # dependent second.
218    fsetAtEpoch = _tzoffset(tz, 0.0)
219    nearTime = x - fsetAtEpoch - long(EPOCH) + 86400L + ms
220    # nearTime is now within an hour of being correct.
221    # Recalculate t according to DST.
222    fset = long(_tzoffset(tz, nearTime))
223    d = (x - fset) / 86400.0 + (ms / 86400.0)
224    t = x - fset - long(EPOCH) + 86400L + ms
225    micros = (x + 86400 - fset) * 1000000 + \
226             long(round(ms * 1000000.0)) - long(EPOCH * 1000000.0)
227    s = d - math.floor(d)
228    return (s, d, t, micros)
229
230
231def _calcHMS(x, ms):
232    # hours, minutes, seconds from integer and float.
233    hr = x / 3600
234    x = x - hr * 3600
235    mn = x / 60
236    sc = x - mn * 60 + ms
237    return (hr, mn, sc)
238
239
240def _calcYMDHMS(x, ms):
241    # x is a timezone-dependent integer of seconds.
242    # Produces yr,mo,dy,hr,mn,sc.
243    yr, mo, dy = _calendarday(x / 86400 + jd1901)
244    x = int(x - (x / 86400) * 86400)
245    hr = x / 3600
246    x = x - hr * 3600
247    mn = x / 60
248    sc = x - mn * 60 + ms
249    return (yr, mo, dy, hr, mn, sc)
250
251
252def _julianday(yr, mo, dy):
253    y, m, d = long(yr), long(mo), long(dy)
254    if m > 12L:
255        y = y + m / 12L
256        m = m % 12L
257    elif m < 1L:
258        m = -m
259        y = y - m / 12L - 1L
260        m = 12L - m % 12L
261    if y > 0L:
262        yr_correct = 0L
263    else:
264        yr_correct = 3L
265    if m < 3L:
266        y, m = y - 1L, m + 12L
267    if y * 10000L + m * 100L + d > 15821014L:
268        b = 2L - y / 100L + y / 400L
269    else:
270        b = 0L
271    return ((1461L * y - yr_correct) / 4L +
272        306001L * (m + 1L) / 10000L + d + 1720994L + b)
273
274
275def _calendarday(j):
276    j = long(j)
277    if (j < 2299160L):
278        b = j + 1525L
279    else:
280        a = (4L * j - 7468861L) / 146097L
281        b = j + 1526L + a - a / 4L
282    c = (20L * b - 2442L) / 7305L
283    d = 1461L * c / 4L
284    e = 10000L * (b - d) / 306001L
285    dy = int(b - d - 306001L * e / 10000L)
286    mo = (e < 14L) and int(e - 1L) or int(e - 13L)
287    yr = (mo > 2) and (c - 4716L) or (c - 4715L)
288    return (int(yr), int(mo), int(dy))
289
290
291def _tzoffset(tz, t):
292    """Returns the offset in seconds to GMT from a specific timezone (tz) at
293    a specific time (t).  NB! The _tzoffset result is the same same sign as
294    the time zone, i.e. GMT+2 has a 7200 second offset. This is the opposite
295    sign of time.timezone which (confusingly) is -7200 for GMT+2."""
296    try:
297        return _TZINFO[tz].info(t)[0]
298    except Exception:
299        if numericTimeZoneMatch(tz) is not None:
300            return int(tz[0:3]) * 3600 + int(tz[0] + tz[3:5]) * 60
301        else:
302            return 0 # ??
303
304
305def _correctYear(year):
306    # Y2K patch.
307    if year >= 0 and year < 100:
308        # 00-69 means 2000-2069, 70-99 means 1970-1999.
309        if year < 70:
310            year = 2000 + year
311        else:
312            year = 1900 + year
313    return year
314
315
316def safegmtime(t):
317    '''gmtime with a safety zone.'''
318    try:
319        t_int = int(t)
320        if isinstance(t_int, long):
321            raise OverflowError # Python 2.3 fix: int can return a long!
322        return gmtime(t_int)
323    except (ValueError, OverflowError):
324        raise TimeError('The time %f is beyond the range of this Python '
325            'implementation.' % float(t))
326
327
328def safelocaltime(t):
329    '''localtime with a safety zone.'''
330    try:
331        t_int = int(t)
332        if isinstance(t_int, long):
333            raise OverflowError # Python 2.3 fix: int can return a long!
334        return localtime(t_int)
335    except (ValueError, OverflowError):
336        raise TimeError('The time %f is beyond the range of this Python '
337            'implementation.' % float(t))
338
339
340def _tzoffset2rfc822zone(seconds):
341    """Takes an offset, such as from _tzoffset(), and returns an rfc822
342       compliant zone specification. Please note that the result of
343       _tzoffset() is the negative of what time.localzone and time.altzone is.
344    """
345    return "%+03d%02d" % divmod((seconds / 60), 60)
346
347
348def _tzoffset2iso8601zone(seconds):
349    """Takes an offset, such as from _tzoffset(), and returns an ISO 8601
350       compliant zone specification. Please note that the result of
351       _tzoffset() is the negative of what time.localzone and time.altzone is.
352    """
353    return "%+03d:%02d" % divmod((seconds / 60), 60)
354
355
356def Timezones():
357    """Return the list of recognized timezone names"""
358    return sorted(list(PytzCache._zmap.values()))
359
360
361class strftimeFormatter(object):
362
363    def __init__(self, dt, format):
364        self.dt = dt
365        self.format = format
366
367    def __call__(self):
368        return self.dt.strftime(self.format)
369
370
371class DateTime(object):
372    """DateTime objects represent instants in time and provide
373       interfaces for controlling its representation without
374       affecting the absolute value of the object.
375
376       DateTime objects may be created from a wide variety of string
377       or numeric data, or may be computed from other DateTime objects.
378       DateTimes support the ability to convert their representations
379       to many major timezones, as well as the ablility to create a
380       DateTime object in the context of a given timezone.
381
382       DateTime objects provide partial numerical behavior:
383
384          - Two date-time objects can be subtracted to obtain a time,
385            in days between the two.
386
387          - A date-time object and a positive or negative number may
388            be added to obtain a new date-time object that is the given
389            number of days later than the input date-time object.
390
391          - A positive or negative number and a date-time object may
392            be added to obtain a new date-time object that is the given
393            number of days later than the input date-time object.
394
395          - A positive or negative number may be subtracted from a
396            date-time object to obtain a new date-time object that is
397            the given number of days earlier than the input date-time
398            object.
399
400        DateTime objects may be converted to integer, long, or float
401        numbers of days since January 1, 1901, using the standard int,
402        long, and float functions (Compatibility Note: int, long and
403        float return the number of days since 1901 in GMT rather than
404        local machine timezone). DateTime objects also provide access
405        to their value in a float format usable with the python time
406        module, provided that the value of the object falls in the
407        range of the epoch-based time module, and as a datetime.datetime
408        object.
409
410        A DateTime object should be considered immutable; all conversion
411        and numeric operations return a new DateTime object rather than
412        modify the current object."""
413
414    implements(IDateTime)
415
416    # For security machinery:
417    __roles__ = None
418    __allow_access_to_unprotected_subobjects__ = 1
419
420    # Limit the amount of instance attributes
421    __slots__ = (
422        '_timezone_naive',
423        '_tz',
424        '_dayoffset',
425        '_year',
426        '_month',
427        '_day',
428        '_hour',
429        '_minute',
430        '_second',
431        '_nearsec',
432        '_d',
433        '_micros',
434        'time',
435    )
436
437    def __init__(self, *args, **kw):
438        """Return a new date-time object"""
439        try:
440            return self._parse_args(*args, **kw)
441        except (DateError, TimeError, DateTimeError):
442            raise
443        except Exception:
444            raise SyntaxError('Unable to parse %s, %s' % (args, kw))
445
446    def __getstate__(self):
447        # We store a float of _micros, instead of the _micros long, as we most
448        # often don't have any sub-second resolution and can save those bytes
449        return (self._micros / 1000000.0,
450            getattr(self, '_timezone_naive', False),
451            self._tz)
452
453    def __setstate__(self, value):
454        if isinstance(value, tuple):
455            self._parse_args(value[0], value[2])
456            self._micros = long(value[0] * 1000000)
457            self._timezone_naive = value[1]
458        else:
459            for k, v in value.items():
460                if k in self.__slots__:
461                    setattr(self, k, v)
462            # BBB: support for very old DateTime pickles
463            if '_micros' not in value:
464                self._micros = long(value['_t'] * 1000000)
465            if '_timezone_naive' not in value:
466                self._timezone_naive = False
467
468    def _parse_args(self, *args, **kw):
469        """Return a new date-time object.
470
471        A DateTime object always maintains its value as an absolute
472        UTC time, and is represented in the context of some timezone
473        based on the arguments used to create the object. A DateTime
474        object's methods return values based on the timezone context.
475
476        Note that in all cases the local machine timezone is used for
477        representation if no timezone is specified.
478
479        DateTimes may be created with from zero to seven arguments.
480
481          - If the function is called with no arguments or with None,
482            then the current date/time is returned, represented in the
483            timezone of the local machine.
484
485          - If the function is invoked with a single string argument
486            which is a recognized timezone name, an object representing
487            the current time is returned, represented in the specified
488            timezone.
489
490          - If the function is invoked with a single string argument
491            representing a valid date/time, an object representing
492            that date/time will be returned.
493
494            As a general rule, any date-time representation that is
495            recognized and unambigous to a resident of North America
496            is acceptable. The reason for this qualification is that
497            in North America, a date like: 2/1/1994 is interpreted
498            as February 1, 1994, while in some parts of the world,
499            it is interpreted as January 2, 1994.
500
501            A date/time string consists of two components, a date
502            component and an optional time component, separated by one
503            or more spaces. If the time component is omited, 12:00am is
504            assumed. Any recognized timezone name specified as the final
505            element of the date/time string will be used for computing
506            the date/time value. If you create a DateTime with the
507            string 'Mar 9, 1997 1:45pm US/Pacific', the value will
508            essentially be the same as if you had captured time.time()
509            at the specified date and time on a machine in that timezone:
510
511            <PRE>
512            e=DateTime('US/Eastern')
513            # returns current date/time, represented in US/Eastern.
514
515            x=DateTime('1997/3/9 1:45pm')
516            # returns specified time, represented in local machine zone.
517
518            y=DateTime('Mar 9, 1997 13:45:00')
519            # y is equal to x
520            </PRE>
521
522            The date component consists of year, month, and day
523            values. The year value must be a one-, two-, or
524            four-digit integer. If a one- or two-digit year is
525            used, the year is assumed to be in the twentieth
526            century. The month may be an integer, from 1 to 12, a
527            month name, or a month abreviation, where a period may
528            optionally follow the abreviation. The day must be an
529            integer from 1 to the number of days in the month. The
530            year, month, and day values may be separated by
531            periods, hyphens, forward, shashes, or spaces. Extra
532            spaces are permitted around the delimiters. Year,
533            month, and day values may be given in any order as long
534            as it is possible to distinguish the components. If all
535            three components are numbers that are less than 13,
536            then a a month-day-year ordering is assumed.
537
538            The time component consists of hour, minute, and second
539            values separated by colons.  The hour value must be an
540            integer between 0 and 23 inclusively. The minute value
541            must be an integer between 0 and 59 inclusively. The
542            second value may be an integer value between 0 and
543            59.999 inclusively. The second value or both the minute
544            and second values may be ommitted. The time may be
545            followed by am or pm in upper or lower case, in which
546            case a 12-hour clock is assumed.
547
548            New in Zope 2.4:
549            The DateTime constructor automatically detects and handles
550            ISO8601 compliant dates (YYYY-MM-DDThh:ss:mmTZD).
551
552            New in Zope 2.9.6:
553            The existing ISO8601 parser was extended to support almost
554            the whole ISO8601 specification. New formats includes:
555
556            <PRE>
557            y=DateTime('1993-045')
558            # returns the 45th day from 1993, which is 14th February
559
560            w=DateTime('1993-W06-7')
561            # returns the 7th day from the 6th week from 1993, which
562            # is also 14th February
563            </PRE>
564
565            See http://en.wikipedia.org/wiki/ISO_8601 for full specs.
566
567            Note that the Zope DateTime parser assumes timezone naive ISO
568            strings to be in UTC rather than local time as specified.
569
570          - If the DateTime function is invoked with a single Numeric
571            argument, the number is assumed to be a floating point value
572            such as that returned by time.time().
573
574            A DateTime object is returned that represents the GMT value
575            of the time.time() float represented in the local machine's
576            timezone.
577
578          - If the DateTime function is invoked with a single argument
579            that is a DateTime instane, a copy of the passed object will
580            be created.
581
582          - New in 2.11:
583            The DateTime function may now be invoked with a single argument
584            that is a datetime.datetime instance. DateTimes may be converted
585            back to datetime.datetime objects with asdatetime().
586            DateTime instances may be converted to a timezone naive
587            datetime.datetime in UTC with utcdatetime().
588
589          - If the function is invoked with two numeric arguments, then
590            the first is taken to be an integer year and the second
591            argument is taken to be an offset in days from the beginning
592            of the year, in the context of the local machine timezone.
593
594            The date-time value returned is the given offset number of
595            days from the beginning of the given year, represented in
596            the timezone of the local machine. The offset may be positive
597            or negative.
598
599            Two-digit years are assumed to be in the twentieth
600            century.
601
602          - If the function is invoked with two arguments, the first
603            a float representing a number of seconds past the epoch
604            in gmt (such as those returned by time.time()) and the
605            second a string naming a recognized timezone, a DateTime
606            with a value of that gmt time will be returned, represented
607            in the given timezone.
608
609            <PRE>
610            import time
611            t=time.time()
612
613            now_east=DateTime(t,'US/Eastern')
614            # Time t represented as US/Eastern
615
616            now_west=DateTime(t,'US/Pacific')
617            # Time t represented as US/Pacific
618
619            # now_east == now_west
620            # only their representations are different
621            </PRE>
622
623          - If the function is invoked with three or more numeric
624            arguments, then the first is taken to be an integer
625            year, the second is taken to be an integer month, and
626            the third is taken to be an integer day. If the
627            combination of values is not valid, then a
628            DateError is raised. Two-digit years are assumed
629            to be in the twentieth century. The fourth, fifth, and
630            sixth arguments specify a time in hours, minutes, and
631            seconds; hours and minutes should be positive integers
632            and seconds is a positive floating point value, all of
633            these default to zero if not given. An optional string may
634            be given as the final argument to indicate timezone (the
635            effect of this is as if you had taken the value of time.time()
636            at that time on a machine in the specified timezone).
637
638            New in Zope 2.7:
639            A new keyword parameter "datefmt" can be passed to the
640            constructor. If set to "international", the constructor
641            is forced to treat ambigious dates as "days before month
642            before year". This useful if you need to parse non-US
643            dates in a reliable way
644
645        In any case that a floating point number of seconds is given
646        or derived, it's rounded to the nearest millisecond.
647
648        If a string argument passed to the DateTime constructor cannot be
649        parsed, it will raise DateTime.SyntaxError. Invalid date components
650        will raise a DateError, while invalid time or timezone components
651        will raise a DateTimeError.
652
653        The module function Timezones() will return a list of the (common)
654        timezones recognized by the DateTime module. Recognition of
655        timezone names is case-insensitive.
656        """
657
658        datefmt = kw.get('datefmt', getDefaultDateFormat())
659        d=t=s=None
660        ac=len(args)
661        microsecs = None
662
663        if ac==10:
664            # Internal format called only by DateTime
665            yr, mo, dy, hr, mn, sc, tz, t, d, s = args
666        elif ac == 11:
667            # Internal format that includes milliseconds (from the epoch)
668            yr, mo, dy, hr, mn, sc, tz, t, d, s, millisecs = args
669            microsecs = millisecs * 1000
670
671        elif ac == 12:
672            # Internal format that includes microseconds (from the epoch) and a
673            # flag indicating whether this was constructed in a timezone naive
674            # manner
675            yr, mo, dy, hr, mn, sc, tz, t, d, s, microsecs, tznaive = args
676            if tznaive is not None: # preserve this information
677                self._timezone_naive = tznaive
678
679        elif not args or (ac and args[0]==None):
680            # Current time, to be displayed in local timezone
681            t = time()
682            lt = safelocaltime(t)
683            tz = self.localZone(lt)
684            ms = (t - math.floor(t))
685            s, d = _calcSD(t)
686            yr, mo, dy, hr, mn, sc = lt[:6]
687            sc = sc + ms
688            self._timezone_naive = False
689
690        elif ac==1:
691            arg=args[0]
692
693            if arg=='':
694                raise SyntaxError(arg)
695
696            if isinstance(arg, DateTime):
697                """Construct a new DateTime instance from a given
698                DateTime instance.
699                """
700                t = arg.timeTime()
701                s, d = _calcSD(t)
702                yr, mo, dy, hr, mn, sc, tz = arg.parts()
703
704            elif isinstance(arg, datetime):
705                yr, mo, dy, hr, mn, sc, numerictz, tznaive = \
706                    self._parse_iso8601_preserving_tznaive(arg.isoformat())
707                if arg.tzinfo is None:
708                    self._timezone_naive = True
709                    tz = None
710                else:
711                    self._timezone_naive = False
712                    # if we have a pytz tzinfo, use the `zone` attribute
713                    # as a key
714                    tz = getattr(arg.tzinfo, 'zone', numerictz)
715                ms = sc - math.floor(sc)
716                x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc)
717
718                if tz:
719                    try:
720                        zone = _TZINFO[tz]
721                    except DateTimeError:
722                        try:
723                            zone = _TZINFO[numerictz]
724                        except DateTimeError:
725                            raise DateTimeError(
726                                  'Unknown time zone in date: %s' % arg)
727                    tz = zone.tzinfo.zone
728                else:
729                    tz = self._calcTimezoneName(x, ms)
730                s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms)
731
732            elif (isinstance(arg, basestring) and
733                  arg.lower() in _TZINFO._zidx):
734                # Current time, to be displayed in specified timezone
735                t, tz = time(), _TZINFO._zmap[arg.lower()]
736                ms = (t - math.floor(t))
737                # Use integer arithmetic as much as possible.
738                s, d = _calcSD(t)
739                x = _calcDependentSecond(tz, t)
740                yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms)
741
742            elif isinstance(arg, basestring):
743                # Date/time string
744
745                iso8601 = iso8601Match(arg.strip())
746                fields_iso8601 = iso8601 and iso8601.groupdict() or {}
747                if fields_iso8601 and not fields_iso8601.get('garbage'):
748                    yr, mo, dy, hr, mn, sc, tz, tznaive = \
749                        self._parse_iso8601_preserving_tznaive(arg)
750                    self._timezone_naive = tznaive
751                else:
752                    yr, mo, dy, hr, mn, sc, tz = self._parse(arg, datefmt)
753
754                if not self._validDate(yr, mo, dy):
755                    raise DateError('Invalid date: %s' % arg)
756                if not self._validTime(hr, mn, int(sc)):
757                    raise TimeError('Invalid time: %s' % arg)
758                ms = sc - math.floor(sc)
759                x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc)
760
761                if tz:
762                    try:
763                        tz= _TZINFO._zmap[tz.lower()]
764                    except KeyError:
765                        if numericTimeZoneMatch(tz) is None:
766                            raise DateTimeError(
767                                  'Unknown time zone in date: %s' % arg)
768                else:
769                    tz = self._calcTimezoneName(x, ms)
770                s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms)
771
772            else:
773                # Seconds from epoch, gmt
774                t = arg
775                lt = safelocaltime(t)
776                tz = self.localZone(lt)
777                ms = (t - math.floor(t))
778                s, d = _calcSD(t)
779                yr, mo, dy, hr, mn, sc = lt[:6]
780                sc = sc + ms
781
782        elif ac==2:
783            if isinstance(args[1], basestring):
784                # Seconds from epoch (gmt) and timezone
785                t, tz = args
786                ms = (t - math.floor(t))
787                tz = _TZINFO._zmap[tz.lower()]
788                # Use integer arithmetic as much as possible.
789                s, d = _calcSD(t)
790                x = _calcDependentSecond(tz, t)
791                yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms)
792            else:
793                # Year, julian expressed in local zone
794                t = time()
795                lt = safelocaltime(t)
796                tz = self.localZone(lt)
797                yr, jul=args
798                yr = _correctYear(yr)
799                d = (_julianday(yr, 1, 0) - jd1901) + jul
800                x_float = d * 86400.0
801                x_floor = math.floor(x_float)
802                ms = x_float - x_floor
803                x = long(x_floor)
804                yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms)
805                s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms)
806        else:
807            # Explicit format
808            yr, mo, dy = args[:3]
809            hr, mn, sc, tz = 0, 0, 0, 0
810            yr = _correctYear(yr)
811            if not self._validDate(yr, mo, dy):
812                raise DateError('Invalid date: %s' % (args, ))
813            args = args[3:]
814            if args:
815                hr, args = args[0], args[1:]
816                if args:
817                    mn, args = args[0], args[1:]
818                    if args:
819                        sc, args = args[0], args[1:]
820                        if args:
821                            tz, args = args[0], args[1:]
822                            if args:
823                                raise DateTimeError('Too many arguments')
824            if not self._validTime(hr, mn, sc):
825                raise TimeError('Invalid time: %s' % repr(args))
826
827            x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc)
828            ms = sc - math.floor(sc)
829            if tz:
830                try:
831                    tz = _TZINFO._zmap[tz.lower()]
832                except KeyError:
833                    if numericTimeZoneMatch(tz) is None:
834                        raise DateTimeError(
835                              'Unknown time zone: %s' % tz)
836            else:
837                # Get local time zone name
838                tz = self._calcTimezoneName(x, ms)
839            s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms)
840
841        self._dayoffset = int((_julianday(yr, mo, dy) + 2L) % 7)
842        # Round to nearest microsecond in platform-independent way.  You
843        # cannot rely on C sprintf (Python '%') formatting to round
844        # consistently; doing it ourselves ensures that all but truly
845        # horrid C sprintf implementations will yield the same result
846        # x-platform, provided the format asks for exactly 6 digits after
847        # the decimal point.
848        sc = round(sc, 6)
849        if sc >= 60.0:  # can happen if, e.g., orig sc was 59.9999999
850            sc = 59.999999
851        self._nearsec=math.floor(sc)
852        self._year, self._month, self._day = yr, mo, dy
853        self._hour, self._minute, self._second = hr, mn, sc
854        self.time, self._d, self._tz = s, d, tz
855        # self._micros is the time since the epoch
856        # in long integer microseconds.
857        if microsecs is None:
858            microsecs = long(math.floor(t * 1000000.0))
859        self._micros = microsecs
860
861    def localZone(self, ltm=None):
862        '''Returns the time zone on the given date.  The time zone
863        can change according to daylight savings.'''
864        if not _multipleZones:
865            return _localzone0
866        if ltm == None:
867            ltm = localtime(time())
868        isDST = ltm[8]
869        lz = isDST and _localzone1 or _localzone0
870        return lz
871
872    def _calcTimezoneName(self, x, ms):
873        # Derive the name of the local time zone at the given
874        # timezone-dependent second.
875        if not _multipleZones:
876            return _localzone0
877        fsetAtEpoch = _tzoffset(_localzone0, 0.0)
878        nearTime = x - fsetAtEpoch - long(EPOCH) + 86400L + ms
879        # nearTime is within an hour of being correct.
880        try:
881            ltm = safelocaltime(nearTime)
882        except:
883            # We are beyond the range of Python's date support.
884            # Hopefully we can assume that daylight savings schedules
885            # repeat every 28 years.  Calculate the name of the
886            # time zone using a supported range of years.
887            yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, 0)
888            yr = ((yr - 1970) % 28) + 1970
889            x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc)
890            nearTime = x - fsetAtEpoch - long(EPOCH) + 86400L + ms
891
892            # nearTime might still be negative if we are east of Greenwich.
893            # But we can asume on 1969/12/31 were no timezone changes.
894            nearTime = max(0, nearTime)
895
896            ltm = safelocaltime(nearTime)
897        tz = self.localZone(ltm)
898        return tz
899
900    def _parse(self, st, datefmt=getDefaultDateFormat()):
901        # Parse date-time components from a string
902        month = year = tz = tm = None
903        ValidZones = _TZINFO._zidx
904        TimeModifiers = ['am', 'pm']
905
906        # Find timezone first, since it should always be the last
907        # element, and may contain a slash, confusing the parser.
908        st = st.strip()
909        sp = st.split()
910        tz = sp[-1]
911        if tz and (tz.lower() in ValidZones):
912            self._timezone_naive = False
913            st = ' '.join(sp[:-1])
914        else:
915            self._timezone_naive = True
916            tz = None  # Decide later, since the default time zone
917        # could depend on the date.
918
919        ints = []
920        i = 0
921        l = len(st)
922        while i < l:
923            while i < l and st[i] in SPACE_CHARS:
924                i += 1
925            if i < l and st[i] in DELIMITERS:
926                d = st[i]
927                i += 1
928            else:
929                d = ''
930            while i < l and st[i] in SPACE_CHARS:
931                i += 1
932
933            # The float pattern needs to look back 1 character, because it
934            # actually looks for a preceding colon like ':33.33'. This is
935            # needed to avoid accidentally matching the date part of a
936            # dot-separated date string such as '1999.12.31'.
937            if i > 0:
938                b = i - 1
939            else:
940                b = i
941
942            ts_results = FLT_PATTERN.match(st, b)
943            if ts_results:
944                s = ts_results.group(1)
945                i = i + len(s)
946                ints.append(float(s))
947                continue
948
949            #AJ
950            ts_results = INT_PATTERN.match(st, i)
951            if ts_results:
952                s = ts_results.group(0)
953
954                ls = len(s)
955                i = i + ls
956                if (ls == 4 and d and d in '+-' and
957                    (len(ints) + (not not month) >= 3)):
958                    tz = '%s%s' % (d, s)
959                else:
960                    v = int(s)
961                    ints.append(v)
962                continue
963
964            ts_results = NAME_PATTERN.match(st, i)
965            if ts_results:
966                s = ts_results.group(0).lower()
967                i = i + len(s)
968                if i < l and st[i] == '.':
969                    i += 1
970                # Check for month name:
971                _v = _MONTHMAP.get(s)
972                if _v is not None:
973                    if month is None:
974                        month = _v
975                    else:
976                        raise SyntaxError(st)
977                    continue
978                # Check for time modifier:
979                if s in TimeModifiers:
980                    if tm is None:
981                        tm = s
982                    else:
983                        raise SyntaxError(st)
984                    continue
985                # Check for and skip day of week:
986                if s in _DAYMAP:
987                    continue
988
989            raise SyntaxError(st)
990
991        day=None
992        if ints[-1] > 60 and d not in ('.', ':', '/') and len(ints) > 2:
993            year = ints[-1]
994            del ints[-1]
995            if month:
996                day = ints[0]
997                del ints[:1]
998            else:
999                if datefmt == "us":
1000                    month = ints[0]
1001                    day = ints[1]
1002                else:
1003                    month = ints[1]
1004                    day = ints[0]
1005                del ints[:2]
1006        elif month:
1007            if len(ints) > 1:
1008                if ints[0] > 31:
1009                    year = ints[0]
1010                    day = ints[1]
1011                else:
1012                    year = ints[1]
1013                    day = ints[0]
1014                del ints[:2]
1015        elif len(ints) > 2:
1016            if ints[0] > 31:
1017                year = ints[0]
1018                if ints[1] > 12:
1019                    day = ints[1]
1020                    month = ints[2]
1021                else:
1022                    day = ints[2]
1023                    month = ints[1]
1024            if ints[1] > 31:
1025                year = ints[1]
1026                if ints[0] > 12 and ints[2] <= 12:
1027                    day = ints[0]
1028                    month = ints[2]
1029                elif ints[2] > 12 and ints[0] <= 12:
1030                    day = ints[2]
1031                    month = ints[0]
1032            elif ints[2] > 31:
1033                year = ints[2]
1034                if ints[0] > 12:
1035                    day = ints[0]
1036                    month = ints[1]
1037                else:
1038                    if datefmt == "us":
1039                        day = ints[1]
1040                        month = ints[0]
1041                    else:
1042                        day = ints[0]
1043                        month = ints[1]
1044
1045            elif ints[0] <= 12:
1046                month = ints[0]
1047                day = ints[1]
1048                year = ints[2]
1049            del ints[:3]
1050
1051        if day is None:
1052            # Use today's date.
1053            year, month, day = localtime(time())[:3]
1054
1055        year = _correctYear(year)
1056        if year < 1000:
1057            raise SyntaxError(st)
1058
1059        leap = year %4 == 0 and (year % 100 != 0 or year % 400 == 0)
1060        try:
1061            if not day or day > _MONTH_LEN[leap][month]:
1062                raise DateError(st)
1063        except IndexError:
1064            raise DateError(st)
1065
1066        tod = 0
1067        if ints:
1068            i = ints[0]
1069            # Modify hour to reflect am/pm
1070            if tm and (tm == 'pm') and i < 12:
1071                i += 12
1072            if tm and (tm == 'am') and i == 12:
1073                i = 0
1074            if i > 24:
1075                raise TimeError(st)
1076            tod = tod + int(i) * 3600
1077            del ints[0]
1078            if ints:
1079                i = ints[0]
1080                if i > 60:
1081                    raise TimeError(st)
1082                tod = tod + int(i) * 60
1083                del ints[0]
1084                if ints:
1085                    i = ints[0]
1086                    if i > 60:
1087                        raise TimeError(st)
1088                    tod = tod + i
1089                    del ints[0]
1090                    if ints:
1091                        raise SyntaxError(st)
1092
1093        tod_int = int(math.floor(tod))
1094        ms = tod - tod_int
1095        hr, mn, sc = _calcHMS(tod_int, ms)
1096        if not tz:
1097            # Figure out what time zone it is in the local area
1098            # on the given date.
1099            x = _calcDependentSecond2(year, month, day, hr, mn, sc)
1100            tz = self._calcTimezoneName(x, ms)
1101
1102        return year, month, day, hr, mn, sc, tz
1103
1104    # Internal methods
1105    def _validDate(self, y, m, d):
1106        if m < 1 or m > 12 or y < 0 or d < 1 or d > 31:
1107            return 0
1108        return d <= _MONTH_LEN[
1109            (y % 4 == 0 and (y % 100 != 0 or y % 400 == 0))][m]
1110
1111    def _validTime(self, h, m, s):
1112        return h>=0 and h<=23 and m>=0 and m<=59 and s>=0 and s < 60
1113
1114    def __getattr__(self, name):
1115        if '%' in name:
1116            return strftimeFormatter(self, name)
1117        raise AttributeError(name)
1118
1119    # Conversion and comparison methods
1120
1121    def timeTime(self):
1122        """Return the date/time as a floating-point number in UTC,
1123        in the format used by the python time module.
1124
1125        Note that it is possible to create date/time values with
1126        DateTime that have no meaningful value to the time module.
1127        """
1128        return self._micros / 1000000.0
1129
1130    def toZone(self, z):
1131        """Return a DateTime with the value as the current
1132        object, represented in the indicated timezone.
1133        """
1134        t, tz = self._t, _TZINFO._zmap[z.lower()]
1135        micros = self.micros()
1136        tznaive = False # you're performing a timzone change, can't be naive
1137
1138        try:
1139            # Try to use time module for speed.
1140            yr, mo, dy, hr, mn, sc = safegmtime(t + _tzoffset(tz, t))[:6]
1141            sc = self._second
1142            return self.__class__(yr, mo, dy, hr, mn, sc, tz, t,
1143                                  self._d, self.time, micros, tznaive)
1144        except:  # gmtime can't perform the calculation in the given range.
1145            # Calculate the difference between the two time zones.
1146            tzdiff = _tzoffset(tz, t) - _tzoffset(self._tz, t)
1147            if tzdiff == 0:
1148                return self
1149            sc = self._second
1150            ms = sc - math.floor(sc)
1151            x = _calcDependentSecond2(self._year, self._month, self._day,
1152                                      self._hour, self._minute, sc)
1153            x_new = x + tzdiff
1154            yr, mo, dy, hr, mn, sc = _calcYMDHMS(x_new, ms)
1155            return self.__class__(yr, mo, dy, hr, mn, sc, tz, t,
1156                                  self._d, self.time, micros, tznaive)
1157
1158    def isFuture(self):
1159        """Return true if this object represents a date/time
1160        later than the time of the call.
1161        """
1162        return (self._t > time())
1163
1164    def isPast(self):
1165        """Return true if this object represents a date/time
1166        earlier than the time of the call.
1167        """
1168        return (self._t < time())
1169
1170    def isCurrentYear(self):
1171        """Return true if this object represents a date/time
1172        that falls within the current year, in the context
1173        of this object\'s timezone representation.
1174        """
1175        t=time()
1176        return safegmtime(t+_tzoffset(self._tz, t))[0]==self._year
1177
1178    def isCurrentMonth(self):
1179        """Return true if this object represents a date/time
1180        that falls within the current month, in the context
1181        of this object\'s timezone representation.
1182        """
1183        t=time()
1184        gmt=safegmtime(t+_tzoffset(self._tz, t))
1185        return gmt[0]==self._year and gmt[1]==self._month
1186
1187    def isCurrentDay(self):
1188        """Return true if this object represents a date/time
1189        that falls within the current day, in the context
1190        of this object\'s timezone representation.
1191        """
1192        t=time()
1193        gmt=safegmtime(t+_tzoffset(self._tz, t))
1194        return gmt[0]==self._year and gmt[1]==self._month and gmt[2]==self._day
1195
1196    def isCurrentHour(self):
1197        """Return true if this object represents a date/time
1198        that falls within the current hour, in the context
1199        of this object\'s timezone representation.
1200        """
1201        t=time()
1202        gmt=safegmtime(t+_tzoffset(self._tz, t))
1203        return (gmt[0]==self._year and gmt[1]==self._month and
1204                gmt[2]==self._day and gmt[3]==self._hour)
1205
1206    def isCurrentMinute(self):
1207        """Return true if this object represents a date/time
1208        that falls within the current minute, in the context
1209        of this object\'s timezone representation.
1210        """
1211        t=time()
1212        gmt=safegmtime(t+_tzoffset(self._tz, t))
1213        return (gmt[0]==self._year and gmt[1]==self._month and
1214                gmt[2]==self._day and gmt[3]==self._hour and
1215                gmt[4]==self._minute)
1216
1217    def earliestTime(self):
1218        """Return a new DateTime object that represents the earliest
1219        possible time (in whole seconds) that still falls within
1220        the current object\'s day, in the object\'s timezone context.
1221        """
1222        return self.__class__(
1223            self._year, self._month, self._day, 0, 0, 0, self._tz)
1224
1225    def latestTime(self):
1226        """Return a new DateTime object that represents the latest
1227        possible time (in whole seconds) that still falls within
1228        the current object\'s day, in the object\'s timezone context.
1229        """
1230        return self.__class__(
1231            self._year, self._month, self._day, 23, 59, 59, self._tz)
1232
1233    def greaterThan(self, t):
1234        """Compare this DateTime object to another DateTime object
1235        OR a floating point number such as that which is returned
1236        by the python time module.
1237
1238        Returns true if the object represents a date/time greater
1239        than the specified DateTime or time module style time.
1240
1241        Revised to give more correct results through comparison of
1242        long integer microseconds.
1243        """
1244        if isinstance(t, float):
1245            return self._micros > long(t * 1000000)
1246        try:
1247            return self._micros > t._micros
1248        except AttributeError:
1249            return self._micros > t
1250
1251    __gt__ = greaterThan
1252
1253    def greaterThanEqualTo(self, t):
1254        """Compare this DateTime object to another DateTime object
1255        OR a floating point number such as that which is returned
1256        by the python time module.
1257
1258        Returns true if the object represents a date/time greater
1259        than or equal to the specified DateTime or time module style
1260        time.
1261
1262        Revised to give more correct results through comparison of
1263        long integer microseconds.
1264        """
1265        if isinstance(t, float):
1266            return self._micros >= long(t * 1000000)
1267        try:
1268            return self._micros >= t._micros
1269        except AttributeError:
1270            return self._micros >= t
1271
1272    __ge__ = greaterThanEqualTo
1273
1274    def equalTo(self, t):
1275        """Compare this DateTime object to another DateTime object
1276        OR a floating point number such as that which is returned
1277        by the python time module.
1278
1279        Returns true if the object represents a date/time equal to
1280        the specified DateTime or time module style time.
1281
1282        Revised to give more correct results through comparison of
1283        long integer microseconds.
1284        """
1285        if isinstance(t, float):
1286            return self._micros == long(t * 1000000)
1287        try:
1288            return self._micros == t._micros
1289        except AttributeError:
1290            return self._micros == t
1291
1292    def notEqualTo(self, t):
1293        """Compare this DateTime object to another DateTime object
1294        OR a floating point number such as that which is returned
1295        by the python time module.
1296
1297        Returns true if the object represents a date/time not equal
1298        to the specified DateTime or time module style time.
1299
1300        Revised to give more correct results through comparison of
1301        long integer microseconds.
1302        """
1303        return not self.equalTo(t)
1304
1305    def __eq__(self, t):
1306        """Compare this DateTime object to another DateTime object.
1307        Return True if their internal state is the same. Two objects
1308        representing the same time in different timezones are regared as
1309        unequal. Use the equalTo method if you are only interested in them
1310        refering to the same moment in time.
1311        """
1312        if not isinstance(t, DateTime):
1313            return False
1314        return (self._micros, self._tz) == (t._micros, t._tz)
1315
1316    def __ne__(self, t):
1317        return not self.__eq__(t)
1318
1319    def lessThan(self, t):
1320        """Compare this DateTime object to another DateTime object
1321        OR a floating point number such as that which is returned
1322        by the python time module.
1323
1324        Returns true if the object represents a date/time less than
1325        the specified DateTime or time module style time.
1326
1327        Revised to give more correct results through comparison of
1328        long integer microseconds.
1329        """
1330        if isinstance(t, float):
1331            return self._micros < long(t * 1000000)
1332        try:
1333            return self._micros < t._micros
1334        except AttributeError:
1335            return self._micros < t
1336
1337    __lt__ = lessThan
1338
1339    def lessThanEqualTo(self, t):
1340        """Compare this DateTime object to another DateTime object
1341        OR a floating point number such as that which is returned
1342        by the python time module.
1343
1344        Returns true if the object represents a date/time less than
1345        or equal to the specified DateTime or time module style time.
1346
1347        Revised to give more correct results through comparison of
1348        long integer microseconds.
1349        """
1350        if isinstance(t, float):
1351            return self._micros <= long(t * 1000000)
1352        try:
1353            return self._micros <= t._micros
1354        except AttributeError:
1355            return self._micros <= t
1356
1357    __le__ = lessThanEqualTo
1358
1359    def isLeapYear(self):
1360        """Return true if the current year (in the context of the
1361        object\'s timezone) is a leap year.
1362        """
1363        return (self._year % 4 == 0 and
1364            (self._year % 100 != 0 or self._year % 400==0))
1365
1366    def dayOfYear(self):
1367        """Return the day of the year, in context of the timezone
1368        representation of the object.
1369        """
1370        d = int(self._d + (_tzoffset(self._tz, self._t) / 86400.0))
1371        return int((d + jd1901) - _julianday(self._year, 1, 0))
1372
1373    # Component access
1374    def parts(self):
1375        """Return a tuple containing the calendar year, month,
1376        day, hour, minute second and timezone of the object.
1377        """
1378        return self._year, self._month, self._day, self._hour, \
1379               self._minute, self._second, self._tz
1380
1381    def timezone(self):
1382        """Return the timezone in which the object is represented."""
1383        return self._tz
1384
1385    def tzoffset(self):
1386        """Return the timezone offset for the objects timezone."""
1387        return _tzoffset(self._tz, self._t)
1388
1389    def year(self):
1390        """Return the calendar year of the object."""
1391        return self._year
1392
1393    def month(self):
1394        """Return the month of the object as an integer."""
1395        return self._month
1396
1397    @property
1398    def _fmon(self):
1399        return _MONTHS[self._month]
1400
1401    def Month(self):
1402        """Return the full month name."""
1403        return self._fmon
1404
1405    @property
1406    def _amon(self):
1407        return _MONTHS_A[self._month]
1408
1409    def aMonth(self):
1410        """Return the abreviated month name."""
1411        return self._amon
1412
1413    def Mon(self):
1414        """Compatibility: see aMonth."""
1415        return self._amon
1416
1417    @property
1418    def _pmon(self):
1419        return _MONTHS_P[self._month]
1420
1421    def pMonth(self):
1422        """Return the abreviated (with period) month name."""
1423        return self._pmon
1424
1425    def Mon_(self):
1426        """Compatibility: see pMonth."""
1427        return self._pmon
1428
1429    def day(self):
1430        """Return the integer day."""
1431        return self._day
1432
1433    @property
1434    def _fday(self):
1435        return _DAYS[self._dayoffset]
1436
1437    def Day(self):
1438        """Return the full name of the day of the week."""
1439        return self._fday
1440
1441    def DayOfWeek(self):
1442        """Compatibility: see Day."""
1443        return self._fday
1444
1445    @property
1446    def _aday(self):
1447        return _DAYS_A[self._dayoffset]
1448
1449    def aDay(self):
1450        """Return the abreviated name of the day of the week."""
1451        return self._aday
1452
1453    @property
1454    def _pday(self):
1455        return _DAYS_P[self._dayoffset]
1456
1457    def pDay(self):
1458        """Return the abreviated (with period) name of the day of the week."""
1459        return self._pday
1460
1461    def Day_(self):
1462        """Compatibility: see pDay."""
1463        return self._pday
1464
1465    def dow(self):
1466        """Return the integer day of the week, where sunday is 0."""
1467        return self._dayoffset
1468
1469    def dow_1(self):
1470        """Return the integer day of the week, where sunday is 1."""
1471        return self._dayoffset+1
1472
1473    @property
1474    def _pmhour(self):
1475        hr = self._hour
1476        if hr > 12:
1477            return hr - 12
1478        return hr or 12
1479
1480    def h_12(self):
1481        """Return the 12-hour clock representation of the hour."""
1482        return self._pmhour
1483
1484    def h_24(self):
1485        """Return the 24-hour clock representation of the hour."""
1486        return self._hour
1487
1488    @property
1489    def _pm(self):
1490        hr = self._hour
1491        if hr >= 12:
1492            return 'pm'
1493        return 'am'
1494
1495    def ampm(self):
1496        """Return the appropriate time modifier (am or pm)."""
1497        return self._pm
1498
1499    def hour(self):
1500        """Return the 24-hour clock representation of the hour."""
1501        return self._hour
1502
1503    def minute(self):
1504        """Return the minute."""
1505        return self._minute
1506
1507    def second(self):
1508        """Return the second."""
1509        return self._second
1510
1511    def millis(self):
1512        """Return the millisecond since the epoch in GMT."""
1513        return self._micros / 1000
1514
1515    def micros(self):
1516        """Return the microsecond since the epoch in GMT."""
1517        return self._micros
1518
1519    def timezoneNaive(self):
1520        """The python datetime module introduces the idea of distinguishing
1521        between timezone aware and timezone naive datetime values. For lossless
1522        conversion to and from datetime.datetime record if we record this
1523        information using True / False. DateTime makes no distinction, when we
1524        don't have any information we return None here.
1525        """
1526        try:
1527            return self._timezone_naive
1528        except AttributeError:
1529            return None
1530
1531    def strftime(self, format):
1532        """Format the date/time using the *current timezone representation*."""
1533        x = _calcDependentSecond2(self._year, self._month, self._day,
1534                                  self._hour, self._minute, self._second)
1535        ltz = self._calcTimezoneName(x, 0)
1536        tzdiff = _tzoffset(ltz, self._t) - _tzoffset(self._tz, self._t)
1537        zself = self + tzdiff/86400.0
1538        microseconds = int((zself._second - zself._nearsec) * 1000000)
1539
1540        # Note: in older versions strftime() accepted also unicode strings
1541        # as format strings (just because time.strftime() did not perform
1542        # any type checking). So we convert unicode strings to utf8,
1543        # pass them to strftime and convert them back to unicode if necessary.
1544
1545        format_is_unicode = False
1546        if isinstance(format, unicode):
1547            format = format.encode('utf-8')
1548            format_is_unicode = True
1549        ds = datetime(zself._year, zself._month, zself._day, zself._hour,
1550               zself._minute, int(zself._nearsec),
1551               microseconds).strftime(format)
1552        return format_is_unicode and unicode(ds, 'utf-8') or ds
1553
1554    # General formats from previous DateTime
1555    def Date(self):
1556        """Return the date string for the object."""
1557        return "%s/%2.2d/%2.2d" % (self._year, self._month, self._day)
1558
1559    def Time(self):
1560        """Return the time string for an object to the nearest second."""
1561        return '%2.2d:%2.2d:%2.2d' % (self._hour, self._minute, self._nearsec)
1562
1563    def TimeMinutes(self):
1564        """Return the time string for an object not showing seconds."""
1565        return '%2.2d:%2.2d' % (self._hour, self._minute)
1566
1567    def AMPM(self):
1568        """Return the time string for an object to the nearest second."""
1569        return '%2.2d:%2.2d:%2.2d %s' % (
1570                self._pmhour, self._minute, self._nearsec, self._pm)
1571
1572    def AMPMMinutes(self):
1573        """Return the time string for an object not showing seconds."""
1574        return '%2.2d:%2.2d %s' % (self._pmhour, self._minute, self._pm)
1575
1576    def PreciseTime(self):
1577        """Return the time string for the object."""
1578        return '%2.2d:%2.2d:%06.3f' % (self._hour, self._minute, self._second)
1579
1580    def PreciseAMPM(self):
1581        """Return the time string for the object."""
1582        return '%2.2d:%2.2d:%06.3f %s' % (
1583                self._pmhour, self._minute, self._second, self._pm)
1584
1585    def yy(self):
1586        """Return calendar year as a 2 digit string."""
1587        return str(self._year)[-2:]
1588
1589    def mm(self):
1590        """Return month as a 2 digit string."""
1591        return '%02d' % self._month
1592
1593    def dd(self):
1594        """Return day as a 2 digit string."""
1595        return '%02d' % self._day
1596
1597    def rfc822(self):
1598        """Return the date in RFC 822 format."""
1599        tzoffset = _tzoffset2rfc822zone(_tzoffset(self._tz, self._t))
1600        return '%s, %2.2d %s %d %2.2d:%2.2d:%2.2d %s' % (
1601            self._aday, self._day, self._amon, self._year,
1602            self._hour, self._minute, self._nearsec, tzoffset)
1603
1604    # New formats
1605    def fCommon(self):
1606        """Return a string representing the object\'s value
1607        in the format: March 1, 1997 1:45 pm.
1608        """
1609        return '%s %s, %4.4d %s:%2.2d %s' % (
1610               self._fmon, self._day, self._year, self._pmhour,
1611               self._minute, self._pm)
1612
1613    def fCommonZ(self):
1614        """Return a string representing the object\'s value
1615        in the format: March 1, 1997 1:45 pm US/Eastern.
1616        """
1617        return '%s %s, %4.4d %d:%2.2d %s %s' % (
1618               self._fmon, self._day, self._year, self._pmhour,
1619               self._minute, self._pm, self._tz)
1620
1621    def aCommon(self):
1622        """Return a string representing the object\'s value
1623        in the format: Mar 1, 1997 1:45 pm.
1624        """
1625        return '%s %s, %4.4d %s:%2.2d %s' % (
1626               self._amon, self._day, self._year, self._pmhour,
1627               self._minute, self._pm)
1628
1629    def aCommonZ(self):
1630        """Return a string representing the object\'s value
1631        in the format: Mar 1, 1997 1:45 pm US/Eastern.
1632        """
1633        return '%s %s, %4.4d %d:%2.2d %s %s' % (
1634               self._amon, self._day, self._year, self._pmhour,
1635               self._minute, self._pm, self._tz)
1636
1637    def pCommon(self):
1638        """Return a string representing the object\'s value
1639        in the format: Mar. 1, 1997 1:45 pm.
1640        """
1641        return '%s %s, %4.4d %s:%2.2d %s' % (
1642               self._pmon, self._day, self._year, self._pmhour,
1643               self._minute, self._pm)
1644
1645    def pCommonZ(self):
1646        """Return a string representing the object\'s value
1647        in the format: Mar. 1, 1997 1:45 pm US/Eastern.
1648        """
1649        return '%s %s, %4.4d %d:%2.2d %s %s' % (
1650               self._pmon, self._day, self._year, self._pmhour,
1651               self._minute, self._pm, self._tz)
1652
1653    def ISO(self):
1654        """Return the object in ISO standard format.
1655
1656        Note: this is *not* ISO 8601-format! See the ISO8601 and
1657        HTML4 methods below for ISO 8601-compliant output.
1658
1659        Dates are output as: YYYY-MM-DD HH:MM:SS
1660        """
1661        return "%.4d-%.2d-%.2d %.2d:%.2d:%.2d" % (
1662            self._year, self._month, self._day,
1663            self._hour, self._minute, self._second)
1664
1665    def ISO8601(self):
1666        """Return the object in ISO 8601-compatible format containing the
1667        date, time with seconds-precision and the time zone identifier.
1668
1669        See: http://www.w3.org/TR/NOTE-datetime
1670
1671        Dates are output as: YYYY-MM-DDTHH:MM:SSTZD
1672            T is a literal character.
1673            TZD is Time Zone Designator, format +HH:MM or -HH:MM
1674
1675        If the instance is timezone naive (it was not specified with a timezone
1676        when it was constructed) then the timezone is ommitted.
1677
1678        The HTML4 method below offers the same formatting, but converts
1679        to UTC before returning the value and sets the TZD "Z".
1680        """
1681        if self.timezoneNaive():
1682            return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d" % (
1683                self._year, self._month, self._day,
1684                self._hour, self._minute, self._second)
1685        tzoffset = _tzoffset2iso8601zone(_tzoffset(self._tz, self._t))
1686        return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d%s" % (
1687            self._year, self._month, self._day,
1688            self._hour, self._minute, self._second, tzoffset)
1689
1690    def HTML4(self):
1691        """Return the object in the format used in the HTML4.0 specification,
1692        one of the standard forms in ISO8601.
1693
1694        See: http://www.w3.org/TR/NOTE-datetime
1695
1696        Dates are output as: YYYY-MM-DDTHH:MM:SSZ
1697           T, Z are literal characters.
1698           The time is in UTC.
1699        """
1700        newdate = self.toZone('UTC')
1701        return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2dZ" % (
1702            newdate._year, newdate._month, newdate._day,
1703            newdate._hour, newdate._minute, newdate._second)
1704
1705    def asdatetime(self):
1706        """Return a standard libary datetime.datetime
1707        """
1708        tznaive = self.timezoneNaive()
1709        if tznaive:
1710            tzinfo = None
1711        else:
1712            tzinfo = _TZINFO[self._tz].tzinfo
1713        second = int(self._second)
1714        microsec = self.micros() % 1000000
1715        dt = datetime(self._year, self._month, self._day, self._hour,
1716                      self._minute, second, microsec, tzinfo)
1717        return dt
1718
1719    def utcdatetime(self):
1720        """Convert the time to UTC then return a timezone naive datetime object
1721        """
1722        utc = self.toZone('UTC')
1723        second = int(utc._second)
1724        microsec = utc.micros() % 1000000
1725        dt = datetime(utc._year, utc._month, utc._day, utc._hour,
1726                      utc._minute, second, microsec)
1727        return dt
1728
1729    def __add__(self, other):
1730        """A DateTime may be added to a number and a number may be
1731        added to a DateTime;  two DateTimes cannot be added.
1732        """
1733        if hasattr(other, '_t'):
1734            raise DateTimeError('Cannot add two DateTimes')
1735        o = float(other)
1736        tz = self._tz
1737        omicros = round(o * 86400000000)
1738        tmicros = self.micros() + omicros
1739        t = tmicros / 1000000.0
1740        d = (tmicros + long(EPOCH*1000000)) / 86400000000.0
1741        s = d - math.floor(d)
1742        ms = t - math.floor(t)
1743        x = _calcDependentSecond(tz, t)
1744        yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms)
1745        return self.__class__(yr, mo, dy, hr, mn, sc, self._tz,
1746            t, d, s, None, self.timezoneNaive())
1747
1748    __radd__ = __add__
1749
1750    def __sub__(self, other):
1751        """Either a DateTime or a number may be subtracted from a
1752        DateTime, however, a DateTime may not be subtracted from
1753        a number.
1754        """
1755        if hasattr(other, '_d'):
1756            return (self.micros() - other.micros()) / 86400000000.0
1757        else:
1758            return self.__add__(-(other))
1759
1760    def __repr__(self):
1761        """Convert a DateTime to a string that looks like a Python
1762        expression.
1763        """
1764        return '%s(\'%s\')' % (self.__class__.__name__, str(self))
1765
1766    def __str__(self):
1767        """Convert a DateTime to a string."""
1768        y, m, d = self._year, self._month, self._day
1769        h, mn, s, t = self._hour, self._minute, self._second, self._tz
1770        if s == int(s):
1771            # A whole number of seconds -- suppress milliseconds.
1772            return '%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d %s' % (
1773                    y, m, d, h, mn, s, t)
1774        else:
1775            # s is already rounded to the nearest microsecond, and
1776            # it's not a whole number of seconds.  Be sure to print
1777            # 2 digits before the decimal point.
1778            return '%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%06.6f %s' % (
1779                    y, m, d, h, mn, s, t)
1780
1781    def __hash__(self):
1782        """Compute a hash value for a DateTime."""
1783        return int(((self._year%100*12+self._month)*31+
1784                     self._day+self.time)*100)
1785
1786    def __int__(self):
1787        """Convert to an integer number of seconds since the epoch (gmt)."""
1788        return int(self.micros() / 1000000)
1789
1790    def __long__(self):
1791        """Convert to a long-int number of seconds since the epoch (gmt)."""
1792        return long(self.micros() / 1000000)
1793
1794    def __float__(self):
1795        """Convert to floating-point number of seconds since the epoch (gmt).
1796        """
1797        return self.micros() / 1000000.0
1798
1799    @property
1800    def _t(self):
1801        return self._micros / 1000000.0
1802
1803    def _parse_iso8601(self, s):
1804        # preserve the previously implied contract
1805        # who know where this could be used...
1806        return self._parse_iso8601_preserving_tznaive(s)[:7]
1807
1808    def _parse_iso8601_preserving_tznaive(self, s):
1809        try:
1810            return self.__parse_iso8601(s)
1811        except IndexError:
1812            raise SyntaxError(
1813                'Not an ISO 8601 compliant date string: "%s"' % s)
1814
1815    def __parse_iso8601(self, s):
1816        """Parse an ISO 8601 compliant date.
1817
1818        See: http://en.wikipedia.org/wiki/ISO_8601
1819        """
1820        month = day = week_day = 1
1821        year = hour = minute = seconds = hour_off = min_off = 0
1822        tznaive = True
1823
1824        iso8601 = iso8601Match(s.strip())
1825        fields = iso8601 and iso8601.groupdict() or {}
1826        if not iso8601 or fields.get('garbage'):
1827            raise IndexError
1828
1829        if fields['year']:
1830            year = int(fields['year'])
1831        if fields['month']:
1832            month = int(fields['month'])
1833        if fields['day']:
1834            day = int(fields['day'])
1835
1836        if fields['year_day']:
1837            d = DateTime('%s-01-01' % year) + int(fields['year_day']) - 1
1838            month = d.month()
1839            day = d.day()
1840
1841        if fields['week']:
1842            week = int(fields['week'])
1843            if fields['week_day']:
1844                week_day = int(fields['week_day'])
1845            d = DateTime('%s-01-04' % year)
1846            d = d - (d.dow()+6) % 7 + week * 7 + week_day - 8
1847            month = d.month()
1848            day = d.day()
1849
1850        if fields['hour']:
1851            hour = int(fields['hour'])
1852
1853        if fields['minute']:
1854            minute = int(fields['minute'])
1855        elif fields['fraction']:
1856            minute = 60.0 * float('0.%s' % fields['fraction'])
1857            seconds, minute = math.modf(minute)
1858            minute = int(minute)
1859            seconds = 60.0 * seconds
1860            # Avoid reprocess when handling seconds, bellow
1861            fields['fraction'] = None
1862
1863        if fields['second']:
1864            seconds = int(fields['second'])
1865            if fields['fraction']:
1866                seconds = seconds + float('0.%s' % fields['fraction'])
1867        elif fields['fraction']:
1868            seconds = 60.0 * float('0.%s' % fields['fraction'])
1869
1870        if fields['hour_off']:
1871            hour_off = int(fields['hour_off'])
1872            if fields['signal'] == '-':
1873                hour_off *= -1
1874
1875        if fields['min_off']:
1876            min_off = int(fields['min_off'])
1877
1878        if fields['signal'] or fields['Z']:
1879            tznaive = False
1880        else:
1881            tznaive = True
1882
1883        # Differ from the specification here. To preserve backwards
1884        # compatibility assume a default timezone == UTC.
1885        tz = 'GMT%+03d%02d' % (hour_off, min_off)
1886
1887        return year, month, day, hour, minute, seconds, tz, tznaive
1888
1889    def JulianDay(self):
1890        """Return the Julian day.
1891
1892        See: http://www.tondering.dk/claus/cal/node3.html#sec-calcjd
1893        """
1894        a = (14 - self._month)/12 #integer division, discard remainder
1895        y = self._year + 4800 - a
1896        m = self._month + (12*a) - 3
1897        return self._day + (153*m+2)/5 + 365*y + y/4 - y/100 + y/400 - 32045
1898
1899    def week(self):
1900        """Return the week number according to ISO.
1901
1902        See: http://www.tondering.dk/claus/cal/node6.html
1903        """
1904        J = self.JulianDay()
1905        d4 = (J + 31741 - (J % 7)) % 146097 % 36524 % 1461
1906        L = d4/1460
1907        d1 = ((d4 - L) % 365) + L
1908        return d1 / 7 + 1
1909
1910    def encode(self, out):
1911        """Encode value for XML-RPC."""
1912        out.write('<value><dateTime.iso8601>')
1913        out.write(self.ISO8601())
1914        out.write('</dateTime.iso8601></value>\n')
1915
1916
1917# Provide the _dt_reconstructor function here, in case something
1918# accidentally creates a reference to this function
1919
1920orig_reconstructor = copy_reg._reconstructor
1921
1922
1923def _dt_reconstructor(cls, base, state):
1924    if cls is DateTime:
1925        return cls(state)
1926    return orig_reconstructor(cls, base, state)
1927