+def get_server_timezone():
+ # timezone detection is safe in multithread, so lazy init is ok here
+ if (not config['timezone']):
+ config['timezone'] = detect_server_timezone()
+ return config['timezone']
+
+
+DEFAULT_SERVER_DATE_FORMAT = "%Y-%m-%d"
+DEFAULT_SERVER_TIME_FORMAT = "%H:%M:%S"
+DEFAULT_SERVER_DATETIME_FORMAT = "%s %s" % (
+ DEFAULT_SERVER_DATE_FORMAT,
+ DEFAULT_SERVER_TIME_FORMAT)
+
+# Python's strftime supports only the format directives
+# that are available on the platform's libc, so in order to
+# be cross-platform we map to the directives required by
+# the C standard (1989 version), always available on platforms
+# with a C standard implementation.
+DATETIME_FORMATS_MAP = {
+ '%C': '', # century
+ '%D': '%m/%d/%Y', # modified %y->%Y
+ '%e': '%d',
+ '%E': '', # special modifier
+ '%F': '%Y-%m-%d',
+ '%g': '%Y', # modified %y->%Y
+ '%G': '%Y',
+ '%h': '%b',
+ '%k': '%H',
+ '%l': '%I',
+ '%n': '\n',
+ '%O': '', # special modifier
+ '%P': '%p',
+ '%R': '%H:%M',
+ '%r': '%I:%M:%S %p',
+ '%s': '', #num of seconds since epoch
+ '%T': '%H:%M:%S',
+ '%t': ' ', # tab
+ '%u': ' %w',
+ '%V': '%W',
+ '%y': '%Y', # Even if %y works, it's ambiguous, so we should use %Y
+ '%+': '%Y-%m-%d %H:%M:%S',
+
+ # %Z is a special case that causes 2 problems at least:
+ # - the timezone names we use (in res_user.context_tz) come
+ # from pytz, but not all these names are recognized by
+ # strptime(), so we cannot convert in both directions
+ # when such a timezone is selected and %Z is in the format
+ # - %Z is replaced by an empty string in strftime() when
+ # there is not tzinfo in a datetime value (e.g when the user
+ # did not pick a context_tz). The resulting string does not
+ # parse back if the format requires %Z.
+ # As a consequence, we strip it completely from format strings.
+ # The user can always have a look at the context_tz in
+ # preferences to check the timezone.
+ '%z': '',
+ '%Z': '',
+}
+
+def server_to_local_timestamp(src_tstamp_str, src_format, dst_format, dst_tz_name,
+ tz_offset=True, ignore_unparsable_time=True):
+ """
+ Convert a source timestamp string into a destination timestamp string, attempting to apply the
+ correct offset if both the server and local timezone are recognized, or no
+ offset at all if they aren't or if tz_offset is false (i.e. assuming they are both in the same TZ).
+
+ WARNING: This method is here to allow formatting dates correctly for inclusion in strings where
+ the client would not be able to format/offset it correctly. DO NOT use it for returning
+ date fields directly, these are supposed to be handled by the client!!
+
+ @param src_tstamp_str: the str value containing the timestamp in the server timezone.
+ @param src_format: the format to use when parsing the server timestamp.
+ @param dst_format: the format to use when formatting the resulting timestamp for the local/client timezone.
+ @param dst_tz_name: name of the destination timezone (such as the 'tz' value of the client context)
+ @param ignore_unparsable_time: if True, return False if src_tstamp_str cannot be parsed
+ using src_format or formatted using dst_format.
+
+ @return: local/client formatted timestamp, expressed in the local/client timezone if possible
+ and if tz_offset is true, or src_tstamp_str if timezone offset could not be determined.
+ """
+ if not src_tstamp_str:
+ return False
+
+ res = src_tstamp_str
+ if src_format and dst_format:
+ # find out server timezone
+ server_tz = get_server_timezone()
+ try:
+ # dt_value needs to be a datetime.datetime object (so no time.struct_time or mx.DateTime.DateTime here!)
+ dt_value = datetime.strptime(src_tstamp_str, src_format)
+ if tz_offset and dst_tz_name:
+ try:
+ import pytz
+ src_tz = pytz.timezone(server_tz)
+ dst_tz = pytz.timezone(dst_tz_name)
+ src_dt = src_tz.localize(dt_value, is_dst=True)
+ dt_value = src_dt.astimezone(dst_tz)
+ except Exception:
+ pass
+ res = dt_value.strftime(dst_format)
+ except Exception:
+ # Normal ways to end up here are if strptime or strftime failed
+ if not ignore_unparsable_time:
+ return False
+ return res
+