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