~ubuntu-branches/ubuntu/quantal/zope.datetime/quantal

« back to all changes in this revision

Viewing changes to src/zope/datetime/__init__.py

  • Committer: Bazaar Package Importer
  • Author(s): Łukasz Czyżykowski
  • Date: 2010-04-15 17:30:03 UTC
  • Revision ID: james.westby@ubuntu.com-20100415173003-0ch61eriz8af4310
Tags: upstream-3.4.0
Import upstream version 3.4.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
##############################################################################
 
2
#
 
3
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
 
4
# All Rights Reserved.
 
5
#
 
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.
 
12
#
 
13
##############################################################################
 
14
"""Commonly used utility functions.
 
15
 
 
16
Encapsulation of date/time values
 
17
 
 
18
$Id: __init__.py 68931 2006-06-30 19:25:53Z hdima $
 
19
"""
 
20
import math
 
21
import re
 
22
import time as _time # there is a method definition that makes just "time"
 
23
                     # problematic while executing a class definition
 
24
 
 
25
from types import StringTypes
 
26
 
 
27
try:
 
28
    from time import tzname
 
29
except ImportError:
 
30
    tzname = ('UNKNOWN', 'UNKNOWN')
 
31
 
 
32
 
 
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']
 
41
 
 
42
 
 
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
 
47
    if ts is None:
 
48
        ts = _time.time()
 
49
    return _time.strftime('%Y-%m-%dT%H:%M:%SZ', _time.gmtime(ts))
 
50
 
 
51
def rfc850_date(ts=None):
 
52
    # Return an HTTP-date formatted date string.
 
53
    # 'Friday, 10-Nov-00 16:21:09 GMT'
 
54
    if ts is None:
 
55
        ts = _time.time()
 
56
    year, month, day, hh, mm, ss, wd, y, z = _time.gmtime(ts)
 
57
    return "%s, %02d-%3s-%2s %02d:%02d:%02d GMT" % (
 
58
            weekday_full[wd],
 
59
            day, monthname[month],
 
60
            str(year)[2:],
 
61
            hh, mm, ss)
 
62
 
 
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'
 
67
    if ts is None:
 
68
        ts = _time.time()
 
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],
 
72
                                                    year,
 
73
                                                    hh, mm, ss)
 
74
 
 
75
 
 
76
 
 
77
from zope.datetime.timezones import historical_zone_info as _data
 
78
 
 
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'
 
83
 
 
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]
 
88
i=int(yr-1)
 
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
 
92
jd1901 =2415385L
 
93
 
 
94
 
 
95
numericTimeZoneMatch=re.compile(r'[+-][0-9][0-9][0-9][0-9]').match #TS
 
96
 
 
97
class _timezone:
 
98
    def __init__(self,data):
 
99
        self.name,self.timect,self.typect, \
 
100
        self.ttrans,self.tindex,self.tinfo,self.az=data
 
101
 
 
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
 
106
        return 0
 
107
 
 
108
    def index(self, t=None):
 
109
        t = t or _time.time()
 
110
        if self.timect == 0:
 
111
            idx = (0, 0, 0)
 
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]:
 
116
            if self.timect > 1:
 
117
                idx=(ord(self.tindex[-1]),ord(self.tindex[-1]),
 
118
                     ord(self.tindex[-2]))
 
119
            else:
 
120
                idx=(ord(self.tindex[-1]),ord(self.tindex[-1]),
 
121
                     self.default_index())
 
122
        else:
 
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]))
 
129
                    break
 
130
        return idx
 
131
 
 
132
    def info(self,t=None):
 
133
        idx=self.index(t)[0]
 
134
        zs =self.az[self.tinfo[idx][2]:]
 
135
        return self.tinfo[idx][0],self.tinfo[idx][1],zs[: zs.find('\000')]
 
136
 
 
137
 
 
138
 
 
139
 
 
140
class _cache:
 
141
 
 
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',
 
155
 
 
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',
 
161
           'GMT+1',
 
162
 
 
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',
 
166
 
 
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',
 
170
 
 
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']
 
175
 
 
176
 
 
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',
 
185
 
 
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',
 
206
 
 
207
           'gmt+0000':'GMT+0', 'gmt+0':'GMT+0',
 
208
 
 
209
 
 
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',
 
214
           'gmt+1300':'GMT+13',
 
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',
 
219
 
 
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',
 
224
           'gmt+13':'GMT+13',
 
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',
 
229
 
 
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',
 
242
 
 
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',
 
255
 
 
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',
 
270
 
 
271
           'ut':'Universal',
 
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',
 
276
           'met':'GMT+1',
 
277
           'mewt':'GMT+1', 'swt':'GMT+1', 'fwt':'GMT+1', 'eet':'GMT+2',
 
278
           'eest':'GMT+3',
 
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',
 
282
           'ret':'GMT+4'
 
283
           }
 
284
 
 
285
    def __init__(self):
 
286
        self._db = _data
 
287
        self._d, self._zidx= {}, self._zmap.keys()
 
288
 
 
289
    def __getitem__(self,k):
 
290
        try:   n=self._zmap[k.lower()]
 
291
        except KeyError:
 
292
            if numericTimeZoneMatch(k) == None:
 
293
                raise DateTimeError('Unrecognized timezone: %s' % k)
 
294
            return k
 
295
        try:
 
296
            return self._d[n]
 
297
        except KeyError:
 
298
            z = self._d[n] = _timezone(self._db[n])
 
299
            return z
 
300
 
 
301
def _findLocalTimeZoneName(isDST):
 
302
    if not _time.daylight:
 
303
        # Daylight savings does not occur in this time zone.
 
304
        isDST = 0
 
305
    try:
 
306
        # Get the name of the current time zone depending
 
307
        # on DST.
 
308
        _localzone = _cache._zmap[tzname[isDST].lower()]
 
309
    except KeyError:
 
310
        try:
 
311
            # Generate a GMT-offset zone name.
 
312
            if isDST:
 
313
                localzone = _time.altzone
 
314
            else:
 
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()]
 
324
        except:
 
325
            _localzone = ''
 
326
    return _localzone
 
327
 
 
328
# Some utility functions for calculating dates:
 
329
 
 
330
def _calcSD(t):
 
331
    # Returns timezone-independent days since epoch and the fractional
 
332
    # part of the days.
 
333
    dd = t + EPOCH - 86400.0
 
334
    d = dd / 86400.0
 
335
    s = d - math.floor(d)
 
336
    return s, d
 
337
 
 
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
 
343
 
 
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
 
349
    return x
 
350
 
 
351
def _calcIndependentSecondEtc(tz, x, ms):
 
352
    # Derive the timezone-independent second from the timezone
 
353
    # dependent second.
 
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)
 
365
    return s,d,t,millis
 
366
 
 
367
def _calcHMS(x, ms):
 
368
    # hours, minutes, seconds from integer and float.
 
369
    hr = x / 3600
 
370
    x = x - hr * 3600
 
371
    mn = x / 60
 
372
    sc = x - mn * 60 + ms
 
373
    return hr,mn,sc
 
374
 
 
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)
 
380
    hr = x / 3600
 
381
    x = x - hr * 3600
 
382
    mn = x / 60
 
383
    sc = x - mn * 60 + ms
 
384
    return yr,mo,dy,hr,mn,sc
 
385
 
 
386
def _julianday(yr,mo,dy):
 
387
    y,m,d=long(yr),long(mo),long(dy)
 
388
    if m > 12L:
 
389
        y=y+m/12L
 
390
        m=m%12L
 
391
    elif m < 1L:
 
392
        m=-m
 
393
        y=y-m/12L-1L
 
394
        m=12L-m%12L
 
395
    if y > 0L: yr_correct=0L
 
396
    else:      yr_correct=3L
 
397
    if m < 3L: y, m=y-1L,m+12L
 
398
    if y*10000L+m*100L+d > 15821014L: b=2L-y/100L+y/400L
 
399
    else: b=0L
 
400
    return (1461L*y-yr_correct)/4L+306001L*(m+1L)/10000L+d+1720994L+b
 
401
 
 
402
def _calendarday(j):
 
403
    j=long(j)
 
404
    if(j < 2299160L):
 
405
        b=j+1525L
 
406
    else:
 
407
        a=(4L*j-7468861L)/146097L
 
408
        b=j+1526L+a-a/4L
 
409
    c=(20L*b-2442L)/7305L
 
410
    d=1461L*c/4L
 
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)
 
416
 
 
417
def _tzoffset(tz, t):
 
418
    try:
 
419
        return DateTimeParser._tzinfo[tz].info(t)[0]
 
420
    except:
 
421
        if numericTimeZoneMatch(tz) is not None:
 
422
            offset = int(tz[1:3])*3600+int(tz[3:5])*60
 
423
            if tz[0] == '-':
 
424
                return -offset
 
425
            else:
 
426
                return offset
 
427
        else:
 
428
            return 0 # Assume UTC
 
429
 
 
430
def _correctYear(year):
 
431
    # Y2K patch.
 
432
    if year >= 0 and year < 100:
 
433
        # 00-69 means 2000-2069, 70-99 means 1970-1999.
 
434
        if year < 70:
 
435
            year = 2000 + year
 
436
        else:
 
437
            year = 1900 + year
 
438
    return year
 
439
 
 
440
def safegmtime(t):
 
441
    '''gmtime with a safety zone.'''
 
442
    try:
 
443
        t_int = int(t)
 
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)
 
448
 
 
449
def safelocaltime(t):
 
450
    '''localtime with a safety zone.'''
 
451
    try:
 
452
        t_int = int(t)
 
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)
 
457
 
 
458
class DateTimeParser:
 
459
 
 
460
    def parse(self, arg, local=True):
 
461
        """Parse a string containing some sort of date-time data.
 
462
 
 
463
        This function returns a tuple (year, month, day, hour, minute,
 
464
        second, timezone_string).
 
465
 
 
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
 
481
        that timezone)
 
482
 
 
483
        x=parse('1997/3/9 1:45pm')
 
484
        # returns specified time, represented in local machine zone.
 
485
 
 
486
        y=parse('Mar 9, 1997 13:45:00')
 
487
        # y is equal to x
 
488
 
 
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.
 
492
 
 
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.
 
508
 
 
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.
 
518
 
 
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.
 
523
        """
 
524
        if not isinstance(arg, StringTypes):
 
525
            raise TypeError('Expected a string argument')
 
526
 
 
527
        if not arg:
 
528
            raise SyntaxError(arg)
 
529
 
 
530
        if arg.find(' ')==-1 and len(arg) >= 5 and arg[4]=='-':
 
531
            yr,mo,dy,hr,mn,sc,tz=self._parse_iso8601(arg)
 
532
        else:
 
533
            yr,mo,dy,hr,mn,sc,tz=self._parse(arg, local)
 
534
 
 
535
 
 
536
        if not self._validDate(yr,mo,dy):
 
537
            raise DateError(arg, yr, mo, dy)
 
538
        if not self._validTime(hr,mn,int(sc)):
 
539
            raise TimeError(arg)
 
540
 
 
541
        return yr, mo, dy, hr, mn, sc, tz
 
542
 
 
543
    def time(self, arg):
 
544
        """Parse a string containing some sort of date-time data.
 
545
 
 
546
        This function returns the time in seconds since the Epoch (in UTC).
 
547
 
 
548
        See date() for the description of allowed input values.
 
549
        """
 
550
 
 
551
        yr, mo, dy, hr, mn, sc, tz = self.parse(arg)
 
552
 
 
553
        ms = sc - math.floor(sc)
 
554
        x = _calcDependentSecond2(yr,mo,dy,hr,mn,sc)
 
555
 
 
556
        if tz:
 
557
            try:
 
558
                tz=self._tzinfo._zmap[tz.lower()]
 
559
            except KeyError:
 
560
                if numericTimeZoneMatch(tz) is None:
 
561
                    raise DateTimeError('Unknown time zone in date: %s' % arg)
 
562
        else:
 
563
            tz = self._calcTimezoneName(x, ms)
 
564
        s,d,t,millisecs = _calcIndependentSecondEtc(tz, x, ms)
 
565
 
 
566
        return t
 
567
 
 
568
 
 
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
 
572
    space_chars  =' \t\n'
 
573
    delimiters   ='-/.:,+'
 
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,
 
582
                  'may': 5,
 
583
                  'june': 6,      'jun': 6,
 
584
                  'july': 7,      'jul': 7,
 
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}
 
597
 
 
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
 
604
 
 
605
    _tzinfo    = _cache()
 
606
 
 
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
 
612
        if ltm == None:
 
613
            ltm = _time.localtime()
 
614
        isDST = ltm[8]
 
615
        lz = isDST and self._localzone1 or self._localzone0
 
616
        return lz
 
617
 
 
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.
 
626
        try:
 
627
            ltm = safelocaltime(nearTime)
 
628
        except:
 
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)
 
639
        return tz
 
640
 
 
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']
 
653
 
 
654
        string = string.strip()
 
655
 
 
656
        # Find timezone first, since it should always be the last
 
657
        # element, and may contain a slash, confusing the parser.
 
658
 
 
659
 
 
660
        # First check for time zone of form +dd:dd
 
661
        tz = _iso_tz_re.search(string)
 
662
        if tz:
 
663
            tz = tz.start(0)
 
664
            tz, string = string[tz:], string[:tz].strip()
 
665
            tz = tz[:3]+tz[4:]
 
666
        else:
 
667
            # Look at last token
 
668
            sp=string.split()
 
669
            tz = sp[-1]
 
670
            if tz and (tz.lower() in ValidZones):
 
671
                string=' '.join(sp[:-1])
 
672
            else:
 
673
                tz = None  # Decide later, since the default time zone
 
674
                           # could depend on the date.
 
675
 
 
676
        ints,dels=[],[]
 
677
        i,l=0,len(string)
 
678
        while i < l:
 
679
            while i < l and string[i] in spaces    : i=i+1
 
680
            if i < l and string[i] in delimiters:
 
681
                d=string[i]
 
682
                i=i+1
 
683
            else: d=''
 
684
            while i < l and string[i] in spaces    : i=i+1
 
685
 
 
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'.
 
690
            if i > 0: b=i-1
 
691
            else: b=i
 
692
 
 
693
            ts_results = fltpat.match(string, b)
 
694
            if ts_results:
 
695
                s=ts_results.group(1)
 
696
                i=i+len(s)
 
697
                ints.append(float(s))
 
698
                continue
 
699
 
 
700
            #AJ
 
701
            ts_results = intpat.match(string, i)
 
702
            if ts_results:
 
703
                s=ts_results.group(0)
 
704
 
 
705
                ls=len(s)
 
706
                i=i+ls
 
707
                if (ls==4 and d and d in '+-' and
 
708
                    (len(ints) + (not not month) >= 3)):
 
709
                    tz='%s%s' % (d,s)
 
710
                else:
 
711
                    v=int(s)
 
712
                    ints.append(v)
 
713
                continue
 
714
 
 
715
 
 
716
            ts_results = wordpat.match(string, i)
 
717
            if ts_results:
 
718
                o,s=ts_results.group(0),ts_results.group(0).lower()
 
719
                i=i+len(s)
 
720
                if i < l and string[i]=='.': i=i+1
 
721
                # Check for month name:
 
722
                if s in MonthNumbers:
 
723
                    v=MonthNumbers[s]
 
724
                    if month is None: month=v
 
725
                    else: raise SyntaxError(string)
 
726
                    continue
 
727
                # Check for time modifier:
 
728
                if s in TimeModifiers:
 
729
                    if tm is None: tm=s
 
730
                    else: raise SyntaxError(string)
 
731
                    continue
 
732
                # Check for and skip day of week:
 
733
                if s in DayOfWeekNames:
 
734
                    continue
 
735
            raise SyntaxError(string)
 
736
 
 
737
        day=None
 
738
        if ints[-1] > 60 and d not in ['.',':'] and len(ints) > 2:
 
739
            year=ints[-1]
 
740
            del ints[-1]
 
741
            if month:
 
742
                day=ints[0]
 
743
                del ints[:1]
 
744
            else:
 
745
                month=ints[0]
 
746
                day=ints[1]
 
747
                del ints[:2]
 
748
        elif month:
 
749
            if len(ints) > 1:
 
750
                if ints[0] > 31:
 
751
                    year=ints[0]
 
752
                    day=ints[1]
 
753
                else:
 
754
                    year=ints[1]
 
755
                    day=ints[0]
 
756
                del ints[:2]
 
757
        elif len(ints) > 2:
 
758
            if ints[0] > 31:
 
759
                year=ints[0]
 
760
                if ints[1] > 12:
 
761
                    day=ints[1]
 
762
                    month=ints[2]
 
763
                else:
 
764
                    day=ints[2]
 
765
                    month=ints[1]
 
766
            if ints[1] > 31:
 
767
                year=ints[1]
 
768
                if ints[0] > 12 and ints[2] <= 12:
 
769
                    day=ints[0]
 
770
                    month=ints[2]
 
771
                elif ints[2] > 12 and ints[0] <= 12:
 
772
                    day=ints[2]
 
773
                    month=ints[0]
 
774
            elif ints[2] > 31:
 
775
                year=ints[2]
 
776
                if ints[0] > 12:
 
777
                    day=ints[0]
 
778
                    month=ints[1]
 
779
                else:
 
780
                    day=ints[1]
 
781
                    month=ints[0]
 
782
            elif ints[0] <= 12:
 
783
                month=ints[0]
 
784
                day=ints[1]
 
785
                year=ints[2]
 
786
            del ints[:3]
 
787
 
 
788
        if day is None:
 
789
            # Use today's date.
 
790
            year,month,day = _time.localtime()[:3]
 
791
 
 
792
        year = _correctYear(year)
 
793
        if year < 1000: raise SyntaxError(string)
 
794
 
 
795
        leap = year%4==0 and (year%100!=0 or year%400==0)
 
796
        try:
 
797
            if not day or day > self._month_len[leap][month]:
 
798
                raise DateError(string)
 
799
        except IndexError:
 
800
            raise DateError(string)
 
801
        tod=0
 
802
        if ints:
 
803
            i=ints[0]
 
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
 
809
            del ints[0]
 
810
            if ints:
 
811
                i=ints[0]
 
812
                if i > 60: raise DateTimeError(string)
 
813
                tod = tod + int(i) * 60
 
814
                del ints[0]
 
815
                if ints:
 
816
                    i=ints[0]
 
817
                    if i > 60: raise DateTimeError(string)
 
818
                    tod = tod + i
 
819
                    del ints[0]
 
820
                    if ints: raise SyntaxError(string)
 
821
 
 
822
 
 
823
        tod_int = int(math.floor(tod))
 
824
        ms = tod - tod_int
 
825
        hr,mn,sc = _calcHMS(tod_int, ms)
 
826
 
 
827
        if local and not tz:
 
828
            # Figure out what time zone it is in the local area
 
829
            # on the given date.
 
830
            x = _calcDependentSecond2(year,month,day,hr,mn,sc)
 
831
            tz = self._calcTimezoneName(x, ms)
 
832
 
 
833
        return year,month,day,hr,mn,sc,tz
 
834
 
 
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]
 
838
 
 
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
 
841
 
 
842
    def _parse_iso8601(self,s):
 
843
        try:
 
844
            return self.__parse_iso8601(s)
 
845
        except IndexError:
 
846
            raise DateError(
 
847
                'Not an ISO 8601 compliant date string: "%s"' %  s)
 
848
 
 
849
 
 
850
    def __parse_iso8601(self,s):
 
851
        """Parse an ISO 8601 compliant date.
 
852
 
 
853
        TODO: Not all allowed formats are recognized (for some examples see
 
854
        http://www.cl.cam.ac.uk/~mgk25/iso-time.html).
 
855
        """
 
856
        year=0
 
857
        month=day=1
 
858
        hour=minute=seconds=hour_off=min_off=0
 
859
        tzsign='+'
 
860
 
 
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}))?)?')
 
866
 
 
867
        # Date part
 
868
 
 
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])
 
873
 
 
874
        if s.find('T')>-1:
 
875
            fields = timereg.split(s[s.find('T')+1:])
 
876
 
 
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])
 
881
 
 
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])
 
885
 
 
886
        return (year,month,day,hour,minute,seconds,
 
887
                '%s%02d%02d' % (tzsign,hour_off,min_off))
 
888
 
 
889
parser = DateTimeParser()
 
890
parse = parser.parse
 
891
time = parser.time
 
892
 
 
893
######################################################################
 
894
# Time-zone info based soley on offsets
 
895
#
 
896
# Share tzinfos for the same offset 
 
897
 
 
898
from datetime import tzinfo as _tzinfo, timedelta as _timedelta
 
899
 
 
900
class _tzinfo(_tzinfo):
 
901
 
 
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)
 
907
 
 
908
    def utcoffset(self, dt):
 
909
        return self.__offset
 
910
 
 
911
    def __reduce__(self):
 
912
        return tzinfo, (self.__minutes, )
 
913
 
 
914
    def dst(self, dt):
 
915
        return None
 
916
 
 
917
    def tzname(self, dt):
 
918
        return None
 
919
 
 
920
    def __repr__(self):
 
921
        return 'tzinfo(%d)' % self.__minutes
 
922
 
 
923
 
 
924
def tzinfo(offset, _tzinfos = {}):
 
925
 
 
926
    info = _tzinfos.get(offset)
 
927
    if info is None:
 
928
        # We haven't seen this one before. we need to save it.
 
929
 
 
930
        # Use setdefault to avoid a race condition and make sure we have
 
931
        # only one
 
932
        info = _tzinfos.setdefault(offset, _tzinfo(offset))
 
933
 
 
934
    return info
 
935
 
 
936
tzinfo.__safe_for_unpickling__ = True
 
937
 
 
938
#
 
939
######################################################################
 
940
 
 
941
from datetime import datetime as _datetime
 
942
 
 
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)
 
947
    if tz:
 
948
        offset = _tzoffset(tz, None) / 60
 
949
        _tzinfo = tzinfo(offset)
 
950
    else:
 
951
        _tzinfo = None
 
952
    return _datetime(y, mo, d, h, m, int(s), int(micro), _tzinfo)
 
953
 
 
954
_iso_tz_re = re.compile("[-+]\d\d:\d\d$")