123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- """
- Timezone-related classes and functions.
- """
-
- import functools
- import sys
- import warnings
-
- try:
- import zoneinfo
- except ImportError:
- from backports import zoneinfo
-
- from contextlib import ContextDecorator
- from datetime import datetime, timedelta, timezone, tzinfo
-
- from asgiref.local import Local
-
- from django.conf import settings
- from django.utils.deprecation import RemovedInDjango50Warning
-
- __all__ = [ # noqa for utc RemovedInDjango50Warning.
- "utc",
- "get_fixed_timezone",
- "get_default_timezone",
- "get_default_timezone_name",
- "get_current_timezone",
- "get_current_timezone_name",
- "activate",
- "deactivate",
- "override",
- "localtime",
- "localdate",
- "now",
- "is_aware",
- "is_naive",
- "make_aware",
- "make_naive",
- ]
-
- # RemovedInDjango50Warning: sentinel for deprecation of is_dst parameters.
- NOT_PASSED = object()
-
-
- def __getattr__(name):
- if name != "utc":
- raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
-
- warnings.warn(
- "The django.utils.timezone.utc alias is deprecated. "
- "Please update your code to use datetime.timezone.utc instead.",
- RemovedInDjango50Warning,
- stacklevel=2,
- )
-
- return timezone.utc
-
-
- def get_fixed_timezone(offset):
- """Return a tzinfo instance with a fixed offset from UTC."""
- if isinstance(offset, timedelta):
- offset = offset.total_seconds() // 60
- sign = "-" if offset < 0 else "+"
- hhmm = "%02d%02d" % divmod(abs(offset), 60)
- name = sign + hhmm
- return timezone(timedelta(minutes=offset), name)
-
-
- # In order to avoid accessing settings at compile time,
- # wrap the logic in a function and cache the result.
- @functools.lru_cache
- def get_default_timezone():
- """
- Return the default time zone as a tzinfo instance.
-
- This is the time zone defined by settings.TIME_ZONE.
- """
- if settings.USE_DEPRECATED_PYTZ:
- import pytz
-
- return pytz.timezone(settings.TIME_ZONE)
- return zoneinfo.ZoneInfo(settings.TIME_ZONE)
-
-
- # This function exists for consistency with get_current_timezone_name
- def get_default_timezone_name():
- """Return the name of the default time zone."""
- return _get_timezone_name(get_default_timezone())
-
-
- _active = Local()
-
-
- def get_current_timezone():
- """Return the currently active time zone as a tzinfo instance."""
- return getattr(_active, "value", get_default_timezone())
-
-
- def get_current_timezone_name():
- """Return the name of the currently active time zone."""
- return _get_timezone_name(get_current_timezone())
-
-
- def _get_timezone_name(timezone):
- """
- Return the offset for fixed offset timezones, or the name of timezone if
- not set.
- """
- return timezone.tzname(None) or str(timezone)
-
-
- # Timezone selection functions.
-
- # These functions don't change os.environ['TZ'] and call time.tzset()
- # because it isn't thread safe.
-
-
- def activate(timezone):
- """
- Set the time zone for the current thread.
-
- The ``timezone`` argument must be an instance of a tzinfo subclass or a
- time zone name.
- """
- if isinstance(timezone, tzinfo):
- _active.value = timezone
- elif isinstance(timezone, str):
- if settings.USE_DEPRECATED_PYTZ:
- import pytz
-
- _active.value = pytz.timezone(timezone)
- else:
- _active.value = zoneinfo.ZoneInfo(timezone)
- else:
- raise ValueError("Invalid timezone: %r" % timezone)
-
-
- def deactivate():
- """
- Unset the time zone for the current thread.
-
- Django will then use the time zone defined by settings.TIME_ZONE.
- """
- if hasattr(_active, "value"):
- del _active.value
-
-
- class override(ContextDecorator):
- """
- Temporarily set the time zone for the current thread.
-
- This is a context manager that uses django.utils.timezone.activate()
- to set the timezone on entry and restores the previously active timezone
- on exit.
-
- The ``timezone`` argument must be an instance of a ``tzinfo`` subclass, a
- time zone name, or ``None``. If it is ``None``, Django enables the default
- time zone.
- """
-
- def __init__(self, timezone):
- self.timezone = timezone
-
- def __enter__(self):
- self.old_timezone = getattr(_active, "value", None)
- if self.timezone is None:
- deactivate()
- else:
- activate(self.timezone)
-
- def __exit__(self, exc_type, exc_value, traceback):
- if self.old_timezone is None:
- deactivate()
- else:
- _active.value = self.old_timezone
-
-
- # Templates
-
-
- def template_localtime(value, use_tz=None):
- """
- Check if value is a datetime and converts it to local time if necessary.
-
- If use_tz is provided and is not None, that will force the value to
- be converted (or not), overriding the value of settings.USE_TZ.
-
- This function is designed for use by the template engine.
- """
- should_convert = (
- isinstance(value, datetime)
- and (settings.USE_TZ if use_tz is None else use_tz)
- and not is_naive(value)
- and getattr(value, "convert_to_local_time", True)
- )
- return localtime(value) if should_convert else value
-
-
- # Utilities
-
-
- def localtime(value=None, timezone=None):
- """
- Convert an aware datetime.datetime to local time.
-
- Only aware datetimes are allowed. When value is omitted, it defaults to
- now().
-
- Local time is defined by the current time zone, unless another time zone
- is specified.
- """
- if value is None:
- value = now()
- if timezone is None:
- timezone = get_current_timezone()
- # Emulate the behavior of astimezone() on Python < 3.6.
- if is_naive(value):
- raise ValueError("localtime() cannot be applied to a naive datetime")
- return value.astimezone(timezone)
-
-
- def localdate(value=None, timezone=None):
- """
- Convert an aware datetime to local time and return the value's date.
-
- Only aware datetimes are allowed. When value is omitted, it defaults to
- now().
-
- Local time is defined by the current time zone, unless another time zone is
- specified.
- """
- return localtime(value, timezone).date()
-
-
- def now():
- """
- Return an aware or naive datetime.datetime, depending on settings.USE_TZ.
- """
- return datetime.now(tz=timezone.utc if settings.USE_TZ else None)
-
-
- # By design, these four functions don't perform any checks on their arguments.
- # The caller should ensure that they don't receive an invalid value like None.
-
-
- def is_aware(value):
- """
- Determine if a given datetime.datetime is aware.
-
- The concept is defined in Python's docs:
- https://docs.python.org/library/datetime.html#datetime.tzinfo
-
- Assuming value.tzinfo is either None or a proper datetime.tzinfo,
- value.utcoffset() implements the appropriate logic.
- """
- return value.utcoffset() is not None
-
-
- def is_naive(value):
- """
- Determine if a given datetime.datetime is naive.
-
- The concept is defined in Python's docs:
- https://docs.python.org/library/datetime.html#datetime.tzinfo
-
- Assuming value.tzinfo is either None or a proper datetime.tzinfo,
- value.utcoffset() implements the appropriate logic.
- """
- return value.utcoffset() is None
-
-
- def make_aware(value, timezone=None, is_dst=NOT_PASSED):
- """Make a naive datetime.datetime in a given time zone aware."""
- if is_dst is NOT_PASSED:
- is_dst = None
- else:
- warnings.warn(
- "The is_dst argument to make_aware(), used by the Trunc() "
- "database functions and QuerySet.datetimes(), is deprecated as it "
- "has no effect with zoneinfo time zones.",
- RemovedInDjango50Warning,
- )
- if timezone is None:
- timezone = get_current_timezone()
- if _is_pytz_zone(timezone):
- # This method is available for pytz time zones.
- return timezone.localize(value, is_dst=is_dst)
- else:
- # Check that we won't overwrite the timezone of an aware datetime.
- if is_aware(value):
- raise ValueError("make_aware expects a naive datetime, got %s" % value)
- # This may be wrong around DST changes!
- return value.replace(tzinfo=timezone)
-
-
- def make_naive(value, timezone=None):
- """Make an aware datetime.datetime naive in a given time zone."""
- if timezone is None:
- timezone = get_current_timezone()
- # Emulate the behavior of astimezone() on Python < 3.6.
- if is_naive(value):
- raise ValueError("make_naive() cannot be applied to a naive datetime")
- return value.astimezone(timezone).replace(tzinfo=None)
-
-
- _PYTZ_IMPORTED = False
-
-
- def _pytz_imported():
- """
- Detects whether or not pytz has been imported without importing pytz.
-
- Copied from pytz_deprecation_shim with thanks to Paul Ganssle.
- """
- global _PYTZ_IMPORTED
-
- if not _PYTZ_IMPORTED and "pytz" in sys.modules:
- _PYTZ_IMPORTED = True
-
- return _PYTZ_IMPORTED
-
-
- def _is_pytz_zone(tz):
- """Checks if a zone is a pytz zone."""
- # See if pytz was already imported rather than checking
- # settings.USE_DEPRECATED_PYTZ to *allow* manually passing a pytz timezone,
- # which some of the test cases (at least) rely on.
- if not _pytz_imported():
- return False
-
- # If tz could be pytz, then pytz is needed here.
- import pytz
-
- _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),)
-
- return isinstance(tz, _PYTZ_BASE_CLASSES)
-
-
- def _datetime_ambiguous_or_imaginary(dt, tz):
- if _is_pytz_zone(tz):
- import pytz
-
- 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)
-
-
- # RemovedInDjango50Warning.
- _DIR = dir()
-
-
- def __dir__():
- return sorted([*_DIR, "utc"])
|