1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
|
# django-timezone-field
[](https://github.com/mfogel/django-timezone-field/actions)
[](https://codecov.io/gh/mfogel/django-timezone-field)
[](https://pypi.python.org/pypi/django-timezone-field/)
[](https://pypi.python.org/pypi/django-timezone-field/)
[](https://pypi.python.org/pypi/django-timezone-field/)
A Django app providing DB, form, and REST framework fields for
[`zoneinfo`](https://docs.python.org/3/library/zoneinfo.html) and [`pytz`](http://pypi.python.org/pypi/pytz/) timezone
objects.
## The transition from `pytz` to `zoneinfo`
Like Django, this app supports both `pytz` and `zoneinfo` objects while the community transitions away from `pytz` to
`zoneinfo`. All exposed fields and functions that return a timezone object accept an optional boolean kwarg `use_pytz`.
If not explicitly specified, the default value used for `use_pytz` matches Django's behavior:
- Django <= 3.X: `use_pytz` defaults to `True`
- Django == 4.X: `use_pytz` defaults to the value of
[`django.conf.settings.USE_DEPRECATED_PYTZ`](https://docs.djangoproject.com/en/4.0/ref/settings/#use-deprecated-pytz),
which itself defaults to `False`
- Django >= 5.X:
[drops support for `pytz` altogether](https://docs.djangoproject.com/en/5.0/releases/5.0/#features-removed-in-5-0),
and this app has done the same.
Note that this app does _not_ declare `pytz` to be a dependency, so if you're using this app with `use_pytz=True`, you'll need
to ensure `pytz` is included in the environment yourself.
### Differences in recognized timezones between `pytz` and `zoneinfo`
`pytz` and `zoneinfo` search for timezone data differently.
- `pytz` bundles and searches within its own copy of the [IANA timezone DB](https://www.iana.org/time-zones)
- `zoneinfo` first searches the local system's timezone DB for a match. If no match is found, it then searches within
the [`tzdata`](https://pypi.org/project/tzdata/) package _if it is installed_. The `tzdata` package contains a copy of
the IANA timezone DB.
If the local system's timezone DB doesn't cover the entire IANA timezone DB and the `tzdata` package is not installed,
you may run across errors like `ZoneInfoNotFoundError: 'No time zone found with key Pacific/Kanton'` for seemingly valid
timezones when transitioning from `pytz` to `zoneinfo`. The easy fix is to add `tzdata` to your project with
`poetry add tzdata` or `pip install tzdata`.
Assuming you have the `tzdata` package installed if needed, no
[data migration](https://docs.djangoproject.com/en/4.0/topics/migrations/#data-migrations) should be necessary when
switching from `pytz` to `zoneinfo`.
## Examples
### Database Field
```python
import zoneinfo
import pytz
from django.db import models
from timezone_field import TimeZoneField
class MyModel(models.Model):
tz1 = TimeZoneField(default="Asia/Dubai") # defaults supported, in ModelForm renders like "Asia/Dubai"
tz2 = TimeZoneField(choices_display="WITH_GMT_OFFSET") # in ModelForm renders like "GMT+04:00 Asia/Dubai"
tz3 = TimeZoneField(use_pytz=True) # returns pytz timezone objects
tz4 = TimeZoneField(use_pytz=False) # returns zoneinfo objects
my_model = MyModel(
tz2="America/Vancouver", # assignment of a string
tz3=pytz.timezone("America/Vancouver"), # assignment of a pytz timezone
tz4=zoneinfo.ZoneInfo("America/Vancouver"), # assignment of a zoneinfo
)
my_model.full_clean() # validates against pytz.common_timezones by default
my_model.save() # values stored in DB as strings
my_model.tz3 # value returned as pytz timezone: <DstTzInfo 'America/Vancouver' LMT-1 day, 15:48:00 STD>
my_model.tz4 # value returned as zoneinfo: zoneinfo.ZoneInfo(key='America/Vancouver')
my_model.tz1 = "UTC" # assignment of a string, immediately converted to timezone object
my_model.tz1 # zoneinfo.ZoneInfo(key='UTC') or pytz.utc, depending on use_pytz default
my_model.tz2 = "Invalid/Not_A_Zone" # immediately raises ValidationError
```
### Form Field
```python
from django import forms
from timezone_field import TimeZoneFormField
class MyForm(forms.Form):
tz1 = TimeZoneFormField() # renders like "Asia/Dubai"
tz2 = TimeZoneFormField(choices_display="WITH_GMT_OFFSET") # renders like "GMT+04:00 Asia/Dubai"
tz3 = TimeZoneFormField(use_pytz=True) # returns pytz timezone objects
tz4 = TimeZoneFormField(use_pytz=False) # returns zoneinfo objects
my_form = MyForm({"tz3": "Europe/Berlin", "tz4": "Europe/Berlin"})
my_form.full_clean() # validates against pytz.common_timezones by default
my_form.cleaned_data["tz3"] # value returned as pytz timezone: <DstTzInfo 'Europe/Berlin' LMT+0:53:00 STD>
my_form.cleaned_data["tz4"] # value returned as zoneinfo: zoneinfo.ZoneInfo(key='Europe/Berlin')
```
### REST Framework Serializer Field
```python
from rest_framework import serializers
from timezone_field.rest_framework import TimeZoneSerializerField
class MySerializer(serializers.Serializer):
tz1 = TimeZoneSerializerField(use_pytz=True)
tz2 = TimeZoneSerializerField(use_pytz=False)
my_serializer = MySerializer(data={
"tz1": "America/Argentina/Buenos_Aires",
"tz2": "America/Argentina/Buenos_Aires",
})
my_serializer.is_valid()
my_serializer.validated_data["tz1"] # <DstTzInfo 'America/Argentina/Buenos_Aires' LMT-1 day, 20:06:00 STD>
my_serializer.validated_data["tz2"] # zoneinfo.ZoneInfo(key='America/Argentina/Buenos_Aires')
```
## Installation
Releases are hosted on [`pypi`](https://pypi.org/project/django-timezone-field/) and can be installed using various
python packaging tools.
```bash
# with poetry
poetry add django-timezone-field
# with pip
pip install django-timezone-field
```
## Running the tests
From the repository root, with [`poetry`](https://python-poetry.org/):
```bash
poetry install
poetry run pytest
```
## Changelog
#### 7.1 (2025-01-11)
- Add support for python 3.13
#### 7.0 (2024-07-07)
- Better default sorting of `choices` ([#116](https://github.com/mfogel/django-timezone-field/issues/116)), ([#123](https://github.com/mfogel/django-timezone-field/issues/123))
- Convert string value to timezone object immediately on creation/assignment.
Accessing a TimeZoneField will _always_ return a timezone or None (never a string).
(Potentially BREAKING: Unknown timezone names now raise `ValidationError` at time of assignment.
Previously, conversion was delayed until model `full_clean` or `save`.)
([#57](https://github.com/mfogel/django-timezone-field/issues/57))
- Add support for django 5.1
- Drop support for django 3.2, 4.0, 4.1
- Change base class of `TimeZoneSerializerField` from DJRF's `Field` to `CharField` ([#137](https://github.com/mfogel/django-timezone-field/issues/137))
#### 6.1.0 (2023-11-25)
- Add support for django 5.0
- Add support for python 3.12
- Fix issue with `Factory` timezone on some BSD systems ([#114](https://github.com/mfogel/django-timezone-field/issues/114))
#### 6.0.1 (2023-09-07)
- Use correct default backend when running with django 3.X ([#109](https://github.com/mfogel/django-timezone-field/issues/109))
#### 6.0 (2023-08-20)
- BREAKING: `pytz` removed from dependencies. If you use this package with `use_pytz=True`, you'll need to install
`pytz` yourself.
- Drop support for django 2.2
- Drop support for python 3.7
#### 5.1 (2023-06-18)
- Add django as a dependency of this package, with correct version constraints
([#90](https://github.com/mfogel/django-timezone-field/issues/90))
- Add support for django 4.1, 4.2
- Add support for python 3.11
#### 5.0 (2022-02-08)
- Add support for `zoneinfo` objects ([#79](https://github.com/mfogel/django-timezone-field/issues/79))
- Add support for django 4.0
- Remove `timezone_field.utils.add_gmt_offset_to_choices`, `display_GMT_offset` kwarg (use `choices_display` instead)
- Drop support for django 3.0, 3.1
- Drop support for python 3.5, 3.6
#### 4.2.3 (2022-01-13)
- Fix sdist installs ([#78](https://github.com/mfogel/django-timezone-field/issues/78))
- Officially support python 3.10
#### 4.2.1 (2021-07-07)
- Reinstate `TimeZoneField.default_choices` ([#76](https://github.com/mfogel/django-timezone-field/issues/76))
#### 4.2 (2021-07-07)
- Officially support django 3.2, python 3.9
- Fix bug with field deconstruction ([#74](https://github.com/mfogel/django-timezone-field/issues/74))
- Housekeeping: use poetry, github actions, pytest
#### 4.1.2 (2021-03-17)
- Avoid `NonExistentTimeError` during DST transition ([#70](https://github.com/mfogel/django-timezone-field/issues/70))
#### 4.1.1 (2020-11-28)
- Don't import `rest_framework` from package root ([#67](https://github.com/mfogel/django-timezone-field/issues/67))
#### 4.1 (2020-11-28)
- Add Django REST Framework serializer field
- Add new `choices_display` kwarg with supported values `WITH_GMT_OFFSET` and `STANDARD`
- Deprecate `display_GMT_offset` kwarg
#### 4.0 (2019-12-03)
- Add support for django 3.0, python 3.8
- Drop support for django 1.11, 2.0, 2.1, python 2.7, 3.4
#### 3.1 (2019-10-02)
- Officially support django 2.2 (already worked)
- Add option to display TZ offsets in form field ([#46](https://github.com/mfogel/django-timezone-field/issues/46))
#### 3.0 (2018-09-15)
- Support django 1.11, 2.0, 2.1
- Add support for python 3.7
- Change default human-readable timezone names to exclude underscores
([#32](https://github.com/mfogel/django-timezone-field/issues/32) &
[#37](https://github.com/mfogel/django-timezone-field/issues/37))
#### 2.1 (2018-03-01)
- Add support for django 1.10, 1.11
- Add support for python 3.6
- Add wheel support
- Support bytes in DB fields ([#38](https://github.com/mfogel/django-timezone-field/issues/38) &
[#39](https://github.com/mfogel/django-timezone-field/issues/39))
#### 2.0 (2016-01-31)
- Drop support for django 1.7, add support for django 1.9
- Drop support for python 3.2, 3.3, add support for python 3.5
- Remove tests from source distribution
#### 1.3 (2015-10-12)
- Drop support for django 1.6, add support for django 1.8
- Various [bug fixes](https://github.com/mfogel/django-timezone-field/issues?q=milestone%3A1.3)
#### 1.2 (2015-02-05)
- For form field, changed default list of accepted timezones from `pytz.all_timezones` to `pytz.common_timezones`, to
match DB field behavior.
#### 1.1 (2014-10-05)
- Django 1.7 compatibility
- Added support for formatting `choices` kwarg as `[[<str>, <str>], ...]`, in addition to previous format of
`[[<pytz.timezone>, <str>], ...]`.
- Changed default list of accepted timezones from `pytz.all_timezones` to `pytz.common_timezones`. If you have timezones
in your DB that are in `pytz.all_timezones` but not in `pytz.common_timezones`, this is a backward-incompatible
change. Old behavior can be restored by specifying `choices=[(tz, tz) for tz in pytz.all_timezones]` in your model
definition.
#### 1.0 (2013-08-04)
- Initial release as `timezone_field`.
## Credits
Originally adapted from [Brian Rosner's django-timezones](https://github.com/brosner/django-timezones).
Made possible thanks to the work of the
[contributors](https://github.com/mfogel/django-timezone-field/graphs/contributors).
|