- Notifications
You must be signed in to change notification settings - Fork 31.7k
/
Copy path_strptime.py
684 lines (632 loc) · 28.7 KB
/
_strptime.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
"""Strptime-related classes and functions.
CLASSES:
LocaleTime -- Discovers and stores locale-specific time information
TimeRE -- Creates regexes for pattern matching a string of text containing
time information
FUNCTIONS:
_getlang -- Figure out what language is being used for the locale
strptime -- Calculates the time struct represented by the passed-in string
"""
importos
importtime
importlocale
importcalendar
fromreimportcompileasre_compile
fromreimportsubasre_sub
fromreimportIGNORECASE
fromreimportescapeasre_escape
fromdatetimeimport (dateasdatetime_date,
timedeltaasdatetime_timedelta,
timezoneasdatetime_timezone)
from_threadimportallocate_lockas_thread_allocate_lock
__all__= []
def_getlang():
# Figure out what the current language is set to.
returnlocale.getlocale(locale.LC_TIME)
def_findall(haystack, needle):
# Find all positions of needle in haystack.
ifnotneedle:
return
i=0
whileTrue:
i=haystack.find(needle, i)
ifi<0:
break
yieldi
i+=len(needle)
classLocaleTime(object):
"""Stores and handles locale-specific information related to time.
ATTRIBUTES:
f_weekday -- full weekday names (7-item list)
a_weekday -- abbreviated weekday names (7-item list)
f_month -- full month names (13-item list; dummy value in [0], which
is added by code)
a_month -- abbreviated month names (13-item list, dummy value in
[0], which is added by code)
am_pm -- AM/PM representation (2-item list)
LC_date_time -- format string for date/time representation (string)
LC_date -- format string for date representation (string)
LC_time -- format string for time representation (string)
timezone -- daylight- and non-daylight-savings timezone representation
(2-item list of sets)
lang -- Language used by instance (2-item tuple)
"""
def__init__(self):
"""Set all attributes.
Order of methods called matters for dependency reasons.
The locale language is set at the offset and then checked again before
exiting. This is to make sure that the attributes were not set with a
mix of information from more than one locale. This would most likely
happen when using threads where one thread calls a locale-dependent
function while another thread changes the locale while the function in
the other thread is still running. Proper coding would call for
locks to prevent changing the locale while locale-dependent code is
running. The check here is done in case someone does not think about
doing this.
Only other possible issue is if someone changed the timezone and did
not call tz.tzset . That is an issue for the programmer, though,
since changing the timezone is worthless without that call.
"""
self.lang=_getlang()
self.__calc_weekday()
self.__calc_month()
self.__calc_am_pm()
self.__calc_timezone()
self.__calc_date_time()
if_getlang() !=self.lang:
raiseValueError("locale changed during initialization")
iftime.tzname!=self.tznameortime.daylight!=self.daylight:
raiseValueError("timezone changed during initialization")
def__calc_weekday(self):
# Set self.a_weekday and self.f_weekday using the calendar
# module.
a_weekday= [calendar.day_abbr[i].lower() foriinrange(7)]
f_weekday= [calendar.day_name[i].lower() foriinrange(7)]
self.a_weekday=a_weekday
self.f_weekday=f_weekday
def__calc_month(self):
# Set self.f_month and self.a_month using the calendar module.
a_month= [calendar.month_abbr[i].lower() foriinrange(13)]
f_month= [calendar.month_name[i].lower() foriinrange(13)]
self.a_month=a_month
self.f_month=f_month
def__calc_am_pm(self):
# Set self.am_pm by using time.strftime().
# The magic date (1999,3,17,hour,44,55,2,76,0) is not really that
# magical; just happened to have used it everywhere else where a
# static date was needed.
am_pm= []
forhourin (1, 22):
time_tuple=time.struct_time((1999,3,17,hour,44,55,2,76,0))
# br_FR has AM/PM info (' ',' ').
am_pm.append(time.strftime("%p", time_tuple).lower().strip())
self.am_pm=am_pm
def__calc_date_time(self):
# Set self.date_time, self.date, & self.time by using
# time.strftime().
# Use (1999,3,17,22,44,55,2,76,0) for magic date because the amount of
# overloaded numbers is minimized. The order in which searches for
# values within the format string is very important; it eliminates
# possible ambiguity for what something represents.
time_tuple=time.struct_time((1999,3,17,22,44,55,2,76,0))
time_tuple2=time.struct_time((1999,1,3,1,1,1,6,3,0))
replacement_pairs= [
('1999', '%Y'), ('99', '%y'), ('22', '%H'),
('44', '%M'), ('55', '%S'), ('76', '%j'),
('17', '%d'), ('03', '%m'), ('3', '%m'),
# '3' needed for when no leading zero.
('2', '%w'), ('10', '%I'),
# Non-ASCII digits
('\u0661\u0669\u0669\u0669', '%Y'),
('\u0669\u0669', '%Oy'),
('\u0662\u0662', '%OH'),
('\u0664\u0664', '%OM'),
('\u0665\u0665', '%OS'),
('\u0661\u0667', '%Od'),
('\u0660\u0663', '%Om'),
('\u0663', '%Om'),
('\u0662', '%Ow'),
('\u0661\u0660', '%OI'),
]
date_time= []
fordirectivein ('%c', '%x', '%X'):
current_format=time.strftime(directive, time_tuple).lower()
current_format=current_format.replace('%', '%%')
# The month and the day of the week formats are treated specially
# because of a possible ambiguity in some locales where the full
# and abbreviated names are equal or names of different types
# are equal. See doc of __find_month_format for more details.
lst, fmt=self.__find_weekday_format(directive)
iflst:
current_format=current_format.replace(lst[2], fmt, 1)
lst, fmt=self.__find_month_format(directive)
iflst:
current_format=current_format.replace(lst[3], fmt, 1)
ifself.am_pm[1]:
# Must deal with possible lack of locale info
# manifesting itself as the empty string (e.g., Swedish's
# lack of AM/PM info) or a platform returning a tuple of empty
# strings (e.g., MacOS 9 having timezone as ('','')).
current_format=current_format.replace(self.am_pm[1], '%p')
fortz_valuesinself.timezone:
fortzintz_values:
iftz:
current_format=current_format.replace(tz, "%Z")
# Transform all non-ASCII digits to digits in range U+0660 to U+0669.
current_format=re_sub(r'\d(?<![0-9])',
lambdam: chr(0x0660+int(m[0])),
current_format)
forold, newinreplacement_pairs:
current_format=current_format.replace(old, new)
# If %W is used, then Sunday, 2005-01-03 will fall on week 0 since
# 2005-01-03 occurs before the first Monday of the year. Otherwise
# %U is used.
if'00'intime.strftime(directive, time_tuple2):
U_W='%W'
else:
U_W='%U'
current_format=current_format.replace('11', U_W)
date_time.append(current_format)
self.LC_date_time=date_time[0]
self.LC_date=date_time[1]
self.LC_time=date_time[2]
def__find_month_format(self, directive):
"""Find the month format appropriate for the current locale.
In some locales (for example French and Hebrew), the default month
used in __calc_date_time has the same name in full and abbreviated
form. Also, the month name can by accident match other part of the
representation: the day of the week name (for example in Morisyen)
or the month number (for example in Japanese). Thus, cycle months
of the year and find all positions that match the month name for
each month, If no common positions are found, the representation
does not use the month name.
"""
full_indices=abbr_indices=None
forminrange(1, 13):
time_tuple=time.struct_time((1999, m, 17, 22, 44, 55, 2, 76, 0))
datetime=time.strftime(directive, time_tuple).lower()
indices=set(_findall(datetime, self.f_month[m]))
iffull_indicesisNone:
full_indices=indices
else:
full_indices&=indices
indices=set(_findall(datetime, self.a_month[m]))
ifabbr_indicesisNone:
abbr_indices=indices
else:
abbr_indices&=indices
ifnotfull_indicesandnotabbr_indices:
returnNone, None
iffull_indices:
returnself.f_month, '%B'
ifabbr_indices:
returnself.a_month, '%b'
returnNone, None
def__find_weekday_format(self, directive):
"""Find the day of the week format appropriate for the current locale.
Similar to __find_month_format().
"""
full_indices=abbr_indices=None
forwdinrange(7):
time_tuple=time.struct_time((1999, 3, 17, 22, 44, 55, wd, 76, 0))
datetime=time.strftime(directive, time_tuple).lower()
indices=set(_findall(datetime, self.f_weekday[wd]))
iffull_indicesisNone:
full_indices=indices
else:
full_indices&=indices
ifself.f_weekday[wd] !=self.a_weekday[wd]:
indices=set(_findall(datetime, self.a_weekday[wd]))
ifabbr_indicesisNone:
abbr_indices=indices
else:
abbr_indices&=indices
ifnotfull_indicesandnotabbr_indices:
returnNone, None
iffull_indices:
returnself.f_weekday, '%A'
ifabbr_indices:
returnself.a_weekday, '%a'
returnNone, None
def__calc_timezone(self):
# Set self.timezone by using time.tzname.
# Do not worry about possibility of time.tzname[0] == time.tzname[1]
# and time.daylight; handle that in strptime.
try:
time.tzset()
exceptAttributeError:
pass
self.tzname=time.tzname
self.daylight=time.daylight
no_saving=frozenset({"utc", "gmt", self.tzname[0].lower()})
ifself.daylight:
has_saving=frozenset({self.tzname[1].lower()})
else:
has_saving=frozenset()
self.timezone= (no_saving, has_saving)
classTimeRE(dict):
"""Handle conversion from format directives to regexes."""
def__init__(self, locale_time=None):
"""Create keys/values.
Order of execution is important for dependency reasons.
"""
iflocale_time:
self.locale_time=locale_time
else:
self.locale_time=LocaleTime()
base=super()
mapping= {
# The " [1-9]" part of the regex is to make %c from ANSI C work
'd': r"(?P<d>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])",
'f': r"(?P<f>[0-9]{1,6})",
'H': r"(?P<H>2[0-3]|[0-1]\d|\d)",
'I': r"(?P<I>1[0-2]|0[1-9]|[1-9]| [1-9])",
'G': r"(?P<G>\d\d\d\d)",
'j': r"(?P<j>36[0-6]|3[0-5]\d|[1-2]\d\d|0[1-9]\d|00[1-9]|[1-9]\d|0[1-9]|[1-9])",
'm': r"(?P<m>1[0-2]|0[1-9]|[1-9])",
'M': r"(?P<M>[0-5]\d|\d)",
'S': r"(?P<S>6[0-1]|[0-5]\d|\d)",
'U': r"(?P<U>5[0-3]|[0-4]\d|\d)",
'w': r"(?P<w>[0-6])",
'u': r"(?P<u>[1-7])",
'V': r"(?P<V>5[0-3]|0[1-9]|[1-4]\d|\d)",
# W is set below by using 'U'
'y': r"(?P<y>\d\d)",
'Y': r"(?P<Y>\d\d\d\d)",
'z': r"(?P<z>[+-]\d\d:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?|(?-i:Z))",
'A': self.__seqToRE(self.locale_time.f_weekday, 'A'),
'a': self.__seqToRE(self.locale_time.a_weekday, 'a'),
'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'),
'b': self.__seqToRE(self.locale_time.a_month[1:], 'b'),
'p': self.__seqToRE(self.locale_time.am_pm, 'p'),
'Z': self.__seqToRE((tzfortz_namesinself.locale_time.timezone
fortzintz_names),
'Z'),
'%': '%'}
fordin'dmyHIMS':
mapping['O'+d] =r'(?P<%s>\d\d|\d| \d)'%d
mapping['Ow'] =r'(?P<w>\d)'
mapping['W'] =mapping['U'].replace('U', 'W')
base.__init__(mapping)
base.__setitem__('X', self.pattern(self.locale_time.LC_time))
base.__setitem__('x', self.pattern(self.locale_time.LC_date))
base.__setitem__('c', self.pattern(self.locale_time.LC_date_time))
def__seqToRE(self, to_convert, directive):
"""Convert a list to a regex string for matching a directive.
Want possible matching values to be from longest to shortest. This
prevents the possibility of a match occurring for a value that also
a substring of a larger value that should have matched (e.g., 'abc'
matching when 'abcdef' should have been the match).
"""
to_convert=sorted(to_convert, key=len, reverse=True)
forvalueinto_convert:
ifvalue!='':
break
else:
return''
regex='|'.join(re_escape(stuff) forstuffinto_convert)
regex='(?P<%s>%s'% (directive, regex)
return'%s)'%regex
defpattern(self, format):
"""Return regex pattern for the format string.
Need to make sure that any characters that might be interpreted as
regex syntax are escaped.
"""
# The sub() call escapes all characters that might be misconstrued
# as regex syntax. Cannot use re.escape since we have to deal with
# format directives (%m, etc.).
format=re_sub(r"([\\.^$*+?\(\){}\[\]|])", r"\\\1", format)
format=re_sub(r'\s+', r'\\s+', format)
format=re_sub(r"'", "['\u02bc]", format) # needed for br_FR
year_in_format=False
day_of_month_in_format=False
defrepl(m):
format_char=m[1]
matchformat_char:
case'Y'|'y'|'G':
nonlocalyear_in_format
year_in_format=True
case'd':
nonlocalday_of_month_in_format
day_of_month_in_format=True
returnself[format_char]
format=re_sub(r'%([OE]?\\?.?)', repl, format)
ifday_of_month_in_formatandnotyear_in_format:
importwarnings
warnings.warn("""\
Parsing dates involving a day of month without a year specified is ambiguious
and fails to parse leap day. The default behavior will change in Python 3.15
to either always raise an exception or to use a different default year (TBD).
To avoid trouble, add a specific year to the input & format.
See https://github.com/python/cpython/issues/70647.""",
DeprecationWarning,
skip_file_prefixes=(os.path.dirname(__file__),))
returnformat
defcompile(self, format):
"""Return a compiled re object for the format string."""
returnre_compile(self.pattern(format), IGNORECASE)
_cache_lock=_thread_allocate_lock()
# DO NOT modify _TimeRE_cache or _regex_cache without acquiring the cache lock
# first!
_TimeRE_cache=TimeRE()
_CACHE_MAX_SIZE=5# Max number of regexes stored in _regex_cache
_regex_cache= {}
def_calc_julian_from_U_or_W(year, week_of_year, day_of_week, week_starts_Mon):
"""Calculate the Julian day based on the year, week of the year, and day of
the week, with week_start_day representing whether the week of the year
assumes the week starts on Sunday or Monday (6 or 0)."""
first_weekday=datetime_date(year, 1, 1).weekday()
# If we are dealing with the %U directive (week starts on Sunday), it's
# easier to just shift the view to Sunday being the first day of the
# week.
ifnotweek_starts_Mon:
first_weekday= (first_weekday+1) %7
day_of_week= (day_of_week+1) %7
# Need to watch out for a week 0 (when the first day of the year is not
# the same as that specified by %U or %W).
week_0_length= (7-first_weekday) %7
ifweek_of_year==0:
return1+day_of_week-first_weekday
else:
days_to_week=week_0_length+ (7* (week_of_year-1))
return1+days_to_week+day_of_week
def_strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
"""Return a 2-tuple consisting of a time struct and an int containing
the number of microseconds based on the input string and the
format string."""
forindex, arginenumerate([data_string, format]):
ifnotisinstance(arg, str):
msg="strptime() argument {} must be str, not {}"
raiseTypeError(msg.format(index, type(arg)))
global_TimeRE_cache, _regex_cache
with_cache_lock:
locale_time=_TimeRE_cache.locale_time
if (_getlang() !=locale_time.langor
time.tzname!=locale_time.tznameor
time.daylight!=locale_time.daylight):
_TimeRE_cache=TimeRE()
_regex_cache.clear()
locale_time=_TimeRE_cache.locale_time
iflen(_regex_cache) >_CACHE_MAX_SIZE:
_regex_cache.clear()
format_regex=_regex_cache.get(format)
ifnotformat_regex:
try:
format_regex=_TimeRE_cache.compile(format)
# KeyError raised when a bad format is found; can be specified as
# \\, in which case it was a stray % but with a space after it
exceptKeyErroraserr:
bad_directive=err.args[0]
delerr
bad_directive=bad_directive.replace('\\s', '')
ifnotbad_directive:
raiseValueError("stray %% in format '%s'"%format) fromNone
bad_directive=bad_directive.replace('\\', '', 1)
raiseValueError("'%s' is a bad directive in format '%s'"%
(bad_directive, format)) fromNone
_regex_cache[format] =format_regex
found=format_regex.match(data_string)
ifnotfound:
raiseValueError("time data %r does not match format %r"%
(data_string, format))
iflen(data_string) !=found.end():
raiseValueError("unconverted data remains: %s"%
data_string[found.end():])
iso_year=year=None
month=day=1
hour=minute=second=fraction=0
tz=-1
gmtoff=None
gmtoff_fraction=0
iso_week=week_of_year=None
week_of_year_start=None
# weekday and julian defaulted to None so as to signal need to calculate
# values
weekday=julian=None
found_dict=found.groupdict()
forgroup_keyinfound_dict.keys():
# Directives not explicitly handled below:
# c, x, X
# handled by making out of other directives
# U, W
# worthless without day of the week
ifgroup_key=='y':
year=int(found_dict['y'])
# Open Group specification for strptime() states that a %y
#value in the range of [00, 68] is in the century 2000, while
#[69,99] is in the century 1900
ifyear<=68:
year+=2000
else:
year+=1900
elifgroup_key=='Y':
year=int(found_dict['Y'])
elifgroup_key=='G':
iso_year=int(found_dict['G'])
elifgroup_key=='m':
month=int(found_dict['m'])
elifgroup_key=='B':
month=locale_time.f_month.index(found_dict['B'].lower())
elifgroup_key=='b':
month=locale_time.a_month.index(found_dict['b'].lower())
elifgroup_key=='d':
day=int(found_dict['d'])
elifgroup_key=='H':
hour=int(found_dict['H'])
elifgroup_key=='I':
hour=int(found_dict['I'])
ampm=found_dict.get('p', '').lower()
# If there was no AM/PM indicator, we'll treat this like AM
ifampmin ('', locale_time.am_pm[0]):
# We're in AM so the hour is correct unless we're
# looking at 12 midnight.
# 12 midnight == 12 AM == hour 0
ifhour==12:
hour=0
elifampm==locale_time.am_pm[1]:
# We're in PM so we need to add 12 to the hour unless
# we're looking at 12 noon.
# 12 noon == 12 PM == hour 12
ifhour!=12:
hour+=12
elifgroup_key=='M':
minute=int(found_dict['M'])
elifgroup_key=='S':
second=int(found_dict['S'])
elifgroup_key=='f':
s=found_dict['f']
# Pad to always return microseconds.
s+="0"* (6-len(s))
fraction=int(s)
elifgroup_key=='A':
weekday=locale_time.f_weekday.index(found_dict['A'].lower())
elifgroup_key=='a':
weekday=locale_time.a_weekday.index(found_dict['a'].lower())
elifgroup_key=='w':
weekday=int(found_dict['w'])
ifweekday==0:
weekday=6
else:
weekday-=1
elifgroup_key=='u':
weekday=int(found_dict['u'])
weekday-=1
elifgroup_key=='j':
julian=int(found_dict['j'])
elifgroup_keyin ('U', 'W'):
week_of_year=int(found_dict[group_key])
ifgroup_key=='U':
# U starts week on Sunday.
week_of_year_start=6
else:
# W starts week on Monday.
week_of_year_start=0
elifgroup_key=='V':
iso_week=int(found_dict['V'])
elifgroup_key=='z':
z=found_dict['z']
ifz=='Z':
gmtoff=0
else:
ifz[3] ==':':
z=z[:3] +z[4:]
iflen(z) >5:
ifz[5] !=':':
msg=f"Inconsistent use of : in {found_dict['z']}"
raiseValueError(msg)
z=z[:5] +z[6:]
hours=int(z[1:3])
minutes=int(z[3:5])
seconds=int(z[5:7] or0)
gmtoff= (hours*60*60) + (minutes*60) +seconds
gmtoff_remainder=z[8:]
# Pad to always return microseconds.
gmtoff_remainder_padding="0"* (6-len(gmtoff_remainder))
gmtoff_fraction=int(gmtoff_remainder+gmtoff_remainder_padding)
ifz.startswith("-"):
gmtoff=-gmtoff
gmtoff_fraction=-gmtoff_fraction
elifgroup_key=='Z':
# Since -1 is default value only need to worry about setting tz if
# it can be something other than -1.
found_zone=found_dict['Z'].lower()
forvalue, tz_valuesinenumerate(locale_time.timezone):
iffound_zoneintz_values:
# Deal with bad locale setup where timezone names are the
# same and yet time.daylight is true; too ambiguous to
# be able to tell what timezone has daylight savings
if (time.tzname[0] ==time.tzname[1] and
time.daylightandfound_zonenotin ("utc", "gmt")):
break
else:
tz=value
break
# Deal with the cases where ambiguities arise
# don't assume default values for ISO week/year
ifiso_yearisnotNone:
ifjulianisnotNone:
raiseValueError("Day of the year directive '%j' is not "
"compatible with ISO year directive '%G'. "
"Use '%Y' instead.")
elifiso_weekisNoneorweekdayisNone:
raiseValueError("ISO year directive '%G' must be used with "
"the ISO week directive '%V' and a weekday "
"directive ('%A', '%a', '%w', or '%u').")
elifiso_weekisnotNone:
ifyearisNoneorweekdayisNone:
raiseValueError("ISO week directive '%V' must be used with "
"the ISO year directive '%G' and a weekday "
"directive ('%A', '%a', '%w', or '%u').")
else:
raiseValueError("ISO week directive '%V' is incompatible with "
"the year directive '%Y'. Use the ISO year '%G' "
"instead.")
leap_year_fix=False
ifyearisNone:
ifmonth==2andday==29:
year=1904# 1904 is first leap year of 20th century
leap_year_fix=True
else:
year=1900
# If we know the week of the year and what day of that week, we can figure
# out the Julian day of the year.
ifjulianisNoneandweekdayisnotNone:
ifweek_of_yearisnotNone:
week_starts_Mon=Trueifweek_of_year_start==0elseFalse
julian=_calc_julian_from_U_or_W(year, week_of_year, weekday,
week_starts_Mon)
elifiso_yearisnotNoneandiso_weekisnotNone:
datetime_result=datetime_date.fromisocalendar(iso_year, iso_week, weekday+1)
year=datetime_result.year
month=datetime_result.month
day=datetime_result.day
ifjulianisnotNoneandjulian<=0:
year-=1
yday=366ifcalendar.isleap(year) else365
julian+=yday
ifjulianisNone:
# Cannot pre-calculate datetime_date() since can change in Julian
# calculation and thus could have different value for the day of
# the week calculation.
# Need to add 1 to result since first day of the year is 1, not 0.
julian=datetime_date(year, month, day).toordinal() - \
datetime_date(year, 1, 1).toordinal() +1
else: # Assume that if they bothered to include Julian day (or if it was
# calculated above with year/week/weekday) it will be accurate.
datetime_result=datetime_date.fromordinal(
(julian-1) +
datetime_date(year, 1, 1).toordinal())
year=datetime_result.year
month=datetime_result.month
day=datetime_result.day
ifweekdayisNone:
weekday=datetime_date(year, month, day).weekday()
# Add timezone info
tzname=found_dict.get("Z")
ifleap_year_fix:
# the caller didn't supply a year but asked for Feb 29th. We couldn't
# use the default of 1900 for computations. We set it back to ensure
# that February 29th is smaller than March 1st.
year=1900
return (year, month, day,
hour, minute, second,
weekday, julian, tz, tzname, gmtoff), fraction, gmtoff_fraction
def_strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"):
"""Return a time struct based on the input string and the
format string."""
tt=_strptime(data_string, format)[0]
returntime.struct_time(tt[:time._STRUCT_TM_ITEMS])
def_strptime_datetime(cls, data_string, format="%a %b %d %H:%M:%S %Y"):
"""Return a class cls instance based on the input string and the
format string."""
tt, fraction, gmtoff_fraction=_strptime(data_string, format)
tzname, gmtoff=tt[-2:]
args=tt[:6] + (fraction,)
ifgmtoffisnotNone:
tzdelta=datetime_timedelta(seconds=gmtoff, microseconds=gmtoff_fraction)
iftzname:
tz=datetime_timezone(tzdelta, tzname)
else:
tz=datetime_timezone(tzdelta)
args+= (tz,)
returncls(*args)