annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/site-packages/DateTime/DateTime.py @ 68:5028fdace37b

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