annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/site-packages/DateTime/DateTime.py @ 69:33d812a61356

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