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