1"""
2Timezone-related classes and functions.
3"""
4
5import functools
6import zoneinfo
7from contextlib import ContextDecorator
8from datetime import datetime, timedelta, timezone, tzinfo
9
10from asgiref.local import Local
11
12from django.conf import settings
13
14__all__ = [
15 "get_fixed_timezone",
16 "get_default_timezone",
17 "get_default_timezone_name",
18 "get_current_timezone",
19 "get_current_timezone_name",
20 "activate",
21 "deactivate",
22 "override",
23 "localtime",
24 "localdate",
25 "now",
26 "is_aware",
27 "is_naive",
28 "make_aware",
29 "make_naive",
30]
31
32
33def get_fixed_timezone(offset):
34 """Return a tzinfo instance with a fixed offset from UTC."""
35 if isinstance(offset, timedelta):
36 offset = offset.total_seconds() // 60
37 sign = "-" if offset < 0 else "+"
38 hhmm = "%02d%02d" % divmod(abs(offset), 60)
39 name = sign + hhmm
40 return timezone(timedelta(minutes=offset), name)
41
42
43# In order to avoid accessing settings at compile time,
44# wrap the logic in a function and cache the result.
45@functools.lru_cache
46def get_default_timezone():
47 """
48 Return the default time zone as a tzinfo instance.
49
50 This is the time zone defined by settings.TIME_ZONE.
51 """
52 return zoneinfo.ZoneInfo(settings.TIME_ZONE)
53
54
55# This function exists for consistency with get_current_timezone_name
56def get_default_timezone_name():
57 """Return the name of the default time zone."""
58 return _get_timezone_name(get_default_timezone())
59
60
61_active = Local()
62
63
64def get_current_timezone():
65 """Return the currently active time zone as a tzinfo instance."""
66 return getattr(_active, "value", get_default_timezone())
67
68
69def get_current_timezone_name():
70 """Return the name of the currently active time zone."""
71 return _get_timezone_name(get_current_timezone())
72
73
74def _get_timezone_name(timezone):
75 """
76 Return the offset for fixed offset timezones, or the name of timezone if
77 not set.
78 """
79 return timezone.tzname(None) or str(timezone)
80
81
82# Timezone selection functions.
83
84# These functions don't change os.environ['TZ'] and call time.tzset()
85# because it isn't thread safe.
86
87
88def activate(timezone):
89 """
90 Set the time zone for the current thread.
91
92 The ``timezone`` argument must be an instance of a tzinfo subclass or a
93 time zone name.
94 """
95 if isinstance(timezone, tzinfo):
96 _active.value = timezone
97 elif isinstance(timezone, str):
98 _active.value = zoneinfo.ZoneInfo(timezone)
99 else:
100 raise ValueError("Invalid timezone: %r" % timezone)
101
102
103def deactivate():
104 """
105 Unset the time zone for the current thread.
106
107 Django will then use the time zone defined by settings.TIME_ZONE.
108 """
109 if hasattr(_active, "value"):
110 del _active.value
111
112
113class override(ContextDecorator):
114 """
115 Temporarily set the time zone for the current thread.
116
117 This is a context manager that uses django.utils.timezone.activate()
118 to set the timezone on entry and restores the previously active timezone
119 on exit.
120
121 The ``timezone`` argument must be an instance of a ``tzinfo`` subclass, a
122 time zone name, or ``None``. If it is ``None``, Django enables the default
123 time zone.
124 """
125
126 def __init__(self, timezone):
127 self.timezone = timezone
128
129 def __enter__(self):
130 self.old_timezone = getattr(_active, "value", None)
131 if self.timezone is None:
132 deactivate()
133 else:
134 activate(self.timezone)
135
136 def __exit__(self, exc_type, exc_value, traceback):
137 if self.old_timezone is None:
138 deactivate()
139 else:
140 _active.value = self.old_timezone
141
142
143# Templates
144
145
146def template_localtime(value, use_tz=None):
147 """
148 Check if value is a datetime and converts it to local time if necessary.
149
150 If use_tz is provided and is not None, that will force the value to
151 be converted (or not), overriding the value of settings.USE_TZ.
152
153 This function is designed for use by the template engine.
154 """
155 should_convert = (
156 isinstance(value, datetime)
157 and (settings.USE_TZ if use_tz is None else use_tz)
158 and not is_naive(value)
159 and getattr(value, "convert_to_local_time", True)
160 )
161 return localtime(value) if should_convert else value
162
163
164# Utilities
165
166
167def localtime(value=None, timezone=None):
168 """
169 Convert an aware datetime.datetime to local time.
170
171 Only aware datetimes are allowed. When value is omitted, it defaults to
172 now().
173
174 Local time is defined by the current time zone, unless another time zone
175 is specified.
176 """
177 if value is None:
178 value = now()
179 if timezone is None:
180 timezone = get_current_timezone()
181 # Emulate the behavior of astimezone() on Python < 3.6.
182 if is_naive(value):
183 raise ValueError("localtime() cannot be applied to a naive datetime")
184 return value.astimezone(timezone)
185
186
187def localdate(value=None, timezone=None):
188 """
189 Convert an aware datetime to local time and return the value's date.
190
191 Only aware datetimes are allowed. When value is omitted, it defaults to
192 now().
193
194 Local time is defined by the current time zone, unless another time zone is
195 specified.
196 """
197 return localtime(value, timezone).date()
198
199
200def now():
201 """
202 Return an aware or naive datetime.datetime, depending on settings.USE_TZ.
203 """
204 return datetime.now(tz=timezone.utc if settings.USE_TZ else None)
205
206
207# By design, these four functions don't perform any checks on their arguments.
208# The caller should ensure that they don't receive an invalid value like None.
209
210
211def is_aware(value):
212 """
213 Determine if a given datetime.datetime is aware.
214
215 The concept is defined in Python's docs:
216 https://docs.python.org/library/datetime.html#datetime.tzinfo
217
218 Assuming value.tzinfo is either None or a proper datetime.tzinfo,
219 value.utcoffset() implements the appropriate logic.
220 """
221 return value.utcoffset() is not None
222
223
224def is_naive(value):
225 """
226 Determine if a given datetime.datetime is naive.
227
228 The concept is defined in Python's docs:
229 https://docs.python.org/library/datetime.html#datetime.tzinfo
230
231 Assuming value.tzinfo is either None or a proper datetime.tzinfo,
232 value.utcoffset() implements the appropriate logic.
233 """
234 return value.utcoffset() is None
235
236
237def make_aware(value, timezone=None):
238 """Make a naive datetime.datetime in a given time zone aware."""
239 if timezone is None:
240 timezone = get_current_timezone()
241 # Check that we won't overwrite the timezone of an aware datetime.
242 if is_aware(value):
243 raise ValueError("make_aware expects a naive datetime, got %s" % value)
244 # This may be wrong around DST changes!
245 return value.replace(tzinfo=timezone)
246
247
248def make_naive(value, timezone=None):
249 """Make an aware datetime.datetime naive in a given time zone."""
250 if timezone is None:
251 timezone = get_current_timezone()
252 # Emulate the behavior of astimezone() on Python < 3.6.
253 if is_naive(value):
254 raise ValueError("make_naive() cannot be applied to a naive datetime")
255 return value.astimezone(timezone).replace(tzinfo=None)
256
257
258def _datetime_ambiguous_or_imaginary(dt, tz):
259 return tz.utcoffset(dt.replace(fold=not dt.fold)) != tz.utcoffset(dt)