• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / DateTime / 3655319845

pending completion
3655319845

push

github

Michael Howitz
Fix GHA: ubuntu-latest no longer contains Python 2.7 up to 3.6

260 of 336 branches covered (77.38%)

Branch coverage included in aggregate %.

1405 of 1550 relevant lines covered (90.65%)

0.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.87
/src/DateTime/DateTime.py
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

14
import math
1✔
15
import re
1✔
16
import sys
1✔
17
from datetime import datetime
1✔
18
from time import altzone
1✔
19
from time import daylight
1✔
20
from time import gmtime
1✔
21
from time import localtime
1✔
22
from time import time
1✔
23
from time import timezone
1✔
24
from time import tzname
1✔
25

26
from zope.interface import implementer
1✔
27

28
from .interfaces import DateError
1✔
29
from .interfaces import DateTimeError
1✔
30
from .interfaces import IDateTime
1✔
31
from .interfaces import SyntaxError
1✔
32
from .interfaces import TimeError
1✔
33
from .pytz_support import PytzCache
1✔
34

35

36
if sys.version_info > (3, ):  # pragma: PY3
1✔
37
    import copyreg as copy_reg
1✔
38
    basestring = str
1✔
39
    long = int
1✔
40
    explicit_unicode_type = type(None)
1✔
41
else:  # pragma: PY2
42
    import copy_reg
43
    explicit_unicode_type = unicode  # noqa: F821 undefined name
44

45
default_datefmt = None
1✔
46

47

48
def getDefaultDateFormat():
1✔
49
    global default_datefmt
50
    if default_datefmt is None:
1!
51
        try:
1✔
52
            from App.config import getConfiguration
1✔
53
            default_datefmt = getConfiguration().datetime_format
×
54
            return default_datefmt
×
55
        except Exception:
1✔
56
            return 'us'
1✔
57
    else:
58
        return default_datefmt
×
59

60

61
# To control rounding errors, we round system time to the nearest
62
# microsecond.  Then delicate calculations can rely on that the
63
# maximum precision that needs to be preserved is known.
64
_system_time = time
1✔
65

66

67
def time():
1✔
68
    return round(_system_time(), 6)
1✔
69

70

71
# Determine machine epoch
72
tm = ((0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334),
1✔
73
      (0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335))
74
yr, mo, dy, hr, mn, sc = gmtime(0)[:6]
1✔
75
i = int(yr - 1)
1✔
76
to_year = int(i * 365 + i // 4 - i // 100 + i // 400 - 693960.0)
1✔
77
to_month = tm[yr % 4 == 0 and (yr % 100 != 0 or yr % 400 == 0)][mo]
1✔
78
EPOCH = ((to_year + to_month + dy +
1✔
79
          (hr / 24.0 + mn / 1440.0 + sc / 86400.0)) * 86400)
80
jd1901 = 2415385
1✔
81

82
_TZINFO = PytzCache()
1✔
83

84
INT_PATTERN = re.compile(r'([0-9]+)')
1✔
85
FLT_PATTERN = re.compile(r':([0-9]+\.[0-9]+)')
1✔
86
NAME_PATTERN = re.compile(r'([a-zA-Z]+)', re.I)
1✔
87
SPACE_CHARS = ' \t\n'
1✔
88
DELIMITERS = '-/.:,+'
1✔
89

90
_MONTH_LEN = ((0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
1✔
91
              (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31))
92
_MONTHS = ('', 'January', 'February', 'March', 'April', 'May', 'June',
1✔
93
           'July', 'August', 'September', 'October', 'November', 'December')
94
_MONTHS_A = ('', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
1✔
95
             'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')
96
_MONTHS_P = ('', 'Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'June',
1✔
97
             'July', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.')
98
_MONTHMAP = {'january': 1, 'jan': 1,
1✔
99
             'february': 2, 'feb': 2,
100
             'march': 3, 'mar': 3,
101
             'april': 4, 'apr': 4,
102
             'may': 5,
103
             'june': 6, 'jun': 6,
104
             'july': 7, 'jul': 7,
105
             'august': 8, 'aug': 8,
106
             'september': 9, 'sep': 9, 'sept': 9,
107
             'october': 10, 'oct': 10,
108
             'november': 11, 'nov': 11,
109
             'december': 12, 'dec': 12}
110
_DAYS = ('Sunday', 'Monday', 'Tuesday', 'Wednesday',
1✔
111
         'Thursday', 'Friday', 'Saturday')
112
_DAYS_A = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat')
1✔
113
_DAYS_P = ('Sun.', 'Mon.', 'Tue.', 'Wed.', 'Thu.', 'Fri.', 'Sat.')
1✔
114
_DAYMAP = {'sunday': 1, 'sun': 1,
1✔
115
           'monday': 2, 'mon': 2,
116
           'tuesday': 3, 'tues': 3, 'tue': 3,
117
           'wednesday': 4, 'wed': 4,
118
           'thursday': 5, 'thurs': 5, 'thur': 5, 'thu': 5,
119
           'friday': 6, 'fri': 6,
120
           'saturday': 7, 'sat': 7}
121

122
numericTimeZoneMatch = re.compile(r'[+-][0-9][0-9][0-9][0-9]').match
1✔
123
iso8601Match = re.compile(r'''
1✔
124
  (?P<year>\d\d\d\d)                # four digits year
125
  (?:-?                             # one optional dash
126
   (?:                              # followed by:
127
    (?P<year_day>\d\d\d             #  three digits year day
128
     (?!\d))                        #  when there is no fourth digit
129
   |                                # or:
130
    W                               #  one W
131
    (?P<week>\d\d)                  #  two digits week
132
    (?:-?                           #  one optional dash
133
     (?P<week_day>\d)               #  one digit week day
134
    )?                              #  week day is optional
135
   |                                # or:
136
    (?P<month>\d\d)?                #  two digits month
137
    (?:-?                           #  one optional dash
138
     (?P<day>\d\d)?                 #  two digits day
139
    )?                              #  after day is optional
140
   )                                #
141
  )?                                # after year is optional
142
  (?:[T ]                           # one T or one whitespace
143
   (?P<hour>\d\d)                   # two digits hour
144
   (?::?                            # one optional colon
145
    (?P<minute>\d\d)?               # two digits minute
146
    (?::?                           # one optional colon
147
     (?P<second>\d\d)?              # two digits second
148
     (?:[.,]                        # one dot or one comma
149
      (?P<fraction>\d+)             # n digits fraction
150
     )?                             # after second is optional
151
    )?                              # after minute is optional
152
   )?                               # after hour is optional
153
   (?:                              # timezone:
154
    (?P<Z>Z)                        #  one Z
155
   |                                # or:
156
    (?P<signal>[-+])                #  one plus or one minus as signal
157
    (?P<hour_off>\d                 #  one digit for hour offset...
158
     (?:\d(?!\d$)                   #  ...or two, if not the last two digits
159
    )?)                             #  second hour offset digit is optional
160
    (?::?                           #  one optional colon
161
     (?P<min_off>\d\d)              #  two digits minute offset
162
    )?                              #  after hour offset is optional
163
   )?                               # timezone is optional
164
  )?                                # time is optional
165
  (?P<garbage>.*)                   # store the extra garbage
166
''', re.VERBOSE).match
167

168

169
def _findLocalTimeZoneName(isDST):
1✔
170
    if not daylight:
1!
171
        # Daylight savings does not occur in this time zone.
172
        isDST = 0
1✔
173
    try:
1✔
174
        # Get the name of the current time zone depending
175
        # on DST.
176
        _localzone = PytzCache._zmap[tzname[isDST].lower()]
1✔
177
    except BaseException:
×
178
        try:
×
179
            # Generate a GMT-offset zone name.
180
            if isDST:
×
181
                localzone = altzone
×
182
            else:
183
                localzone = timezone
×
184
            offset = (-localzone / 3600.0)
×
185
            majorOffset = int(offset)
×
186
            if majorOffset != 0:
×
187
                minorOffset = abs(int((offset % majorOffset) * 60.0))
×
188
            else:
189
                minorOffset = 0
×
190
            m = majorOffset >= 0 and '+' or ''
×
191
            lz = '%s%0.02d%0.02d' % (m, majorOffset, minorOffset)
×
192
            _localzone = PytzCache._zmap[('GMT%s' % lz).lower()]
×
193
        except BaseException:
×
194
            _localzone = ''
×
195
    return _localzone
1✔
196

197

198
_localzone0 = _findLocalTimeZoneName(0)
1✔
199
_localzone1 = _findLocalTimeZoneName(1)
1✔
200
_multipleZones = (_localzone0 != _localzone1)
1✔
201

202
# Some utility functions for calculating dates:
203

204

205
def _calcSD(t):
1✔
206
    # Returns timezone-independent days since epoch and the fractional
207
    # part of the days.
208
    dd = t + EPOCH - 86400.0
1✔
209
    d = dd / 86400.0
1✔
210
    s = d - math.floor(d)
1✔
211
    return s, d
1✔
212

213

214
def _calcDependentSecond(tz, t):
1✔
215
    # Calculates the timezone-dependent second (integer part only)
216
    # from the timezone-independent second.
217
    fset = _tzoffset(tz, t)
1✔
218
    return fset + long(math.floor(t)) + long(EPOCH) - 86400
1✔
219

220

221
def _calcDependentSecond2(yr, mo, dy, hr, mn, sc):
1✔
222
    # Calculates the timezone-dependent second (integer part only)
223
    # from the date given.
224
    ss = int(hr) * 3600 + int(mn) * 60 + int(sc)
1✔
225
    x = long(_julianday(yr, mo, dy) - jd1901) * 86400 + ss
1✔
226
    return x
1✔
227

228

229
def _calcIndependentSecondEtc(tz, x, ms):
1✔
230
    # Derive the timezone-independent second from the timezone
231
    # dependent second.
232
    fsetAtEpoch = _tzoffset(tz, 0.0)
1✔
233
    nearTime = x - fsetAtEpoch - long(EPOCH) + 86400 + ms
1✔
234
    # nearTime is now within an hour of being correct.
235
    # Recalculate t according to DST.
236
    fset = long(_tzoffset(tz, nearTime))
1✔
237
    d = (x - fset) / 86400.0 + (ms / 86400.0)
1✔
238
    t = x - fset - long(EPOCH) + 86400 + ms
1✔
239
    micros = (x + 86400 - fset) * 1000000 + \
1✔
240
        long(round(ms * 1000000.0)) - long(EPOCH * 1000000.0)
241
    s = d - math.floor(d)
1✔
242
    return (s, d, t, micros)
1✔
243

244

245
def _calcHMS(x, ms):
1✔
246
    # hours, minutes, seconds from integer and float.
247
    hr = x // 3600
1✔
248
    x = x - hr * 3600
1✔
249
    mn = x // 60
1✔
250
    sc = x - mn * 60 + ms
1✔
251
    return (hr, mn, sc)
1✔
252

253

254
def _calcYMDHMS(x, ms):
1✔
255
    # x is a timezone-dependent integer of seconds.
256
    # Produces yr,mo,dy,hr,mn,sc.
257
    yr, mo, dy = _calendarday(x // 86400 + jd1901)
1✔
258
    x = int(x - (x // 86400) * 86400)
1✔
259
    hr = x // 3600
1✔
260
    x = x - hr * 3600
1✔
261
    mn = x // 60
1✔
262
    sc = x - mn * 60 + ms
1✔
263
    return (yr, mo, dy, hr, mn, sc)
1✔
264

265

266
def _julianday(yr, mo, dy):
1✔
267
    y, m, d = long(yr), long(mo), long(dy)
1✔
268
    if m > 12:
1!
269
        y = y + m // 12
×
270
        m = m % 12
×
271
    elif m < 1:
1!
272
        m = -m
×
273
        y = y - m // 12 - 1
×
274
        m = 12 - m % 12
×
275
    if y > 0:
1!
276
        yr_correct = 0
1✔
277
    else:
278
        yr_correct = 3
×
279
    if m < 3:
1✔
280
        y, m = y - 1, m + 12
1✔
281
    if y * 10000 + m * 100 + d > 15821014:
1!
282
        b = 2 - y // 100 + y // 400
1✔
283
    else:
284
        b = 0
×
285
    return ((1461 * y - yr_correct) // 4 +
1✔
286
            306001 * (m + 1) // 10000 + d + 1720994 + b)
287

288

289
def _calendarday(j):
1✔
290
    j = long(j)
1✔
291
    if (j < 2299160):
1!
292
        b = j + 1525
×
293
    else:
294
        a = (4 * j - 7468861) // 146097
1✔
295
        b = j + 1526 + a - a // 4
1✔
296
    c = (20 * b - 2442) // 7305
1✔
297
    d = 1461 * c // 4
1✔
298
    e = 10000 * (b - d) // 306001
1✔
299
    dy = int(b - d - 306001 * e // 10000)
1✔
300
    mo = (e < 14) and int(e - 1) or int(e - 13)
1✔
301
    yr = (mo > 2) and (c - 4716) or (c - 4715)
1✔
302
    return (int(yr), int(mo), int(dy))
1✔
303

304

305
def _tzoffset(tz, t):
1✔
306
    """Returns the offset in seconds to GMT from a specific timezone (tz) at
307
    a specific time (t).  NB! The _tzoffset result is the same same sign as
308
    the time zone, i.e. GMT+2 has a 7200 second offset. This is the opposite
309
    sign of time.timezone which (confusingly) is -7200 for GMT+2."""
310
    try:
1✔
311
        return _TZINFO[tz].info(t)[0]
1✔
312
    except Exception:
1✔
313
        if numericTimeZoneMatch(tz) is not None:
1✔
314
            return int(tz[0:3]) * 3600 + int(tz[0] + tz[3:5]) * 60
1✔
315
        else:
316
            return 0  # ??
1✔
317

318

319
def _correctYear(year):
1✔
320
    # Y2K patch.
321
    if year >= 0 and year < 100:
1!
322
        # 00-69 means 2000-2069, 70-99 means 1970-1999.
323
        if year < 70:
×
324
            year = 2000 + year
×
325
        else:
326
            year = 1900 + year
×
327
    return year
1✔
328

329

330
def safegmtime(t):
1✔
331
    '''gmtime with a safety zone.'''
332
    try:
1✔
333
        return gmtime(t)
1✔
334
    except (ValueError, OverflowError):
×
335
        raise TimeError('The time %f is beyond the range of this Python '
×
336
                        'implementation.' % float(t))
337

338

339
def safelocaltime(t):
1✔
340
    '''localtime with a safety zone.'''
341
    try:
1✔
342
        return localtime(t)
1✔
343
    except (ValueError, OverflowError):
×
344
        raise TimeError('The time %f is beyond the range of this Python '
×
345
                        'implementation.' % float(t))
346

347

348
def _tzoffset2rfc822zone(seconds):
1✔
349
    """Takes an offset, such as from _tzoffset(), and returns an rfc822
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)
1✔
354

355

356
def _tzoffset2iso8601zone(seconds):
1✔
357
    """Takes an offset, such as from _tzoffset(), and returns an ISO 8601
358
       compliant zone specification. Please note that the result of
359
       _tzoffset() is the negative of what time.localzone and time.altzone is.
360
    """
361
    return "%+03d:%02d" % divmod((seconds // 60), 60)
1✔
362

363

364
def Timezones():
1✔
365
    """Return the list of recognized timezone names"""
366
    return sorted(list(PytzCache._zmap.values()))
1✔
367

368

369
class strftimeFormatter(object):
1✔
370

371
    def __init__(self, dt, format):
1✔
372
        self.dt = dt
×
373
        self.format = format
×
374

375
    def __call__(self):
1✔
376
        return self.dt.strftime(self.format)
×
377

378

379
@implementer(IDateTime)
1✔
380
class DateTime(object):
1✔
381
    """DateTime objects represent instants in time and provide
382
       interfaces for controlling its representation without
383
       affecting the absolute value of the object.
384

385
       DateTime objects may be created from a wide variety of string
386
       or numeric data, or may be computed from other DateTime objects.
387
       DateTimes support the ability to convert their representations
388
       to many major timezones, as well as the ability to create a
389
       DateTime object in the context of a given timezone.
390

391
       DateTime objects provide partial numerical behavior:
392

393
          - Two date-time objects can be subtracted to obtain a time,
394
            in days between the two.
395

396
          - A date-time object and a positive or negative number may
397
            be added to obtain a new date-time object that is the given
398
            number of days later than the input date-time object.
399

400
          - A positive or negative number and a date-time object may
401
            be added to obtain a new date-time object that is the given
402
            number of days later than the input date-time object.
403

404
          - A positive or negative number may be subtracted from a
405
            date-time object to obtain a new date-time object that is
406
            the given number of days earlier than the input date-time
407
            object.
408

409
        DateTime objects may be converted to integer, long, or float
410
        numbers of days since January 1, 1901, using the standard int,
411
        long, and float functions (Compatibility Note: int, long and
412
        float return the number of days since 1901 in GMT rather than
413
        local machine timezone). DateTime objects also provide access
414
        to their value in a float format usable with the python time
415
        module, provided that the value of the object falls in the
416
        range of the epoch-based time module, and as a datetime.datetime
417
        object.
418

419
        A DateTime object should be considered immutable; all conversion
420
        and numeric operations return a new DateTime object rather than
421
        modify the current object."""
422

423
    # For security machinery:
424
    __roles__ = None
1✔
425
    __allow_access_to_unprotected_subobjects__ = 1
1✔
426

427
    # Limit the amount of instance attributes
428
    __slots__ = (
1✔
429
        '_timezone_naive',
430
        '_tz',
431
        '_dayoffset',
432
        '_year',
433
        '_month',
434
        '_day',
435
        '_hour',
436
        '_minute',
437
        '_second',
438
        '_nearsec',
439
        '_d',
440
        '_micros',
441
        'time',
442
    )
443

444
    def __init__(self, *args, **kw):
1✔
445
        """Return a new date-time object"""
446
        try:
1✔
447
            return self._parse_args(*args, **kw)
1✔
448
        except (DateError, TimeError, DateTimeError):
1!
449
            raise
1✔
450
        except Exception:
×
451
            raise SyntaxError('Unable to parse %s, %s' % (args, kw))
×
452

453
    def __getstate__(self):
1✔
454
        # We store a float of _micros, instead of the _micros long, as we most
455
        # often don't have any sub-second resolution and can save those bytes
456
        return (self._micros / 1000000.0,
1✔
457
                getattr(self, '_timezone_naive', False),
458
                self._tz)
459

460
    def __setstate__(self, value):
1✔
461
        if isinstance(value, tuple):
1✔
462
            self._parse_args(value[0], value[2])
1✔
463
            self._micros = long(value[0] * 1000000)
1✔
464
            self._timezone_naive = value[1]
1✔
465
        else:
466
            for k, v in value.items():
1✔
467
                if k in self.__slots__:
1✔
468
                    setattr(self, k, v)
1✔
469
            # BBB: support for very old DateTime pickles
470
            if '_micros' not in value:
1✔
471
                self._micros = long(value['_t'] * 1000000)
1✔
472
            if '_timezone_naive' not in value:
1!
473
                self._timezone_naive = False
×
474

475
    def _parse_args(self, *args, **kw):
1✔
476
        """Return a new date-time object.
477

478
        A DateTime object always maintains its value as an absolute
479
        UTC time, and is represented in the context of some timezone
480
        based on the arguments used to create the object. A DateTime
481
        object's methods return values based on the timezone context.
482

483
        Note that in all cases the local machine timezone is used for
484
        representation if no timezone is specified.
485

486
        DateTimes may be created with from zero to seven arguments.
487

488
          - If the function is called with no arguments or with None,
489
            then the current date/time is returned, represented in the
490
            timezone of the local machine.
491

492
          - If the function is invoked with a single string argument
493
            which is a recognized timezone name, an object representing
494
            the current time is returned, represented in the specified
495
            timezone.
496

497
          - If the function is invoked with a single string argument
498
            representing a valid date/time, an object representing
499
            that date/time will be returned.
500

501
            As a general rule, any date-time representation that is
502
            recognized and unambiguous to a resident of North America
503
            is acceptable. The reason for this qualification is that
504
            in North America, a date like: 2/1/1994 is interpreted
505
            as February 1, 1994, while in some parts of the world,
506
            it is interpreted as January 2, 1994.
507

508
            A date/time string consists of two components, a date
509
            component and an optional time component, separated by one
510
            or more spaces. If the time component is omitted, 12:00am is
511
            assumed. Any recognized timezone name specified as the final
512
            element of the date/time string will be used for computing
513
            the date/time value. If you create a DateTime with the
514
            string 'Mar 9, 1997 1:45pm US/Pacific', the value will
515
            essentially be the same as if you had captured time.time()
516
            at the specified date and time on a machine in that timezone:
517

518
            <PRE>
519
            e=DateTime('US/Eastern')
520
            # returns current date/time, represented in US/Eastern.
521

522
            x=DateTime('1997/3/9 1:45pm')
523
            # returns specified time, represented in local machine zone.
524

525
            y=DateTime('Mar 9, 1997 13:45:00')
526
            # y is equal to x
527
            </PRE>
528

529
            The date component consists of year, month, and day
530
            values. The year value must be a one-, two-, or
531
            four-digit integer. If a one- or two-digit year is
532
            used, the year is assumed to be in the twentieth
533
            century. The month may be an integer, from 1 to 12, a
534
            month name, or a month abbreviation, where a period may
535
            optionally follow the abbreviation. The day must be an
536
            integer from 1 to the number of days in the month. The
537
            year, month, and day values may be separated by
538
            periods, hyphens, forward slashes, or spaces. Extra
539
            spaces are permitted around the delimiters. Year,
540
            month, and day values may be given in any order as long
541
            as it is possible to distinguish the components. If all
542
            three components are numbers that are less than 13,
543
            then a a month-day-year ordering is assumed.
544

545
            The time component consists of hour, minute, and second
546
            values separated by colons.  The hour value must be an
547
            integer between 0 and 23 inclusively. The minute value
548
            must be an integer between 0 and 59 inclusively. The
549
            second value may be an integer value between 0 and
550
            59.999 inclusively. The second value or both the minute
551
            and second values may be omitted. The time may be
552
            followed by am or pm in upper or lower case, in which
553
            case a 12-hour clock is assumed.
554

555
            New in Zope 2.4:
556
            The DateTime constructor automatically detects and handles
557
            ISO8601 compliant dates (YYYY-MM-DDThh:ss:mmTZD).
558

559
            New in Zope 2.9.6:
560
            The existing ISO8601 parser was extended to support almost
561
            the whole ISO8601 specification. New formats includes:
562

563
            <PRE>
564
            y=DateTime('1993-045')
565
            # returns the 45th day from 1993, which is 14th February
566

567
            w=DateTime('1993-W06-7')
568
            # returns the 7th day from the 6th week from 1993, which
569
            # is also 14th February
570
            </PRE>
571

572
            See http://en.wikipedia.org/wiki/ISO_8601 for full specs.
573

574
            Note that the Zope DateTime parser assumes timezone naive ISO
575
            strings to be in UTC rather than local time as specified.
576

577
          - If the DateTime function is invoked with a single numeric
578
            argument, the number is assumed to be a floating point value
579
            such as that returned by time.time().
580

581
            A DateTime object is returned that represents the GMT value
582
            of the time.time() float represented in the local machine's
583
            timezone.
584

585
          - If the DateTime function is invoked with a single argument
586
            that is a DateTime instance, a copy of the passed object will
587
            be created.
588

589
          - New in 2.11:
590
            The DateTime function may now be invoked with a single argument
591
            that is a datetime.datetime instance. DateTimes may be converted
592
            back to datetime.datetime objects with asdatetime().
593
            DateTime instances may be converted to a timezone naive
594
            datetime.datetime in UTC with utcdatetime().
595

596
          - If the function is invoked with two numeric arguments, then
597
            the first is taken to be an integer year and the second
598
            argument is taken to be an offset in days from the beginning
599
            of the year, in the context of the local machine timezone.
600

601
            The date-time value returned is the given offset number of
602
            days from the beginning of the given year, represented in
603
            the timezone of the local machine. The offset may be positive
604
            or negative.
605

606
            Two-digit years are assumed to be in the twentieth
607
            century.
608

609
          - If the function is invoked with two arguments, the first
610
            a float representing a number of seconds past the epoch
611
            in gmt (such as those returned by time.time()) and the
612
            second a string naming a recognized timezone, a DateTime
613
            with a value of that gmt time will be returned, represented
614
            in the given timezone.
615

616
            <PRE>
617
            import time
618
            t=time.time()
619

620
            now_east=DateTime(t,'US/Eastern')
621
            # Time t represented as US/Eastern
622

623
            now_west=DateTime(t,'US/Pacific')
624
            # Time t represented as US/Pacific
625

626
            # now_east == now_west
627
            # only their representations are different
628
            </PRE>
629

630
          - If the function is invoked with three or more numeric
631
            arguments, then the first is taken to be an integer
632
            year, the second is taken to be an integer month, and
633
            the third is taken to be an integer day. If the
634
            combination of values is not valid, then a
635
            DateError is raised. Two-digit years are assumed
636
            to be in the twentieth century. The fourth, fifth, and
637
            sixth arguments specify a time in hours, minutes, and
638
            seconds; hours and minutes should be positive integers
639
            and seconds is a positive floating point value, all of
640
            these default to zero if not given. An optional string may
641
            be given as the final argument to indicate timezone (the
642
            effect of this is as if you had taken the value of time.time()
643
            at that time on a machine in the specified timezone).
644

645
            New in Zope 2.7:
646
            A new keyword parameter "datefmt" can be passed to the
647
            constructor. If set to "international", the constructor
648
            is forced to treat ambiguous dates as "days before month
649
            before year". This useful if you need to parse non-US
650
            dates in a reliable way
651

652
        In any case that a floating point number of seconds is given
653
        or derived, it's rounded to the nearest millisecond.
654

655
        If a string argument passed to the DateTime constructor cannot be
656
        parsed, it will raise DateTime.SyntaxError. Invalid date components
657
        will raise a DateError, while invalid time or timezone components
658
        will raise a DateTimeError.
659

660
        The module function Timezones() will return a list of the (common)
661
        timezones recognized by the DateTime module. Recognition of
662
        timezone names is case-insensitive.
663
        """
664

665
        datefmt = kw.get('datefmt', getDefaultDateFormat())
1✔
666
        d = t = s = None
1✔
667
        ac = len(args)
1✔
668
        microsecs = None
1✔
669

670
        if ac == 10:
1!
671
            # Internal format called only by DateTime
672
            yr, mo, dy, hr, mn, sc, tz, t, d, s = args
×
673
        elif ac == 11:
1!
674
            # Internal format that includes milliseconds (from the epoch)
675
            yr, mo, dy, hr, mn, sc, tz, t, d, s, millisecs = args
×
676
            microsecs = millisecs * 1000
×
677

678
        elif ac == 12:
1✔
679
            # Internal format that includes microseconds (from the epoch) and a
680
            # flag indicating whether this was constructed in a timezone naive
681
            # manner
682
            yr, mo, dy, hr, mn, sc, tz, t, d, s, microsecs, tznaive = args
1✔
683
            if tznaive is not None:  # preserve this information
1✔
684
                self._timezone_naive = tznaive
1✔
685

686
        elif not args or (ac and args[0] is None):
1✔
687
            # Current time, to be displayed in local timezone
688
            t = time()
1✔
689
            lt = safelocaltime(t)
1✔
690
            tz = self.localZone(lt)
1✔
691
            ms = (t - math.floor(t))
1✔
692
            s, d = _calcSD(t)
1✔
693
            yr, mo, dy, hr, mn, sc = lt[:6]
1✔
694
            sc = sc + ms
1✔
695
            self._timezone_naive = False
1✔
696

697
        elif ac == 1:
1✔
698
            arg = args[0]
1✔
699

700
            if arg == '':
1!
701
                raise SyntaxError(arg)
×
702

703
            if isinstance(arg, DateTime):
1✔
704
                """Construct a new DateTime instance from a given
705
                DateTime instance.
706
                """
707
                t = arg.timeTime()
1✔
708
                s, d = _calcSD(t)
1✔
709
                yr, mo, dy, hr, mn, sc, tz = arg.parts()
1✔
710

711
            elif isinstance(arg, datetime):
1✔
712
                yr, mo, dy, hr, mn, sc, numerictz, tznaive = \
1✔
713
                    self._parse_iso8601_preserving_tznaive(arg.isoformat())
714
                if arg.tzinfo is None:
1✔
715
                    self._timezone_naive = True
1✔
716
                    tz = None
1✔
717
                else:
718
                    self._timezone_naive = False
1✔
719
                    # if we have a pytz tzinfo, use the `zone` attribute
720
                    # as a key
721
                    tz = getattr(arg.tzinfo, 'zone', numerictz)
1✔
722
                ms = sc - math.floor(sc)
1✔
723
                x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc)
1✔
724

725
                if tz:
1✔
726
                    try:
1✔
727
                        zone = _TZINFO[tz]
1✔
728
                    except DateTimeError:
×
729
                        try:
×
730
                            zone = _TZINFO[numerictz]
×
731
                        except DateTimeError:
×
732
                            raise DateTimeError(
×
733
                                'Unknown time zone in date: %s' % arg)
734
                    tz = zone.tzinfo.zone
1✔
735
                else:
736
                    tz = self._calcTimezoneName(x, ms)
1✔
737
                s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms)
1✔
738

739
            elif (isinstance(arg, basestring) and
1✔
740
                  arg.lower() in _TZINFO._zidx):
741
                # Current time, to be displayed in specified timezone
742
                t, tz = time(), _TZINFO._zmap[arg.lower()]
1✔
743
                ms = (t - math.floor(t))
1✔
744
                # Use integer arithmetic as much as possible.
745
                s, d = _calcSD(t)
1✔
746
                x = _calcDependentSecond(tz, t)
1✔
747
                yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms)
1✔
748

749
            elif isinstance(arg, basestring):
1✔
750
                # Date/time string
751
                iso8601 = iso8601Match(arg.strip())
1✔
752
                fields_iso8601 = iso8601 and iso8601.groupdict() or {}
1✔
753
                if fields_iso8601 and not fields_iso8601.get('garbage'):
1✔
754
                    yr, mo, dy, hr, mn, sc, tz, tznaive = \
1✔
755
                        self._parse_iso8601_preserving_tznaive(arg)
756
                    self._timezone_naive = tznaive
1✔
757
                else:
758
                    yr, mo, dy, hr, mn, sc, tz = self._parse(arg, datefmt)
1✔
759

760
                if not self._validDate(yr, mo, dy):
1!
761
                    raise DateError('Invalid date: %s' % arg)
×
762
                if not self._validTime(hr, mn, int(sc)):
1!
763
                    raise TimeError('Invalid time: %s' % arg)
×
764
                ms = sc - math.floor(sc)
1✔
765
                x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc)
1✔
766

767
                if tz:
1!
768
                    try:
1✔
769
                        tz = _TZINFO._zmap[tz.lower()]
1✔
770
                    except KeyError:
1✔
771
                        if numericTimeZoneMatch(tz) is None:
1!
772
                            raise DateTimeError(
×
773
                                'Unknown time zone in date: %s' % arg)
774
                else:
775
                    tz = self._calcTimezoneName(x, ms)
×
776
                s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms)
1✔
777

778
            else:
779
                # Seconds from epoch, gmt
780
                t = arg
1✔
781
                lt = safelocaltime(t)
1✔
782
                tz = self.localZone(lt)
1✔
783
                ms = (t - math.floor(t))
1✔
784
                s, d = _calcSD(t)
1✔
785
                yr, mo, dy, hr, mn, sc = lt[:6]
1✔
786
                sc = sc + ms
1✔
787

788
        elif ac == 2:
1✔
789
            if isinstance(args[1], basestring):
1✔
790
                # Seconds from epoch (gmt) and timezone
791
                t, tz = args
1✔
792
                ms = (t - math.floor(t))
1✔
793
                try:
1✔
794
                    tz = _TZINFO._zmap[tz.lower()]
1✔
795
                except KeyError:
1✔
796
                    if numericTimeZoneMatch(tz) is None:
1!
797
                        raise DateTimeError('Unknown time zone: %s' % tz)
×
798
                # Use integer arithmetic as much as possible.
799
                s, d = _calcSD(t)
1✔
800
                x = _calcDependentSecond(tz, t)
1✔
801
                yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms)
1✔
802
            else:
803
                # Year, julian expressed in local zone
804
                t = time()
1✔
805
                lt = safelocaltime(t)
1✔
806
                tz = self.localZone(lt)
1✔
807
                yr, jul = args
1✔
808
                yr = _correctYear(yr)
1✔
809
                d = (_julianday(yr, 1, 0) - jd1901) + jul
1✔
810
                x_float = d * 86400.0
1✔
811
                x_floor = math.floor(x_float)
1✔
812
                ms = x_float - x_floor
1✔
813
                x = long(x_floor)
1✔
814
                yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms)
1✔
815
                s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms)
1✔
816
        else:
817
            # Explicit format
818
            yr, mo, dy = args[:3]
1✔
819
            hr, mn, sc, tz = 0, 0, 0, 0
1✔
820
            yr = _correctYear(yr)
1✔
821
            if not self._validDate(yr, mo, dy):
1!
822
                raise DateError('Invalid date: %s' % (args, ))
×
823
            args = args[3:]
1✔
824
            if args:
1✔
825
                hr, args = args[0], args[1:]
1✔
826
                if args:
1!
827
                    mn, args = args[0], args[1:]
1✔
828
                    if args:
1!
829
                        sc, args = args[0], args[1:]
1✔
830
                        if args:
1✔
831
                            tz, args = args[0], args[1:]
1✔
832
                            if args:
1!
833
                                raise DateTimeError('Too many arguments')
×
834
            if not self._validTime(hr, mn, sc):
1!
835
                raise TimeError('Invalid time: %s' % repr(args))
×
836

837
            x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc)
1✔
838
            ms = sc - math.floor(sc)
1✔
839
            if tz:
1✔
840
                try:
1✔
841
                    tz = _TZINFO._zmap[tz.lower()]
1✔
842
                except KeyError:
×
843
                    if numericTimeZoneMatch(tz) is None:
×
844
                        raise DateTimeError('Unknown time zone: %s' % tz)
×
845
            else:
846
                # Get local time zone name
847
                tz = self._calcTimezoneName(x, ms)
1✔
848
            s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms)
1✔
849

850
        self._dayoffset = int((_julianday(yr, mo, dy) + 2) % 7)
1✔
851
        # Round to nearest microsecond in platform-independent way. You
852
        # cannot rely on C sprintf (Python '%') formatting to round
853
        # consistently; doing it ourselves ensures that all but truly
854
        # horrid C sprintf implementations will yield the same result
855
        # x-platform, provided the format asks for exactly 6 digits after
856
        # the decimal point.
857
        sc = round(sc, 6)
1✔
858
        if sc >= 60.0:  # can happen if, e.g., orig sc was 59.9999999
1!
859
            sc = 59.999999
×
860
        self._nearsec = math.floor(sc)
1✔
861
        self._year, self._month, self._day = yr, mo, dy
1✔
862
        self._hour, self._minute, self._second = hr, mn, sc
1✔
863
        self.time, self._d, self._tz = s, d, tz
1✔
864
        # self._micros is the time since the epoch
865
        # in long integer microseconds.
866
        if microsecs is None:
1✔
867
            microsecs = long(round(t * 1000000.0))
1✔
868
        self._micros = microsecs
1✔
869

870
    def localZone(self, ltm=None):
1✔
871
        '''Returns the time zone on the given date.  The time zone
872
        can change according to daylight savings.'''
873
        if not _multipleZones:
1!
874
            return _localzone0
1✔
875
        if ltm is None:
×
876
            ltm = localtime(time())
×
877
        isDST = ltm[8]
×
878
        lz = isDST and _localzone1 or _localzone0
×
879
        return lz
×
880

881
    def _calcTimezoneName(self, x, ms):
1✔
882
        # Derive the name of the local time zone at the given
883
        # timezone-dependent second.
884
        if not _multipleZones:
1!
885
            return _localzone0
1✔
886
        fsetAtEpoch = _tzoffset(_localzone0, 0.0)
×
887
        nearTime = x - fsetAtEpoch - long(EPOCH) + 86400 + ms
×
888
        # nearTime is within an hour of being correct.
889
        try:
×
890
            ltm = safelocaltime(nearTime)
×
891
        except BaseException:
×
892
            # We are beyond the range of Python's date support.
893
            # Hopefully we can assume that daylight savings schedules
894
            # repeat every 28 years.  Calculate the name of the
895
            # time zone using a supported range of years.
896
            yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, 0)
×
897
            yr = ((yr - 1970) % 28) + 1970
×
898
            x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc)
×
899
            nearTime = x - fsetAtEpoch - long(EPOCH) + 86400 + ms
×
900

901
            # nearTime might still be negative if we are east of Greenwich.
902
            # But we can assume on 1969/12/31 were no timezone changes.
903
            nearTime = max(0, nearTime)
×
904

905
            ltm = safelocaltime(nearTime)
×
906
        tz = self.localZone(ltm)
×
907
        return tz
×
908

909
    def _parse(self, st, datefmt=getDefaultDateFormat()):
1✔
910
        # Parse date-time components from a string
911
        month = year = tz = tm = None
1✔
912
        ValidZones = _TZINFO._zidx
1✔
913
        TimeModifiers = ['am', 'pm']
1✔
914

915
        # Find timezone first, since it should always be the last
916
        # element, and may contain a slash, confusing the parser.
917
        st = st.strip()
1✔
918
        sp = st.split()
1✔
919
        tz = sp[-1]
1✔
920
        if tz and (tz.lower() in ValidZones):
1✔
921
            self._timezone_naive = False
1✔
922
            st = ' '.join(sp[:-1])
1✔
923
        else:
924
            self._timezone_naive = True
1✔
925
            tz = None  # Decide later, since the default time zone
1✔
926
        # could depend on the date.
927

928
        ints = []
1✔
929
        i = 0
1✔
930
        len_st = len(st)
1✔
931
        while i < len_st:
1✔
932
            while i < len_st and st[i] in SPACE_CHARS:
1✔
933
                i += 1
1✔
934
            if i < len_st and st[i] in DELIMITERS:
1✔
935
                d = st[i]
1✔
936
                i += 1
1✔
937
            else:
938
                d = ''
1✔
939
            while i < len_st and st[i] in SPACE_CHARS:
1✔
940
                i += 1
1✔
941

942
            # The float pattern needs to look back 1 character, because it
943
            # actually looks for a preceding colon like ':33.33'. This is
944
            # needed to avoid accidentally matching the date part of a
945
            # dot-separated date string such as '1999.12.31'.
946
            if i > 0:
1✔
947
                b = i - 1
1✔
948
            else:
949
                b = i
1✔
950

951
            ts_results = FLT_PATTERN.match(st, b)
1✔
952
            if ts_results:
1✔
953
                s = ts_results.group(1)
1✔
954
                i = i + len(s)
1✔
955
                ints.append(float(s))
1✔
956
                continue
1✔
957

958
            # AJ
959
            ts_results = INT_PATTERN.match(st, i)
1✔
960
            if ts_results:
1✔
961
                s = ts_results.group(0)
1✔
962

963
                ls = len(s)
1✔
964
                i = i + ls
1✔
965
                if (ls == 4 and d and d in '+-' and
1✔
966
                        (len(ints) + (not not month) >= 3)):
967
                    tz = '%s%s' % (d, s)
1✔
968
                else:
969
                    v = int(s)
1✔
970
                    ints.append(v)
1✔
971
                continue
1✔
972

973
            ts_results = NAME_PATTERN.match(st, i)
1✔
974
            if ts_results:
1!
975
                s = ts_results.group(0).lower()
1✔
976
                i = i + len(s)
1✔
977
                if i < len_st and st[i] == '.':
1!
978
                    i += 1
×
979
                # Check for month name:
980
                _v = _MONTHMAP.get(s)
1✔
981
                if _v is not None:
1✔
982
                    if month is None:
1!
983
                        month = _v
1✔
984
                    else:
985
                        raise SyntaxError(st)
×
986
                    continue
×
987
                # Check for time modifier:
988
                if s in TimeModifiers:
1✔
989
                    if tm is None:
1!
990
                        tm = s
1✔
991
                    else:
992
                        raise SyntaxError(st)
×
993
                    continue
×
994
                # Check for and skip day of week:
995
                if s in _DAYMAP:
1✔
996
                    continue
1✔
997

998
            raise SyntaxError(st)
1✔
999

1000
        day = None
1✔
1001
        if ints[-1] > 60 and d not in ('.', ':', '/') and len(ints) > 2:
1✔
1002
            year = ints[-1]
1✔
1003
            del ints[-1]
1✔
1004
            if month:
1✔
1005
                day = ints[0]
1✔
1006
                del ints[:1]
1✔
1007
            else:
1008
                if datefmt == "us":
1✔
1009
                    month = ints[0]
1✔
1010
                    day = ints[1]
1✔
1011
                else:
1012
                    month = ints[1]
1✔
1013
                    day = ints[0]
1✔
1014
                del ints[:2]
1✔
1015
        elif month:
1✔
1016
            if len(ints) > 1:
1!
1017
                if ints[0] > 31:
1!
1018
                    year = ints[0]
×
1019
                    day = ints[1]
×
1020
                else:
1021
                    year = ints[1]
1✔
1022
                    day = ints[0]
1✔
1023
                del ints[:2]
1✔
1024
        elif len(ints) > 2:
1!
1025
            if ints[0] > 31:
1✔
1026
                year = ints[0]
1✔
1027
                if ints[1] > 12:
1!
1028
                    day = ints[1]
×
1029
                    month = ints[2]
×
1030
                else:
1031
                    day = ints[2]
1✔
1032
                    month = ints[1]
1✔
1033
            if ints[1] > 31:
1!
1034
                year = ints[1]
×
1035
                if ints[0] > 12 and ints[2] <= 12:
×
1036
                    day = ints[0]
×
1037
                    month = ints[2]
×
1038
                elif ints[2] > 12 and ints[0] <= 12:
×
1039
                    day = ints[2]
×
1040
                    month = ints[0]
×
1041
            elif ints[2] > 31:
1✔
1042
                year = ints[2]
1✔
1043
                if ints[0] > 12:
1✔
1044
                    day = ints[0]
1✔
1045
                    month = ints[1]
1✔
1046
                else:
1047
                    if datefmt == "us":
1!
1048
                        day = ints[1]
×
1049
                        month = ints[0]
×
1050
                    else:
1051
                        day = ints[0]
1✔
1052
                        month = ints[1]
1✔
1053

1054
            elif ints[0] <= 12:
1!
1055
                month = ints[0]
×
1056
                day = ints[1]
×
1057
                year = ints[2]
×
1058
            del ints[:3]
1✔
1059

1060
        if day is None:
1!
1061
            # Use today's date.
1062
            year, month, day = localtime(time())[:3]
×
1063

1064
        year = _correctYear(year)
1✔
1065
        if year < 1000:
1!
1066
            raise SyntaxError(st)
×
1067

1068
        leap = year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
1✔
1069
        try:
1✔
1070
            if not day or day > _MONTH_LEN[leap][month]:
1!
1071
                raise DateError(st)
×
1072
        except IndexError:
×
1073
            raise DateError(st)
×
1074

1075
        tod = 0
1✔
1076
        if ints:
1✔
1077
            i = ints[0]
1✔
1078
            # Modify hour to reflect am/pm
1079
            if tm and (tm == 'pm') and i < 12:
1✔
1080
                i += 12
1✔
1081
            if tm and (tm == 'am') and i == 12:
1✔
1082
                i = 0
1✔
1083
            if i > 24:
1!
1084
                raise TimeError(st)
×
1085
            tod = tod + int(i) * 3600
1✔
1086
            del ints[0]
1✔
1087
            if ints:
1!
1088
                i = ints[0]
1✔
1089
                if i > 60:
1!
1090
                    raise TimeError(st)
×
1091
                tod = tod + int(i) * 60
1✔
1092
                del ints[0]
1✔
1093
                if ints:
1✔
1094
                    i = ints[0]
1✔
1095
                    if i > 60:
1!
1096
                        raise TimeError(st)
×
1097
                    tod = tod + i
1✔
1098
                    del ints[0]
1✔
1099
                    if ints:
1!
1100
                        raise SyntaxError(st)
×
1101

1102
        tod_int = int(math.floor(tod))
1✔
1103
        ms = tod - tod_int
1✔
1104
        hr, mn, sc = _calcHMS(tod_int, ms)
1✔
1105
        if not tz:
1✔
1106
            # Figure out what time zone it is in the local area
1107
            # on the given date.
1108
            x = _calcDependentSecond2(year, month, day, hr, mn, sc)
1✔
1109
            tz = self._calcTimezoneName(x, ms)
1✔
1110

1111
        return year, month, day, hr, mn, sc, tz
1✔
1112

1113
    # Internal methods
1114
    def _validDate(self, y, m, d):
1✔
1115
        if m < 1 or m > 12 or y < 0 or d < 1 or d > 31:
1!
1116
            return 0
×
1117
        return d <= _MONTH_LEN[
1✔
1118
            (y % 4 == 0 and (y % 100 != 0 or y % 400 == 0))][m]
1119

1120
    def _validTime(self, h, m, s):
1✔
1121
        return h >= 0 and h <= 23 and m >= 0 and m <= 59 and s >= 0 and s < 60
1✔
1122

1123
    def __getattr__(self, name):
1✔
1124
        if '%' in name:
1!
1125
            return strftimeFormatter(self, name)
×
1126
        raise AttributeError(name)
1✔
1127

1128
    # Conversion and comparison methods
1129

1130
    def timeTime(self):
1✔
1131
        """Return the date/time as a floating-point number in UTC,
1132
        in the format used by the python time module.
1133

1134
        Note that it is possible to create date/time values with
1135
        DateTime that have no meaningful value to the time module.
1136
        """
1137
        return self._micros / 1000000.0
1✔
1138

1139
    def toZone(self, z):
1✔
1140
        """Return a DateTime with the value as the current
1141
        object, represented in the indicated timezone.
1142
        """
1143
        t, tz = self._t, _TZINFO._zmap[z.lower()]
1✔
1144
        micros = self.micros()
1✔
1145
        tznaive = False  # you're performing a timzone change, can't be naive
1✔
1146

1147
        try:
1✔
1148
            # Try to use time module for speed.
1149
            yr, mo, dy, hr, mn, sc = safegmtime(t + _tzoffset(tz, t))[:6]
1✔
1150
            sc = self._second
1✔
1151
            return self.__class__(yr, mo, dy, hr, mn, sc, tz, t,
1✔
1152
                                  self._d, self.time, micros, tznaive)
1153
        except Exception:
×
1154
            # gmtime can't perform the calculation in the given range.
1155
            # Calculate the difference between the two time zones.
1156
            tzdiff = _tzoffset(tz, t) - _tzoffset(self._tz, t)
×
1157
            if tzdiff == 0:
×
1158
                return self
×
1159
            sc = self._second
×
1160
            ms = sc - math.floor(sc)
×
1161
            x = _calcDependentSecond2(self._year, self._month, self._day,
×
1162
                                      self._hour, self._minute, sc)
1163
            x_new = x + tzdiff
×
1164
            yr, mo, dy, hr, mn, sc = _calcYMDHMS(x_new, ms)
×
1165
            return self.__class__(yr, mo, dy, hr, mn, sc, tz, t,
×
1166
                                  self._d, self.time, micros, tznaive)
1167

1168
    def isFuture(self):
1✔
1169
        """Return true if this object represents a date/time
1170
        later than the time of the call.
1171
        """
1172
        return (self._t > time())
1✔
1173

1174
    def isPast(self):
1✔
1175
        """Return true if this object represents a date/time
1176
        earlier than the time of the call.
1177
        """
1178
        return (self._t < time())
1✔
1179

1180
    def isCurrentYear(self):
1✔
1181
        """Return true if this object represents a date/time
1182
        that falls within the current year, in the context
1183
        of this object\'s timezone representation.
1184
        """
1185
        t = time()
1✔
1186
        return safegmtime(t + _tzoffset(self._tz, t))[0] == self._year
1✔
1187

1188
    def isCurrentMonth(self):
1✔
1189
        """Return true if this object represents a date/time
1190
        that falls within the current month, in the context
1191
        of this object\'s timezone representation.
1192
        """
1193
        t = time()
1✔
1194
        gmt = safegmtime(t + _tzoffset(self._tz, t))
1✔
1195
        return gmt[0] == self._year and gmt[1] == self._month
1✔
1196

1197
    def isCurrentDay(self):
1✔
1198
        """Return true if this object represents a date/time
1199
        that falls within the current day, in the context
1200
        of this object\'s timezone representation.
1201
        """
1202
        t = time()
1✔
1203
        gmt = safegmtime(t + _tzoffset(self._tz, t))
1✔
1204
        return (gmt[0] == self._year and gmt[1] == self._month and
1✔
1205
                gmt[2] == self._day)
1206

1207
    def isCurrentHour(self):
1✔
1208
        """Return true if this object represents a date/time
1209
        that falls within the current hour, in the context
1210
        of this object\'s timezone representation.
1211
        """
1212
        t = time()
1✔
1213
        gmt = safegmtime(t + _tzoffset(self._tz, t))
1✔
1214
        return (gmt[0] == self._year and gmt[1] == self._month and
1✔
1215
                gmt[2] == self._day and gmt[3] == self._hour)
1216

1217
    def isCurrentMinute(self):
1✔
1218
        """Return true if this object represents a date/time
1219
        that falls within the current minute, in the context
1220
        of this object\'s timezone representation.
1221
        """
1222
        t = time()
1✔
1223
        gmt = safegmtime(t + _tzoffset(self._tz, t))
1✔
1224
        return (gmt[0] == self._year and gmt[1] == self._month and
1✔
1225
                gmt[2] == self._day and gmt[3] == self._hour and
1226
                gmt[4] == self._minute)
1227

1228
    def earliestTime(self):
1✔
1229
        """Return a new DateTime object that represents the earliest
1230
        possible time (in whole seconds) that still falls within
1231
        the current object\'s day, in the object\'s timezone context.
1232
        """
1233
        return self.__class__(
1✔
1234
            self._year, self._month, self._day, 0, 0, 0, self._tz)
1235

1236
    def latestTime(self):
1✔
1237
        """Return a new DateTime object that represents the latest
1238
        possible time (in whole seconds) that still falls within
1239
        the current object\'s day, in the object\'s timezone context.
1240
        """
1241
        return self.__class__(
1✔
1242
            self._year, self._month, self._day, 23, 59, 59, self._tz)
1243

1244
    def greaterThan(self, t):
1✔
1245
        """Compare this DateTime object to another DateTime object
1246
        OR a floating point number such as that which is returned
1247
        by the python time module.
1248

1249
        Returns true if the object represents a date/time greater
1250
        than the specified DateTime or time module style time.
1251

1252
        Revised to give more correct results through comparison of
1253
        long integer microseconds.
1254
        """
1255
        if t is None:
1✔
1256
            t = 0
1✔
1257
        if isinstance(t, float):
1✔
1258
            return self._micros > long(t * 1000000)
1✔
1259
        try:
1✔
1260
            return self._micros > t._micros
1✔
1261
        except AttributeError:
1✔
1262
            return self._micros > t
1✔
1263

1264
    __gt__ = greaterThan
1✔
1265

1266
    def greaterThanEqualTo(self, t):
1✔
1267
        """Compare this DateTime object to another DateTime object
1268
        OR a floating point number such as that which is returned
1269
        by the python time module.
1270

1271
        Returns true if the object represents a date/time greater
1272
        than or equal to the specified DateTime or time module style
1273
        time.
1274

1275
        Revised to give more correct results through comparison of
1276
        long integer microseconds.
1277
        """
1278
        if t is None:
1✔
1279
            t = 0
1✔
1280
        if isinstance(t, float):
1✔
1281
            return self._micros >= long(t * 1000000)
1✔
1282
        try:
1✔
1283
            return self._micros >= t._micros
1✔
1284
        except AttributeError:
1✔
1285
            return self._micros >= t
1✔
1286

1287
    __ge__ = greaterThanEqualTo
1✔
1288

1289
    def equalTo(self, t):
1✔
1290
        """Compare this DateTime object to another DateTime object
1291
        OR a floating point number such as that which is returned
1292
        by the python time module.
1293

1294
        Returns true if the object represents a date/time equal to
1295
        the specified DateTime or time module style time.
1296

1297
        Revised to give more correct results through comparison of
1298
        long integer microseconds.
1299
        """
1300
        if t is None:
1✔
1301
            t = 0
1✔
1302
        if isinstance(t, float):
1✔
1303
            return self._micros == long(t * 1000000)
1✔
1304
        try:
1✔
1305
            return self._micros == t._micros
1✔
1306
        except AttributeError:
1✔
1307
            return self._micros == t
1✔
1308

1309
    def notEqualTo(self, t):
1✔
1310
        """Compare this DateTime object to another DateTime object
1311
        OR a floating point number such as that which is returned
1312
        by the python time module.
1313

1314
        Returns true if the object represents a date/time not equal
1315
        to the specified DateTime or time module style time.
1316

1317
        Revised to give more correct results through comparison of
1318
        long integer microseconds.
1319
        """
1320
        return not self.equalTo(t)
1✔
1321

1322
    def __eq__(self, t):
1✔
1323
        """Compare this DateTime object to another DateTime object.
1324
        Return True if their internal state is the same. Two objects
1325
        representing the same time in different timezones are regared as
1326
        unequal. Use the equalTo method if you are only interested in them
1327
        refering to the same moment in time.
1328
        """
1329
        if not isinstance(t, DateTime):
1✔
1330
            return False
1✔
1331
        return (self._micros, self._tz) == (t._micros, t._tz)
1✔
1332

1333
    def __ne__(self, t):
1✔
1334
        return not self.__eq__(t)
1✔
1335

1336
    def lessThan(self, t):
1✔
1337
        """Compare this DateTime object to another DateTime object
1338
        OR a floating point number such as that which is returned
1339
        by the python time module.
1340

1341
        Returns true if the object represents a date/time less than
1342
        the specified DateTime or time module style time.
1343

1344
        Revised to give more correct results through comparison of
1345
        long integer microseconds.
1346
        """
1347
        if t is None:
1✔
1348
            t = 0
1✔
1349
        if isinstance(t, float):
1✔
1350
            return self._micros < long(t * 1000000)
1✔
1351
        try:
1✔
1352
            return self._micros < t._micros
1✔
1353
        except AttributeError:
1✔
1354
            return self._micros < t
1✔
1355

1356
    __lt__ = lessThan
1✔
1357

1358
    def lessThanEqualTo(self, t):
1✔
1359
        """Compare this DateTime object to another DateTime object
1360
        OR a floating point number such as that which is returned
1361
        by the python time module.
1362

1363
        Returns true if the object represents a date/time less than
1364
        or equal to the specified DateTime or time module style time.
1365

1366
        Revised to give more correct results through comparison of
1367
        long integer microseconds.
1368
        """
1369
        if t is None:
1✔
1370
            t = 0
1✔
1371
        if isinstance(t, float):
1✔
1372
            return self._micros <= long(t * 1000000)
1✔
1373
        try:
1✔
1374
            return self._micros <= t._micros
1✔
1375
        except AttributeError:
1✔
1376
            return self._micros <= t
1✔
1377

1378
    __le__ = lessThanEqualTo
1✔
1379

1380
    def isLeapYear(self):
1✔
1381
        """Return true if the current year (in the context of the
1382
        object\'s timezone) is a leap year.
1383
        """
1384
        return (self._year % 4 == 0 and
1✔
1385
                (self._year % 100 != 0 or self._year % 400 == 0))
1386

1387
    def dayOfYear(self):
1✔
1388
        """Return the day of the year, in context of the timezone
1389
        representation of the object.
1390
        """
1391
        d = int(self._d + (_tzoffset(self._tz, self._t) / 86400.0))
1✔
1392
        return int((d + jd1901) - _julianday(self._year, 1, 0))
1✔
1393

1394
    # Component access
1395
    def parts(self):
1✔
1396
        """Return a tuple containing the calendar year, month,
1397
        day, hour, minute second and timezone of the object.
1398
        """
1399
        return (self._year, self._month, self._day, self._hour,
1✔
1400
                self._minute, self._second, self._tz)
1401

1402
    def timezone(self):
1✔
1403
        """Return the timezone in which the object is represented."""
1404
        return self._tz
1✔
1405

1406
    def tzoffset(self):
1✔
1407
        """Return the timezone offset for the objects timezone."""
1408
        return _tzoffset(self._tz, self._t)
1✔
1409

1410
    def year(self):
1✔
1411
        """Return the calendar year of the object."""
1412
        return self._year
1✔
1413

1414
    def month(self):
1✔
1415
        """Return the month of the object as an integer."""
1416
        return self._month
1✔
1417

1418
    @property
1✔
1419
    def _fmon(self):
1✔
1420
        return _MONTHS[self._month]
1✔
1421

1422
    def Month(self):
1✔
1423
        """Return the full month name."""
1424
        return self._fmon
1✔
1425

1426
    @property
1✔
1427
    def _amon(self):
1✔
1428
        return _MONTHS_A[self._month]
1✔
1429

1430
    def aMonth(self):
1✔
1431
        """Return the abbreviated month name."""
1432
        return self._amon
1✔
1433

1434
    def Mon(self):
1✔
1435
        """Compatibility: see aMonth."""
1436
        return self._amon
×
1437

1438
    @property
1✔
1439
    def _pmon(self):
1✔
1440
        return _MONTHS_P[self._month]
1✔
1441

1442
    def pMonth(self):
1✔
1443
        """Return the abbreviated (with period) month name."""
1444
        return self._pmon
1✔
1445

1446
    def Mon_(self):
1✔
1447
        """Compatibility: see pMonth."""
1448
        return self._pmon
×
1449

1450
    def day(self):
1✔
1451
        """Return the integer day."""
1452
        return self._day
1✔
1453

1454
    @property
1✔
1455
    def _fday(self):
1✔
1456
        return _DAYS[self._dayoffset]
1✔
1457

1458
    def Day(self):
1✔
1459
        """Return the full name of the day of the week."""
1460
        return self._fday
1✔
1461

1462
    def DayOfWeek(self):
1✔
1463
        """Compatibility: see Day."""
1464
        return self._fday
×
1465

1466
    @property
1✔
1467
    def _aday(self):
1✔
1468
        return _DAYS_A[self._dayoffset]
1✔
1469

1470
    def aDay(self):
1✔
1471
        """Return the abbreviated name of the day of the week."""
1472
        return self._aday
1✔
1473

1474
    @property
1✔
1475
    def _pday(self):
1✔
1476
        return _DAYS_P[self._dayoffset]
1✔
1477

1478
    def pDay(self):
1✔
1479
        """Return the abbreviated (with period) name of the day of the week."""
1480
        return self._pday
1✔
1481

1482
    def Day_(self):
1✔
1483
        """Compatibility: see pDay."""
1484
        return self._pday
×
1485

1486
    def dow(self):
1✔
1487
        """Return the integer day of the week, where sunday is 0."""
1488
        return self._dayoffset
1✔
1489

1490
    def dow_1(self):
1✔
1491
        """Return the integer day of the week, where sunday is 1."""
1492
        return self._dayoffset + 1
1✔
1493

1494
    @property
1✔
1495
    def _pmhour(self):
1✔
1496
        hr = self._hour
1✔
1497
        if hr > 12:
1!
1498
            return hr - 12
1✔
1499
        return hr or 12
×
1500

1501
    def h_12(self):
1✔
1502
        """Return the 12-hour clock representation of the hour."""
1503
        return self._pmhour
1✔
1504

1505
    def h_24(self):
1✔
1506
        """Return the 24-hour clock representation of the hour."""
1507
        return self._hour
1✔
1508

1509
    @property
1✔
1510
    def _pm(self):
1✔
1511
        hr = self._hour
1✔
1512
        if hr >= 12:
1!
1513
            return 'pm'
1✔
1514
        return 'am'
×
1515

1516
    def ampm(self):
1✔
1517
        """Return the appropriate time modifier (am or pm)."""
1518
        return self._pm
1✔
1519

1520
    def hour(self):
1✔
1521
        """Return the 24-hour clock representation of the hour."""
1522
        return self._hour
1✔
1523

1524
    def minute(self):
1✔
1525
        """Return the minute."""
1526
        return self._minute
1✔
1527

1528
    def second(self):
1✔
1529
        """Return the second."""
1530
        return self._second
1✔
1531

1532
    def millis(self):
1✔
1533
        """Return the millisecond since the epoch in GMT."""
1534
        return self._micros // 1000
1✔
1535

1536
    def micros(self):
1✔
1537
        """Return the microsecond since the epoch in GMT."""
1538
        return self._micros
1✔
1539

1540
    def timezoneNaive(self):
1✔
1541
        """The python datetime module introduces the idea of distinguishing
1542
        between timezone aware and timezone naive datetime values. For lossless
1543
        conversion to and from datetime.datetime record if we record this
1544
        information using True / False. DateTime makes no distinction, when we
1545
        don't have any information we return None here.
1546
        """
1547
        try:
1✔
1548
            return self._timezone_naive
1✔
1549
        except AttributeError:
1✔
1550
            return None
1✔
1551

1552
    def strftime(self, format):
1✔
1553
        """Format the date/time using the *current timezone representation*."""
1554
        x = _calcDependentSecond2(self._year, self._month, self._day,
1✔
1555
                                  self._hour, self._minute, self._second)
1556
        ltz = self._calcTimezoneName(x, 0)
1✔
1557
        tzdiff = _tzoffset(ltz, self._t) - _tzoffset(self._tz, self._t)
1✔
1558
        zself = self + tzdiff / 86400.0
1✔
1559
        microseconds = int((zself._second - zself._nearsec) * 1000000)
1✔
1560
        unicode_format = False
1✔
1561
        if isinstance(format, explicit_unicode_type):
1!
1562
            format = format.encode('utf-8')
×
1563
            unicode_format = True
×
1564
        ds = datetime(zself._year, zself._month, zself._day, zself._hour,
1✔
1565
                      zself._minute, int(zself._nearsec),
1566
                      microseconds).strftime(format)
1567
        if unicode_format:
1!
1568
            return ds.decode('utf-8')
×
1569
        return ds
1✔
1570

1571
    # General formats from previous DateTime
1572
    def Date(self):
1✔
1573
        """Return the date string for the object."""
1574
        return "%s/%2.2d/%2.2d" % (self._year, self._month, self._day)
1✔
1575

1576
    def Time(self):
1✔
1577
        """Return the time string for an object to the nearest second."""
1578
        return '%2.2d:%2.2d:%2.2d' % (self._hour, self._minute, self._nearsec)
1✔
1579

1580
    def TimeMinutes(self):
1✔
1581
        """Return the time string for an object not showing seconds."""
1582
        return '%2.2d:%2.2d' % (self._hour, self._minute)
1✔
1583

1584
    def AMPM(self):
1✔
1585
        """Return the time string for an object to the nearest second."""
1586
        return '%2.2d:%2.2d:%2.2d %s' % (
1✔
1587
            self._pmhour, self._minute, self._nearsec, self._pm)
1588

1589
    def AMPMMinutes(self):
1✔
1590
        """Return the time string for an object not showing seconds."""
1591
        return '%2.2d:%2.2d %s' % (self._pmhour, self._minute, self._pm)
1✔
1592

1593
    def PreciseTime(self):
1✔
1594
        """Return the time string for the object."""
1595
        return '%2.2d:%2.2d:%06.3f' % (self._hour, self._minute, self._second)
1✔
1596

1597
    def PreciseAMPM(self):
1✔
1598
        """Return the time string for the object."""
1599
        return '%2.2d:%2.2d:%06.3f %s' % (
1✔
1600
            self._pmhour, self._minute, self._second, self._pm)
1601

1602
    def yy(self):
1✔
1603
        """Return calendar year as a 2 digit string."""
1604
        return str(self._year)[-2:]
1✔
1605

1606
    def mm(self):
1✔
1607
        """Return month as a 2 digit string."""
1608
        return '%02d' % self._month
1✔
1609

1610
    def dd(self):
1✔
1611
        """Return day as a 2 digit string."""
1612
        return '%02d' % self._day
1✔
1613

1614
    def rfc822(self):
1✔
1615
        """Return the date in RFC 822 format."""
1616
        tzoffset = _tzoffset2rfc822zone(_tzoffset(self._tz, self._t))
1✔
1617
        return '%s, %2.2d %s %d %2.2d:%2.2d:%2.2d %s' % (
1✔
1618
            self._aday, self._day, self._amon, self._year,
1619
            self._hour, self._minute, self._nearsec, tzoffset)
1620

1621
    # New formats
1622
    def fCommon(self):
1✔
1623
        """Return a string representing the object\'s value
1624
        in the format: March 1, 1997 1:45 pm.
1625
        """
1626
        return '%s %s, %4.4d %s:%2.2d %s' % (
1✔
1627
               self._fmon, self._day, self._year, self._pmhour,
1628
               self._minute, self._pm)
1629

1630
    def fCommonZ(self):
1✔
1631
        """Return a string representing the object\'s value
1632
        in the format: March 1, 1997 1:45 pm US/Eastern.
1633
        """
1634
        return '%s %s, %4.4d %d:%2.2d %s %s' % (
1✔
1635
               self._fmon, self._day, self._year, self._pmhour,
1636
               self._minute, self._pm, self._tz)
1637

1638
    def aCommon(self):
1✔
1639
        """Return a string representing the object\'s value
1640
        in the format: Mar 1, 1997 1:45 pm.
1641
        """
1642
        return '%s %s, %4.4d %s:%2.2d %s' % (
1✔
1643
               self._amon, self._day, self._year, self._pmhour,
1644
               self._minute, self._pm)
1645

1646
    def aCommonZ(self):
1✔
1647
        """Return a string representing the object\'s value
1648
        in the format: Mar 1, 1997 1:45 pm US/Eastern.
1649
        """
1650
        return '%s %s, %4.4d %d:%2.2d %s %s' % (
1✔
1651
               self._amon, self._day, self._year, self._pmhour,
1652
               self._minute, self._pm, self._tz)
1653

1654
    def pCommon(self):
1✔
1655
        """Return a string representing the object\'s value
1656
        in the format: Mar. 1, 1997 1:45 pm.
1657
        """
1658
        return '%s %s, %4.4d %s:%2.2d %s' % (
1✔
1659
               self._pmon, self._day, self._year, self._pmhour,
1660
               self._minute, self._pm)
1661

1662
    def pCommonZ(self):
1✔
1663
        """Return a string representing the object\'s value
1664
        in the format: Mar. 1, 1997 1:45 pm US/Eastern.
1665
        """
1666
        return '%s %s, %4.4d %d:%2.2d %s %s' % (
1✔
1667
               self._pmon, self._day, self._year, self._pmhour,
1668
               self._minute, self._pm, self._tz)
1669

1670
    def ISO(self):
1✔
1671
        """Return the object in ISO standard format.
1672

1673
        Note: this is *not* ISO 8601-format! See the ISO8601 and
1674
        HTML4 methods below for ISO 8601-compliant output.
1675

1676
        Dates are output as: YYYY-MM-DD HH:MM:SS
1677
        """
1678
        return "%.4d-%.2d-%.2d %.2d:%.2d:%.2d" % (
1✔
1679
            self._year, self._month, self._day,
1680
            self._hour, self._minute, self._second)
1681

1682
    def ISO8601(self):
1✔
1683
        """Return the object in ISO 8601-compatible format containing the
1684
        date, time with seconds-precision and the time zone identifier.
1685

1686
        See: http://www.w3.org/TR/NOTE-datetime
1687

1688
        Dates are output as: YYYY-MM-DDTHH:MM:SSTZD
1689
            T is a literal character.
1690
            TZD is Time Zone Designator, format +HH:MM or -HH:MM
1691

1692
        If the instance is timezone naive (it was not specified with a timezone
1693
        when it was constructed) then the timezone is omitted.
1694

1695
        The HTML4 method below offers the same formatting, but converts
1696
        to UTC before returning the value and sets the TZD "Z".
1697
        """
1698
        if self.timezoneNaive():
1✔
1699
            return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d" % (
1✔
1700
                self._year, self._month, self._day,
1701
                self._hour, self._minute, self._second)
1702
        tzoffset = _tzoffset2iso8601zone(_tzoffset(self._tz, self._t))
1✔
1703
        return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d%s" % (
1✔
1704
            self._year, self._month, self._day,
1705
            self._hour, self._minute, self._second, tzoffset)
1706

1707
    def HTML4(self):
1✔
1708
        """Return the object in the format used in the HTML4.0 specification,
1709
        one of the standard forms in ISO8601.
1710

1711
        See: http://www.w3.org/TR/NOTE-datetime
1712

1713
        Dates are output as: YYYY-MM-DDTHH:MM:SSZ
1714
           T, Z are literal characters.
1715
           The time is in UTC.
1716
        """
1717
        newdate = self.toZone('UTC')
1✔
1718
        return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2dZ" % (
1✔
1719
            newdate._year, newdate._month, newdate._day,
1720
            newdate._hour, newdate._minute, newdate._second)
1721

1722
    def asdatetime(self):
1✔
1723
        """Return a standard library datetime.datetime
1724
        """
1725
        tznaive = self.timezoneNaive()
1✔
1726
        if tznaive:
1✔
1727
            tzinfo = None
1✔
1728
        else:
1729
            tzinfo = _TZINFO[self._tz].tzinfo
1✔
1730
        second = int(self._second)
1✔
1731
        microsec = self.micros() % 1000000
1✔
1732
        dt = datetime(self._year, self._month, self._day, self._hour,
1✔
1733
                      self._minute, second, microsec, tzinfo)
1734
        return dt
1✔
1735

1736
    def utcdatetime(self):
1✔
1737
        """Convert the time to UTC then return a timezone naive datetime object
1738
        """
1739
        utc = self.toZone('UTC')
1✔
1740
        second = int(utc._second)
1✔
1741
        microsec = utc.micros() % 1000000
1✔
1742
        dt = datetime(utc._year, utc._month, utc._day, utc._hour,
1✔
1743
                      utc._minute, second, microsec)
1744
        return dt
1✔
1745

1746
    def __add__(self, other):
1✔
1747
        """A DateTime may be added to a number and a number may be
1748
        added to a DateTime;  two DateTimes cannot be added.
1749
        """
1750
        if hasattr(other, '_t'):
1✔
1751
            raise DateTimeError('Cannot add two DateTimes')
1✔
1752
        o = float(other)
1✔
1753
        tz = self._tz
1✔
1754
        omicros = round(o * 86400000000)
1✔
1755
        tmicros = self.micros() + omicros
1✔
1756
        t = tmicros / 1000000.0
1✔
1757
        d = (tmicros + long(EPOCH * 1000000)) / 86400000000.0
1✔
1758
        s = d - math.floor(d)
1✔
1759
        ms = t - math.floor(t)
1✔
1760
        x = _calcDependentSecond(tz, t)
1✔
1761
        yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms)
1✔
1762
        return self.__class__(yr, mo, dy, hr, mn, sc, self._tz,
1✔
1763
                              t, d, s, tmicros, self.timezoneNaive())
1764

1765
    __radd__ = __add__
1✔
1766

1767
    def __sub__(self, other):
1✔
1768
        """Either a DateTime or a number may be subtracted from a
1769
        DateTime, however, a DateTime may not be subtracted from
1770
        a number.
1771
        """
1772
        if hasattr(other, '_d'):
1✔
1773
            return (self.micros() - other.micros()) / 86400000000.0
1✔
1774
        else:
1775
            return self.__add__(-(other))
1✔
1776

1777
    def __repr__(self):
1✔
1778
        """Convert a DateTime to a string that looks like a Python
1779
        expression.
1780
        """
1781
        return '%s(\'%s\')' % (self.__class__.__name__, str(self))
1✔
1782

1783
    def __str__(self):
1✔
1784
        """Convert a DateTime to a string."""
1785
        y, m, d = self._year, self._month, self._day
1✔
1786
        h, mn, s, t = self._hour, self._minute, self._second, self._tz
1✔
1787
        if s == int(s):
1✔
1788
            # A whole number of seconds -- suppress milliseconds.
1789
            return '%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d %s' % (
1✔
1790
                y, m, d, h, mn, s, t)
1791
        else:
1792
            # s is already rounded to the nearest microsecond, and
1793
            # it's not a whole number of seconds.  Be sure to print
1794
            # 2 digits before the decimal point.
1795
            return '%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%06.6f %s' % (
1✔
1796
                y, m, d, h, mn, s, t)
1797

1798
    def __format__(self, fmt):
1✔
1799
        """Render a DateTime in an f-string."""
1800
        if not isinstance(fmt, str):
1!
1801
            raise TypeError("must be str, not %s" % type(fmt).__name__)
×
1802
        if len(fmt) != 0:
1✔
1803
            return self.strftime(fmt)
1✔
1804
        return str(self)
1✔
1805

1806
    def __hash__(self):
1✔
1807
        """Compute a hash value for a DateTime."""
1808
        return int(((self._year % 100 * 12 + self._month) * 31 +
1✔
1809
                    self._day + self.time) * 100)
1810

1811
    def __int__(self):
1✔
1812
        """Convert to an integer number of seconds since the epoch (gmt)."""
1813
        return int(self.micros() // 1000000)
1✔
1814

1815
    def __long__(self):
1✔
1816
        """Convert to a long-int number of seconds since the epoch (gmt)."""
1817
        return long(self.micros() // 1000000)  # pragma: PY2
1818

1819
    def __float__(self):
1✔
1820
        """Convert to floating-point number of seconds since the epoch (gmt).
1821
        """
1822
        return self.micros() / 1000000.0
1✔
1823

1824
    @property
1✔
1825
    def _t(self):
1✔
1826
        return self._micros / 1000000.0
1✔
1827

1828
    def _parse_iso8601(self, s):
1✔
1829
        # preserve the previously implied contract
1830
        # who know where this could be used...
1831
        return self._parse_iso8601_preserving_tznaive(s)[:7]
1✔
1832

1833
    def _parse_iso8601_preserving_tznaive(self, s):
1✔
1834
        try:
1✔
1835
            return self.__parse_iso8601(s)
1✔
1836
        except IndexError:
×
1837
            raise SyntaxError(
×
1838
                'Not an ISO 8601 compliant date string: "%s"' % s)
1839

1840
    def __parse_iso8601(self, s):
1✔
1841
        """Parse an ISO 8601 compliant date.
1842

1843
        See: http://en.wikipedia.org/wiki/ISO_8601
1844
        """
1845
        month = day = week_day = 1
1✔
1846
        year = hour = minute = seconds = hour_off = min_off = 0
1✔
1847
        tznaive = True
1✔
1848

1849
        iso8601 = iso8601Match(s.strip())
1✔
1850
        fields = iso8601 and iso8601.groupdict() or {}
1✔
1851
        if not iso8601 or fields.get('garbage'):
1!
1852
            raise IndexError
×
1853

1854
        if fields['year']:
1!
1855
            year = int(fields['year'])
1✔
1856
        if fields['month']:
1✔
1857
            month = int(fields['month'])
1✔
1858
        if fields['day']:
1✔
1859
            day = int(fields['day'])
1✔
1860

1861
        if fields['year_day']:
1✔
1862
            d = DateTime('%s-01-01' % year) + int(fields['year_day']) - 1
1✔
1863
            month = d.month()
1✔
1864
            day = d.day()
1✔
1865

1866
        if fields['week']:
1✔
1867
            week = int(fields['week'])
1✔
1868
            if fields['week_day']:
1✔
1869
                week_day = int(fields['week_day'])
1✔
1870
            d = DateTime('%s-01-04' % year)
1✔
1871
            d = d - (d.dow() + 6) % 7 + week * 7 + week_day - 8
1✔
1872
            month = d.month()
1✔
1873
            day = d.day()
1✔
1874

1875
        if fields['hour']:
1✔
1876
            hour = int(fields['hour'])
1✔
1877

1878
        if fields['minute']:
1✔
1879
            minute = int(fields['minute'])
1✔
1880
        elif fields['fraction']:
1✔
1881
            minute = 60.0 * float('0.%s' % fields['fraction'])
1✔
1882
            seconds, minute = math.modf(minute)
1✔
1883
            minute = int(minute)
1✔
1884
            seconds = 60.0 * seconds
1✔
1885
            # Avoid reprocess when handling seconds, bellow
1886
            fields['fraction'] = None
1✔
1887

1888
        if fields['second']:
1✔
1889
            seconds = int(fields['second'])
1✔
1890
            if fields['fraction']:
1✔
1891
                seconds = seconds + float('0.%s' % fields['fraction'])
1✔
1892
        elif fields['fraction']:
1✔
1893
            seconds = 60.0 * float('0.%s' % fields['fraction'])
1✔
1894

1895
        if fields['hour_off']:
1✔
1896
            hour_off = int(fields['hour_off'])
1✔
1897
            if fields['signal'] == '-':
1✔
1898
                hour_off *= -1
1✔
1899

1900
        if fields['min_off']:
1✔
1901
            min_off = int(fields['min_off'])
1✔
1902

1903
        if fields['signal'] or fields['Z']:
1✔
1904
            tznaive = False
1✔
1905
        else:
1906
            tznaive = True
1✔
1907

1908
        # Differ from the specification here. To preserve backwards
1909
        # compatibility assume a default timezone == UTC.
1910
        tz = 'GMT%+03d%02d' % (hour_off, min_off)
1✔
1911

1912
        return year, month, day, hour, minute, seconds, tz, tznaive
1✔
1913

1914
    def JulianDay(self):
1✔
1915
        """Return the Julian day.
1916

1917
        See: http://www.tondering.dk/claus/cal/node3.html#sec-calcjd
1918
        """
1919
        a = (14 - self._month) // 12
1✔
1920
        y = self._year + 4800 - a
1✔
1921
        m = self._month + (12 * a) - 3
1✔
1922
        return (self._day + (153 * m + 2) // 5 + 365 * y +
1✔
1923
                y // 4 - y // 100 + y // 400 - 32045)
1924

1925
    def week(self):
1✔
1926
        """Return the week number according to ISO.
1927

1928
        See: http://www.tondering.dk/claus/cal/node6.html
1929
        """
1930
        J = self.JulianDay()
1✔
1931
        d4 = (J + 31741 - (J % 7)) % 146097 % 36524 % 1461
1✔
1932
        L = d4 // 1460
1✔
1933
        d1 = ((d4 - L) % 365) + L
1✔
1934
        return d1 // 7 + 1
1✔
1935

1936
    def encode(self, out):
1✔
1937
        """Encode value for XML-RPC."""
1938
        out.write('<value><dateTime.iso8601>')
×
1939
        out.write(self.ISO8601())
×
1940
        out.write('</dateTime.iso8601></value>\n')
×
1941

1942

1943
# Provide the _dt_reconstructor function here, in case something
1944
# accidentally creates a reference to this function
1945

1946
orig_reconstructor = copy_reg._reconstructor
1✔
1947

1948

1949
def _dt_reconstructor(cls, base, state):
1✔
1950
    if cls is DateTime:
×
1951
        return cls(state)
×
1952
    return orig_reconstructor(cls, base, state)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc