1
##############################################################################
3
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
6
# This software is subject to the provisions of the Zope Public License,
7
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
8
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11
# FOR A PARTICULAR PURPOSE.
13
##############################################################################
14
"""Commonly used utility functions.
16
Encapsulation of date/time values
18
$Id: __init__.py 68931 2006-06-30 19:25:53Z hdima $
22
import time as _time # there is a method definition that makes just "time"
23
# problematic while executing a class definition
25
from types import StringTypes
28
from time import tzname
30
tzname = ('UNKNOWN', 'UNKNOWN')
33
# These are needed because the various date formats below must
34
# be in english per the RFCs. That means we can't use strftime,
35
# which is affected by different locale settings.
36
weekday_abbr = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
37
weekday_full = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
38
'Friday', 'Saturday', 'Sunday']
39
monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
40
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
43
def iso8601_date(ts=None):
44
# Return an ISO 8601 formatted date string, required
45
# for certain DAV properties.
46
# '2000-11-10T16:21:09-08:00
49
return _time.strftime('%Y-%m-%dT%H:%M:%SZ', _time.gmtime(ts))
51
def rfc850_date(ts=None):
52
# Return an HTTP-date formatted date string.
53
# 'Friday, 10-Nov-00 16:21:09 GMT'
56
year, month, day, hh, mm, ss, wd, y, z = _time.gmtime(ts)
57
return "%s, %02d-%3s-%2s %02d:%02d:%02d GMT" % (
59
day, monthname[month],
63
def rfc1123_date(ts=None):
64
# Return an RFC 1123 format date string, required for
65
# use in HTTP Date headers per the HTTP 1.1 spec.
66
# 'Fri, 10 Nov 2000 16:21:09 GMT'
69
year, month, day, hh, mm, ss, wd, y, z = _time.gmtime(ts)
70
return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (weekday_abbr[wd],
71
day, monthname[month],
77
from zope.datetime.timezones import historical_zone_info as _data
79
class DateTimeError(Exception): "Date-time error"
80
class DateError(DateTimeError): 'Invalid Date Components'
81
class TimeError(DateTimeError): 'Invalid Time Components'
82
class SyntaxError(DateTimeError): 'Invalid Date-Time String'
84
# Determine machine epoch
85
tm=((0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334),
86
(0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335))
87
yr,mo,dy,hr,mn,sc = _time.gmtime(0)[:6]
89
to_year =int(i*365+i/4-i/100+i/400-693960.0)
90
to_month=tm[yr%4==0 and (yr%100!=0 or yr%400==0)][mo]
91
EPOCH =(to_year+to_month+dy+(hr/24.0+mn/1440.0+sc/86400.0))*86400
95
numericTimeZoneMatch=re.compile(r'[+-][0-9][0-9][0-9][0-9]').match #TS
98
def __init__(self,data):
99
self.name,self.timect,self.typect, \
100
self.ttrans,self.tindex,self.tinfo,self.az=data
102
def default_index(self):
103
if self.timect == 0: return 0
104
for i in range(self.typect):
105
if self.tinfo[i][1] == 0: return i
108
def index(self, t=None):
109
t = t or _time.time()
112
elif t < self.ttrans[0]:
113
i = self.default_index()
114
idx = (i, ord(self.tindex[0]),i)
115
elif t >= self.ttrans[-1]:
117
idx=(ord(self.tindex[-1]),ord(self.tindex[-1]),
118
ord(self.tindex[-2]))
120
idx=(ord(self.tindex[-1]),ord(self.tindex[-1]),
121
self.default_index())
123
for i in range(self.timect-1):
124
if t < self.ttrans[i+1]:
125
if i==0: idx=(ord(self.tindex[0]),ord(self.tindex[1]),
126
self.default_index())
127
else: idx=(ord(self.tindex[i]),ord(self.tindex[i+1]),
128
ord(self.tindex[i-1]))
132
def info(self,t=None):
134
zs =self.az[self.tinfo[idx][2]:]
135
return self.tinfo[idx][0],self.tinfo[idx][1],zs[: zs.find('\000')]
142
_zlst=['Brazil/Acre','Brazil/DeNoronha','Brazil/East',
143
'Brazil/West','Canada/Atlantic','Canada/Central',
144
'Canada/Eastern','Canada/East-Saskatchewan',
145
'Canada/Mountain','Canada/Newfoundland',
146
'Canada/Pacific','Canada/Yukon',
147
'Chile/Continental','Chile/EasterIsland','CST','Cuba',
148
'Egypt','EST','GB-Eire','Greenwich','Hongkong','Iceland',
149
'Iran','Israel','Jamaica','Japan','Mexico/BajaNorte',
150
'Mexico/BajaSur','Mexico/General','MST','Poland','PST',
151
'Singapore','Turkey','Universal','US/Alaska','US/Aleutian',
152
'US/Arizona','US/Central','US/Eastern','US/East-Indiana',
153
'US/Hawaii','US/Indiana-Starke','US/Michigan',
154
'US/Mountain','US/Pacific','US/Samoa','UTC','UCT','GMT',
156
'GMT+0100','GMT+0200','GMT+0300','GMT+0400','GMT+0500',
157
'GMT+0600','GMT+0700','GMT+0800','GMT+0900','GMT+1000',
158
'GMT+1100','GMT+1200','GMT+1300','GMT-0100','GMT-0200',
159
'GMT-0300','GMT-0400','GMT-0500','GMT-0600','GMT-0700',
160
'GMT-0800','GMT-0900','GMT-1000','GMT-1100','GMT-1200',
163
'GMT+0130', 'GMT+0230', 'GMT+0330', 'GMT+0430', 'GMT+0530',
164
'GMT+0630', 'GMT+0730', 'GMT+0830', 'GMT+0930', 'GMT+1030',
165
'GMT+1130', 'GMT+1230',
167
'GMT-0130', 'GMT-0230', 'GMT-0330', 'GMT-0430', 'GMT-0530',
168
'GMT-0630', 'GMT-0730', 'GMT-0830', 'GMT-0930', 'GMT-1030',
169
'GMT-1130', 'GMT-1230',
171
'UT','BST','MEST','SST','FST','WADT','EADT','NZDT',
172
'WET','WAT','AT','AST','NT','IDLW','CET','MET',
173
'MEWT','SWT','FWT','EET','EEST','BT','ZP4','ZP5','ZP6',
174
'WAST','CCT','JST','EAST','GST','NZT','NZST','IDLE']
177
_zmap={'aest':'GMT+1000', 'aedt':'GMT+1100',
178
'aus eastern standard time':'GMT+1000',
179
'sydney standard time':'GMT+1000',
180
'tasmania standard time':'GMT+1000',
181
'e. australia standard time':'GMT+1000',
182
'aus central standard time':'GMT+0930',
183
'cen. australia standard time':'GMT+0930',
184
'w. australia standard time':'GMT+0800',
186
'brazil/acre':'Brazil/Acre',
187
'brazil/denoronha':'Brazil/Denoronha',
188
'brazil/east':'Brazil/East','brazil/west':'Brazil/West',
189
'canada/atlantic':'Canada/Atlantic',
190
'canada/central':'Canada/Central',
191
'canada/eastern':'Canada/Eastern',
192
'canada/east-saskatchewan':'Canada/East-Saskatchewan',
193
'canada/mountain':'Canada/Mountain',
194
'canada/newfoundland':'Canada/Newfoundland',
195
'canada/pacific':'Canada/Pacific','canada/yukon':'Canada/Yukon',
196
'central europe standard time':'GMT+0100',
197
'chile/continental':'Chile/Continental',
198
'chile/easterisland':'Chile/EasterIsland',
199
'cst':'US/Central','cuba':'Cuba','est':'US/Eastern','egypt':'Egypt',
200
'eastern standard time':'US/Eastern',
201
'us eastern standard time':'US/Eastern',
202
'central standard time':'US/Central',
203
'mountain standard time':'US/Mountain',
204
'pacific standard time':'US/Pacific',
205
'gb-eire':'GB-Eire','gmt':'GMT',
207
'gmt+0000':'GMT+0', 'gmt+0':'GMT+0',
210
'gmt+0100':'GMT+1', 'gmt+0200':'GMT+2', 'gmt+0300':'GMT+3',
211
'gmt+0400':'GMT+4', 'gmt+0500':'GMT+5', 'gmt+0600':'GMT+6',
212
'gmt+0700':'GMT+7', 'gmt+0800':'GMT+8', 'gmt+0900':'GMT+9',
213
'gmt+1000':'GMT+10','gmt+1100':'GMT+11','gmt+1200':'GMT+12',
215
'gmt-0100':'GMT-1', 'gmt-0200':'GMT-2', 'gmt-0300':'GMT-3',
216
'gmt-0400':'GMT-4', 'gmt-0500':'GMT-5', 'gmt-0600':'GMT-6',
217
'gmt-0700':'GMT-7', 'gmt-0800':'GMT-8', 'gmt-0900':'GMT-9',
218
'gmt-1000':'GMT-10','gmt-1100':'GMT-11','gmt-1200':'GMT-12',
220
'gmt+1': 'GMT+1', 'gmt+2': 'GMT+2', 'gmt+3': 'GMT+3',
221
'gmt+4': 'GMT+4', 'gmt+5': 'GMT+5', 'gmt+6': 'GMT+6',
222
'gmt+7': 'GMT+7', 'gmt+8': 'GMT+8', 'gmt+9': 'GMT+9',
223
'gmt+10':'GMT+10','gmt+11':'GMT+11','gmt+12':'GMT+12',
225
'gmt-1': 'GMT-1', 'gmt-2': 'GMT-2', 'gmt-3': 'GMT-3',
226
'gmt-4': 'GMT-4', 'gmt-5': 'GMT-5', 'gmt-6': 'GMT-6',
227
'gmt-7': 'GMT-7', 'gmt-8': 'GMT-8', 'gmt-9': 'GMT-9',
228
'gmt-10':'GMT-10','gmt-11':'GMT-11','gmt-12':'GMT-12',
230
'gmt+130':'GMT+0130', 'gmt+0130':'GMT+0130',
231
'gmt+230':'GMT+0230', 'gmt+0230':'GMT+0230',
232
'gmt+330':'GMT+0330', 'gmt+0330':'GMT+0330',
233
'gmt+430':'GMT+0430', 'gmt+0430':'GMT+0430',
234
'gmt+530':'GMT+0530', 'gmt+0530':'GMT+0530',
235
'gmt+630':'GMT+0630', 'gmt+0630':'GMT+0630',
236
'gmt+730':'GMT+0730', 'gmt+0730':'GMT+0730',
237
'gmt+830':'GMT+0830', 'gmt+0830':'GMT+0830',
238
'gmt+930':'GMT+0930', 'gmt+0930':'GMT+0930',
239
'gmt+1030':'GMT+1030',
240
'gmt+1130':'GMT+1130',
241
'gmt+1230':'GMT+1230',
243
'gmt-130':'GMT-0130', 'gmt-0130':'GMT-0130',
244
'gmt-230':'GMT-0230', 'gmt-0230':'GMT-0230',
245
'gmt-330':'GMT-0330', 'gmt-0330':'GMT-0330',
246
'gmt-430':'GMT-0430', 'gmt-0430':'GMT-0430',
247
'gmt-530':'GMT-0530', 'gmt-0530':'GMT-0530',
248
'gmt-630':'GMT-0630', 'gmt-0630':'GMT-0630',
249
'gmt-730':'GMT-0730', 'gmt-0730':'GMT-0730',
250
'gmt-830':'GMT-0830', 'gmt-0830':'GMT-0830',
251
'gmt-930':'GMT-0930', 'gmt-0930':'GMT-0930',
252
'gmt-1030':'GMT-1030',
253
'gmt-1130':'GMT-1130',
254
'gmt-1230':'GMT-1230',
256
'greenwich':'Greenwich','hongkong':'Hongkong',
257
'iceland':'Iceland','iran':'Iran','israel':'Israel',
258
'jamaica':'Jamaica','japan':'Japan',
259
'mexico/bajanorte':'Mexico/BajaNorte',
260
'mexico/bajasur':'Mexico/BajaSur','mexico/general':'Mexico/General',
261
'mst':'US/Mountain','pst':'US/Pacific','poland':'Poland',
262
'singapore':'Singapore','turkey':'Turkey','universal':'Universal',
263
'utc':'Universal','uct':'Universal','us/alaska':'US/Alaska',
264
'us/aleutian':'US/Aleutian','us/arizona':'US/Arizona',
265
'us/central':'US/Central','us/eastern':'US/Eastern',
266
'us/east-indiana':'US/East-Indiana','us/hawaii':'US/Hawaii',
267
'us/indiana-starke':'US/Indiana-Starke','us/michigan':'US/Michigan',
268
'us/mountain':'US/Mountain','us/pacific':'US/Pacific',
269
'us/samoa':'US/Samoa',
272
'bst':'GMT+1', 'mest':'GMT+2', 'sst':'GMT+2',
273
'fst':'GMT+2', 'wadt':'GMT+8', 'eadt':'GMT+11', 'nzdt':'GMT+13',
274
'wet':'GMT', 'wat':'GMT-1', 'at':'GMT-2', 'ast':'GMT-4',
275
'nt':'GMT-11', 'idlw':'GMT-12', 'cet':'GMT+1', 'cest':'GMT+2',
277
'mewt':'GMT+1', 'swt':'GMT+1', 'fwt':'GMT+1', 'eet':'GMT+2',
279
'bt':'GMT+3', 'zp4':'GMT+4', 'zp5':'GMT+5', 'zp6':'GMT+6',
280
'wast':'GMT+7', 'cct':'GMT+8', 'jst':'GMT+9', 'east':'GMT+10',
281
'gst':'GMT+10', 'nzt':'GMT+12', 'nzst':'GMT+12', 'idle':'GMT+12',
287
self._d, self._zidx= {}, self._zmap.keys()
289
def __getitem__(self,k):
290
try: n=self._zmap[k.lower()]
292
if numericTimeZoneMatch(k) == None:
293
raise DateTimeError('Unrecognized timezone: %s' % k)
298
z = self._d[n] = _timezone(self._db[n])
301
def _findLocalTimeZoneName(isDST):
302
if not _time.daylight:
303
# Daylight savings does not occur in this time zone.
306
# Get the name of the current time zone depending
308
_localzone = _cache._zmap[tzname[isDST].lower()]
311
# Generate a GMT-offset zone name.
313
localzone = _time.altzone
315
localzone = _time.timezone
316
offset=(-localzone/(60*60))
317
majorOffset=int(offset)
318
if majorOffset != 0 :
319
minorOffset=abs(int((offset % majorOffset) * 60.0))
320
else: minorOffset = 0
321
m=majorOffset >= 0 and '+' or ''
322
lz='%s%0.02d%0.02d' % (m, majorOffset, minorOffset)
323
_localzone = _cache._zmap[('GMT%s' % lz).lower()]
328
# Some utility functions for calculating dates:
331
# Returns timezone-independent days since epoch and the fractional
333
dd = t + EPOCH - 86400.0
335
s = d - math.floor(d)
338
def _calcDependentSecond(tz, t):
339
# Calculates the timezone-dependent second (integer part only)
340
# from the timezone-independent second.
341
fset = _tzoffset(tz, t)
342
return fset + long(math.floor(t)) + long(EPOCH) - 86400L
344
def _calcDependentSecond2(yr,mo,dy,hr,mn,sc):
345
# Calculates the timezone-dependent second (integer part only)
346
# from the date given.
347
ss = int(hr) * 3600 + int(mn) * 60 + int(sc)
348
x = long(_julianday(yr,mo,dy)-jd1901) * 86400 + ss
351
def _calcIndependentSecondEtc(tz, x, ms):
352
# Derive the timezone-independent second from the timezone
354
fsetAtEpoch = _tzoffset(tz, 0.0)
355
nearTime = x - fsetAtEpoch - long(EPOCH) + 86400L + ms
356
# nearTime is now within an hour of being correct.
357
# Recalculate t according to DST.
358
fset = long(_tzoffset(tz, nearTime))
359
x_adjusted = x - fset + ms
360
d = x_adjusted / 86400.0
361
t = x_adjusted - long(EPOCH) + 86400L
362
millis = (x + 86400 - fset) * 1000 + \
363
long(ms * 1000.0) - long(EPOCH * 1000.0)
364
s = d - math.floor(d)
368
# hours, minutes, seconds from integer and float.
372
sc = x - mn * 60 + ms
375
def _calcYMDHMS(x, ms):
376
# x is a timezone-dependent integer of seconds.
377
# Produces yr,mo,dy,hr,mn,sc.
378
yr,mo,dy=_calendarday(x / 86400 + jd1901)
379
x = int(x - (x / 86400) * 86400)
383
sc = x - mn * 60 + ms
384
return yr,mo,dy,hr,mn,sc
386
def _julianday(yr,mo,dy):
387
y,m,d=long(yr),long(mo),long(dy)
395
if y > 0L: yr_correct=0L
397
if m < 3L: y, m=y-1L,m+12L
398
if y*10000L+m*100L+d > 15821014L: b=2L-y/100L+y/400L
400
return (1461L*y-yr_correct)/4L+306001L*(m+1L)/10000L+d+1720994L+b
407
a=(4L*j-7468861L)/146097L
409
c=(20L*b-2442L)/7305L
411
e=10000L*(b-d)/306001L
412
dy=int(b-d-306001L*e/10000L)
413
mo=(e < 14L) and int(e-1L) or int(e-13L)
414
yr=(mo > 2) and (c-4716L) or (c-4715L)
415
return int(yr),int(mo),int(dy)
417
def _tzoffset(tz, t):
419
return DateTimeParser._tzinfo[tz].info(t)[0]
421
if numericTimeZoneMatch(tz) is not None:
422
offset = int(tz[1:3])*3600+int(tz[3:5])*60
428
return 0 # Assume UTC
430
def _correctYear(year):
432
if year >= 0 and year < 100:
433
# 00-69 means 2000-2069, 70-99 means 1970-1999.
441
'''gmtime with a safety zone.'''
444
except OverflowError:
445
raise TimeError('The time %f is beyond the range '
446
'of this Python implementation.' % float(t))
447
return _time.gmtime(t_int)
449
def safelocaltime(t):
450
'''localtime with a safety zone.'''
453
except OverflowError:
454
raise TimeError('The time %f is beyond the range '
455
'of this Python implementation.' % float(t))
456
return _time.localtime(t_int)
458
class DateTimeParser:
460
def parse(self, arg, local=True):
461
"""Parse a string containing some sort of date-time data.
463
This function returns a tuple (year, month, day, hour, minute,
464
second, timezone_string).
466
As a general rule, any date-time representation that is
467
recognized and unambigous to a resident of North America is
468
acceptable.(The reason for this qualification is that
469
in North America, a date like: 2/1/1994 is interpreted
470
as February 1, 1994, while in some parts of the world,
471
it is interpreted as January 2, 1994.) A date/time
472
string consists of two components, a date component and
473
an optional time component, separated by one or more
474
spaces. If the time component is omited, 12:00am is
475
assumed. Any recognized timezone name specified as the
476
final element of the date/time string will be used for
477
computing the date/time value. (If you create a DateTime
478
with the string 'Mar 9, 1997 1:45pm US/Pacific', the
479
value will essentially be the same as if you had captured
480
time.time() at the specified date and time on a machine in
483
x=parse('1997/3/9 1:45pm')
484
# returns specified time, represented in local machine zone.
486
y=parse('Mar 9, 1997 13:45:00')
489
The function automatically detects and handles
490
ISO8601 compliant dates (YYYY-MM-DDThh:ss:mmTZD).
491
See http://www.w3.org/TR/NOTE-datetime for full specs.
493
The date component consists of year, month, and day
494
values. The year value must be a one-, two-, or
495
four-digit integer. If a one- or two-digit year is
496
used, the year is assumed to be in the twentieth
497
century. The month may an integer, from 1 to 12, a
498
month name, or a month abreviation, where a period may
499
optionally follow the abreviation. The day must be an
500
integer from 1 to the number of days in the month. The
501
year, month, and day values may be separated by
502
periods, hyphens, forward, shashes, or spaces. Extra
503
spaces are permitted around the delimiters. Year,
504
month, and day values may be given in any order as long
505
as it is possible to distinguish the components. If all
506
three components are numbers that are less than 13,
507
then a a month-day-year ordering is assumed.
509
The time component consists of hour, minute, and second
510
values separated by colons. The hour value must be an
511
integer between 0 and 23 inclusively. The minute value
512
must be an integer between 0 and 59 inclusively. The
513
second value may be an integer value between 0 and
514
59.999 inclusively. The second value or both the minute
515
and second values may be ommitted. The time may be
516
followed by am or pm in upper or lower case, in which
517
case a 12-hour clock is assumed.
519
If a string argument passed to the DateTime constructor cannot be
520
parsed, it will raise SyntaxError. Invalid date components
521
will raise a DateError, while invalid time or timezone components
522
will raise a DateTimeError.
524
if not isinstance(arg, StringTypes):
525
raise TypeError('Expected a string argument')
528
raise SyntaxError(arg)
530
if arg.find(' ')==-1 and len(arg) >= 5 and arg[4]=='-':
531
yr,mo,dy,hr,mn,sc,tz=self._parse_iso8601(arg)
533
yr,mo,dy,hr,mn,sc,tz=self._parse(arg, local)
536
if not self._validDate(yr,mo,dy):
537
raise DateError(arg, yr, mo, dy)
538
if not self._validTime(hr,mn,int(sc)):
541
return yr, mo, dy, hr, mn, sc, tz
544
"""Parse a string containing some sort of date-time data.
546
This function returns the time in seconds since the Epoch (in UTC).
548
See date() for the description of allowed input values.
551
yr, mo, dy, hr, mn, sc, tz = self.parse(arg)
553
ms = sc - math.floor(sc)
554
x = _calcDependentSecond2(yr,mo,dy,hr,mn,sc)
558
tz=self._tzinfo._zmap[tz.lower()]
560
if numericTimeZoneMatch(tz) is None:
561
raise DateTimeError('Unknown time zone in date: %s' % arg)
563
tz = self._calcTimezoneName(x, ms)
564
s,d,t,millisecs = _calcIndependentSecondEtc(tz, x, ms)
569
int_pattern =re.compile(r'([0-9]+)') #AJ
570
flt_pattern =re.compile(r':([0-9]+\.[0-9]+)') #AJ
571
name_pattern =re.compile(r'([a-zA-Z]+)', re.I) #AJ
574
_month_len =((0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
575
(0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31))
576
_until_month=((0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334),
577
(0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335))
578
_monthmap ={'january': 1, 'jan': 1,
579
'february': 2, 'feb': 2,
580
'march': 3, 'mar': 3,
581
'april': 4, 'apr': 4,
585
'august': 8, 'aug': 8,
586
'september': 9, 'sep': 9, 'sept': 9,
587
'october': 10, 'oct': 10,
588
'november': 11, 'nov': 11,
589
'december': 12, 'dec': 12}
590
_daymap ={'sunday': 1, 'sun': 1,
591
'monday': 2, 'mon': 2,
592
'tuesday': 3, 'tues': 3, 'tue': 3,
593
'wednesday': 4, 'wed': 4,
594
'thursday': 5, 'thurs': 5, 'thur': 5, 'thu': 5,
595
'friday': 6, 'fri': 6,
596
'saturday': 7, 'sat': 7}
598
_localzone0 = _findLocalTimeZoneName(0)
599
_localzone1 = _findLocalTimeZoneName(1)
600
_multipleZones = (_localzone0 != _localzone1)
601
# For backward compatibility only:
602
_isDST = _time.localtime()[8]
603
_localzone = _isDST and _localzone1 or _localzone0
607
def localZone(self, ltm=None):
608
'''Returns the time zone on the given date. The time zone
609
can change according to daylight savings.'''
610
if not self._multipleZones:
611
return self._localzone0
613
ltm = _time.localtime()
615
lz = isDST and self._localzone1 or self._localzone0
618
def _calcTimezoneName(self, x, ms):
619
# Derive the name of the local time zone at the given
620
# timezone-dependent second.
621
if not self._multipleZones:
622
return self._localzone0
623
fsetAtEpoch = _tzoffset(self._localzone0, 0.0)
624
nearTime = x - fsetAtEpoch - long(EPOCH) + 86400L + ms
625
# nearTime is within an hour of being correct.
627
ltm = safelocaltime(nearTime)
629
# We are beyond the range of Python's date support.
630
# Hopefully we can assume that daylight savings schedules
631
# repeat every 28 years. Calculate the name of the
632
# time zone using a supported range of years.
633
yr,mo,dy,hr,mn,sc = _calcYMDHMS(x, 0)
634
yr = ((yr - 1970) % 28) + 1970
635
x = _calcDependentSecond2(yr,mo,dy,hr,mn,sc)
636
nearTime = x - fsetAtEpoch - long(EPOCH) + 86400L + ms
637
ltm = safelocaltime(nearTime)
638
tz = self.localZone(ltm)
641
def _parse(self, string, local=True):
642
# Parse date-time components from a string
643
month = year = tz = tm = None
644
spaces = self.space_chars
645
intpat = self.int_pattern
646
fltpat = self.flt_pattern
647
wordpat = self.name_pattern
648
delimiters = self.delimiters
649
MonthNumbers = self._monthmap
650
DayOfWeekNames = self._daymap
651
ValidZones = self._tzinfo._zidx
652
TimeModifiers = ['am','pm']
654
string = string.strip()
656
# Find timezone first, since it should always be the last
657
# element, and may contain a slash, confusing the parser.
660
# First check for time zone of form +dd:dd
661
tz = _iso_tz_re.search(string)
664
tz, string = string[tz:], string[:tz].strip()
670
if tz and (tz.lower() in ValidZones):
671
string=' '.join(sp[:-1])
673
tz = None # Decide later, since the default time zone
674
# could depend on the date.
679
while i < l and string[i] in spaces : i=i+1
680
if i < l and string[i] in delimiters:
684
while i < l and string[i] in spaces : i=i+1
686
# The float pattern needs to look back 1 character, because it
687
# actually looks for a preceding colon like ':33.33'. This is
688
# needed to avoid accidentally matching the date part of a
689
# dot-separated date string such as '1999.12.31'.
693
ts_results = fltpat.match(string, b)
695
s=ts_results.group(1)
697
ints.append(float(s))
701
ts_results = intpat.match(string, i)
703
s=ts_results.group(0)
707
if (ls==4 and d and d in '+-' and
708
(len(ints) + (not not month) >= 3)):
716
ts_results = wordpat.match(string, i)
718
o,s=ts_results.group(0),ts_results.group(0).lower()
720
if i < l and string[i]=='.': i=i+1
721
# Check for month name:
722
if s in MonthNumbers:
724
if month is None: month=v
725
else: raise SyntaxError(string)
727
# Check for time modifier:
728
if s in TimeModifiers:
730
else: raise SyntaxError(string)
732
# Check for and skip day of week:
733
if s in DayOfWeekNames:
735
raise SyntaxError(string)
738
if ints[-1] > 60 and d not in ['.',':'] and len(ints) > 2:
768
if ints[0] > 12 and ints[2] <= 12:
771
elif ints[2] > 12 and ints[0] <= 12:
790
year,month,day = _time.localtime()[:3]
792
year = _correctYear(year)
793
if year < 1000: raise SyntaxError(string)
795
leap = year%4==0 and (year%100!=0 or year%400==0)
797
if not day or day > self._month_len[leap][month]:
798
raise DateError(string)
800
raise DateError(string)
804
# Modify hour to reflect am/pm
805
if tm and (tm=='pm') and i<12: i=i+12
806
if tm and (tm=='am') and i==12: i=0
807
if i > 24: raise DateTimeError(string)
808
tod = tod + int(i) * 3600
812
if i > 60: raise DateTimeError(string)
813
tod = tod + int(i) * 60
817
if i > 60: raise DateTimeError(string)
820
if ints: raise SyntaxError(string)
823
tod_int = int(math.floor(tod))
825
hr,mn,sc = _calcHMS(tod_int, ms)
828
# Figure out what time zone it is in the local area
830
x = _calcDependentSecond2(year,month,day,hr,mn,sc)
831
tz = self._calcTimezoneName(x, ms)
833
return year,month,day,hr,mn,sc,tz
835
def _validDate(self,y,m,d):
836
if m<1 or m>12 or y<0 or d<1 or d>31: return 0
837
return d<=self._month_len[(y%4==0 and (y%100!=0 or y%400==0))][m]
839
def _validTime(self,h,m,s):
840
return h>=0 and h<=23 and m>=0 and m<=59 and s>=0 and s < 60
842
def _parse_iso8601(self,s):
844
return self.__parse_iso8601(s)
847
'Not an ISO 8601 compliant date string: "%s"' % s)
850
def __parse_iso8601(self,s):
851
"""Parse an ISO 8601 compliant date.
853
TODO: Not all allowed formats are recognized (for some examples see
854
http://www.cl.cam.ac.uk/~mgk25/iso-time.html).
858
hour=minute=seconds=hour_off=min_off=0
861
datereg = re.compile(
862
'([0-9]{4})(-([0-9][0-9]))?(-([0-9][0-9]))?')
863
timereg = re.compile(
864
'([0-9]{2})(:([0-9][0-9]))?(:([0-9][0-9]))?(\.[0-9]{1,20})?'
865
'(\s*([-+])([0-9]{2})(:?([0-9]{2}))?)?')
869
fields = datereg.split(s.strip())
870
if fields[1]: year = int(fields[1])
871
if fields[3]: month = int(fields[3])
872
if fields[5]: day = int(fields[5])
875
fields = timereg.split(s[s.find('T')+1:])
877
if fields[1]: hour = int(fields[1])
878
if fields[3]: minute = int(fields[3])
879
if fields[5]: seconds = int(fields[5])
880
if fields[6]: seconds = seconds+float(fields[6])
882
if fields[8]: tzsign = fields[8]
883
if fields[9]: hour_off = int(fields[9])
884
if fields[11]: min_off = int(fields[11])
886
return (year,month,day,hour,minute,seconds,
887
'%s%02d%02d' % (tzsign,hour_off,min_off))
889
parser = DateTimeParser()
893
######################################################################
894
# Time-zone info based soley on offsets
896
# Share tzinfos for the same offset
898
from datetime import tzinfo as _tzinfo, timedelta as _timedelta
900
class _tzinfo(_tzinfo):
902
def __init__(self, minutes):
903
if abs(minutes) > 1439:
904
raise ValueError("Time-zone offset is too large,", minutes)
905
self.__minutes = minutes
906
self.__offset = _timedelta(minutes=minutes)
908
def utcoffset(self, dt):
911
def __reduce__(self):
912
return tzinfo, (self.__minutes, )
917
def tzname(self, dt):
921
return 'tzinfo(%d)' % self.__minutes
924
def tzinfo(offset, _tzinfos = {}):
926
info = _tzinfos.get(offset)
928
# We haven't seen this one before. we need to save it.
930
# Use setdefault to avoid a race condition and make sure we have
932
info = _tzinfos.setdefault(offset, _tzinfo(offset))
936
tzinfo.__safe_for_unpickling__ = True
939
######################################################################
941
from datetime import datetime as _datetime
943
def parseDatetimetz(string, local=True):
944
y, mo, d, h, m, s, tz = parse(string, local)
945
s, micro = divmod(s, 1.0)
946
micro = round(micro * 1000000)
948
offset = _tzoffset(tz, None) / 60
949
_tzinfo = tzinfo(offset)
952
return _datetime(y, mo, d, h, m, int(s), int(micro), _tzinfo)
954
_iso_tz_re = re.compile("[-+]\d\d:\d\d$")