python-dateutil 2.4.1__zip

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1174 @@
1
+ # -*- coding:iso-8859-1 -*-
2
+ """
3
+ This module offers a generic date/time string parser which is able to parse
4
+ most known formats to represent a date and/or time.
5
+
6
+ Additional resources about date/time string formats can be found below:
7
+
8
+ - `A summary of the international standard date and time notation
9
+ <http://www.cl.cam.ac.uk/~mgk25/iso-time.html>`_
10
+ - `W3C Date and Time Formats <http://www.w3.org/TR/NOTE-datetime>`_
11
+ - `Time Formats (Planetary Rings Node) <http://pds-rings.seti.org/tools/time_formats.html>`_
12
+ - `CPAN ParseDate module
13
+ <http://search.cpan.org/~muir/Time-modules-2013.0912/lib/Time/ParseDate.pm>`_
14
+ - `Java SimpleDateFormat Class
15
+ <https://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>`_
16
+ """
17
+ from __future__ import unicode_literals
18
+
19
+ import datetime
20
+ import string
21
+ import time
22
+ import collections
23
+ from io import StringIO
24
+
25
+ from six import text_type, binary_type, integer_types
26
+
27
+ from . import relativedelta
28
+ from . import tz
29
+
30
+ __all__ = ["parse", "parserinfo"]
31
+
32
+
33
+ class _timelex(object):
34
+
35
+ def __init__(self, instream):
36
+ if isinstance(instream, text_type):
37
+ instream = StringIO(instream)
38
+
39
+ self.instream = instream
40
+ self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
41
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'
42
+ '��������������������������������'
43
+ '������������������������������')
44
+ self.numchars = '0123456789'
45
+ self.whitespace = ' \t\r\n'
46
+ self.charstack = []
47
+ self.tokenstack = []
48
+ self.eof = False
49
+
50
+ def get_token(self):
51
+ """
52
+ This function breaks the time string into lexical units (tokens), which
53
+ can be parsed by the parser. Lexical units are demarcated by changes in
54
+ the character set, so any continuous string of letters is considered one
55
+ unit, any continuous string of numbers is considered one unit.
56
+
57
+ The main complication arises from the fact that dots ('.') can be used
58
+ both as separators (e.g. "Sep.20.2009") or decimal points (e.g.
59
+ "4:30:21.447"). As such, it is necessary to read the full context of
60
+ any dot-separated strings before breaking it into tokens; as such, this
61
+ function maintains a "token stack", for when the ambiguous context
62
+ demands that multiple tokens be parsed at once.
63
+ """
64
+ if self.tokenstack:
65
+ return self.tokenstack.pop(0)
66
+
67
+ seenletters = False
68
+ token = None
69
+ state = None
70
+ wordchars = self.wordchars
71
+ numchars = self.numchars
72
+ whitespace = self.whitespace
73
+
74
+ while not self.eof:
75
+ # We only realize that we've reached the end of a token when we find
76
+ # a character that's not part of the current token - since that
77
+ # character may be part of the next token, it's stored in the
78
+ # charstack.
79
+ if self.charstack:
80
+ nextchar = self.charstack.pop(0)
81
+ else:
82
+ nextchar = self.instream.read(1)
83
+ while nextchar == '\x00':
84
+ nextchar = self.instream.read(1)
85
+
86
+ if not nextchar:
87
+ self.eof = True
88
+ break
89
+ elif not state:
90
+ # First character of the token - determines if we're starting
91
+ # to parse a word, a number or something else.
92
+ token = nextchar
93
+ if nextchar in wordchars:
94
+ state = 'a'
95
+ elif nextchar in numchars:
96
+ state = '0'
97
+ elif nextchar in whitespace:
98
+ token = ' '
99
+ break # emit token
100
+ else:
101
+ break # emit token
102
+ elif state == 'a':
103
+ # If we've already started reading a word, we keep reading
104
+ # letters until we find something that's not part of a word.
105
+ seenletters = True
106
+ if nextchar in wordchars:
107
+ token += nextchar
108
+ elif nextchar == '.':
109
+ token += nextchar
110
+ state = 'a.'
111
+ else:
112
+ self.charstack.append(nextchar)
113
+ break # emit token
114
+ elif state == '0':
115
+ # If we've already started reading a number, we keep reading
116
+ # numbers until we find something that doesn't fit.
117
+ if nextchar in numchars:
118
+ token += nextchar
119
+ elif nextchar == '.':
120
+ token += nextchar
121
+ state = '0.'
122
+ else:
123
+ self.charstack.append(nextchar)
124
+ break # emit token
125
+ elif state == 'a.':
126
+ # If we've seen some letters and a dot separator, continue
127
+ # parsing, and the tokens will be broken up later.
128
+ seenletters = True
129
+ if nextchar == '.' or nextchar in wordchars:
130
+ token += nextchar
131
+ elif nextchar in numchars and token[-1] == '.':
132
+ token += nextchar
133
+ state = '0.'
134
+ else:
135
+ self.charstack.append(nextchar)
136
+ break # emit token
137
+ elif state == '0.':
138
+ # If we've seen at least one dot separator, keep going, we'll
139
+ # break up the tokens later.
140
+ if nextchar == '.' or nextchar in numchars:
141
+ token += nextchar
142
+ elif nextchar in wordchars and token[-1] == '.':
143
+ token += nextchar
144
+ state = 'a.'
145
+ else:
146
+ self.charstack.append(nextchar)
147
+ break # emit token
148
+
149
+ if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or
150
+ token[-1] == '.')):
151
+ l = token.split('.')
152
+ token = l[0]
153
+ for tok in l[1:]:
154
+ self.tokenstack.append('.')
155
+ if tok:
156
+ self.tokenstack.append(tok)
157
+
158
+ return token
159
+
160
+ def __iter__(self):
161
+ return self
162
+
163
+ def __next__(self):
164
+ token = self.get_token()
165
+ if token is None:
166
+ raise StopIteration
167
+
168
+ return token
169
+
170
+ def next(self):
171
+ return self.__next__() # Python 2.x support
172
+
173
+ def split(cls, s):
174
+ return list(cls(s))
175
+ split = classmethod(split)
176
+
177
+
178
+ class _resultbase(object):
179
+
180
+ def __init__(self):
181
+ for attr in self.__slots__:
182
+ setattr(self, attr, None)
183
+
184
+ def _repr(self, classname):
185
+ l = []
186
+ for attr in self.__slots__:
187
+ value = getattr(self, attr)
188
+ if value is not None:
189
+ l.append("%s=%s" % (attr, repr(value)))
190
+ return "%s(%s)" % (classname, ", ".join(l))
191
+
192
+ def __repr__(self):
193
+ return self._repr(self.__class__.__name__)
194
+
195
+
196
+ class parserinfo(object):
197
+ """
198
+ Class which handles what inputs are accepted. Subclass this to customize the
199
+ language and acceptable values for each parameter.
200
+
201
+ :param dayfirst:
202
+ Whether to interpret the first value in an ambiguous 3-integer date
203
+ (e.g. 01/05/09) as the day (`True`) or month (`False`). If
204
+ `yearfirst` is set to `True`, this distinguishes between YDM and
205
+ YMD. Default is `False`.
206
+
207
+ :param yearfirst:
208
+ Whether to interpret the first value in an ambiguous 3-integer date
209
+ (e.g. 01/05/09) as the year. If `True`, the first number is taken to
210
+ be the year, otherwise the last number is taken to be the year.
211
+ Default is `False`.
212
+ """
213
+
214
+ # m from a.m/p.m, t from ISO T separator
215
+ JUMP = [" ", ".", ",", ";", "-", "/", "'",
216
+ "at", "on", "and", "ad", "m", "t", "of",
217
+ "st", "nd", "rd", "th"]
218
+
219
+ WEEKDAYS = [("Mon", "Monday"),
220
+ ("Tue", "Tuesday"),
221
+ ("Wed", "Wednesday"),
222
+ ("Thu", "Thursday"),
223
+ ("Fri", "Friday"),
224
+ ("Sat", "Saturday"),
225
+ ("Sun", "Sunday")]
226
+ MONTHS = [("Jan", "January"),
227
+ ("Feb", "February"),
228
+ ("Mar", "March"),
229
+ ("Apr", "April"),
230
+ ("May", "May"),
231
+ ("Jun", "June"),
232
+ ("Jul", "July"),
233
+ ("Aug", "August"),
234
+ ("Sep", "Sept", "September"),
235
+ ("Oct", "October"),
236
+ ("Nov", "November"),
237
+ ("Dec", "December")]
238
+ HMS = [("h", "hour", "hours"),
239
+ ("m", "minute", "minutes"),
240
+ ("s", "second", "seconds")]
241
+ AMPM = [("am", "a"),
242
+ ("pm", "p")]
243
+ UTCZONE = ["UTC", "GMT", "Z"]
244
+ PERTAIN = ["of"]
245
+ TZOFFSET = {}
246
+
247
+ def __init__(self, dayfirst=False, yearfirst=False):
248
+ self._jump = self._convert(self.JUMP)
249
+ self._weekdays = self._convert(self.WEEKDAYS)
250
+ self._months = self._convert(self.MONTHS)
251
+ self._hms = self._convert(self.HMS)
252
+ self._ampm = self._convert(self.AMPM)
253
+ self._utczone = self._convert(self.UTCZONE)
254
+ self._pertain = self._convert(self.PERTAIN)
255
+
256
+ self.dayfirst = dayfirst
257
+ self.yearfirst = yearfirst
258
+
259
+ self._year = time.localtime().tm_year
260
+ self._century = self._year // 100*100
261
+
262
+ def _convert(self, lst):
263
+ dct = {}
264
+ for i, v in enumerate(lst):
265
+ if isinstance(v, tuple):
266
+ for v in v:
267
+ dct[v.lower()] = i
268
+ else:
269
+ dct[v.lower()] = i
270
+ return dct
271
+
272
+ def jump(self, name):
273
+ return name.lower() in self._jump
274
+
275
+ def weekday(self, name):
276
+ if len(name) >= 3:
277
+ try:
278
+ return self._weekdays[name.lower()]
279
+ except KeyError:
280
+ pass
281
+ return None
282
+
283
+ def month(self, name):
284
+ if len(name) >= 3:
285
+ try:
286
+ return self._months[name.lower()]+1
287
+ except KeyError:
288
+ pass
289
+ return None
290
+
291
+ def hms(self, name):
292
+ try:
293
+ return self._hms[name.lower()]
294
+ except KeyError:
295
+ return None
296
+
297
+ def ampm(self, name):
298
+ try:
299
+ return self._ampm[name.lower()]
300
+ except KeyError:
301
+ return None
302
+
303
+ def pertain(self, name):
304
+ return name.lower() in self._pertain
305
+
306
+ def utczone(self, name):
307
+ return name.lower() in self._utczone
308
+
309
+ def tzoffset(self, name):
310
+ if name in self._utczone:
311
+ return 0
312
+
313
+ return self.TZOFFSET.get(name)
314
+
315
+ def convertyear(self, year):
316
+ if year < 100:
317
+ year += self._century
318
+ if abs(year-self._year) >= 50:
319
+ if year < self._year:
320
+ year += 100
321
+ else:
322
+ year -= 100
323
+ return year
324
+
325
+ def validate(self, res):
326
+ # move to info
327
+ if res.year is not None:
328
+ res.year = self.convertyear(res.year)
329
+
330
+ if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z':
331
+ res.tzname = "UTC"
332
+ res.tzoffset = 0
333
+ elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname):
334
+ res.tzoffset = 0
335
+ return True
336
+
337
+
338
+ class parser(object):
339
+
340
+ def __init__(self, info=None):
341
+ self.info = info or parserinfo()
342
+
343
+ def parse(self, timestr, default=None, ignoretz=False, tzinfos=None,
344
+ **kwargs):
345
+ """
346
+ Parse the date/time string into a datetime object.
347
+
348
+ :param timestr:
349
+ Any date/time string using the supported formats.
350
+
351
+ :param default:
352
+ The default datetime object, if this is a datetime object and not
353
+ `None`, elements specified in `timestr` replace elements in the
354
+ default object.
355
+
356
+ :param ignoretz:
357
+ Whether or not to ignore the time zone.
358
+
359
+ :param tzinfos:
360
+ A time zone, to be applied to the date, if `ignoretz` is `True`.
361
+ This can be either a subclass of `tzinfo`, a time zone string or an
362
+ integer offset.
363
+
364
+ :param **kwargs:
365
+ Keyword arguments as passed to `_parse()`.
366
+
367
+ :return:
368
+ Returns a `datetime.datetime` object or, if the `fuzzy_with_tokens`
369
+ option is `True`, returns a tuple, the first element being a
370
+ `datetime.datetime` object, the second a tuple containing the
371
+ fuzzy tokens.
372
+
373
+ :raises ValueError: Raised for invalid or unknown string format.
374
+ :raises ValueError: Raised if provided `tzinfos` are not in a valid
375
+ format.
376
+ """
377
+
378
+ default_specified = default is not None
379
+
380
+ if not default_specified:
381
+ default = datetime.datetime.now().replace(hour=0, minute=0,
382
+ second=0, microsecond=0)
383
+
384
+ if kwargs.get('fuzzy_with_tokens', False):
385
+ res, skipped_tokens = self._parse(timestr, **kwargs)
386
+ else:
387
+ res = self._parse(timestr, **kwargs)
388
+
389
+ if res is None:
390
+ raise ValueError("Unknown string format")
391
+
392
+ repl = {}
393
+ for attr in ["year", "month", "day", "hour",
394
+ "minute", "second", "microsecond"]:
395
+ value = getattr(res, attr)
396
+ if value is not None:
397
+ repl[attr] = value
398
+
399
+ ret = default.replace(**repl)
400
+
401
+ if res.weekday is not None and not res.day:
402
+ ret = ret+relativedelta.relativedelta(weekday=res.weekday)
403
+
404
+ if not ignoretz:
405
+ if (isinstance(tzinfos, collections.Callable) or
406
+ tzinfos and res.tzname in tzinfos):
407
+
408
+ if isinstance(tzinfos, collections.Callable):
409
+ tzdata = tzinfos(res.tzname, res.tzoffset)
410
+ else:
411
+ tzdata = tzinfos.get(res.tzname)
412
+
413
+ if isinstance(tzdata, datetime.tzinfo):
414
+ tzinfo = tzdata
415
+ elif isinstance(tzdata, text_type):
416
+ tzinfo = tz.tzstr(tzdata)
417
+ elif isinstance(tzdata, integer_types):
418
+ tzinfo = tz.tzoffset(res.tzname, tzdata)
419
+ else:
420
+ raise ValueError("Offset must be tzinfo subclass, "
421
+ "tz string, or int offset.")
422
+ ret = ret.replace(tzinfo=tzinfo)
423
+ elif res.tzname and res.tzname in time.tzname:
424
+ ret = ret.replace(tzinfo=tz.tzlocal())
425
+ elif res.tzoffset == 0:
426
+ ret = ret.replace(tzinfo=tz.tzutc())
427
+ elif res.tzoffset:
428
+ ret = ret.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset))
429
+
430
+ if kwargs.get('fuzzy_with_tokens', False):
431
+ return ret, skipped_tokens
432
+ else:
433
+ return ret
434
+
435
+ class _result(_resultbase):
436
+ __slots__ = ["year", "month", "day", "weekday",
437
+ "hour", "minute", "second", "microsecond",
438
+ "tzname", "tzoffset"]
439
+
440
+ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False,
441
+ fuzzy_with_tokens=False):
442
+ """
443
+ Private method which performs the heavy lifting of parsing, called from
444
+ `parse()`, which passes on its `kwargs` to this function.
445
+
446
+ :param timestr:
447
+ The string to parse.
448
+
449
+ :param dayfirst:
450
+ Whether to interpret the first value in an ambiguous 3-integer date
451
+ (e.g. 01/05/09) as the day (`True`) or month (`False`). If
452
+ `yearfirst` is set to `True`, this distinguishes between YDM and
453
+ YMD. If set to `None`, this value is retrieved from the current
454
+ `parserinfo` object (which itself defaults to `False`).
455
+
456
+ :param yearfirst:
457
+ Whether to interpret the first value in an ambiguous 3-integer date
458
+ (e.g. 01/05/09) as the year. If `True`, the first number is taken to
459
+ be the year, otherwise the last number is taken to be the year. If
460
+ this is set to `None`, the value is retrieved from the current
461
+ `parserinfo` object (which itself defaults to `False`).
462
+
463
+ :param fuzzy:
464
+ Whether to allow fuzzy parsing, allowing for string like "Today is
465
+ January 1, 2047 at 8:21:00AM".
466
+
467
+ :param fuzzy_with_tokens:
468
+ If `True`, `fuzzy` is automatically set to True, and the parser will
469
+ return a tuple where the first element is the parsed
470
+ `datetime.datetime` datetimestamp and the second element is a tuple
471
+ containing the portions of the string which were ignored, e.g.
472
+ "Today is January 1, 2047 at 8:21:00AM" should return
473
+ `(datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))`
474
+ """
475
+ if fuzzy_with_tokens:
476
+ fuzzy = True
477
+
478
+ info = self.info
479
+
480
+ if dayfirst is None:
481
+ dayfirst = info.dayfirst
482
+
483
+ if yearfirst is None:
484
+ yearfirst = info.yearfirst
485
+
486
+ res = self._result()
487
+ l = _timelex.split(timestr) # Splits the timestr into tokens
488
+
489
+ # keep up with the last token skipped so we can recombine
490
+ # consecutively skipped tokens (-2 for when i begins at 0).
491
+ last_skipped_token_i = -2
492
+ skipped_tokens = list()
493
+
494
+ try:
495
+ # year/month/day list
496
+ ymd = []
497
+
498
+ # Index of the month string in ymd
499
+ mstridx = -1
500
+
501
+ len_l = len(l)
502
+ i = 0
503
+ while i < len_l:
504
+
505
+ # Check if it's a number
506
+ try:
507
+ value_repr = l[i]
508
+ value = float(value_repr)
509
+ except ValueError:
510
+ value = None
511
+
512
+ if value is not None:
513
+ # Token is a number
514
+ len_li = len(l[i])
515
+ i += 1
516
+
517
+ if (len(ymd) == 3 and len_li in (2, 4)
518
+ and res.hour is None and (i >= len_l or (l[i] != ':' and
519
+ info.hms(l[i]) is None))):
520
+ # 19990101T23[59]
521
+ s = l[i-1]
522
+ res.hour = int(s[:2])
523
+
524
+ if len_li == 4:
525
+ res.minute = int(s[2:])
526
+
527
+ elif len_li == 6 or (len_li > 6 and l[i-1].find('.') == 6):
528
+ # YYMMDD or HHMMSS[.ss]
529
+ s = l[i-1]
530
+
531
+ if not ymd and l[i-1].find('.') == -1:
532
+ ymd.append(info.convertyear(int(s[:2])))
533
+ ymd.append(int(s[2:4]))
534
+ ymd.append(int(s[4:]))
535
+ else:
536
+ # 19990101T235959[.59]
537
+ res.hour = int(s[:2])
538
+ res.minute = int(s[2:4])
539
+ res.second, res.microsecond = _parsems(s[4:])
540
+
541
+ elif len_li == 8:
542
+ # YYYYMMDD
543
+ s = l[i-1]
544
+ ymd.append(int(s[:4]))
545
+ ymd.append(int(s[4:6]))
546
+ ymd.append(int(s[6:]))
547
+
548
+ elif len_li in (12, 14):
549
+ # YYYYMMDDhhmm[ss]
550
+ s = l[i-1]
551
+ ymd.append(int(s[:4]))
552
+ ymd.append(int(s[4:6]))
553
+ ymd.append(int(s[6:8]))
554
+ res.hour = int(s[8:10])
555
+ res.minute = int(s[10:12])
556
+
557
+ if len_li == 14:
558
+ res.second = int(s[12:])
559
+
560
+ elif ((i < len_l and info.hms(l[i]) is not None) or
561
+ (i+1 < len_l and l[i] == ' ' and
562
+ info.hms(l[i+1]) is not None)):
563
+
564
+ # HH[ ]h or MM[ ]m or SS[.ss][ ]s
565
+ if l[i] == ' ':
566
+ i += 1
567
+
568
+ idx = info.hms(l[i])
569
+
570
+ while True:
571
+ if idx == 0:
572
+ res.hour = int(value)
573
+
574
+ if value % 1:
575
+ res.minute = int(60*(value % 1))
576
+
577
+ elif idx == 1:
578
+ res.minute = int(value)
579
+
580
+ if value % 1:
581
+ res.second = int(60*(value % 1))
582
+
583
+ elif idx == 2:
584
+ res.second, res.microsecond = \
585
+ _parsems(value_repr)
586
+
587
+ i += 1
588
+
589
+ if i >= len_l or idx == 2:
590
+ break
591
+
592
+ # 12h00
593
+ try:
594
+ value_repr = l[i]
595
+ value = float(value_repr)
596
+ except ValueError:
597
+ break
598
+ else:
599
+ i += 1
600
+ idx += 1
601
+
602
+ if i < len_l:
603
+ newidx = info.hms(l[i])
604
+
605
+ if newidx is not None:
606
+ idx = newidx
607
+
608
+ elif (i == len_l and l[i-2] == ' ' and
609
+ info.hms(l[i-3]) is not None):
610
+ # X h MM or X m SS
611
+ idx = info.hms(l[i-3]) + 1
612
+
613
+ if idx == 1:
614
+ res.minute = int(value)
615
+
616
+ if value % 1:
617
+ res.second = int(60*(value % 1))
618
+ elif idx == 2:
619
+ res.second, res.microsecond = \
620
+ _parsems(value_repr)
621
+ i += 1
622
+
623
+ elif i+1 < len_l and l[i] == ':':
624
+ # HH:MM[:SS[.ss]]
625
+ res.hour = int(value)
626
+ i += 1
627
+ value = float(l[i])
628
+ res.minute = int(value)
629
+
630
+ if value % 1:
631
+ res.second = int(60*(value % 1))
632
+
633
+ i += 1
634
+
635
+ if i < len_l and l[i] == ':':
636
+ res.second, res.microsecond = _parsems(l[i+1])
637
+ i += 2
638
+
639
+ elif i < len_l and l[i] in ('-', '/', '.'):
640
+ sep = l[i]
641
+ ymd.append(int(value))
642
+ i += 1
643
+
644
+ if i < len_l and not info.jump(l[i]):
645
+ try:
646
+ # 01-01[-01]
647
+ ymd.append(int(l[i]))
648
+ except ValueError:
649
+ # 01-Jan[-01]
650
+ value = info.month(l[i])
651
+
652
+ if value is not None:
653
+ ymd.append(value)
654
+ assert mstridx == -1
655
+ mstridx = len(ymd)-1
656
+ else:
657
+ return None
658
+
659
+ i += 1
660
+
661
+ if i < len_l and l[i] == sep:
662
+ # We have three members
663
+ i += 1
664
+ value = info.month(l[i])
665
+
666
+ if value is not None:
667
+ ymd.append(value)
668
+ mstridx = len(ymd)-1
669
+ assert mstridx == -1
670
+ else:
671
+ ymd.append(int(l[i]))
672
+
673
+ i += 1
674
+ elif i >= len_l or info.jump(l[i]):
675
+ if i+1 < len_l and info.ampm(l[i+1]) is not None:
676
+ # 12 am
677
+ res.hour = int(value)
678
+
679
+ if res.hour < 12 and info.ampm(l[i+1]) == 1:
680
+ res.hour += 12
681
+ elif res.hour == 12 and info.ampm(l[i+1]) == 0:
682
+ res.hour = 0
683
+
684
+ i += 1
685
+ else:
686
+ # Year, month or day
687
+ ymd.append(int(value))
688
+ i += 1
689
+ elif info.ampm(l[i]) is not None:
690
+
691
+ # 12am
692
+ res.hour = int(value)
693
+
694
+ if res.hour < 12 and info.ampm(l[i]) == 1:
695
+ res.hour += 12
696
+ elif res.hour == 12 and info.ampm(l[i]) == 0:
697
+ res.hour = 0
698
+ i += 1
699
+
700
+ elif not fuzzy:
701
+ return None
702
+ else:
703
+ i += 1
704
+ continue
705
+
706
+ # Check weekday
707
+ value = info.weekday(l[i])
708
+ if value is not None:
709
+ res.weekday = value
710
+ i += 1
711
+ continue
712
+
713
+ # Check month name
714
+ value = info.month(l[i])
715
+ if value is not None:
716
+ ymd.append(value)
717
+ assert mstridx == -1
718
+ mstridx = len(ymd)-1
719
+
720
+ i += 1
721
+ if i < len_l:
722
+ if l[i] in ('-', '/'):
723
+ # Jan-01[-99]
724
+ sep = l[i]
725
+ i += 1
726
+ ymd.append(int(l[i]))
727
+ i += 1
728
+
729
+ if i < len_l and l[i] == sep:
730
+ # Jan-01-99
731
+ i += 1
732
+ ymd.append(int(l[i]))
733
+ i += 1
734
+
735
+ elif (i+3 < len_l and l[i] == l[i+2] == ' '
736
+ and info.pertain(l[i+1])):
737
+ # Jan of 01
738
+ # In this case, 01 is clearly year
739
+ try:
740
+ value = int(l[i+3])
741
+ except ValueError:
742
+ # Wrong guess
743
+ pass
744
+ else:
745
+ # Convert it here to become unambiguous
746
+ ymd.append(info.convertyear(value))
747
+ i += 4
748
+ continue
749
+
750
+ # Check am/pm
751
+ value = info.ampm(l[i])
752
+ if value is not None:
753
+ # If AM/PM is found and hour is not, raise a ValueError
754
+ if res.hour is None:
755
+ raise ValueError('No hour specified with AM or PM flag.')
756
+
757
+ # If AM/PM is found, it's a 12 hour clock, so raise an error for invalid range
758
+ if not 0 <= res.hour <= 12:
759
+ raise ValueError('Invalid hour specified for 12-hour clock.')
760
+
761
+ if value == 1 and res.hour < 12:
762
+ res.hour += 12
763
+ elif value == 0 and res.hour == 12:
764
+ res.hour = 0
765
+ i += 1
766
+ continue
767
+
768
+ # Check for a timezone name
769
+ if (res.hour is not None and len(l[i]) <= 5 and
770
+ res.tzname is None and res.tzoffset is None and
771
+ not [x for x in l[i] if x not in
772
+ string.ascii_uppercase]):
773
+ res.tzname = l[i]
774
+ res.tzoffset = info.tzoffset(res.tzname)
775
+ i += 1
776
+
777
+ # Check for something like GMT+3, or BRST+3. Notice
778
+ # that it doesn't mean "I am 3 hours after GMT", but
779
+ # "my time +3 is GMT". If found, we reverse the
780
+ # logic so that timezone parsing code will get it
781
+ # right.
782
+ if i < len_l and l[i] in ('+', '-'):
783
+ l[i] = ('+', '-')[l[i] == '+']
784
+ res.tzoffset = None
785
+ if info.utczone(res.tzname):
786
+ # With something like GMT+3, the timezone
787
+ # is *not* GMT.
788
+ res.tzname = None
789
+
790
+ continue
791
+
792
+ # Check for a numbered timezone
793
+ if res.hour is not None and l[i] in ('+', '-'):
794
+ signal = (-1, 1)[l[i] == '+']
795
+ i += 1
796
+ len_li = len(l[i])
797
+
798
+ if len_li == 4:
799
+ # -0300
800
+ res.tzoffset = int(l[i][:2])*3600+int(l[i][2:])*60
801
+ elif i+1 < len_l and l[i+1] == ':':
802
+ # -03:00
803
+ res.tzoffset = int(l[i])*3600+int(l[i+2])*60
804
+ i += 2
805
+ elif len_li <= 2:
806
+ # -[0]3
807
+ res.tzoffset = int(l[i][:2])*3600
808
+ else:
809
+ return None
810
+ i += 1
811
+
812
+ res.tzoffset *= signal
813
+
814
+ # Look for a timezone name between parenthesis
815
+ if (i+3 < len_l and
816
+ info.jump(l[i]) and l[i+1] == '(' and l[i+3] == ')' and
817
+ 3 <= len(l[i+2]) <= 5 and
818
+ not [x for x in l[i+2]
819
+ if x not in string.ascii_uppercase]):
820
+ # -0300 (BRST)
821
+ res.tzname = l[i+2]
822
+ i += 4
823
+ continue
824
+
825
+ # Check jumps
826
+ if not (info.jump(l[i]) or fuzzy):
827
+ return None
828
+
829
+ if last_skipped_token_i == i - 1:
830
+ # recombine the tokens
831
+ skipped_tokens[-1] += l[i]
832
+ else:
833
+ # just append
834
+ skipped_tokens.append(l[i])
835
+ last_skipped_token_i = i
836
+ i += 1
837
+
838
+ # Process year/month/day
839
+ len_ymd = len(ymd)
840
+ if len_ymd > 3:
841
+ # More than three members!?
842
+ return None
843
+ elif len_ymd == 1 or (mstridx != -1 and len_ymd == 2):
844
+ # One member, or two members with a month string
845
+ if mstridx != -1:
846
+ res.month = ymd[mstridx]
847
+ del ymd[mstridx]
848
+
849
+ if len_ymd > 1 or mstridx == -1:
850
+ if ymd[0] > 31:
851
+ res.year = ymd[0]
852
+ else:
853
+ res.day = ymd[0]
854
+
855
+ elif len_ymd == 2:
856
+ # Two members with numbers
857
+ if ymd[0] > 31:
858
+ # 99-01
859
+ res.year, res.month = ymd
860
+ elif ymd[1] > 31:
861
+ # 01-99
862
+ res.month, res.year = ymd
863
+ elif dayfirst and ymd[1] <= 12:
864
+ # 13-01
865
+ res.day, res.month = ymd
866
+ else:
867
+ # 01-13
868
+ res.month, res.day = ymd
869
+
870
+ elif len_ymd == 3:
871
+ # Three members
872
+ if mstridx == 0:
873
+ res.month, res.day, res.year = ymd
874
+ elif mstridx == 1:
875
+ if ymd[0] > 31 or (yearfirst and ymd[2] <= 31):
876
+ # 99-Jan-01
877
+ res.year, res.month, res.day = ymd
878
+ else:
879
+ # 01-Jan-01
880
+ # Give precendence to day-first, since
881
+ # two-digit years is usually hand-written.
882
+ res.day, res.month, res.year = ymd
883
+
884
+ elif mstridx == 2:
885
+ # WTF!?
886
+ if ymd[1] > 31:
887
+ # 01-99-Jan
888
+ res.day, res.year, res.month = ymd
889
+ else:
890
+ # 99-01-Jan
891
+ res.year, res.day, res.month = ymd
892
+
893
+ else:
894
+ if ymd[0] > 31 or \
895
+ (yearfirst and ymd[1] <= 12 and ymd[2] <= 31):
896
+ # 99-01-01
897
+ res.year, res.month, res.day = ymd
898
+ elif ymd[0] > 12 or (dayfirst and ymd[1] <= 12):
899
+ # 13-01-01
900
+ res.day, res.month, res.year = ymd
901
+ else:
902
+ # 01-13-01
903
+ res.month, res.day, res.year = ymd
904
+
905
+ except (IndexError, ValueError, AssertionError):
906
+ return None
907
+
908
+ if not info.validate(res):
909
+ return None
910
+
911
+ if fuzzy_with_tokens:
912
+ return res, tuple(skipped_tokens)
913
+ else:
914
+ return res
915
+
916
+ DEFAULTPARSER = parser()
917
+
918
+
919
+ def parse(timestr, parserinfo=None, **kwargs):
920
+ """
921
+ Parse a string in one of the supported formats, using the `parserinfo`
922
+ parameters.
923
+
924
+ :param timestr:
925
+ A string containing a date/time stamp.
926
+
927
+ :param parserinfo:
928
+ A :class:`parserinfo` object containing parameters for the parser.
929
+ If `None`, the default arguments to the `parserinfo` constructor are
930
+ used.
931
+
932
+ The `**kwargs` parameter takes the following keyword arguments:
933
+
934
+ :param default:
935
+ The default datetime object, if this is a datetime object and not
936
+ `None`, elements specified in `timestr` replace elements in the
937
+ default object.
938
+
939
+ :param ignoretz:
940
+ Whether or not to ignore the time zone (boolean).
941
+
942
+ :param tzinfos:
943
+ A time zone, to be applied to the date, if `ignoretz` is `True`.
944
+ This can be either a subclass of `tzinfo`, a time zone string or an
945
+ integer offset.
946
+
947
+ :param dayfirst:
948
+ Whether to interpret the first value in an ambiguous 3-integer date
949
+ (e.g. 01/05/09) as the day (`True`) or month (`False`). If
950
+ `yearfirst` is set to `True`, this distinguishes between YDM and
951
+ YMD. If set to `None`, this value is retrieved from the current
952
+ :class:`parserinfo` object (which itself defaults to `False`).
953
+
954
+ :param yearfirst:
955
+ Whether to interpret the first value in an ambiguous 3-integer date
956
+ (e.g. 01/05/09) as the year. If `True`, the first number is taken to
957
+ be the year, otherwise the last number is taken to be the year. If
958
+ this is set to `None`, the value is retrieved from the current
959
+ :class:`parserinfo` object (which itself defaults to `False`).
960
+
961
+ :param fuzzy:
962
+ Whether to allow fuzzy parsing, allowing for string like "Today is
963
+ January 1, 2047 at 8:21:00AM".
964
+
965
+ :param fuzzy_with_tokens:
966
+ If `True`, `fuzzy` is automatically set to True, and the parser will
967
+ return a tuple where the first element is the parsed
968
+ `datetime.datetime` datetimestamp and the second element is a tuple
969
+ containing the portions of the string which were ignored, e.g.
970
+ "Today is January 1, 2047 at 8:21:00AM" should return
971
+ `(datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))`
972
+ """
973
+ # Python 2.x support: datetimes return their string presentation as
974
+ # bytes in 2.x and unicode in 3.x, so it's reasonable to expect that
975
+ # the parser will get both kinds. Internally we use unicode only.
976
+ if isinstance(timestr, binary_type):
977
+ timestr = timestr.decode()
978
+
979
+ if parserinfo:
980
+ return parser(parserinfo).parse(timestr, **kwargs)
981
+ else:
982
+ return DEFAULTPARSER.parse(timestr, **kwargs)
983
+
984
+
985
+ class _tzparser(object):
986
+
987
+ class _result(_resultbase):
988
+
989
+ __slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset",
990
+ "start", "end"]
991
+
992
+ class _attr(_resultbase):
993
+ __slots__ = ["month", "week", "weekday",
994
+ "yday", "jyday", "day", "time"]
995
+
996
+ def __repr__(self):
997
+ return self._repr("")
998
+
999
+ def __init__(self):
1000
+ _resultbase.__init__(self)
1001
+ self.start = self._attr()
1002
+ self.end = self._attr()
1003
+
1004
+ def parse(self, tzstr):
1005
+ res = self._result()
1006
+ l = _timelex.split(tzstr)
1007
+ try:
1008
+
1009
+ len_l = len(l)
1010
+
1011
+ i = 0
1012
+ while i < len_l:
1013
+ # BRST+3[BRDT[+2]]
1014
+ j = i
1015
+ while j < len_l and not [x for x in l[j]
1016
+ if x in "0123456789:,-+"]:
1017
+ j += 1
1018
+ if j != i:
1019
+ if not res.stdabbr:
1020
+ offattr = "stdoffset"
1021
+ res.stdabbr = "".join(l[i:j])
1022
+ else:
1023
+ offattr = "dstoffset"
1024
+ res.dstabbr = "".join(l[i:j])
1025
+ i = j
1026
+ if (i < len_l and (l[i] in ('+', '-') or l[i][0] in
1027
+ "0123456789")):
1028
+ if l[i] in ('+', '-'):
1029
+ # Yes, that's right. See the TZ variable
1030
+ # documentation.
1031
+ signal = (1, -1)[l[i] == '+']
1032
+ i += 1
1033
+ else:
1034
+ signal = -1
1035
+ len_li = len(l[i])
1036
+ if len_li == 4:
1037
+ # -0300
1038
+ setattr(res, offattr, (int(l[i][:2])*3600 +
1039
+ int(l[i][2:])*60)*signal)
1040
+ elif i+1 < len_l and l[i+1] == ':':
1041
+ # -03:00
1042
+ setattr(res, offattr,
1043
+ (int(l[i])*3600+int(l[i+2])*60)*signal)
1044
+ i += 2
1045
+ elif len_li <= 2:
1046
+ # -[0]3
1047
+ setattr(res, offattr,
1048
+ int(l[i][:2])*3600*signal)
1049
+ else:
1050
+ return None
1051
+ i += 1
1052
+ if res.dstabbr:
1053
+ break
1054
+ else:
1055
+ break
1056
+
1057
+ if i < len_l:
1058
+ for j in range(i, len_l):
1059
+ if l[j] == ';':
1060
+ l[j] = ','
1061
+
1062
+ assert l[i] == ','
1063
+
1064
+ i += 1
1065
+
1066
+ if i >= len_l:
1067
+ pass
1068
+ elif (8 <= l.count(',') <= 9 and
1069
+ not [y for x in l[i:] if x != ','
1070
+ for y in x if y not in "0123456789"]):
1071
+ # GMT0BST,3,0,30,3600,10,0,26,7200[,3600]
1072
+ for x in (res.start, res.end):
1073
+ x.month = int(l[i])
1074
+ i += 2
1075
+ if l[i] == '-':
1076
+ value = int(l[i+1])*-1
1077
+ i += 1
1078
+ else:
1079
+ value = int(l[i])
1080
+ i += 2
1081
+ if value:
1082
+ x.week = value
1083
+ x.weekday = (int(l[i])-1) % 7
1084
+ else:
1085
+ x.day = int(l[i])
1086
+ i += 2
1087
+ x.time = int(l[i])
1088
+ i += 2
1089
+ if i < len_l:
1090
+ if l[i] in ('-', '+'):
1091
+ signal = (-1, 1)[l[i] == "+"]
1092
+ i += 1
1093
+ else:
1094
+ signal = 1
1095
+ res.dstoffset = (res.stdoffset+int(l[i]))*signal
1096
+ elif (l.count(',') == 2 and l[i:].count('/') <= 2 and
1097
+ not [y for x in l[i:] if x not in (',', '/', 'J', 'M',
1098
+ '.', '-', ':')
1099
+ for y in x if y not in "0123456789"]):
1100
+ for x in (res.start, res.end):
1101
+ if l[i] == 'J':
1102
+ # non-leap year day (1 based)
1103
+ i += 1
1104
+ x.jyday = int(l[i])
1105
+ elif l[i] == 'M':
1106
+ # month[-.]week[-.]weekday
1107
+ i += 1
1108
+ x.month = int(l[i])
1109
+ i += 1
1110
+ assert l[i] in ('-', '.')
1111
+ i += 1
1112
+ x.week = int(l[i])
1113
+ if x.week == 5:
1114
+ x.week = -1
1115
+ i += 1
1116
+ assert l[i] in ('-', '.')
1117
+ i += 1
1118
+ x.weekday = (int(l[i])-1) % 7
1119
+ else:
1120
+ # year day (zero based)
1121
+ x.yday = int(l[i])+1
1122
+
1123
+ i += 1
1124
+
1125
+ if i < len_l and l[i] == '/':
1126
+ i += 1
1127
+ # start time
1128
+ len_li = len(l[i])
1129
+ if len_li == 4:
1130
+ # -0300
1131
+ x.time = (int(l[i][:2])*3600+int(l[i][2:])*60)
1132
+ elif i+1 < len_l and l[i+1] == ':':
1133
+ # -03:00
1134
+ x.time = int(l[i])*3600+int(l[i+2])*60
1135
+ i += 2
1136
+ if i+1 < len_l and l[i+1] == ':':
1137
+ i += 2
1138
+ x.time += int(l[i])
1139
+ elif len_li <= 2:
1140
+ # -[0]3
1141
+ x.time = (int(l[i][:2])*3600)
1142
+ else:
1143
+ return None
1144
+ i += 1
1145
+
1146
+ assert i == len_l or l[i] == ','
1147
+
1148
+ i += 1
1149
+
1150
+ assert i >= len_l
1151
+
1152
+ except (IndexError, ValueError, AssertionError):
1153
+ return None
1154
+
1155
+ return res
1156
+
1157
+
1158
+ DEFAULTTZPARSER = _tzparser()
1159
+
1160
+
1161
+ def _parsetz(tzstr):
1162
+ return DEFAULTTZPARSER.parse(tzstr)
1163
+
1164
+
1165
+ def _parsems(value):
1166
+ """Parse a I[.F] seconds value into (seconds, microseconds)."""
1167
+ if "." not in value:
1168
+ return int(value), 0
1169
+ else:
1170
+ i, f = value.split(".")
1171
+ return int(i), int(f.ljust(6, "0")[:6])
1172
+
1173
+
1174
+ # vim:ts=4:sw=4:et