#!/usr/bin/env python3 # vim: set ts=2 sw=2 et sts=2 ai: # # Copyright 2009 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Disable the invalid name warning as we are inheriting from a standard library # object. # pylint: disable=invalid-name,protected-access """ Stripped down version of `python-datetime-tz` ( https://github.com/mithro/python-datetime-tz/blob/master/datetime_tz/__init__.py ) that only contains the "find local timezone" bits. """ import datetime import os.path import time import warnings import pytz # Need to patch pytz.utc to have a _utcoffset so you can normalize/localize # using it. pytz.utc._utcoffset = datetime.timedelta() timedelta = datetime.timedelta def _tzinfome(tzinfo): """Gets a tzinfo object from a string. Args: tzinfo: A string (or string like) object, or a datetime.tzinfo object. Returns: An datetime.tzinfo object. Raises: UnknownTimeZoneError: If the timezone given can't be decoded. """ if not isinstance(tzinfo, datetime.tzinfo): try: tzinfo = pytz.timezone(tzinfo) assert tzinfo.zone in pytz.all_timezones except AttributeError: raise pytz.UnknownTimeZoneError("Unknown timezone! %s" % tzinfo) return tzinfo # Our "local" timezone _localtz = None def localtz(): """Get the local timezone. Returns: The localtime timezone as a tzinfo object. """ # pylint: disable=global-statement global _localtz if _localtz is None: _localtz = detect_timezone() return _localtz def detect_timezone(): """Try and detect the timezone that Python is currently running in. We have a bunch of different methods for trying to figure this out (listed in order they are attempted). * In windows, use win32timezone.TimeZoneInfo.local() * Try TZ environment variable. * Try and find /etc/timezone file (with timezone name). * Try and find /etc/localtime file (with timezone data). * Try and match a TZ to the current dst/offset/shortname. Returns: The detected local timezone as a tzinfo object Raises: pytz.UnknownTimeZoneError: If it was unable to detect a timezone. """ # First we try the TZ variable tz = _detect_timezone_environ() if tz is not None: return tz # Second we try /etc/timezone and use the value in that tz = _detect_timezone_etc_timezone() if tz is not None: return tz # Next we try and see if something matches the tzinfo in /etc/localtime tz = _detect_timezone_etc_localtime() if tz is not None: return tz # Next we try and use a similiar method to what PHP does. # We first try to search on time.tzname, time.timezone, time.daylight to # match a pytz zone. warnings.warn("Had to fall back to worst detection method (the 'PHP' " "method).") tz = _detect_timezone_php() if tz is not None: return tz raise pytz.UnknownTimeZoneError("Unable to detect your timezone!") def _detect_timezone_environ(): if "TZ" in os.environ: try: return pytz.timezone(os.environ["TZ"]) except (IOError, pytz.UnknownTimeZoneError): warnings.warn("You provided a TZ environment value (%r) we did not " "understand!" % os.environ["TZ"]) def _detect_timezone_etc_timezone(): if os.path.exists("/etc/timezone"): try: tz = open("/etc/timezone").read().strip() try: return pytz.timezone(tz) except (IOError, pytz.UnknownTimeZoneError) as ei: warnings.warn("Your /etc/timezone file references a timezone (%r) that" " is not valid (%r)." % (tz, ei)) # Problem reading the /etc/timezone file except IOError as eo: warnings.warn("Could not access your /etc/timezone file: %s" % eo) def _load_local_tzinfo(): """Load zoneinfo from local disk.""" tzdir = os.environ.get("TZDIR", "/usr/share/zoneinfo/posix") localtzdata = {} for dirpath, _, filenames in os.walk(tzdir): for filename in filenames: filepath = os.path.join(dirpath, filename) name = os.path.relpath(filepath, tzdir) f = open(filepath, "rb") tzinfo = pytz.tzfile.build_tzinfo(name, f) f.close() localtzdata[name] = tzinfo return localtzdata def _detect_timezone_etc_localtime(): """Detect timezone based on /etc/localtime file.""" matches = [] if os.path.exists("/etc/localtime"): f = open("/etc/localtime", "rb") localtime = pytz.tzfile.build_tzinfo("/etc/localtime", f) f.close() # We want to match against the local database because /etc/localtime will # be copied from that. Once we have found a name for /etc/localtime, we can # use the name to get the "same" timezone from the inbuilt pytz database. tzdatabase = _load_local_tzinfo() if tzdatabase: tznames = tzdatabase.keys() tzvalues = tzdatabase.__getitem__ else: tznames = pytz.all_timezones tzvalues = _tzinfome # See if we can find a "Human Name" for this.. for tzname in tznames: tz = tzvalues(tzname) if dir(tz) != dir(localtime): continue for attrib in dir(tz): # Ignore functions and specials if callable(getattr(tz, attrib)) or attrib.startswith("__"): continue # This will always be different if attrib == "zone" or attrib == "_tzinfos": continue if getattr(tz, attrib) != getattr(localtime, attrib): break # We get here iff break didn't happen, i.e. no meaningful attributes # differ between tz and localtime else: # Try and get a timezone from pytz which has the same name as the zone # which matches in the local database. if tzname not in pytz.all_timezones: warnings.warn("Skipping %s because not in pytz database." % tzname) continue matches.append(_tzinfome(tzname)) matches.sort(key=lambda x: x.zone) if len(matches) == 1: return matches[0] if len(matches) > 1: warnings.warn("We detected multiple matches for your /etc/localtime. " "(Matches where %s)" % matches) return matches[0] else: warnings.warn("We detected no matches for your /etc/localtime.") # Register /etc/localtime as the timezone loaded. pytz._tzinfo_cache["/etc/localtime"] = localtime return localtime def _detect_timezone_php(): tomatch = (time.tzname[0], time.timezone, time.daylight) now = datetime.datetime.now() matches = [] for tzname in pytz.all_timezones: try: tz = pytz.timezone(tzname) except IOError: continue try: indst = tz.localize(now).timetuple()[-1] if tomatch == (tz._tzname, -tz._utcoffset.seconds, indst): matches.append(tzname) # pylint: disable=pointless-except except AttributeError: pass if len(matches) > 1: warnings.warn("We detected multiple matches for the timezone, choosing " "the first %s. (Matches where %s)" % (matches[0], matches)) if matches: return pytz.timezone(matches[0])