utils.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import calendar
  2. import datetime
  3. import logging
  4. import os
  5. import time
  6. import warnings
  7. import zoneinfo
  8. from tzlocal import windows_tz
  9. log = logging.getLogger("tzlocal")
  10. def get_tz_offset(tz):
  11. """Get timezone's offset using built-in function datetime.utcoffset()."""
  12. return int(datetime.datetime.now(tz).utcoffset().total_seconds())
  13. def assert_tz_offset(tz, error=True):
  14. """Assert that system's timezone offset equals to the timezone offset found.
  15. If they don't match, we probably have a misconfiguration, for example, an
  16. incorrect timezone set in /etc/timezone file in systemd distributions.
  17. If error is True, this method will raise a ValueError, otherwise it will
  18. emit a warning.
  19. """
  20. tz_offset = get_tz_offset(tz)
  21. system_offset = calendar.timegm(time.localtime()) - calendar.timegm(time.gmtime())
  22. # No one has timezone offsets less than a minute, so this should be close enough:
  23. if abs(tz_offset - system_offset) > 60:
  24. msg = (
  25. f"Timezone offset does not match system offset: {tz_offset} != {system_offset}. "
  26. "Please, check your config files."
  27. )
  28. if error:
  29. raise ValueError(msg)
  30. warnings.warn(msg)
  31. def _tz_name_from_env(tzenv=None):
  32. if tzenv is None:
  33. tzenv = os.environ.get("TZ")
  34. if not tzenv:
  35. return None
  36. log.debug(f"Found a TZ environment: {tzenv}")
  37. if tzenv[0] == ":":
  38. tzenv = tzenv[1:]
  39. if tzenv in windows_tz.tz_win:
  40. # Yup, it's a timezone
  41. return tzenv
  42. if os.path.isabs(tzenv) and os.path.exists(tzenv):
  43. # It's a file specification, expand it, if possible
  44. parts = os.path.realpath(tzenv).split(os.sep)
  45. # Is it a zone info zone?
  46. possible_tz = "/".join(parts[-2:])
  47. if possible_tz in windows_tz.tz_win:
  48. # Yup, it is
  49. return possible_tz
  50. # Maybe it's a short one, like UTC?
  51. if parts[-1] in windows_tz.tz_win:
  52. # Indeed
  53. return parts[-1]
  54. log.debug("TZ does not contain a time zone name")
  55. return None
  56. def _tz_from_env(tzenv=None):
  57. if tzenv is None:
  58. tzenv = os.environ.get("TZ")
  59. if not tzenv:
  60. return None
  61. # Some weird format that exists:
  62. if tzenv[0] == ":":
  63. tzenv = tzenv[1:]
  64. # TZ specifies a file
  65. if os.path.isabs(tzenv) and os.path.exists(tzenv):
  66. # Try to see if we can figure out the name
  67. tzname = _tz_name_from_env(tzenv)
  68. if not tzname:
  69. # Nope, not a standard timezone name, just take the filename
  70. tzname = tzenv.split(os.sep)[-1]
  71. with open(tzenv, "rb") as tzfile:
  72. return zoneinfo.ZoneInfo.from_file(tzfile, key=tzname)
  73. # TZ must specify a zoneinfo zone.
  74. try:
  75. tz = zoneinfo.ZoneInfo(tzenv)
  76. # That worked, so we return this:
  77. return tz
  78. except zoneinfo.ZoneInfoNotFoundError:
  79. # Nope, it's something like "PST4DST" etc, we can't handle that.
  80. raise zoneinfo.ZoneInfoNotFoundError(
  81. f"tzlocal() does not support non-zoneinfo timezones like {tzenv}. \n"
  82. "Please use a timezone in the form of Continent/City"
  83. ) from None