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