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