#! /usr/bin/env python3.3 """Tools for converting to/from different possible time zones. Optimally lazy programmers do all their work in UTC, convert to time zone only at the last minute before displaying a value. Common stuff: dt = git_to_utc_dt("1423782419 -0800") dt = now_utc_dt() # current time in UTC # from seconds since the epoch dt = datetime.datetime.fromtimestamp(seconds_since_epoch) dt.timestamp() # to seconds since the epoch # in Git Fusion server's timezone dt.astimezone(local_tzinfo()) # in Perforce server's timezone dt.astimezone(server_tzinfo(ctx)) """ # lru_cache(maxsize=1) is easier to write than # our own module-wide caching variables and # "if x is not None then do work" code. from functools import lru_cache import datetime import logging import pytz import p4gf_const from p4gf_l10n import _, NTR import p4gf_p4key as P4Key LOG = logging.getLogger(__name__) @lru_cache(maxsize=1) def server_tzname(ctx): """Return the Perforce server's timezone name, such as "US/Pacific". Does NOT return ambiguous and annoying local abbreviations such as "EST" or "PDT". Cache this result, or use get_server_time(), to avoid pointless round-trips to the server. """ value = P4Key.get(ctx.p4gf, p4gf_const.P4GF_P4KEY_TIME_ZONE_NAME) if value == '0' or value is None: # Upgrade from an EA system, perhaps, in which the upgrade p4keys # have been set but the later changes where not applied during init. msg = _("p4key '{key}' not set, using UTC as default." " Change this to your Perforce server's time zone.") \ .format(key=p4gf_const.P4GF_P4KEY_TIME_ZONE_NAME) LOG.warning(msg) value = NTR('UTC') return value @lru_cache(maxsize=1) def server_tzinfo(ctx): """Return the pytz timezone object for the Perforce server.""" tzname = server_tzname(ctx) try: return pytz.timezone(tzname) except pytz.exceptions.UnknownTimeZoneError: LOG.warning("Time zone name '{}' unrecognized, using UTC as default" .format(tzname)) return pytz.utc @lru_cache(maxsize=1) def local_tzinfo(ctx): """Return this Git Fusion server's local timezone pytz object. Uses optional module tzlocal to fetch the local computer's timezone object. Module pytz lacks an API for this (!). If tzlocal not installed, assume this Git Fusion server runs on the same timezone as the Perforce server. ctx used only if we have to fallback to server timezone. """ try: import tzlocal # Optional, see https://github.com/regebro/tzlocal return tzlocal.get_localzone() except ImportError: pass return server_tzinfo(ctx) def now_utc_dt(): """Return the current time, in UTC.""" return datetime.datetime.now(tz=pytz.utc) def git_to_utc_dt(git_str): """Convert a `git fast-export` string such as "1423782419 -0800" to a datetime object in UTC. If tzoffset is present, applies it. """ if " " not in git_str: seconds = int(git_str) return datetime.datetime.fromtimestamp(seconds, pytz.utc) w = git_str.split(" ") return git_strs_to_utc_dt( seconds_str = w[0] , tzoffset_str = w[1] ) def seconds_to_utc_dt(seconds_int): """Convert integer seconds since the epoch to a utc_dt object. No input timezone: assumes those seconds are already in UTC. """ return datetime.datetime.fromtimestamp(seconds_int, pytz.utc) def git_strs_to_utc_dt(seconds_str, tzoffset_str): """Convert `git fast-export` time strings, already split into two such as "1423782419", "-0800", into a datetime object in UTC. """ seconds = int(seconds_str) source_tzinfo = datetime.datetime.strptime(tzoffset_str, "%z") source_dt = datetime.datetime.fromtimestamp(seconds, source_tzinfo) return source_dt.astimezone(pytz.utc) @lru_cache(maxsize=1) def utc_dt_to_p4d_secs(utc_dt, ctx): """Convert UTC to seconds-since-the-epoch, in the server's timezone.""" return int(utc_dt.astimezone(server_tzinfo(ctx)).timestamp())