A lightweight Django template tags and middleware library for automatic client-site timezone conversion
Project description
django-usertz-localize
A lightweight Django template tags and middleware library for automatic client-side timezone conversion. This library automatically detects the user's timezone and converts all datetime values to their local timezone.
Features
- Automatic client-side timezone detection
- Seamless datetime conversion in templates
- Flexible datetime formatting options (date, time, year, month, custom formats)
- Cookie-based timezone persistence
- Zero JavaScript framework dependencies
- Lightweight and production-ready
Installation
Using pip
pip install django-usertz-localize
Manual Installation
- Clone the repository
- Copy the
django-usertz-localizedirectory to your Django project
Quick Start
1. Add to Django Settings
Add django-usertz-localize to your INSTALLED_APPS in settings.py:
INSTALLED_APPS = [
# ... other apps
'django_usertz_localize',
]
2. Add Middleware
Add the timezone middleware to your MIDDLEWARE list in settings.py:
MIDDLEWARE = [
# ... other middleware
'django_usertz_localize.middleware.TimezoneMiddleware',
]
3. Load Template Tags
In your Django templates, load the template tags at the top(Never load before/above {% extend 'index.html' %}):
{% load usertz %}
4. Initialize Timezone Detection
Include the timezone loader script in your base template (typically in <head> or before closing </body>):
{% usertz %}
{{ usertz_lib.get }}
This injects a JavaScript snippet that:
- Detects the user's timezone using the browser's
IntlAPI - Saves it to a cookie named
django_user_tz - Reloads the page if the cookie wasn't set properly
Usage
Automatic Timezone Conversion
Once configured, all datetime values passed through the |tz filter will be converted to the user's local timezone.
Full Datetime Format (Default)
{{ item.created_at|tz }}
Output: 2026-06-11 14:30:45+02:00 (in user's timezone)
Date Only
{{ item.created_at|tz:"date" }}
Output: 2026-06-11
Time Only
{{ item.created_at|tz:"time" }}
Output: 14:30:45
Year Only
{{ item.created_at|tz:"year" }}
Output: 2026
Month Name
{{ item.created_at|tz:"month" }}
Output: June
Custom Format
Use Python's strftime format codes with the custom: prefix:
{{ item.created_at|tz:"custom:%B %d, %Y" }}
Output: June 11, 2026
{{ item.created_at|tz:"custom:%A, %b %d at %I:%M %p" }}
Output: Wednesday, Jun 11 at 02:30 PM
Common Format Examples
<!-- Long date format -->
{{ item.created_at|tz:"custom:%B %d, %Y" }}
<!-- Short date format -->
{{ item.created_at|tz:"custom:%m/%d/%Y" }}
<!-- 12-hour time format -->
{{ item.created_at|tz:"custom:%I:%M %p" }}
<!-- 24-hour time format -->
{{ item.created_at|tz:"custom:%H:%M:%S" }}
<!-- ISO format -->
{{ item.created_at|tz:"custom:%Y-%m-%d %H:%M:%S" }}
<!-- Combined readable format -->
{{ item.created_at|tz:"custom:%A, %B %d, %Y at %I:%M %p" }}
How It Works
1. Browser Timezone Detection
When a page loads, the JavaScript snippet uses the browser's built-in Intl.DateTimeFormat API to detect the user's timezone and stores it in a cookie:
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
document.cookie = "django_user_tz=" + tz + "; path=/; max-age=31536000; SameSite=Lax";
Supported Timezone Format: IANA timezone identifiers (e.g., America/New_York, Europe/London, Asia/Tokyo)
2. Middleware Processing
The TimezoneMiddleware reads the timezone cookie from each request and activates it for the entire request cycle:
user_timezone = request.COOKIES.get('django_user_tz')
if user_timezone:
timezone.activate(zoneinfo.ZoneInfo(user_timezone))
3. Template Filter
The |tz filter converts datetime objects to the active timezone and formats them according to the specified format:
localized = timezone.localtime(value) # Convert to user's timezone
return localized.strftime(format_code) # Format as requested
Real-World Example
View
from django.shortcuts import render
from .models import Post
def blog_list(request):
posts = Post.objects.all()
return render(request, 'blog/list.html', {'posts': posts})
Template
{% load usertz %}
<!DOCTYPE html>
<html>
<head>
<title>Blog Posts</title>
{% usertz %}
</head>
<body>
<h1>Blog Posts</h1>
<table>
<thead>
<tr>
<th>Title</th>
<th>Created</th>
<th>Updated</th>
</tr>
</thead>
<tbody>
{% for post in posts %}
<tr>
<td>{{ post.title }}</td>
<td>{{ post.created_at|tz:"custom:%B %d, %Y at %I:%M %p" }}</td>
<td>{{ post.updated_at|tz:"date" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
User Experience
- User in New York sees:
June 11, 2026 at 10:30 AM(EDT) - User in London sees:
June 11, 2026 at 03:30 PM(BST) - User in Tokyo sees:
June 12, 2026 at 12:30 AM(JST)
All displayed times are automatically localized to each user's timezone.
Timezone Format Codes
| Code | Meaning | Example |
|---|---|---|
%Y |
Year with century | 2026 |
%m |
Month (01-12) | 06 |
%d |
Day (01-31) | 11 |
%B |
Full month name | June |
%b |
Abbreviated month | Jun |
%A |
Full weekday name | Wednesday |
%a |
Abbreviated weekday | Wed |
%H |
Hour (00-23) | 14 |
%I |
Hour (01-12) | 02 |
%M |
Minute (00-59) | 30 |
%S |
Second (00-59) | 45 |
%p |
AM/PM | PM |
%Z |
Timezone name | EDT |
Troubleshooting
Timezone Not Detecting
- Ensure
{% usertz %}is included in your template - Check that
TimezoneMiddlewareis added toMIDDLEWAREin settings - Verify cookies are enabled in the browser
- Open browser DevTools → Application → Cookies and look for
django_user_tz
All Users Seeing Same Timezone
- Check that the middleware is properly added to
MIDDLEWARE - Verify the cookie name matches:
django_user_tz(case-sensitive) - Clear browser cookies and reload
Naive Datetime Warnings
If you see warnings about naive datetimes, ensure your model datetimes are timezone-aware:
from django.db import models
from django.utils import timezone
class Post(models.Model):
created_at = models.DateTimeField(auto_now_add=True) # Timezone-aware
updated_at = models.DateTimeField(auto_now=True) # Timezone-aware
Configuration
Custom Cookie Name
If you need to change the cookie name, modify the JavaScript in your template or create a custom template tag:
# settings.py
USER_TIMEZONE_COOKIE_NAME = 'my_custom_tz'
User Timezone Model Fallback
Set USER_TIMEZONE_USER_MODEL = True to enable storing a user's timezone history and using their last known timezone as a fallback when the cookie is missing.
# settings.py
USER_TIMEZONE_USER_MODEL = True
When enabled, the middleware will:
- read the timezone cookie first
- if the cookie is missing, look up the user's latest timezone from the stored history
- record the user timezone on each authenticated request
When USER_TIMEZONE_USER_MODEL = False, no timezone history model will be loaded or used and the middleware will rely only on the cookie plus the default TIME_ZONE fallback.
Fallback Timezone
Set a default timezone in settings.py for users without a detected timezone or stored history:
TIME_ZONE = 'UTC' # Fallback timezone
USE_TZ = True # Enable timezone support
Browser Support
- Chrome/Edge 24+
- Firefox 20+
- Safari 10+
- Opera 11+
- IE 11+ (partial support)
All modern browsers support the Intl.DateTimeFormat API used for timezone detection.
Performance
- Zero database queries for timezone detection
- Single HTTP cookie per user
- No external API calls
- Lightweight middleware with minimal overhead
License
MIT License - See LICENSE file for details
Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
Support
For issues, questions, or suggestions, please open an issue on the repository.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_usertz_localize-0.1.1.tar.gz.
File metadata
- Download URL: django_usertz_localize-0.1.1.tar.gz
- Upload date:
- Size: 11.4 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fa6d293077e8acea157efd63689181b59e00583665f7d7878a24a1c0b82d11ca
|
|
| MD5 |
9e3b3f0816a461ccd061334d946fdd2f
|
|
| BLAKE2b-256 |
8bd50850e2d3479d5a51f0f8463668432d52549f38f5cc4fc4ad18ef46fcab95
|
File details
Details for the file django_usertz_localize-0.1.1-py3-none-any.whl.
File metadata
- Download URL: django_usertz_localize-0.1.1-py3-none-any.whl
- Upload date:
- Size: 8.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fabd8a8071ba87f0d1512b20101527e79f597b14fe72b8bc48d2ecc4e1f6884b
|
|
| MD5 |
001d19abbdaf61e31dfe605c6c657988
|
|
| BLAKE2b-256 |
c09146c307cedfcde963827e9cd36084ed74dccd83678b261d4bf1584bad3e8c
|