diff options
| author | Paul Ganssle <paul@ganssle.io> | 2021-01-19 11:16:01 +0100 |
|---|---|---|
| committer | Carlton Gibson <carlton.gibson@noumenal.es> | 2021-01-19 12:00:40 +0100 |
| commit | a5d70cca12aaa1067871eb4e67ab2ed088d1edd6 (patch) | |
| tree | 9cee8f3ffda53629160df0fab580d8e03ae13ce7 /django | |
| parent | de4e854f079dd3a638b9919ad73e5835d5e90d3f (diff) | |
[3.2.x] Refs #32365 -- Allowed use of non-pytz timezone implementations.
Backport of 10d126198434810529e0220b0c6896ed64ca0e88 from master
Diffstat (limited to 'django')
| -rw-r--r-- | django/forms/utils.py | 5 | ||||
| -rw-r--r-- | django/utils/dateformat.py | 40 | ||||
| -rw-r--r-- | django/utils/timezone.py | 26 |
3 files changed, 43 insertions, 28 deletions
diff --git a/django/forms/utils.py b/django/forms/utils.py index fbe79f1142..50412f414b 100644 --- a/django/forms/utils.py +++ b/django/forms/utils.py @@ -161,6 +161,11 @@ def from_current_timezone(value): if settings.USE_TZ and value is not None and timezone.is_naive(value): current_timezone = timezone.get_current_timezone() try: + if ( + not timezone._is_pytz_zone(current_timezone) and + timezone._datetime_ambiguous_or_imaginary(value, current_timezone) + ): + raise ValueError('Ambiguous or non-existent time.') return timezone.make_aware(value, current_timezone) except Exception as exc: raise ValidationError( diff --git a/django/utils/dateformat.py b/django/utils/dateformat.py index 9bd05a437b..38e89c47bb 100644 --- a/django/utils/dateformat.py +++ b/django/utils/dateformat.py @@ -20,7 +20,8 @@ from django.utils.dates import ( ) from django.utils.regex_helper import _lazy_re_compile from django.utils.timezone import ( - get_default_timezone, is_aware, is_naive, make_aware, + _datetime_ambiguous_or_imaginary, get_default_timezone, is_aware, is_naive, + make_aware, ) from django.utils.translation import gettext as _ @@ -160,15 +161,9 @@ class TimeFormat(Formatter): if not self.timezone: return "" - name = None - try: + if not _datetime_ambiguous_or_imaginary(self.data, self.timezone): name = self.timezone.tzname(self.data) - except Exception: - # pytz raises AmbiguousTimeError during the autumn DST change. - # This happens mainly when __init__ receives a naive datetime - # and sets self.timezone = get_default_timezone(). - pass - if name is None: + else: name = self.format('O') return str(name) @@ -184,16 +179,13 @@ class TimeFormat(Formatter): If timezone information is not available, return an empty string. """ - if not self.timezone: + if ( + not self.timezone or + _datetime_ambiguous_or_imaginary(self.data, self.timezone) + ): return "" - try: - offset = self.timezone.utcoffset(self.data) - except Exception: - # pytz raises AmbiguousTimeError during the autumn DST change. - # This happens mainly when __init__ receives a naive datetime - # and sets self.timezone = get_default_timezone(). - return "" + offset = self.timezone.utcoffset(self.data) # `offset` is a datetime.timedelta. For negative values (to the west of # UTC) only days can be negative (days=-1) and seconds are always @@ -232,16 +224,12 @@ class DateFormat(TimeFormat): def I(self): # NOQA: E743, E741 "'1' if Daylight Savings Time, '0' otherwise." - try: - if self.timezone and self.timezone.dst(self.data): - return '1' - else: - return '0' - except Exception: - # pytz raises AmbiguousTimeError during the autumn DST change. - # This happens mainly when __init__ receives a naive datetime - # and sets self.timezone = get_default_timezone(). + if ( + not self.timezone or + _datetime_ambiguous_or_imaginary(self.data, self.timezone) + ): return '' + return '1' if self.timezone.dst(self.data) else '0' def j(self): "Day of the month without leading zeros; i.e. '1' to '31'" diff --git a/django/utils/timezone.py b/django/utils/timezone.py index a87ec5fc33..cf22ec34d0 100644 --- a/django/utils/timezone.py +++ b/django/utils/timezone.py @@ -24,6 +24,11 @@ __all__ = [ # UTC time zone as a tzinfo instance. utc = pytz.utc +_PYTZ_BASE_CLASSES = (pytz.tzinfo.BaseTzInfo, pytz._FixedOffset) +# In releases prior to 2018.4, pytz.UTC was not a subclass of BaseTzInfo +if not isinstance(pytz.UTC, pytz._FixedOffset): + _PYTZ_BASE_CLASSES = _PYTZ_BASE_CLASSES + (type(pytz.UTC),) + def get_fixed_timezone(offset): """Return a tzinfo instance with a fixed offset from UTC.""" @@ -68,7 +73,7 @@ def get_current_timezone_name(): def _get_timezone_name(timezone): """Return the name of ``timezone``.""" - return timezone.tzname(None) + return str(timezone) # Timezone selection functions. @@ -229,7 +234,7 @@ def make_aware(value, timezone=None, is_dst=None): """Make a naive datetime.datetime in a given time zone aware.""" if timezone is None: timezone = get_current_timezone() - if hasattr(timezone, 'localize'): + if _is_pytz_zone(timezone): # This method is available for pytz time zones. return timezone.localize(value, is_dst=is_dst) else: @@ -249,3 +254,20 @@ def make_naive(value, timezone=None): if is_naive(value): raise ValueError("make_naive() cannot be applied to a naive datetime") return value.astimezone(timezone).replace(tzinfo=None) + + +def _is_pytz_zone(tz): + """Checks if a zone is a pytz zone.""" + return isinstance(tz, _PYTZ_BASE_CLASSES) + + +def _datetime_ambiguous_or_imaginary(dt, tz): + if _is_pytz_zone(tz): + try: + tz.utcoffset(dt) + except (pytz.AmbiguousTimeError, pytz.NonExistentTimeError): + return True + else: + return False + + return tz.utcoffset(dt.replace(fold=not dt.fold)) != tz.utcoffset(dt) |
