From 3faf65f9a14640439782ae7a918c26790f8c3fb8 Mon Sep 17 00:00:00 2001 From: Steven Willis Date: Tue, 27 Jul 2021 14:40:35 -0400 Subject: [PATCH] Consider the local tz when creating a datetime object from timestamp Otherwise datetime.fromtimestamp will return a naive datetime in the local timezone and applying timezone modifications later with TIMEZONE and TO_TIMEZONE won't do the right thing. Parsing a unix timestamp value should always result in the exact same instant in time regardless of current time zone, so it shouldn't matter what the current TIMEZONE setting is, whether 'UTC', 'local', any other timezone, or even unset. --- dateparser/date.py | 15 ++++++++++++--- dateparser/utils/__init__.py | 19 ++++++++++--------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/dateparser/date.py b/dateparser/date.py index 01e269554..8cc3d2d57 100644 --- a/dateparser/date.py +++ b/dateparser/date.py @@ -4,6 +4,7 @@ from datetime import datetime, timedelta import regex as re +from tzlocal import get_localzone from dateutil.relativedelta import relativedelta from dateparser.date_parser import date_parser @@ -13,7 +14,8 @@ from dateparser.parser import _parse_absolute, _parse_nospaces from dateparser.timezone_parser import pop_tz_offset_from_string from dateparser.utils import apply_timezone_from_settings, \ - set_correct_day_from_settings + set_correct_day_from_settings, \ + get_timezone_from_tz_string APOSTROPHE_LOOK_ALIKE_CHARS = [ '\N{RIGHT SINGLE QUOTATION MARK}', # '\u2019' @@ -114,11 +116,18 @@ def sanitize_date(date_string): def get_date_from_timestamp(date_string, settings): match = RE_SEARCH_TIMESTAMP.search(date_string) if match: + if settings is not None and settings.TIMEZONE is not None and 'local' not in settings.TIMEZONE.lower(): + local_timezone = get_timezone_from_tz_string(settings.TIMEZONE) + else: + local_timezone = get_localzone() + seconds = int(match.group(1)) millis = int(match.group(2) or 0) micros = int(match.group(3) or 0) - date_obj = datetime.fromtimestamp(seconds) - date_obj = date_obj.replace(microsecond=millis * 1000 + micros) + date_obj = (datetime + .fromtimestamp(seconds, local_timezone) + .replace(microsecond=millis * 1000 + micros, tzinfo=None) + ) date_obj = apply_timezone_from_settings(date_obj, settings) return date_obj diff --git a/dateparser/utils/__init__.py b/dateparser/utils/__init__.py index 70b2fae48..b3ae557e7 100644 --- a/dateparser/utils/__init__.py +++ b/dateparser/utils/__init__.py @@ -65,22 +65,23 @@ def _get_missing_parts(fmt): return missing -def localize_timezone(date_time, tz_string): - if date_time.tzinfo: - return date_time - - tz = None - +def get_timezone_from_tz_string(tz_string): try: - tz = timezone(tz_string) + return timezone(tz_string) except UnknownTimeZoneError as e: for name, info in _tz_offsets: if info['regex'].search(' %s' % tz_string): - tz = StaticTzInfo(name, info['offset']) - break + return StaticTzInfo(name, info['offset']) else: raise e + +def localize_timezone(date_time, tz_string): + if date_time.tzinfo: + return date_time + + tz = get_timezone_from_tz_string(tz_string) + if hasattr(tz, 'localize'): date_time = tz.localize(date_time) else: