DEV Community

Cover image for User Timezones in Django
Aidas Bendoraitis
Aidas Bendoraitis

Posted on • Originally published at djangotricks.com

User Timezones in Django

When you create a local website, the local time usually matches your country’s timezone, and all visitors see times in that timezone. That’s not a big issue if your country has only one timezone and your audience is local.

But when building a social platform like pybazaar.com, users are international and need to see times in their timezones. In this article, I’ll show you how to handle that in Django.

Time Zone Database

Since version 4.0, Django has used the zoneinfo library for managing timezones, and it used pytz up to version 3.2. Both rely on the IANA Time Zone Database (tzdata). IANA is the same organization that manages the DNS root zone, IP addresses, and other global internet resources.

Install tzdata in your virtual environment as usual:

(venv)$ pip install --upgrade tzdata 
Enter fullscreen mode Exit fullscreen mode

Timezone Changes

Timezone information changes several times a year due to:

  1. Daylight Saving Time (DST) adjustments
  2. Political and border changes
  3. Shifts in standard time offset

Daylight Saving Time (DST) was first introduced in 1914 in Canada and later standardized in the U.S. in 1966. When dealing with historic dates before 1966—or future dates with uncertain timezone rules—precise time calculations can be unreliable.

# Before U.S. DST standardization: old_date = datetime(1960, 6, 15, 12, 0) # DST rules may change in the future: future_date = datetime(2030, 6, 15, 12, 0) 
Enter fullscreen mode Exit fullscreen mode

Some timezone changes are driven by politics:

  • Country splits or mergers — new countries may adopt different timezones.
  • Regional preferences — states or provinces may change timezone alignment.
  • Symbolic actions — e.g., North Korea introduced "Pyongyang Time" in 2015 by shifting 30 minutes back to symbolically break from Japan’s colonial legacy.

And countries sometimes adjust their UTC offsets:

  • Russia — changed its timezone policy multiple times
  • Venezuela — changed from UTC-4 to UTC-4:30 in 2007, then back in 2016
  • Samoa — jumped from UTC-11 to UTC+13 in 2011, skipping Dec 30 entirely

Best Practices for Django

  • Use named timezones, not fixed UTC offsets.
  • Update tzdata monthly or quarterly.
  • Test with historic and future dates.
  • Handle conversion errors gracefully, falling back to UTC.
  • Store all times in UTC internally.
  • Convert to user’s timezone only in the UI.
  • Include tzdata updates in deployment (Docker, Ansible, etc.).

Timezone Management for a Social Platform

For platforms with global users:

  • Store all datetimes in UTC.
  • Store each user's preferred timezone.
  • Convert times on input/output according to the user’s timezone.

1. Enable Timezone Support in Django Settings

Set the default timezone to UTC:

# settings.py USE_TZ = True TIME_ZONE = "UTC" # Store everything in UTC 
Enter fullscreen mode Exit fullscreen mode

2. Add a timezone Field to the Custom User Model

Use a function for dynamic timezone choices, so you don’t need new migrations when the list changes.

def get_timezone_choices(): import zoneinfo return [(tz, tz) for tz in sorted(zoneinfo.available_timezones())] class User(AbstractUser): # ...  timezone = models.CharField( _("Timezone"), max_length=50, choices=get_timezone_choices, default="UTC" ) 
Enter fullscreen mode Exit fullscreen mode

3. Detect Timezone on the Frontend

Add hidden fields in your Login and Signup forms to capture the user’s timezone from their browser:

document.addEventListener('DOMContentLoaded', function () { const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; const timezoneInput = document.getElementById('id_timezone'); if (timezoneInput) { timezoneInput.value = userTimezone; } }); 
Enter fullscreen mode Exit fullscreen mode

You can also let users change their timezone manually in account settings.

4. Use a Custom DateTime Field in Forms

This field will convert datetimes between UTC and the user’s local timezone:

import datetime from zoneinfo import ZoneInfo from django import forms from django.utils import timezone from django.utils.dateparse import parse_datetime class TimezoneAwareDateTimeField(forms.DateTimeField): widget = forms.DateTimeInput(attrs={"type": "datetime-local"}) def __init__(self, user_timezone=None, *args, **kwargs): self.user_timezone = user_timezone super().__init__(*args, **kwargs) def prepare_value(self, value): if value and self.user_timezone: try: user_tz = ZoneInfo(self.user_timezone) if timezone.is_aware(value): value = value.astimezone(user_tz) except Exception: pass return value def to_python(self, value): if value in self.empty_values: return None if isinstance(value, datetime.datetime): result = value elif isinstance(value, datetime.date): result = datetime.datetime(value.year, value.month, value.day) else: try: result = parse_datetime(value.strip()) except ValueError: raise forms.ValidationError( self.error_messages["invalid"], code="invalid" ) if not result: result = super(forms.DateTimeField).to_python(value) if result and self.user_timezone: try: user_tz = ZoneInfo(self.user_timezone) if timezone.is_naive(result): result = result.replace(tzinfo=user_tz) result = result.astimezone(ZoneInfo("UTC")) except Exception: pass return result 
Enter fullscreen mode Exit fullscreen mode

The type="datetime-local" widget uses the browser’s native date/time picker.

Use the custom field like this:

from django import forms from django.utils.translation import gettext_lazy as _ from myproject.apps.core.form_fields import TimezoneAwareDateTimeField from .models import Post class PostForm(forms.ModelForm): class Meta: model = Post fields = ["title", "content", "published_from"] def __init__(self, request, *args, **kwargs): super().__init__(*args, **kwargs) self.request = request self.fields["published_from"] = TimezoneAwareDateTimeField( label=_("Published from"), help_text=_("Enter date and time in your local timezone."), required=False, user_timezone=self.request.user.timezone, ) 
Enter fullscreen mode Exit fullscreen mode

5. Output Dates and Times in User's Timezone

{% load tz %} {% with user_timezone=request.user.timezone|default:"UTC" %} {{ post.published_from|timezone:user_timezone|date:"j M, Y H:i" }} {% endwith %} 
Enter fullscreen mode Exit fullscreen mode

Other Options

You can also detect the visitor’s timezone in JavaScript and send it via Ajax to be saved in the Django session. Then you can use it even for anonymous users.

Final Words

Timezones aren’t so scary if you follow Django’s best practices:

  • Store all times in UTC.
  • Update tzdata regularly.
  • Use the user's timezone only at input/output stages.
  • Detect the user’s timezone via JavaScript—no need to ask them manually.

This keeps your website accurate, user-friendly, and ready for global audiences.


Cover photo by Andrey Grushnikov

Top comments (0)