12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940 |
- ##############################################################################
- #
- # Copyright (c) 2002 Zope Foundation and Contributors.
- #
- # This software is subject to the provisions of the Zope Public License,
- # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
- # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
- # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
- # FOR A PARTICULAR PURPOSE
- #
- ##############################################################################
- import math
- import re
- import sys
- from time import altzone
- from time import daylight
- from time import gmtime
- from time import localtime
- from time import time
- from time import timezone
- from time import tzname
- from datetime import datetime
- from zope.interface import implementer
- from .interfaces import IDateTime
- from .interfaces import DateTimeError
- from .interfaces import SyntaxError
- from .interfaces import DateError
- from .interfaces import TimeError
- from .pytz_support import PytzCache
- if sys.version_info > (3, ):
- import copyreg as copy_reg
- basestring = str
- long = int
- explicit_unicode_type = type(None)
- else:
- import copy_reg
- explicit_unicode_type = unicode
- default_datefmt = None
- def getDefaultDateFormat():
- global default_datefmt
- if default_datefmt is None:
- try:
- from App.config import getConfiguration
- default_datefmt = getConfiguration().datetime_format
- return default_datefmt
- except Exception:
- return 'us'
- else:
- return default_datefmt
- # To control rounding errors, we round system time to the nearest
- # microsecond. Then delicate calculations can rely on that the
- # maximum precision that needs to be preserved is known.
- _system_time = time
- def time():
- return round(_system_time(), 6)
- # Determine machine epoch
- tm = ((0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334),
- (0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335))
- yr, mo, dy, hr, mn, sc = gmtime(0)[:6]
- i = int(yr - 1)
- to_year = int(i * 365 + i // 4 - i // 100 + i // 400 - 693960.0)
- to_month = tm[yr % 4 == 0 and (yr % 100 != 0 or yr % 400 == 0)][mo]
- EPOCH = ((to_year + to_month + dy +
- (hr / 24.0 + mn / 1440.0 + sc / 86400.0)) * 86400)
- jd1901 = 2415385
- _TZINFO = PytzCache()
- INT_PATTERN = re.compile(r'([0-9]+)')
- FLT_PATTERN = re.compile(r':([0-9]+\.[0-9]+)')
- NAME_PATTERN = re.compile(r'([a-zA-Z]+)', re.I)
- SPACE_CHARS = ' \t\n'
- DELIMITERS = '-/.:,+'
- _MONTH_LEN = ((0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
- (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31))
- _MONTHS = ('', 'January', 'February', 'March', 'April', 'May', 'June',
- 'July', 'August', 'September', 'October', 'November', 'December')
- _MONTHS_A = ('', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
- 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')
- _MONTHS_P = ('', 'Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'June',
- 'July', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.')
- _MONTHMAP = {'january': 1, 'jan': 1,
- 'february': 2, 'feb': 2,
- 'march': 3, 'mar': 3,
- 'april': 4, 'apr': 4,
- 'may': 5,
- 'june': 6, 'jun': 6,
- 'july': 7, 'jul': 7,
- 'august': 8, 'aug': 8,
- 'september': 9, 'sep': 9, 'sept': 9,
- 'october': 10, 'oct': 10,
- 'november': 11, 'nov': 11,
- 'december': 12, 'dec': 12}
- _DAYS = ('Sunday', 'Monday', 'Tuesday', 'Wednesday',
- 'Thursday', 'Friday', 'Saturday')
- _DAYS_A = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat')
- _DAYS_P = ('Sun.', 'Mon.', 'Tue.', 'Wed.', 'Thu.', 'Fri.', 'Sat.')
- _DAYMAP = {'sunday': 1, 'sun': 1,
- 'monday': 2, 'mon': 2,
- 'tuesday': 3, 'tues': 3, 'tue': 3,
- 'wednesday': 4, 'wed': 4,
- 'thursday': 5, 'thurs': 5, 'thur': 5, 'thu': 5,
- 'friday': 6, 'fri': 6,
- 'saturday': 7, 'sat': 7}
- numericTimeZoneMatch = re.compile(r'[+-][0-9][0-9][0-9][0-9]').match
- iso8601Match = re.compile(r'''
- (?P<year>\d\d\d\d) # four digits year
- (?:-? # one optional dash
- (?: # followed by:
- (?P<year_day>\d\d\d # three digits year day
- (?!\d)) # when there is no fourth digit
- | # or:
- W # one W
- (?P<week>\d\d) # two digits week
- (?:-? # one optional dash
- (?P<week_day>\d) # one digit week day
- )? # week day is optional
- | # or:
- (?P<month>\d\d)? # two digits month
- (?:-? # one optional dash
- (?P<day>\d\d)? # two digits day
- )? # after day is optional
- ) #
- )? # after year is optional
- (?:[T ] # one T or one whitespace
- (?P<hour>\d\d) # two digits hour
- (?::? # one optional colon
- (?P<minute>\d\d)? # two digits minute
- (?::? # one optional colon
- (?P<second>\d\d)? # two digits second
- (?:[.,] # one dot or one comma
- (?P<fraction>\d+) # n digits fraction
- )? # after second is optional
- )? # after minute is optional
- )? # after hour is optional
- (?: # timezone:
- (?P<Z>Z) # one Z
- | # or:
- (?P<signal>[-+]) # one plus or one minus as signal
- (?P<hour_off>\d # one digit for hour offset...
- (?:\d(?!\d$) # ...or two, if not the last two digits
- )?) # second hour offset digit is optional
- (?::? # one optional colon
- (?P<min_off>\d\d) # two digits minute offset
- )? # after hour offset is optional
- )? # timezone is optional
- )? # time is optional
- (?P<garbage>.*) # store the extra garbage
- ''', re.VERBOSE).match
- def _findLocalTimeZoneName(isDST):
- if not daylight:
- # Daylight savings does not occur in this time zone.
- isDST = 0
- try:
- # Get the name of the current time zone depending
- # on DST.
- _localzone = PytzCache._zmap[tzname[isDST].lower()]
- except:
- try:
- # Generate a GMT-offset zone name.
- if isDST:
- localzone = altzone
- else:
- localzone = timezone
- offset = (-localzone / 3600.0)
- majorOffset = int(offset)
- if majorOffset != 0:
- minorOffset = abs(int((offset % majorOffset) * 60.0))
- else:
- minorOffset = 0
- m = majorOffset >= 0 and '+' or ''
- lz = '%s%0.02d%0.02d' % (m, majorOffset, minorOffset)
- _localzone = PytzCache._zmap[('GMT%s' % lz).lower()]
- except:
- _localzone = ''
- return _localzone
- _localzone0 = _findLocalTimeZoneName(0)
- _localzone1 = _findLocalTimeZoneName(1)
- _multipleZones = (_localzone0 != _localzone1)
- # Some utility functions for calculating dates:
- def _calcSD(t):
- # Returns timezone-independent days since epoch and the fractional
- # part of the days.
- dd = t + EPOCH - 86400.0
- d = dd / 86400.0
- s = d - math.floor(d)
- return s, d
- def _calcDependentSecond(tz, t):
- # Calculates the timezone-dependent second (integer part only)
- # from the timezone-independent second.
- fset = _tzoffset(tz, t)
- return fset + long(math.floor(t)) + long(EPOCH) - 86400
- def _calcDependentSecond2(yr, mo, dy, hr, mn, sc):
- # Calculates the timezone-dependent second (integer part only)
- # from the date given.
- ss = int(hr) * 3600 + int(mn) * 60 + int(sc)
- x = long(_julianday(yr, mo, dy) - jd1901) * 86400 + ss
- return x
- def _calcIndependentSecondEtc(tz, x, ms):
- # Derive the timezone-independent second from the timezone
- # dependent second.
- fsetAtEpoch = _tzoffset(tz, 0.0)
- nearTime = x - fsetAtEpoch - long(EPOCH) + 86400 + ms
- # nearTime is now within an hour of being correct.
- # Recalculate t according to DST.
- fset = long(_tzoffset(tz, nearTime))
- d = (x - fset) / 86400.0 + (ms / 86400.0)
- t = x - fset - long(EPOCH) + 86400 + ms
- micros = (x + 86400 - fset) * 1000000 + \
- long(round(ms * 1000000.0)) - long(EPOCH * 1000000.0)
- s = d - math.floor(d)
- return (s, d, t, micros)
- def _calcHMS(x, ms):
- # hours, minutes, seconds from integer and float.
- hr = x // 3600
- x = x - hr * 3600
- mn = x // 60
- sc = x - mn * 60 + ms
- return (hr, mn, sc)
- def _calcYMDHMS(x, ms):
- # x is a timezone-dependent integer of seconds.
- # Produces yr,mo,dy,hr,mn,sc.
- yr, mo, dy = _calendarday(x // 86400 + jd1901)
- x = int(x - (x // 86400) * 86400)
- hr = x // 3600
- x = x - hr * 3600
- mn = x // 60
- sc = x - mn * 60 + ms
- return (yr, mo, dy, hr, mn, sc)
- def _julianday(yr, mo, dy):
- y, m, d = long(yr), long(mo), long(dy)
- if m > 12:
- y = y + m // 12
- m = m % 12
- elif m < 1:
- m = -m
- y = y - m // 12 - 1
- m = 12 - m % 12
- if y > 0:
- yr_correct = 0
- else:
- yr_correct = 3
- if m < 3:
- y, m = y - 1, m + 12
- if y * 10000 + m * 100 + d > 15821014:
- b = 2 - y // 100 + y // 400
- else:
- b = 0
- return ((1461 * y - yr_correct) // 4 +
- 306001 * (m + 1) // 10000 + d + 1720994 + b)
- def _calendarday(j):
- j = long(j)
- if (j < 2299160):
- b = j + 1525
- else:
- a = (4 * j - 7468861) // 146097
- b = j + 1526 + a - a // 4
- c = (20 * b - 2442) // 7305
- d = 1461 * c // 4
- e = 10000 * (b - d) // 306001
- dy = int(b - d - 306001 * e // 10000)
- mo = (e < 14) and int(e - 1) or int(e - 13)
- yr = (mo > 2) and (c - 4716) or (c - 4715)
- return (int(yr), int(mo), int(dy))
- def _tzoffset(tz, t):
- """Returns the offset in seconds to GMT from a specific timezone (tz) at
- a specific time (t). NB! The _tzoffset result is the same same sign as
- the time zone, i.e. GMT+2 has a 7200 second offset. This is the opposite
- sign of time.timezone which (confusingly) is -7200 for GMT+2."""
- try:
- return _TZINFO[tz].info(t)[0]
- except Exception:
- if numericTimeZoneMatch(tz) is not None:
- return int(tz[0:3]) * 3600 + int(tz[0] + tz[3:5]) * 60
- else:
- return 0 # ??
- def _correctYear(year):
- # Y2K patch.
- if year >= 0 and year < 100:
- # 00-69 means 2000-2069, 70-99 means 1970-1999.
- if year < 70:
- year = 2000 + year
- else:
- year = 1900 + year
- return year
- def safegmtime(t):
- '''gmtime with a safety zone.'''
- try:
- return gmtime(t)
- except (ValueError, OverflowError):
- raise TimeError('The time %f is beyond the range of this Python '
- 'implementation.' % float(t))
- def safelocaltime(t):
- '''localtime with a safety zone.'''
- try:
- return localtime(t)
- except (ValueError, OverflowError):
- raise TimeError('The time %f is beyond the range of this Python '
- 'implementation.' % float(t))
- def _tzoffset2rfc822zone(seconds):
- """Takes an offset, such as from _tzoffset(), and returns an rfc822
- compliant zone specification. Please note that the result of
- _tzoffset() is the negative of what time.localzone and time.altzone is.
- """
- return "%+03d%02d" % divmod((seconds // 60), 60)
- def _tzoffset2iso8601zone(seconds):
- """Takes an offset, such as from _tzoffset(), and returns an ISO 8601
- compliant zone specification. Please note that the result of
- _tzoffset() is the negative of what time.localzone and time.altzone is.
- """
- return "%+03d:%02d" % divmod((seconds // 60), 60)
- def Timezones():
- """Return the list of recognized timezone names"""
- return sorted(list(PytzCache._zmap.values()))
- class strftimeFormatter(object):
- def __init__(self, dt, format):
- self.dt = dt
- self.format = format
- def __call__(self):
- return self.dt.strftime(self.format)
- @implementer(IDateTime)
- class DateTime(object):
- """DateTime objects represent instants in time and provide
- interfaces for controlling its representation without
- affecting the absolute value of the object.
- DateTime objects may be created from a wide variety of string
- or numeric data, or may be computed from other DateTime objects.
- DateTimes support the ability to convert their representations
- to many major timezones, as well as the ablility to create a
- DateTime object in the context of a given timezone.
- DateTime objects provide partial numerical behavior:
- - Two date-time objects can be subtracted to obtain a time,
- in days between the two.
- - A date-time object and a positive or negative number may
- be added to obtain a new date-time object that is the given
- number of days later than the input date-time object.
- - A positive or negative number and a date-time object may
- be added to obtain a new date-time object that is the given
- number of days later than the input date-time object.
- - A positive or negative number may be subtracted from a
- date-time object to obtain a new date-time object that is
- the given number of days earlier than the input date-time
- object.
- DateTime objects may be converted to integer, long, or float
- numbers of days since January 1, 1901, using the standard int,
- long, and float functions (Compatibility Note: int, long and
- float return the number of days since 1901 in GMT rather than
- local machine timezone). DateTime objects also provide access
- to their value in a float format usable with the python time
- module, provided that the value of the object falls in the
- range of the epoch-based time module, and as a datetime.datetime
- object.
- A DateTime object should be considered immutable; all conversion
- and numeric operations return a new DateTime object rather than
- modify the current object."""
- # For security machinery:
- __roles__ = None
- __allow_access_to_unprotected_subobjects__ = 1
- # Limit the amount of instance attributes
- __slots__ = (
- '_timezone_naive',
- '_tz',
- '_dayoffset',
- '_year',
- '_month',
- '_day',
- '_hour',
- '_minute',
- '_second',
- '_nearsec',
- '_d',
- '_micros',
- 'time',
- )
- def __init__(self, *args, **kw):
- """Return a new date-time object"""
- try:
- return self._parse_args(*args, **kw)
- except (DateError, TimeError, DateTimeError):
- raise
- except Exception:
- raise SyntaxError('Unable to parse %s, %s' % (args, kw))
- def __getstate__(self):
- # We store a float of _micros, instead of the _micros long, as we most
- # often don't have any sub-second resolution and can save those bytes
- return (self._micros / 1000000.0,
- getattr(self, '_timezone_naive', False),
- self._tz)
- def __setstate__(self, value):
- if isinstance(value, tuple):
- self._parse_args(value[0], value[2])
- self._micros = long(value[0] * 1000000)
- self._timezone_naive = value[1]
- else:
- for k, v in value.items():
- if k in self.__slots__:
- setattr(self, k, v)
- # BBB: support for very old DateTime pickles
- if '_micros' not in value:
- self._micros = long(value['_t'] * 1000000)
- if '_timezone_naive' not in value:
- self._timezone_naive = False
- def _parse_args(self, *args, **kw):
- """Return a new date-time object.
- A DateTime object always maintains its value as an absolute
- UTC time, and is represented in the context of some timezone
- based on the arguments used to create the object. A DateTime
- object's methods return values based on the timezone context.
- Note that in all cases the local machine timezone is used for
- representation if no timezone is specified.
- DateTimes may be created with from zero to seven arguments.
- - If the function is called with no arguments or with None,
- then the current date/time is returned, represented in the
- timezone of the local machine.
- - If the function is invoked with a single string argument
- which is a recognized timezone name, an object representing
- the current time is returned, represented in the specified
- timezone.
- - If the function is invoked with a single string argument
- representing a valid date/time, an object representing
- that date/time will be returned.
- As a general rule, any date-time representation that is
- recognized and unambigous to a resident of North America
- is acceptable. The reason for this qualification is that
- in North America, a date like: 2/1/1994 is interpreted
- as February 1, 1994, while in some parts of the world,
- it is interpreted as January 2, 1994.
- A date/time string consists of two components, a date
- component and an optional time component, separated by one
- or more spaces. If the time component is omited, 12:00am is
- assumed. Any recognized timezone name specified as the final
- element of the date/time string will be used for computing
- the date/time value. If you create a DateTime with the
- string 'Mar 9, 1997 1:45pm US/Pacific', the value will
- essentially be the same as if you had captured time.time()
- at the specified date and time on a machine in that timezone:
- <PRE>
- e=DateTime('US/Eastern')
- # returns current date/time, represented in US/Eastern.
- x=DateTime('1997/3/9 1:45pm')
- # returns specified time, represented in local machine zone.
- y=DateTime('Mar 9, 1997 13:45:00')
- # y is equal to x
- </PRE>
- The date component consists of year, month, and day
- values. The year value must be a one-, two-, or
- four-digit integer. If a one- or two-digit year is
- used, the year is assumed to be in the twentieth
- century. The month may be an integer, from 1 to 12, a
- month name, or a month abreviation, where a period may
- optionally follow the abreviation. The day must be an
- integer from 1 to the number of days in the month. The
- year, month, and day values may be separated by
- periods, hyphens, forward, shashes, or spaces. Extra
- spaces are permitted around the delimiters. Year,
- month, and day values may be given in any order as long
- as it is possible to distinguish the components. If all
- three components are numbers that are less than 13,
- then a a month-day-year ordering is assumed.
- The time component consists of hour, minute, and second
- values separated by colons. The hour value must be an
- integer between 0 and 23 inclusively. The minute value
- must be an integer between 0 and 59 inclusively. The
- second value may be an integer value between 0 and
- 59.999 inclusively. The second value or both the minute
- and second values may be ommitted. The time may be
- followed by am or pm in upper or lower case, in which
- case a 12-hour clock is assumed.
- New in Zope 2.4:
- The DateTime constructor automatically detects and handles
- ISO8601 compliant dates (YYYY-MM-DDThh:ss:mmTZD).
- New in Zope 2.9.6:
- The existing ISO8601 parser was extended to support almost
- the whole ISO8601 specification. New formats includes:
- <PRE>
- y=DateTime('1993-045')
- # returns the 45th day from 1993, which is 14th February
- w=DateTime('1993-W06-7')
- # returns the 7th day from the 6th week from 1993, which
- # is also 14th February
- </PRE>
- See http://en.wikipedia.org/wiki/ISO_8601 for full specs.
- Note that the Zope DateTime parser assumes timezone naive ISO
- strings to be in UTC rather than local time as specified.
- - If the DateTime function is invoked with a single Numeric
- argument, the number is assumed to be a floating point value
- such as that returned by time.time().
- A DateTime object is returned that represents the GMT value
- of the time.time() float represented in the local machine's
- timezone.
- - If the DateTime function is invoked with a single argument
- that is a DateTime instane, a copy of the passed object will
- be created.
- - New in 2.11:
- The DateTime function may now be invoked with a single argument
- that is a datetime.datetime instance. DateTimes may be converted
- back to datetime.datetime objects with asdatetime().
- DateTime instances may be converted to a timezone naive
- datetime.datetime in UTC with utcdatetime().
- - If the function is invoked with two numeric arguments, then
- the first is taken to be an integer year and the second
- argument is taken to be an offset in days from the beginning
- of the year, in the context of the local machine timezone.
- The date-time value returned is the given offset number of
- days from the beginning of the given year, represented in
- the timezone of the local machine. The offset may be positive
- or negative.
- Two-digit years are assumed to be in the twentieth
- century.
- - If the function is invoked with two arguments, the first
- a float representing a number of seconds past the epoch
- in gmt (such as those returned by time.time()) and the
- second a string naming a recognized timezone, a DateTime
- with a value of that gmt time will be returned, represented
- in the given timezone.
- <PRE>
- import time
- t=time.time()
- now_east=DateTime(t,'US/Eastern')
- # Time t represented as US/Eastern
- now_west=DateTime(t,'US/Pacific')
- # Time t represented as US/Pacific
- # now_east == now_west
- # only their representations are different
- </PRE>
- - If the function is invoked with three or more numeric
- arguments, then the first is taken to be an integer
- year, the second is taken to be an integer month, and
- the third is taken to be an integer day. If the
- combination of values is not valid, then a
- DateError is raised. Two-digit years are assumed
- to be in the twentieth century. The fourth, fifth, and
- sixth arguments specify a time in hours, minutes, and
- seconds; hours and minutes should be positive integers
- and seconds is a positive floating point value, all of
- these default to zero if not given. An optional string may
- be given as the final argument to indicate timezone (the
- effect of this is as if you had taken the value of time.time()
- at that time on a machine in the specified timezone).
- New in Zope 2.7:
- A new keyword parameter "datefmt" can be passed to the
- constructor. If set to "international", the constructor
- is forced to treat ambigious dates as "days before month
- before year". This useful if you need to parse non-US
- dates in a reliable way
- In any case that a floating point number of seconds is given
- or derived, it's rounded to the nearest millisecond.
- If a string argument passed to the DateTime constructor cannot be
- parsed, it will raise DateTime.SyntaxError. Invalid date components
- will raise a DateError, while invalid time or timezone components
- will raise a DateTimeError.
- The module function Timezones() will return a list of the (common)
- timezones recognized by the DateTime module. Recognition of
- timezone names is case-insensitive.
- """
- datefmt = kw.get('datefmt', getDefaultDateFormat())
- d = t = s = None
- ac = len(args)
- microsecs = None
- if ac == 10:
- # Internal format called only by DateTime
- yr, mo, dy, hr, mn, sc, tz, t, d, s = args
- elif ac == 11:
- # Internal format that includes milliseconds (from the epoch)
- yr, mo, dy, hr, mn, sc, tz, t, d, s, millisecs = args
- microsecs = millisecs * 1000
- elif ac == 12:
- # Internal format that includes microseconds (from the epoch) and a
- # flag indicating whether this was constructed in a timezone naive
- # manner
- yr, mo, dy, hr, mn, sc, tz, t, d, s, microsecs, tznaive = args
- if tznaive is not None: # preserve this information
- self._timezone_naive = tznaive
- elif not args or (ac and args[0] is None):
- # Current time, to be displayed in local timezone
- t = time()
- lt = safelocaltime(t)
- tz = self.localZone(lt)
- ms = (t - math.floor(t))
- s, d = _calcSD(t)
- yr, mo, dy, hr, mn, sc = lt[:6]
- sc = sc + ms
- self._timezone_naive = False
- elif ac == 1:
- arg = args[0]
- if arg == '':
- raise SyntaxError(arg)
- if isinstance(arg, DateTime):
- """Construct a new DateTime instance from a given
- DateTime instance.
- """
- t = arg.timeTime()
- s, d = _calcSD(t)
- yr, mo, dy, hr, mn, sc, tz = arg.parts()
- elif isinstance(arg, datetime):
- yr, mo, dy, hr, mn, sc, numerictz, tznaive = \
- self._parse_iso8601_preserving_tznaive(arg.isoformat())
- if arg.tzinfo is None:
- self._timezone_naive = True
- tz = None
- else:
- self._timezone_naive = False
- # if we have a pytz tzinfo, use the `zone` attribute
- # as a key
- tz = getattr(arg.tzinfo, 'zone', numerictz)
- ms = sc - math.floor(sc)
- x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc)
- if tz:
- try:
- zone = _TZINFO[tz]
- except DateTimeError:
- try:
- zone = _TZINFO[numerictz]
- except DateTimeError:
- raise DateTimeError(
- 'Unknown time zone in date: %s' % arg)
- tz = zone.tzinfo.zone
- else:
- tz = self._calcTimezoneName(x, ms)
- s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms)
- elif (isinstance(arg, basestring) and
- arg.lower() in _TZINFO._zidx):
- # Current time, to be displayed in specified timezone
- t, tz = time(), _TZINFO._zmap[arg.lower()]
- ms = (t - math.floor(t))
- # Use integer arithmetic as much as possible.
- s, d = _calcSD(t)
- x = _calcDependentSecond(tz, t)
- yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms)
- elif isinstance(arg, basestring):
- # Date/time string
- iso8601 = iso8601Match(arg.strip())
- fields_iso8601 = iso8601 and iso8601.groupdict() or {}
- if fields_iso8601 and not fields_iso8601.get('garbage'):
- yr, mo, dy, hr, mn, sc, tz, tznaive = \
- self._parse_iso8601_preserving_tznaive(arg)
- self._timezone_naive = tznaive
- else:
- yr, mo, dy, hr, mn, sc, tz = self._parse(arg, datefmt)
- if not self._validDate(yr, mo, dy):
- raise DateError('Invalid date: %s' % arg)
- if not self._validTime(hr, mn, int(sc)):
- raise TimeError('Invalid time: %s' % arg)
- ms = sc - math.floor(sc)
- x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc)
- if tz:
- try:
- tz = _TZINFO._zmap[tz.lower()]
- except KeyError:
- if numericTimeZoneMatch(tz) is None:
- raise DateTimeError(
- 'Unknown time zone in date: %s' % arg)
- else:
- tz = self._calcTimezoneName(x, ms)
- s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms)
- else:
- # Seconds from epoch, gmt
- t = arg
- lt = safelocaltime(t)
- tz = self.localZone(lt)
- ms = (t - math.floor(t))
- s, d = _calcSD(t)
- yr, mo, dy, hr, mn, sc = lt[:6]
- sc = sc + ms
- elif ac == 2:
- if isinstance(args[1], basestring):
- # Seconds from epoch (gmt) and timezone
- t, tz = args
- ms = (t - math.floor(t))
- try:
- tz = _TZINFO._zmap[tz.lower()]
- except KeyError:
- if numericTimeZoneMatch(tz) is None:
- raise DateTimeError('Unknown time zone: %s' % tz)
- # Use integer arithmetic as much as possible.
- s, d = _calcSD(t)
- x = _calcDependentSecond(tz, t)
- yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms)
- else:
- # Year, julian expressed in local zone
- t = time()
- lt = safelocaltime(t)
- tz = self.localZone(lt)
- yr, jul = args
- yr = _correctYear(yr)
- d = (_julianday(yr, 1, 0) - jd1901) + jul
- x_float = d * 86400.0
- x_floor = math.floor(x_float)
- ms = x_float - x_floor
- x = long(x_floor)
- yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms)
- s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms)
- else:
- # Explicit format
- yr, mo, dy = args[:3]
- hr, mn, sc, tz = 0, 0, 0, 0
- yr = _correctYear(yr)
- if not self._validDate(yr, mo, dy):
- raise DateError('Invalid date: %s' % (args, ))
- args = args[3:]
- if args:
- hr, args = args[0], args[1:]
- if args:
- mn, args = args[0], args[1:]
- if args:
- sc, args = args[0], args[1:]
- if args:
- tz, args = args[0], args[1:]
- if args:
- raise DateTimeError('Too many arguments')
- if not self._validTime(hr, mn, sc):
- raise TimeError('Invalid time: %s' % repr(args))
- x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc)
- ms = sc - math.floor(sc)
- if tz:
- try:
- tz = _TZINFO._zmap[tz.lower()]
- except KeyError:
- if numericTimeZoneMatch(tz) is None:
- raise DateTimeError('Unknown time zone: %s' % tz)
- else:
- # Get local time zone name
- tz = self._calcTimezoneName(x, ms)
- s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms)
- self._dayoffset = int((_julianday(yr, mo, dy) + 2) % 7)
- # Round to nearest microsecond in platform-independent way. You
- # cannot rely on C sprintf (Python '%') formatting to round
- # consistently; doing it ourselves ensures that all but truly
- # horrid C sprintf implementations will yield the same result
- # x-platform, provided the format asks for exactly 6 digits after
- # the decimal point.
- sc = round(sc, 6)
- if sc >= 60.0: # can happen if, e.g., orig sc was 59.9999999
- sc = 59.999999
- self._nearsec = math.floor(sc)
- self._year, self._month, self._day = yr, mo, dy
- self._hour, self._minute, self._second = hr, mn, sc
- self.time, self._d, self._tz = s, d, tz
- # self._micros is the time since the epoch
- # in long integer microseconds.
- if microsecs is None:
- microsecs = long(math.floor(t * 1000000.0))
- self._micros = microsecs
- def localZone(self, ltm=None):
- '''Returns the time zone on the given date. The time zone
- can change according to daylight savings.'''
- if not _multipleZones:
- return _localzone0
- if ltm is None:
- ltm = localtime(time())
- isDST = ltm[8]
- lz = isDST and _localzone1 or _localzone0
- return lz
- def _calcTimezoneName(self, x, ms):
- # Derive the name of the local time zone at the given
- # timezone-dependent second.
- if not _multipleZones:
- return _localzone0
- fsetAtEpoch = _tzoffset(_localzone0, 0.0)
- nearTime = x - fsetAtEpoch - long(EPOCH) + 86400 + ms
- # nearTime is within an hour of being correct.
- try:
- ltm = safelocaltime(nearTime)
- except:
- # We are beyond the range of Python's date support.
- # Hopefully we can assume that daylight savings schedules
- # repeat every 28 years. Calculate the name of the
- # time zone using a supported range of years.
- yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, 0)
- yr = ((yr - 1970) % 28) + 1970
- x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc)
- nearTime = x - fsetAtEpoch - long(EPOCH) + 86400 + ms
- # nearTime might still be negative if we are east of Greenwich.
- # But we can asume on 1969/12/31 were no timezone changes.
- nearTime = max(0, nearTime)
- ltm = safelocaltime(nearTime)
- tz = self.localZone(ltm)
- return tz
- def _parse(self, st, datefmt=getDefaultDateFormat()):
- # Parse date-time components from a string
- month = year = tz = tm = None
- ValidZones = _TZINFO._zidx
- TimeModifiers = ['am', 'pm']
- # Find timezone first, since it should always be the last
- # element, and may contain a slash, confusing the parser.
- st = st.strip()
- sp = st.split()
- tz = sp[-1]
- if tz and (tz.lower() in ValidZones):
- self._timezone_naive = False
- st = ' '.join(sp[:-1])
- else:
- self._timezone_naive = True
- tz = None # Decide later, since the default time zone
- # could depend on the date.
- ints = []
- i = 0
- l = len(st)
- while i < l:
- while i < l and st[i] in SPACE_CHARS:
- i += 1
- if i < l and st[i] in DELIMITERS:
- d = st[i]
- i += 1
- else:
- d = ''
- while i < l and st[i] in SPACE_CHARS:
- i += 1
- # The float pattern needs to look back 1 character, because it
- # actually looks for a preceding colon like ':33.33'. This is
- # needed to avoid accidentally matching the date part of a
- # dot-separated date string such as '1999.12.31'.
- if i > 0:
- b = i - 1
- else:
- b = i
- ts_results = FLT_PATTERN.match(st, b)
- if ts_results:
- s = ts_results.group(1)
- i = i + len(s)
- ints.append(float(s))
- continue
- #AJ
- ts_results = INT_PATTERN.match(st, i)
- if ts_results:
- s = ts_results.group(0)
- ls = len(s)
- i = i + ls
- if (ls == 4 and d and d in '+-' and
- (len(ints) + (not not month) >= 3)):
- tz = '%s%s' % (d, s)
- else:
- v = int(s)
- ints.append(v)
- continue
- ts_results = NAME_PATTERN.match(st, i)
- if ts_results:
- s = ts_results.group(0).lower()
- i = i + len(s)
- if i < l and st[i] == '.':
- i += 1
- # Check for month name:
- _v = _MONTHMAP.get(s)
- if _v is not None:
- if month is None:
- month = _v
- else:
- raise SyntaxError(st)
- continue
- # Check for time modifier:
- if s in TimeModifiers:
- if tm is None:
- tm = s
- else:
- raise SyntaxError(st)
- continue
- # Check for and skip day of week:
- if s in _DAYMAP:
- continue
- raise SyntaxError(st)
- day = None
- if ints[-1] > 60 and d not in ('.', ':', '/') and len(ints) > 2:
- year = ints[-1]
- del ints[-1]
- if month:
- day = ints[0]
- del ints[:1]
- else:
- if datefmt == "us":
- month = ints[0]
- day = ints[1]
- else:
- month = ints[1]
- day = ints[0]
- del ints[:2]
- elif month:
- if len(ints) > 1:
- if ints[0] > 31:
- year = ints[0]
- day = ints[1]
- else:
- year = ints[1]
- day = ints[0]
- del ints[:2]
- elif len(ints) > 2:
- if ints[0] > 31:
- year = ints[0]
- if ints[1] > 12:
- day = ints[1]
- month = ints[2]
- else:
- day = ints[2]
- month = ints[1]
- if ints[1] > 31:
- year = ints[1]
- if ints[0] > 12 and ints[2] <= 12:
- day = ints[0]
- month = ints[2]
- elif ints[2] > 12 and ints[0] <= 12:
- day = ints[2]
- month = ints[0]
- elif ints[2] > 31:
- year = ints[2]
- if ints[0] > 12:
- day = ints[0]
- month = ints[1]
- else:
- if datefmt == "us":
- day = ints[1]
- month = ints[0]
- else:
- day = ints[0]
- month = ints[1]
- elif ints[0] <= 12:
- month = ints[0]
- day = ints[1]
- year = ints[2]
- del ints[:3]
- if day is None:
- # Use today's date.
- year, month, day = localtime(time())[:3]
- year = _correctYear(year)
- if year < 1000:
- raise SyntaxError(st)
- leap = year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
- try:
- if not day or day > _MONTH_LEN[leap][month]:
- raise DateError(st)
- except IndexError:
- raise DateError(st)
- tod = 0
- if ints:
- i = ints[0]
- # Modify hour to reflect am/pm
- if tm and (tm == 'pm') and i < 12:
- i += 12
- if tm and (tm == 'am') and i == 12:
- i = 0
- if i > 24:
- raise TimeError(st)
- tod = tod + int(i) * 3600
- del ints[0]
- if ints:
- i = ints[0]
- if i > 60:
- raise TimeError(st)
- tod = tod + int(i) * 60
- del ints[0]
- if ints:
- i = ints[0]
- if i > 60:
- raise TimeError(st)
- tod = tod + i
- del ints[0]
- if ints:
- raise SyntaxError(st)
- tod_int = int(math.floor(tod))
- ms = tod - tod_int
- hr, mn, sc = _calcHMS(tod_int, ms)
- if not tz:
- # Figure out what time zone it is in the local area
- # on the given date.
- x = _calcDependentSecond2(year, month, day, hr, mn, sc)
- tz = self._calcTimezoneName(x, ms)
- return year, month, day, hr, mn, sc, tz
- # Internal methods
- def _validDate(self, y, m, d):
- if m < 1 or m > 12 or y < 0 or d < 1 or d > 31:
- return 0
- return d <= _MONTH_LEN[
- (y % 4 == 0 and (y % 100 != 0 or y % 400 == 0))][m]
- def _validTime(self, h, m, s):
- return h >= 0 and h <= 23 and m >= 0 and m <= 59 and s >= 0 and s < 60
- def __getattr__(self, name):
- if '%' in name:
- return strftimeFormatter(self, name)
- raise AttributeError(name)
- # Conversion and comparison methods
- def timeTime(self):
- """Return the date/time as a floating-point number in UTC,
- in the format used by the python time module.
- Note that it is possible to create date/time values with
- DateTime that have no meaningful value to the time module.
- """
- return self._micros / 1000000.0
- def toZone(self, z):
- """Return a DateTime with the value as the current
- object, represented in the indicated timezone.
- """
- t, tz = self._t, _TZINFO._zmap[z.lower()]
- micros = self.micros()
- tznaive = False # you're performing a timzone change, can't be naive
- try:
- # Try to use time module for speed.
- yr, mo, dy, hr, mn, sc = safegmtime(t + _tzoffset(tz, t))[:6]
- sc = self._second
- return self.__class__(yr, mo, dy, hr, mn, sc, tz, t,
- self._d, self.time, micros, tznaive)
- except Exception:
- # gmtime can't perform the calculation in the given range.
- # Calculate the difference between the two time zones.
- tzdiff = _tzoffset(tz, t) - _tzoffset(self._tz, t)
- if tzdiff == 0:
- return self
- sc = self._second
- ms = sc - math.floor(sc)
- x = _calcDependentSecond2(self._year, self._month, self._day,
- self._hour, self._minute, sc)
- x_new = x + tzdiff
- yr, mo, dy, hr, mn, sc = _calcYMDHMS(x_new, ms)
- return self.__class__(yr, mo, dy, hr, mn, sc, tz, t,
- self._d, self.time, micros, tznaive)
- def isFuture(self):
- """Return true if this object represents a date/time
- later than the time of the call.
- """
- return (self._t > time())
- def isPast(self):
- """Return true if this object represents a date/time
- earlier than the time of the call.
- """
- return (self._t < time())
- def isCurrentYear(self):
- """Return true if this object represents a date/time
- that falls within the current year, in the context
- of this object\'s timezone representation.
- """
- t = time()
- return safegmtime(t + _tzoffset(self._tz, t))[0] == self._year
- def isCurrentMonth(self):
- """Return true if this object represents a date/time
- that falls within the current month, in the context
- of this object\'s timezone representation.
- """
- t = time()
- gmt = safegmtime(t + _tzoffset(self._tz, t))
- return gmt[0] == self._year and gmt[1] == self._month
- def isCurrentDay(self):
- """Return true if this object represents a date/time
- that falls within the current day, in the context
- of this object\'s timezone representation.
- """
- t = time()
- gmt = safegmtime(t + _tzoffset(self._tz, t))
- return (gmt[0] == self._year and gmt[1] == self._month and
- gmt[2] == self._day)
- def isCurrentHour(self):
- """Return true if this object represents a date/time
- that falls within the current hour, in the context
- of this object\'s timezone representation.
- """
- t = time()
- gmt = safegmtime(t + _tzoffset(self._tz, t))
- return (gmt[0] == self._year and gmt[1] == self._month and
- gmt[2] == self._day and gmt[3] == self._hour)
- def isCurrentMinute(self):
- """Return true if this object represents a date/time
- that falls within the current minute, in the context
- of this object\'s timezone representation.
- """
- t = time()
- gmt = safegmtime(t + _tzoffset(self._tz, t))
- return (gmt[0] == self._year and gmt[1] == self._month and
- gmt[2] == self._day and gmt[3] == self._hour and
- gmt[4] == self._minute)
- def earliestTime(self):
- """Return a new DateTime object that represents the earliest
- possible time (in whole seconds) that still falls within
- the current object\'s day, in the object\'s timezone context.
- """
- return self.__class__(
- self._year, self._month, self._day, 0, 0, 0, self._tz)
- def latestTime(self):
- """Return a new DateTime object that represents the latest
- possible time (in whole seconds) that still falls within
- the current object\'s day, in the object\'s timezone context.
- """
- return self.__class__(
- self._year, self._month, self._day, 23, 59, 59, self._tz)
- def greaterThan(self, t):
- """Compare this DateTime object to another DateTime object
- OR a floating point number such as that which is returned
- by the python time module.
- Returns true if the object represents a date/time greater
- than the specified DateTime or time module style time.
- Revised to give more correct results through comparison of
- long integer microseconds.
- """
- if t is None:
- t = 0
- if isinstance(t, float):
- return self._micros > long(t * 1000000)
- try:
- return self._micros > t._micros
- except AttributeError:
- return self._micros > t
- __gt__ = greaterThan
- def greaterThanEqualTo(self, t):
- """Compare this DateTime object to another DateTime object
- OR a floating point number such as that which is returned
- by the python time module.
- Returns true if the object represents a date/time greater
- than or equal to the specified DateTime or time module style
- time.
- Revised to give more correct results through comparison of
- long integer microseconds.
- """
- if t is None:
- t = 0
- if isinstance(t, float):
- return self._micros >= long(t * 1000000)
- try:
- return self._micros >= t._micros
- except AttributeError:
- return self._micros >= t
- __ge__ = greaterThanEqualTo
- def equalTo(self, t):
- """Compare this DateTime object to another DateTime object
- OR a floating point number such as that which is returned
- by the python time module.
- Returns true if the object represents a date/time equal to
- the specified DateTime or time module style time.
- Revised to give more correct results through comparison of
- long integer microseconds.
- """
- if t is None:
- t = 0
- if isinstance(t, float):
- return self._micros == long(t * 1000000)
- try:
- return self._micros == t._micros
- except AttributeError:
- return self._micros == t
- def notEqualTo(self, t):
- """Compare this DateTime object to another DateTime object
- OR a floating point number such as that which is returned
- by the python time module.
- Returns true if the object represents a date/time not equal
- to the specified DateTime or time module style time.
- Revised to give more correct results through comparison of
- long integer microseconds.
- """
- return not self.equalTo(t)
- def __eq__(self, t):
- """Compare this DateTime object to another DateTime object.
- Return True if their internal state is the same. Two objects
- representing the same time in different timezones are regared as
- unequal. Use the equalTo method if you are only interested in them
- refering to the same moment in time.
- """
- if not isinstance(t, DateTime):
- return False
- return (self._micros, self._tz) == (t._micros, t._tz)
- def __ne__(self, t):
- return not self.__eq__(t)
- def lessThan(self, t):
- """Compare this DateTime object to another DateTime object
- OR a floating point number such as that which is returned
- by the python time module.
- Returns true if the object represents a date/time less than
- the specified DateTime or time module style time.
- Revised to give more correct results through comparison of
- long integer microseconds.
- """
- if t is None:
- t = 0
- if isinstance(t, float):
- return self._micros < long(t * 1000000)
- try:
- return self._micros < t._micros
- except AttributeError:
- return self._micros < t
- __lt__ = lessThan
- def lessThanEqualTo(self, t):
- """Compare this DateTime object to another DateTime object
- OR a floating point number such as that which is returned
- by the python time module.
- Returns true if the object represents a date/time less than
- or equal to the specified DateTime or time module style time.
- Revised to give more correct results through comparison of
- long integer microseconds.
- """
- if t is None:
- t = 0
- if isinstance(t, float):
- return self._micros <= long(t * 1000000)
- try:
- return self._micros <= t._micros
- except AttributeError:
- return self._micros <= t
- __le__ = lessThanEqualTo
- def isLeapYear(self):
- """Return true if the current year (in the context of the
- object\'s timezone) is a leap year.
- """
- return (self._year % 4 == 0 and
- (self._year % 100 != 0 or self._year % 400 == 0))
- def dayOfYear(self):
- """Return the day of the year, in context of the timezone
- representation of the object.
- """
- d = int(self._d + (_tzoffset(self._tz, self._t) / 86400.0))
- return int((d + jd1901) - _julianday(self._year, 1, 0))
- # Component access
- def parts(self):
- """Return a tuple containing the calendar year, month,
- day, hour, minute second and timezone of the object.
- """
- return (self._year, self._month, self._day, self._hour,
- self._minute, self._second, self._tz)
- def timezone(self):
- """Return the timezone in which the object is represented."""
- return self._tz
- def tzoffset(self):
- """Return the timezone offset for the objects timezone."""
- return _tzoffset(self._tz, self._t)
- def year(self):
- """Return the calendar year of the object."""
- return self._year
- def month(self):
- """Return the month of the object as an integer."""
- return self._month
- @property
- def _fmon(self):
- return _MONTHS[self._month]
- def Month(self):
- """Return the full month name."""
- return self._fmon
- @property
- def _amon(self):
- return _MONTHS_A[self._month]
- def aMonth(self):
- """Return the abreviated month name."""
- return self._amon
- def Mon(self):
- """Compatibility: see aMonth."""
- return self._amon
- @property
- def _pmon(self):
- return _MONTHS_P[self._month]
- def pMonth(self):
- """Return the abreviated (with period) month name."""
- return self._pmon
- def Mon_(self):
- """Compatibility: see pMonth."""
- return self._pmon
- def day(self):
- """Return the integer day."""
- return self._day
- @property
- def _fday(self):
- return _DAYS[self._dayoffset]
- def Day(self):
- """Return the full name of the day of the week."""
- return self._fday
- def DayOfWeek(self):
- """Compatibility: see Day."""
- return self._fday
- @property
- def _aday(self):
- return _DAYS_A[self._dayoffset]
- def aDay(self):
- """Return the abreviated name of the day of the week."""
- return self._aday
- @property
- def _pday(self):
- return _DAYS_P[self._dayoffset]
- def pDay(self):
- """Return the abreviated (with period) name of the day of the week."""
- return self._pday
- def Day_(self):
- """Compatibility: see pDay."""
- return self._pday
- def dow(self):
- """Return the integer day of the week, where sunday is 0."""
- return self._dayoffset
- def dow_1(self):
- """Return the integer day of the week, where sunday is 1."""
- return self._dayoffset + 1
- @property
- def _pmhour(self):
- hr = self._hour
- if hr > 12:
- return hr - 12
- return hr or 12
- def h_12(self):
- """Return the 12-hour clock representation of the hour."""
- return self._pmhour
- def h_24(self):
- """Return the 24-hour clock representation of the hour."""
- return self._hour
- @property
- def _pm(self):
- hr = self._hour
- if hr >= 12:
- return 'pm'
- return 'am'
- def ampm(self):
- """Return the appropriate time modifier (am or pm)."""
- return self._pm
- def hour(self):
- """Return the 24-hour clock representation of the hour."""
- return self._hour
- def minute(self):
- """Return the minute."""
- return self._minute
- def second(self):
- """Return the second."""
- return self._second
- def millis(self):
- """Return the millisecond since the epoch in GMT."""
- return self._micros // 1000
- def micros(self):
- """Return the microsecond since the epoch in GMT."""
- return self._micros
- def timezoneNaive(self):
- """The python datetime module introduces the idea of distinguishing
- between timezone aware and timezone naive datetime values. For lossless
- conversion to and from datetime.datetime record if we record this
- information using True / False. DateTime makes no distinction, when we
- don't have any information we return None here.
- """
- try:
- return self._timezone_naive
- except AttributeError:
- return None
- def strftime(self, format):
- """Format the date/time using the *current timezone representation*."""
- x = _calcDependentSecond2(self._year, self._month, self._day,
- self._hour, self._minute, self._second)
- ltz = self._calcTimezoneName(x, 0)
- tzdiff = _tzoffset(ltz, self._t) - _tzoffset(self._tz, self._t)
- zself = self + tzdiff / 86400.0
- microseconds = int((zself._second - zself._nearsec) * 1000000)
- unicode_format = False
- if isinstance(format, explicit_unicode_type):
- format = format.encode('utf-8')
- unicode_format = True
- ds = datetime(zself._year, zself._month, zself._day, zself._hour,
- zself._minute, int(zself._nearsec),
- microseconds).strftime(format)
- if unicode_format:
- return ds.decode('utf-8')
- return ds
- # General formats from previous DateTime
- def Date(self):
- """Return the date string for the object."""
- return "%s/%2.2d/%2.2d" % (self._year, self._month, self._day)
- def Time(self):
- """Return the time string for an object to the nearest second."""
- return '%2.2d:%2.2d:%2.2d' % (self._hour, self._minute, self._nearsec)
- def TimeMinutes(self):
- """Return the time string for an object not showing seconds."""
- return '%2.2d:%2.2d' % (self._hour, self._minute)
- def AMPM(self):
- """Return the time string for an object to the nearest second."""
- return '%2.2d:%2.2d:%2.2d %s' % (
- self._pmhour, self._minute, self._nearsec, self._pm)
- def AMPMMinutes(self):
- """Return the time string for an object not showing seconds."""
- return '%2.2d:%2.2d %s' % (self._pmhour, self._minute, self._pm)
- def PreciseTime(self):
- """Return the time string for the object."""
- return '%2.2d:%2.2d:%06.3f' % (self._hour, self._minute, self._second)
- def PreciseAMPM(self):
- """Return the time string for the object."""
- return '%2.2d:%2.2d:%06.3f %s' % (
- self._pmhour, self._minute, self._second, self._pm)
- def yy(self):
- """Return calendar year as a 2 digit string."""
- return str(self._year)[-2:]
- def mm(self):
- """Return month as a 2 digit string."""
- return '%02d' % self._month
- def dd(self):
- """Return day as a 2 digit string."""
- return '%02d' % self._day
- def rfc822(self):
- """Return the date in RFC 822 format."""
- tzoffset = _tzoffset2rfc822zone(_tzoffset(self._tz, self._t))
- return '%s, %2.2d %s %d %2.2d:%2.2d:%2.2d %s' % (
- self._aday, self._day, self._amon, self._year,
- self._hour, self._minute, self._nearsec, tzoffset)
- # New formats
- def fCommon(self):
- """Return a string representing the object\'s value
- in the format: March 1, 1997 1:45 pm.
- """
- return '%s %s, %4.4d %s:%2.2d %s' % (
- self._fmon, self._day, self._year, self._pmhour,
- self._minute, self._pm)
- def fCommonZ(self):
- """Return a string representing the object\'s value
- in the format: March 1, 1997 1:45 pm US/Eastern.
- """
- return '%s %s, %4.4d %d:%2.2d %s %s' % (
- self._fmon, self._day, self._year, self._pmhour,
- self._minute, self._pm, self._tz)
- def aCommon(self):
- """Return a string representing the object\'s value
- in the format: Mar 1, 1997 1:45 pm.
- """
- return '%s %s, %4.4d %s:%2.2d %s' % (
- self._amon, self._day, self._year, self._pmhour,
- self._minute, self._pm)
- def aCommonZ(self):
- """Return a string representing the object\'s value
- in the format: Mar 1, 1997 1:45 pm US/Eastern.
- """
- return '%s %s, %4.4d %d:%2.2d %s %s' % (
- self._amon, self._day, self._year, self._pmhour,
- self._minute, self._pm, self._tz)
- def pCommon(self):
- """Return a string representing the object\'s value
- in the format: Mar. 1, 1997 1:45 pm.
- """
- return '%s %s, %4.4d %s:%2.2d %s' % (
- self._pmon, self._day, self._year, self._pmhour,
- self._minute, self._pm)
- def pCommonZ(self):
- """Return a string representing the object\'s value
- in the format: Mar. 1, 1997 1:45 pm US/Eastern.
- """
- return '%s %s, %4.4d %d:%2.2d %s %s' % (
- self._pmon, self._day, self._year, self._pmhour,
- self._minute, self._pm, self._tz)
- def ISO(self):
- """Return the object in ISO standard format.
- Note: this is *not* ISO 8601-format! See the ISO8601 and
- HTML4 methods below for ISO 8601-compliant output.
- Dates are output as: YYYY-MM-DD HH:MM:SS
- """
- return "%.4d-%.2d-%.2d %.2d:%.2d:%.2d" % (
- self._year, self._month, self._day,
- self._hour, self._minute, self._second)
- def ISO8601(self):
- """Return the object in ISO 8601-compatible format containing the
- date, time with seconds-precision and the time zone identifier.
- See: http://www.w3.org/TR/NOTE-datetime
- Dates are output as: YYYY-MM-DDTHH:MM:SSTZD
- T is a literal character.
- TZD is Time Zone Designator, format +HH:MM or -HH:MM
- If the instance is timezone naive (it was not specified with a timezone
- when it was constructed) then the timezone is ommitted.
- The HTML4 method below offers the same formatting, but converts
- to UTC before returning the value and sets the TZD "Z".
- """
- if self.timezoneNaive():
- return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d" % (
- self._year, self._month, self._day,
- self._hour, self._minute, self._second)
- tzoffset = _tzoffset2iso8601zone(_tzoffset(self._tz, self._t))
- return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d%s" % (
- self._year, self._month, self._day,
- self._hour, self._minute, self._second, tzoffset)
- def HTML4(self):
- """Return the object in the format used in the HTML4.0 specification,
- one of the standard forms in ISO8601.
- See: http://www.w3.org/TR/NOTE-datetime
- Dates are output as: YYYY-MM-DDTHH:MM:SSZ
- T, Z are literal characters.
- The time is in UTC.
- """
- newdate = self.toZone('UTC')
- return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2dZ" % (
- newdate._year, newdate._month, newdate._day,
- newdate._hour, newdate._minute, newdate._second)
- def asdatetime(self):
- """Return a standard libary datetime.datetime
- """
- tznaive = self.timezoneNaive()
- if tznaive:
- tzinfo = None
- else:
- tzinfo = _TZINFO[self._tz].tzinfo
- second = int(self._second)
- microsec = self.micros() % 1000000
- dt = datetime(self._year, self._month, self._day, self._hour,
- self._minute, second, microsec, tzinfo)
- return dt
- def utcdatetime(self):
- """Convert the time to UTC then return a timezone naive datetime object
- """
- utc = self.toZone('UTC')
- second = int(utc._second)
- microsec = utc.micros() % 1000000
- dt = datetime(utc._year, utc._month, utc._day, utc._hour,
- utc._minute, second, microsec)
- return dt
- def __add__(self, other):
- """A DateTime may be added to a number and a number may be
- added to a DateTime; two DateTimes cannot be added.
- """
- if hasattr(other, '_t'):
- raise DateTimeError('Cannot add two DateTimes')
- o = float(other)
- tz = self._tz
- omicros = round(o * 86400000000)
- tmicros = self.micros() + omicros
- t = tmicros / 1000000.0
- d = (tmicros + long(EPOCH * 1000000)) / 86400000000.0
- s = d - math.floor(d)
- ms = t - math.floor(t)
- x = _calcDependentSecond(tz, t)
- yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms)
- return self.__class__(yr, mo, dy, hr, mn, sc, self._tz,
- t, d, s, None, self.timezoneNaive())
- __radd__ = __add__
- def __sub__(self, other):
- """Either a DateTime or a number may be subtracted from a
- DateTime, however, a DateTime may not be subtracted from
- a number.
- """
- if hasattr(other, '_d'):
- return (self.micros() - other.micros()) / 86400000000.0
- else:
- return self.__add__(-(other))
- def __repr__(self):
- """Convert a DateTime to a string that looks like a Python
- expression.
- """
- return '%s(\'%s\')' % (self.__class__.__name__, str(self))
- def __str__(self):
- """Convert a DateTime to a string."""
- y, m, d = self._year, self._month, self._day
- h, mn, s, t = self._hour, self._minute, self._second, self._tz
- if s == int(s):
- # A whole number of seconds -- suppress milliseconds.
- return '%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d %s' % (
- y, m, d, h, mn, s, t)
- else:
- # s is already rounded to the nearest microsecond, and
- # it's not a whole number of seconds. Be sure to print
- # 2 digits before the decimal point.
- return '%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%06.6f %s' % (
- y, m, d, h, mn, s, t)
- def __hash__(self):
- """Compute a hash value for a DateTime."""
- return int(((self._year % 100 * 12 + self._month) * 31 +
- self._day + self.time) * 100)
- def __int__(self):
- """Convert to an integer number of seconds since the epoch (gmt)."""
- return int(self.micros() // 1000000)
- def __long__(self):
- """Convert to a long-int number of seconds since the epoch (gmt)."""
- return long(self.micros() // 1000000)
- def __float__(self):
- """Convert to floating-point number of seconds since the epoch (gmt).
- """
- return self.micros() / 1000000.0
- @property
- def _t(self):
- return self._micros / 1000000.0
- def _parse_iso8601(self, s):
- # preserve the previously implied contract
- # who know where this could be used...
- return self._parse_iso8601_preserving_tznaive(s)[:7]
- def _parse_iso8601_preserving_tznaive(self, s):
- try:
- return self.__parse_iso8601(s)
- except IndexError:
- raise SyntaxError(
- 'Not an ISO 8601 compliant date string: "%s"' % s)
- def __parse_iso8601(self, s):
- """Parse an ISO 8601 compliant date.
- See: http://en.wikipedia.org/wiki/ISO_8601
- """
- month = day = week_day = 1
- year = hour = minute = seconds = hour_off = min_off = 0
- tznaive = True
- iso8601 = iso8601Match(s.strip())
- fields = iso8601 and iso8601.groupdict() or {}
- if not iso8601 or fields.get('garbage'):
- raise IndexError
- if fields['year']:
- year = int(fields['year'])
- if fields['month']:
- month = int(fields['month'])
- if fields['day']:
- day = int(fields['day'])
- if fields['year_day']:
- d = DateTime('%s-01-01' % year) + int(fields['year_day']) - 1
- month = d.month()
- day = d.day()
- if fields['week']:
- week = int(fields['week'])
- if fields['week_day']:
- week_day = int(fields['week_day'])
- d = DateTime('%s-01-04' % year)
- d = d - (d.dow() + 6) % 7 + week * 7 + week_day - 8
- month = d.month()
- day = d.day()
- if fields['hour']:
- hour = int(fields['hour'])
- if fields['minute']:
- minute = int(fields['minute'])
- elif fields['fraction']:
- minute = 60.0 * float('0.%s' % fields['fraction'])
- seconds, minute = math.modf(minute)
- minute = int(minute)
- seconds = 60.0 * seconds
- # Avoid reprocess when handling seconds, bellow
- fields['fraction'] = None
- if fields['second']:
- seconds = int(fields['second'])
- if fields['fraction']:
- seconds = seconds + float('0.%s' % fields['fraction'])
- elif fields['fraction']:
- seconds = 60.0 * float('0.%s' % fields['fraction'])
- if fields['hour_off']:
- hour_off = int(fields['hour_off'])
- if fields['signal'] == '-':
- hour_off *= -1
- if fields['min_off']:
- min_off = int(fields['min_off'])
- if fields['signal'] or fields['Z']:
- tznaive = False
- else:
- tznaive = True
- # Differ from the specification here. To preserve backwards
- # compatibility assume a default timezone == UTC.
- tz = 'GMT%+03d%02d' % (hour_off, min_off)
- return year, month, day, hour, minute, seconds, tz, tznaive
- def JulianDay(self):
- """Return the Julian day.
- See: http://www.tondering.dk/claus/cal/node3.html#sec-calcjd
- """
- a = (14 - self._month) // 12
- y = self._year + 4800 - a
- m = self._month + (12 * a) - 3
- return (self._day + (153 * m + 2) // 5 + 365 * y +
- y // 4 - y // 100 + y // 400 - 32045)
- def week(self):
- """Return the week number according to ISO.
- See: http://www.tondering.dk/claus/cal/node6.html
- """
- J = self.JulianDay()
- d4 = (J + 31741 - (J % 7)) % 146097 % 36524 % 1461
- L = d4 // 1460
- d1 = ((d4 - L) % 365) + L
- return d1 // 7 + 1
- def encode(self, out):
- """Encode value for XML-RPC."""
- out.write('<value><dateTime.iso8601>')
- out.write(self.ISO8601())
- out.write('</dateTime.iso8601></value>\n')
- # Provide the _dt_reconstructor function here, in case something
- # accidentally creates a reference to this function
- orig_reconstructor = copy_reg._reconstructor
- def _dt_reconstructor(cls, base, state):
- if cls is DateTime:
- return cls(state)
- return orig_reconstructor(cls, base, state)
|