{"record":{"id":"c0cc894497110754","repo":"python/cpython","slug":"locale-changed-during-initialization","errorCode":null,"errorMessage":"locale changed during initialization","messagePattern":"locale changed during initialization","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_strptime.py","lineNumber":115,"sourceCode":"        the other thread is still running.  Proper coding would call for\n        locks to prevent changing the locale while locale-dependent code is\n        running.  The check here is done in case someone does not think about\n        doing this.\n\n        Only other possible issue is if someone changed the timezone and did\n        not call tz.tzset .  That is an issue for the programmer, though,\n        since changing the timezone is worthless without that call.\n\n        \"\"\"\n        self.lang = _getlang()\n        self.__calc_weekday()\n        self.__calc_month()\n        self.__calc_am_pm()\n        self.__calc_alt_digits()\n        self.__calc_timezone()\n        self.__calc_date_time()\n        if _getlang() != self.lang:\n            raise ValueError(\"locale changed during initialization\")\n        if time.tzname != self.tzname or time.daylight != self.daylight:\n            raise ValueError(\"timezone changed during initialization\")\n\n    def __calc_weekday(self):\n        # Set self.a_weekday and self.f_weekday using the calendar\n        # module.\n        a_weekday = [calendar.day_abbr[i].lower() for i in range(7)]\n        f_weekday = [calendar.day_name[i].lower() for i in range(7)]\n        self.a_weekday = a_weekday\n        self.f_weekday = f_weekday\n\n    def __calc_month(self):\n        # Set self.f_month and self.a_month using the calendar module.\n        a_month = [calendar.month_abbr[i].lower() for i in range(13)]\n        f_month = [calendar.month_name[i].lower() for i in range(13)]\n        self.a_month = a_month\n        self.f_month = f_month\n","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_strptime.py#L97-L133","documentation":"ValueError from the LocaleTime constructor in Lib/_strptime.py. The object snapshots the locale (via _getlang()) at the start of __init__, then computes weekday/month names, AM/PM, alt digits, timezone and date/time formats; if the locale changed underneath it during that sequence (checked by comparing _getlang() before and after), the cached tables would be inconsistent, so it raises 'locale changed during initialization'. The companion check below it covers timezone changes.","triggerScenarios":"One thread calls time.strptime (which lazily builds/caches LocaleTime) while another thread calls locale.setlocale() concurrently; also embedding scenarios where a C extension or signal handler mutates locale mid-construction. The check compares the language code before vs after the six __calc_* passes.","commonSituations":"Multithreaded apps that call locale.setlocale(locale.LC_ALL, '') per-request (common after porting to mod_wsgi/gunicorn or when a dependency like matplotlib/gettext changes locale); first strptime call racing with startup locale configuration; tests that flip locales without locks.","solutions":["Set the locale once at process startup, before spawning threads that use strptime; never call setlocale per request.","On the next strptime call after the locale settles, _strptime rebuilds LocaleTime automatically — so simply retrying strptime after the race window usually succeeds.","Wrap concurrent locale changes and strptime users with a lock, or pin LC_TIME to 'C' if localized parsing is not needed."],"exampleFix":"# before (race: thread A) locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8')\n#              (thread B) time.strptime(d, '%B %d, %Y')  -> ValueError\n\n# after\n# main thread, before starting workers:\nimport locale\nlocale.setlocale(locale.LC_ALL, '')   # set once, then never again","handlingStrategy":"retry","validationCode":"import locale, threading\n\n_locale_lock = threading.Lock()\n\ndef setlocale_safe(category, value):\n    with _locale_lock:\n        locale.setlocale(category, value)","typeGuard":null,"tryCatchPattern":"import time\n\ndef strptime_stable(fmt, s, attempts=3):\n    for _ in range(attempts):\n        try:\n            return time.strptime(s, fmt)\n        except ValueError as e:\n            if 'locale changed' in str(e) or 'timezone changed' in str(e):\n                continue\n            raise\n    raise","preventionTips":["Call locale.setlocale exactly once at startup, before worker threads exist.","Never flip locales per request in threaded servers.","Serialize locale changes and strptime with one lock, or pin LC_TIME to 'C'.","After a legitimate locale change, the next strptime rebuilds the cache — a single retry is safe."],"tags":["python","strptime","locale","threading","race-condition","stdlib"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}