Modern web applications serve users across multiple time zones, making proper timezone handling essential for accurate time display and scheduling. Django provides comprehensive timezone support that automatically handles timezone conversion, user preferences, and daylight saving time transitions while maintaining data integrity and user experience.
# settings.py
import os
from django.utils.translation import gettext_lazy as _
# Enable timezone support
USE_TZ = True
# Default timezone (UTC recommended for storage)
TIME_ZONE = 'UTC'
# Available timezones for user selection
COMMON_TIMEZONES = [
('UTC', 'UTC'),
('US/Eastern', 'Eastern Time (US & Canada)'),
('US/Central', 'Central Time (US & Canada)'),
('US/Mountain', 'Mountain Time (US & Canada)'),
('US/Pacific', 'Pacific Time (US & Canada)'),
('Europe/London', 'London'),
('Europe/Paris', 'Paris'),
('Europe/Berlin', 'Berlin'),
('Asia/Tokyo', 'Tokyo'),
('Asia/Shanghai', 'Shanghai'),
('Australia/Sydney', 'Sydney'),
]
# Timezone detection settings
TIMEZONE_COOKIE_NAME = 'timezone'
TIMEZONE_COOKIE_AGE = 365 * 24 * 60 * 60 # 1 year
# settings/base.py
import pytz
from django.conf import settings
# Generate timezone choices from pytz
def get_timezone_choices():
"""Generate timezone choices for forms."""
common_timezones = [
'UTC',
'US/Eastern', 'US/Central', 'US/Mountain', 'US/Pacific',
'Europe/London', 'Europe/Paris', 'Europe/Berlin', 'Europe/Rome',
'Asia/Tokyo', 'Asia/Shanghai', 'Asia/Kolkata', 'Asia/Dubai',
'Australia/Sydney', 'Australia/Melbourne',
'America/New_York', 'America/Chicago', 'America/Denver', 'America/Los_Angeles',
'America/Toronto', 'America/Mexico_City', 'America/Sao_Paulo',
]
choices = []
for tz in common_timezones:
try:
timezone = pytz.timezone(tz)
# Get timezone display name
display_name = tz.replace('_', ' ').replace('/', ' - ')
choices.append((tz, display_name))
except pytz.exceptions.UnknownTimeZoneError:
continue
return sorted(choices, key=lambda x: x[1])
TIMEZONE_CHOICES = get_timezone_choices()
# Timezone middleware configuration
TIMEZONE_MIDDLEWARE_ENABLED = True
TIMEZONE_AUTO_DETECT = True
# models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _
from django.conf import settings
import pytz
class UserProfile(models.Model):
"""User profile with timezone preferences."""
user = models.OneToOneField(User, on_delete=models.CASCADE)
timezone = models.CharField(
max_length=50,
choices=settings.TIMEZONE_CHOICES,
default='UTC',
verbose_name=_('Timezone'),
help_text=_('Select your local timezone')
)
auto_detect_timezone = models.BooleanField(
default=True,
verbose_name=_('Auto-detect timezone'),
help_text=_('Automatically detect timezone from browser')
)
date_format = models.CharField(
max_length=20,
choices=[
('%Y-%m-%d', 'YYYY-MM-DD'),
('%m/%d/%Y', 'MM/DD/YYYY'),
('%d/%m/%Y', 'DD/MM/YYYY'),
('%d.%m.%Y', 'DD.MM.YYYY'),
],
default='%Y-%m-%d',
verbose_name=_('Date Format')
)
time_format = models.CharField(
max_length=20,
choices=[
('%H:%M', '24-hour (HH:MM)'),
('%I:%M %p', '12-hour (HH:MM AM/PM)'),
],
default='%H:%M',
verbose_name=_('Time Format')
)
class Meta:
verbose_name = _('User Profile')
verbose_name_plural = _('User Profiles')
def get_timezone(self):
"""Get user's timezone as pytz timezone object."""
return pytz.timezone(self.timezone)
def get_local_time(self, utc_datetime):
"""Convert UTC datetime to user's local time."""
if not utc_datetime:
return None
user_tz = self.get_timezone()
if utc_datetime.tzinfo is None:
# Assume UTC if no timezone info
from django.utils import timezone
utc_datetime = timezone.make_aware(utc_datetime, pytz.UTC)
return utc_datetime.astimezone(user_tz)
def format_datetime(self, dt):
"""Format datetime according to user preferences."""
if not dt:
return ''
local_dt = self.get_local_time(dt)
date_str = local_dt.strftime(self.date_format)
time_str = local_dt.strftime(self.time_format)
return f"{date_str} {time_str}"
# Signal to create profile
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
# middleware/timezone.py
import pytz
from django.utils import timezone
from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
class TimezoneMiddleware(MiddlewareMixin):
"""Middleware to activate user's timezone."""
def process_request(self, request):
"""Activate appropriate timezone for the request."""
user_timezone = self.get_user_timezone(request)
if user_timezone:
timezone.activate(user_timezone)
else:
timezone.deactivate()
def get_user_timezone(self, request):
"""Determine user's timezone from various sources."""
# 1. Check authenticated user's profile
if hasattr(request, 'user') and request.user.is_authenticated:
try:
profile = request.user.userprofile
if not profile.auto_detect_timezone:
return pytz.timezone(profile.timezone)
except:
pass
# 2. Check session
session_tz = request.session.get('timezone')
if session_tz:
try:
return pytz.timezone(session_tz)
except pytz.exceptions.UnknownTimeZoneError:
pass
# 3. Check cookie
cookie_tz = request.COOKIES.get(settings.TIMEZONE_COOKIE_NAME)
if cookie_tz:
try:
return pytz.timezone(cookie_tz)
except pytz.exceptions.UnknownTimeZoneError:
pass
# 4. Check HTTP headers (from JavaScript detection)
header_tz = request.META.get('HTTP_X_TIMEZONE')
if header_tz:
try:
return pytz.timezone(header_tz)
except pytz.exceptions.UnknownTimeZoneError:
pass
return None
# models.py
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import User
import pytz
class TimestampedModel(models.Model):
"""Abstract model with timezone-aware timestamps."""
created_at = models.DateTimeField(
auto_now_add=True,
verbose_name=_('Created at')
)
updated_at = models.DateTimeField(
auto_now=True,
verbose_name=_('Updated at')
)
class Meta:
abstract = True
def get_created_at_local(self, user_timezone='UTC'):
"""Get creation time in specified timezone."""
if isinstance(user_timezone, str):
user_timezone = pytz.timezone(user_timezone)
return self.created_at.astimezone(user_timezone)
class Event(TimestampedModel):
"""Event model with timezone-aware scheduling."""
title = models.CharField(max_length=200, verbose_name=_('Title'))
description = models.TextField(blank=True, verbose_name=_('Description'))
# Store in UTC, display in user's timezone
start_time = models.DateTimeField(verbose_name=_('Start Time'))
end_time = models.DateTimeField(verbose_name=_('End Time'))
# Store the timezone for reference
timezone = models.CharField(
max_length=50,
default='UTC',
verbose_name=_('Timezone'),
help_text=_('Timezone for this event')
)
organizer = models.ForeignKey(
User,
on_delete=models.CASCADE,
verbose_name=_('Organizer')
)
class Meta:
verbose_name = _('Event')
verbose_name_plural = _('Events')
ordering = ['start_time']
def __str__(self):
return self.title
def get_local_start_time(self, user_timezone=None):
"""Get start time in user's timezone."""
if user_timezone is None:
user_timezone = self.timezone
if isinstance(user_timezone, str):
user_timezone = pytz.timezone(user_timezone)
return self.start_time.astimezone(user_timezone)
def get_local_end_time(self, user_timezone=None):
"""Get end time in user's timezone."""
if user_timezone is None:
user_timezone = self.timezone
if isinstance(user_timezone, str):
user_timezone = pytz.timezone(user_timezone)
return self.end_time.astimezone(user_timezone)
def get_duration(self):
"""Get event duration."""
return self.end_time - self.start_time
def is_happening_now(self):
"""Check if event is currently happening."""
now = timezone.now()
return self.start_time <= now <= self.end_time
def is_upcoming(self):
"""Check if event is upcoming."""
return self.start_time > timezone.now()
def save(self, *args, **kwargs):
"""Ensure times are stored in UTC."""
if self.start_time and self.start_time.tzinfo is None:
# If no timezone info, assume it's in the event's timezone
event_tz = pytz.timezone(self.timezone)
self.start_time = event_tz.localize(self.start_time).astimezone(pytz.UTC)
if self.end_time and self.end_time.tzinfo is None:
event_tz = pytz.timezone(self.timezone)
self.end_time = event_tz.localize(self.end_time).astimezone(pytz.UTC)
super().save(*args, **kwargs)
class BlogPost(TimestampedModel):
"""Blog post with timezone-aware publishing."""
title = models.CharField(max_length=200, verbose_name=_('Title'))
content = models.TextField(verbose_name=_('Content'))
author = models.ForeignKey(User, on_delete=models.CASCADE)
# Scheduled publishing
publish_at = models.DateTimeField(
null=True,
blank=True,
verbose_name=_('Publish at'),
help_text=_('Schedule when this post should be published')
)
is_published = models.BooleanField(default=False, verbose_name=_('Published'))
class Meta:
verbose_name = _('Blog Post')
verbose_name_plural = _('Blog Posts')
ordering = ['-created_at']
def is_scheduled(self):
"""Check if post is scheduled for future publishing."""
if not self.publish_at:
return False
return self.publish_at > timezone.now()
def should_be_published(self):
"""Check if scheduled post should now be published."""
if not self.publish_at or self.is_published:
return False
return self.publish_at <= timezone.now()
def get_publish_time_local(self, user_timezone='UTC'):
"""Get publish time in user's timezone."""
if not self.publish_at:
return None
if isinstance(user_timezone, str):
user_timezone = pytz.timezone(user_timezone)
return self.publish_at.astimezone(user_timezone)
# views.py
from django.shortcuts import render, get_object_or_404
from django.utils import timezone
from django.http import JsonResponse
from django.views.generic import ListView, DetailView
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
import pytz
from datetime import datetime, timedelta
from .models import Event, BlogPost
class TimezoneAwareListView(ListView):
"""Base list view with timezone awareness."""
def get_user_timezone(self):
"""Get current user's timezone."""
if self.request.user.is_authenticated:
try:
return self.request.user.userprofile.get_timezone()
except:
pass
return pytz.UTC
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user_timezone'] = self.get_user_timezone()
context['current_time'] = timezone.now()
return context
class EventListView(TimezoneAwareListView):
"""List events with timezone-aware filtering."""
model = Event
template_name = 'events/event_list.html'
context_object_name = 'events'
paginate_by = 20
def get_queryset(self):
"""Filter events based on timezone and date range."""
queryset = Event.objects.all()
# Filter by date range if provided
start_date = self.request.GET.get('start_date')
end_date = self.request.GET.get('end_date')
if start_date:
try:
start_dt = datetime.strptime(start_date, '%Y-%m-%d')
user_tz = self.get_user_timezone()
start_dt = user_tz.localize(start_dt)
queryset = queryset.filter(start_time__gte=start_dt)
except ValueError:
pass
if end_date:
try:
end_dt = datetime.strptime(end_date, '%Y-%m-%d')
user_tz = self.get_user_timezone()
end_dt = user_tz.localize(end_dt).replace(hour=23, minute=59, second=59)
queryset = queryset.filter(end_time__lte=end_dt)
except ValueError:
pass
return queryset.order_by('start_time')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Add timezone-aware date ranges
user_tz = self.get_user_timezone()
now = timezone.now().astimezone(user_tz)
context.update({
'today': now.date(),
'tomorrow': (now + timedelta(days=1)).date(),
'this_week_start': (now - timedelta(days=now.weekday())).date(),
'this_week_end': (now + timedelta(days=6-now.weekday())).date(),
'next_week_start': (now + timedelta(days=7-now.weekday())).date(),
'next_week_end': (now + timedelta(days=13-now.weekday())).date(),
})
return context
@method_decorator(login_required, name='dispatch')
class EventDetailView(DetailView):
"""Event detail view with timezone conversion."""
model = Event
template_name = 'events/event_detail.html'
context_object_name = 'event'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
event = self.object
# Get user's timezone
try:
user_tz = self.request.user.userprofile.get_timezone()
except:
user_tz = pytz.UTC
# Convert event times to user's timezone
context.update({
'local_start_time': event.get_local_start_time(user_tz),
'local_end_time': event.get_local_end_time(user_tz),
'user_timezone': user_tz,
'event_timezone': pytz.timezone(event.timezone),
'duration': event.get_duration(),
'is_happening_now': event.is_happening_now(),
'is_upcoming': event.is_upcoming(),
})
return context
def timezone_api_view(request):
"""API endpoint for timezone operations."""
if request.method == 'POST':
# Set user's timezone preference
timezone_name = request.POST.get('timezone')
if timezone_name:
try:
# Validate timezone
pytz.timezone(timezone_name)
# Save to session
request.session['timezone'] = timezone_name
# Save to user profile if authenticated
if request.user.is_authenticated:
try:
profile = request.user.userprofile
profile.timezone = timezone_name
profile.save()
except:
pass
return JsonResponse({
'success': True,
'timezone': timezone_name,
'message': 'Timezone updated successfully'
})
except pytz.exceptions.UnknownTimeZoneError:
return JsonResponse({
'success': False,
'error': 'Invalid timezone'
})
elif request.method == 'GET':
# Get current timezone info
current_tz = timezone.get_current_timezone()
now = timezone.now()
return JsonResponse({
'current_timezone': str(current_tz),
'current_time_utc': now.isoformat(),
'current_time_local': now.astimezone(current_tz).isoformat(),
'available_timezones': [tz[0] for tz in settings.TIMEZONE_CHOICES],
})
return JsonResponse({'error': 'Method not allowed'}, status=405)
def world_clock_view(request):
"""Display world clock with multiple timezones."""
world_timezones = [
('UTC', 'UTC'),
('US/Eastern', 'New York'),
('US/Pacific', 'Los Angeles'),
('Europe/London', 'London'),
('Europe/Paris', 'Paris'),
('Asia/Tokyo', 'Tokyo'),
('Asia/Shanghai', 'Shanghai'),
('Australia/Sydney', 'Sydney'),
]
current_time = timezone.now()
world_times = []
for tz_name, display_name in world_timezones:
tz = pytz.timezone(tz_name)
local_time = current_time.astimezone(tz)
world_times.append({
'timezone': tz_name,
'display_name': display_name,
'time': local_time,
'formatted_time': local_time.strftime('%H:%M'),
'formatted_date': local_time.strftime('%Y-%m-%d'),
'is_dst': local_time.dst() != timedelta(0),
})
return render(request, 'timezone/world_clock.html', {
'world_times': world_times,
'current_utc': current_time,
})
<!-- templates/events/event_list.html -->
{% load i18n tz %}
<div class="events-container">
<header class="events-header">
<h1>{% trans "Events" %}</h1>
<div class="timezone-info">
{% blocktrans with tz=user_timezone %}
Times shown in {{ tz }}
{% endblocktrans %}
<a href="#" id="change-timezone">{% trans "Change timezone" %}</a>
</div>
</header>
<div class="date-filters">
<a href="?start_date={{ today }}&end_date={{ today }}">
{% trans "Today" %}
</a>
<a href="?start_date={{ tomorrow }}&end_date={{ tomorrow }}">
{% trans "Tomorrow" %}
</a>
<a href="?start_date={{ this_week_start }}&end_date={{ this_week_end }}">
{% trans "This Week" %}
</a>
<a href="?start_date={{ next_week_start }}&end_date={{ next_week_end }}">
{% trans "Next Week" %}
</a>
</div>
<div class="events-list">
{% for event in events %}
<div class="event-card">
<h3><a href="{% url 'events:detail' event.pk %}">{{ event.title }}</a></h3>
<div class="event-time">
{% timezone user_timezone %}
<time datetime="{{ event.start_time|date:'c' }}">
{{ event.start_time|date:'F j, Y' }} at {{ event.start_time|time:'H:i' }}
</time>
{% if event.end_time %}
-
<time datetime="{{ event.end_time|date:'c' }}">
{% if event.start_time|date:'Y-m-d' == event.end_time|date:'Y-m-d' %}
{{ event.end_time|time:'H:i' }}
{% else %}
{{ event.end_time|date:'F j, Y' }} at {{ event.end_time|time:'H:i' }}
{% endif %}
</time>
{% endif %}
{% endtimezone %}
</div>
<div class="event-status">
{% if event.is_happening_now %}
<span class="status happening">{% trans "Happening now" %}</span>
{% elif event.is_upcoming %}
<span class="status upcoming">{% trans "Upcoming" %}</span>
{% else %}
<span class="status past">{% trans "Past" %}</span>
{% endif %}
</div>
{% if event.description %}
<p class="event-description">{{ event.description|truncatewords:20 }}</p>
{% endif %}
</div>
{% empty %}
<p class="no-events">{% trans "No events found." %}</p>
{% endfor %}
</div>
<!-- Pagination -->
{% if is_paginated %}
<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">{% trans "Previous" %}</a>
{% endif %}
<span class="current">
{% blocktrans with current=page_obj.number total=page_obj.paginator.num_pages %}
Page {{ current }} of {{ total }}
{% endblocktrans %}
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">{% trans "Next" %}</a>
{% endif %}
</div>
{% endif %}
</div>
<!-- templates/timezone/world_clock.html -->
{% load i18n tz %}
<div class="world-clock">
<h1>{% trans "World Clock" %}</h1>
<div class="current-utc">
<h2>{% trans "Current UTC Time" %}</h2>
<time datetime="{{ current_utc|date:'c' }}" class="utc-time">
{{ current_utc|date:'Y-m-d H:i:s' }} UTC
</time>
</div>
<div class="timezone-grid">
{% for time_info in world_times %}
<div class="timezone-card">
<h3>{{ time_info.display_name }}</h3>
<div class="time-display">
<time datetime="{{ time_info.time|date:'c' }}" class="local-time">
{{ time_info.formatted_time }}
</time>
<div class="date">{{ time_info.formatted_date }}</div>
</div>
<div class="timezone-info">
<span class="timezone-name">{{ time_info.timezone }}</span>
{% if time_info.is_dst %}
<span class="dst-indicator">{% trans "DST" %}</span>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
<script>
// Update times every second
function updateTimes() {
const now = new Date();
document.querySelectorAll('.timezone-card').forEach(card => {
const timeElement = card.querySelector('.local-time');
const dateElement = card.querySelector('.date');
const timezoneName = card.querySelector('.timezone-name').textContent;
// Format time for timezone
const localTime = new Intl.DateTimeFormat('en-US', {
timeZone: timezoneName,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}).format(now);
const localDate = new Intl.DateTimeFormat('en-CA', {
timeZone: timezoneName,
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).format(now);
timeElement.textContent = localTime;
dateElement.textContent = localDate;
});
// Update UTC time
const utcElement = document.querySelector('.utc-time');
if (utcElement) {
utcElement.textContent = now.toISOString().slice(0, 19).replace('T', ' ') + ' UTC';
}
}
// Update immediately and then every second
updateTimes();
setInterval(updateTimes, 1000);
</script>
<!-- templates/base.html -->
<script>
// Detect and set user's timezone
function detectAndSetTimezone() {
// Get timezone from browser
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
if (timezone) {
// Set timezone cookie
document.cookie = `timezone=${timezone}; path=/; max-age=31536000`; // 1 year
// Send to server via AJAX
fetch('/api/timezone/', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'X-Timezone': timezone
},
body: `timezone=${encodeURIComponent(timezone)}`
})
.then(response => response.json())
.then(data => {
if (data.success) {
console.log('Timezone set to:', timezone);
// Optionally reload page to apply new timezone
// window.location.reload();
}
})
.catch(error => {
console.error('Error setting timezone:', error);
});
}
}
// Run on page load
document.addEventListener('DOMContentLoaded', detectAndSetTimezone);
// Timezone selector functionality
function initTimezoneSelector() {
const selector = document.getElementById('timezone-selector');
if (selector) {
selector.addEventListener('change', function() {
const selectedTimezone = this.value;
fetch('/api/timezone/', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
},
body: `timezone=${encodeURIComponent(selectedTimezone)}`
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Reload page to apply new timezone
window.location.reload();
} else {
alert('Error setting timezone: ' + data.error);
}
})
.catch(error => {
console.error('Error:', error);
alert('Error setting timezone');
});
});
}
}
document.addEventListener('DOMContentLoaded', initTimezoneSelector);
</script>
# management/commands/update_scheduled_posts.py
from django.core.management.base import BaseCommand
from django.utils import timezone
from blog.models import BlogPost
class Command(BaseCommand):
help = 'Publish scheduled blog posts'
def handle(self, *args, **options):
# Find posts that should be published
posts_to_publish = BlogPost.objects.filter(
is_published=False,
publish_at__lte=timezone.now()
)
count = 0
for post in posts_to_publish:
post.is_published = True
post.save()
count += 1
self.stdout.write(
self.style.SUCCESS(f'Published: {post.title}')
)
self.stdout.write(
self.style.SUCCESS(f'Published {count} scheduled posts')
)
# management/commands/timezone_stats.py
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
from collections import Counter
import pytz
class Command(BaseCommand):
help = 'Show timezone usage statistics'
def handle(self, *args, **options):
# Get timezone distribution
timezones = []
for user in User.objects.select_related('userprofile'):
try:
timezones.append(user.userprofile.timezone)
except:
timezones.append('UTC') # Default
timezone_counts = Counter(timezones)
self.stdout.write('Timezone Usage Statistics\n' + '=' * 40)
for tz, count in timezone_counts.most_common():
percentage = (count / len(timezones)) * 100
self.stdout.write(f'{tz}: {count} users ({percentage:.1f}%)')
self.stdout.write(f'\nTotal users: {len(timezones)}')
# tests/test_timezone.py
from django.test import TestCase, override_settings
from django.utils import timezone
from django.contrib.auth.models import User
from django.urls import reverse
import pytz
from datetime import datetime, timedelta
from events.models import Event
class TimezoneTestCase(TestCase):
def setUp(self):
self.user = User.objects.create_user(
username='testuser',
password='testpass'
)
# Set user timezone to Eastern Time
profile = self.user.userprofile
profile.timezone = 'US/Eastern'
profile.save()
def test_timezone_activation(self):
"""Test timezone activation in middleware."""
self.client.login(username='testuser', password='testpass')
response = self.client.get('/')
# Check if Eastern timezone is activated
current_tz = timezone.get_current_timezone()
self.assertEqual(str(current_tz), 'US/Eastern')
def test_event_timezone_conversion(self):
"""Test event time conversion to user timezone."""
# Create event in UTC
utc_time = timezone.now().replace(hour=15, minute=0, second=0, microsecond=0)
event = Event.objects.create(
title='Test Event',
start_time=utc_time,
end_time=utc_time + timedelta(hours=2),
organizer=self.user
)
# Get event in Eastern time (UTC-5 or UTC-4 depending on DST)
eastern_tz = pytz.timezone('US/Eastern')
local_start = event.get_local_start_time(eastern_tz)
# Verify conversion
self.assertEqual(local_start.tzinfo.zone, 'US/Eastern')
def test_scheduled_post_publishing(self):
"""Test timezone-aware scheduled publishing."""
from blog.models import BlogPost
# Create scheduled post
future_time = timezone.now() + timedelta(hours=1)
post = BlogPost.objects.create(
title='Scheduled Post',
content='Test content',
author=self.user,
publish_at=future_time,
is_published=False
)
# Should not be published yet
self.assertFalse(post.should_be_published())
# Move time forward
past_time = timezone.now() - timedelta(hours=1)
post.publish_at = past_time
post.save()
# Should now be published
self.assertTrue(post.should_be_published())
@override_settings(USE_TZ=False)
def test_timezone_disabled(self):
"""Test behavior when timezone support is disabled."""
# When USE_TZ=False, Django should work with naive datetimes
event = Event.objects.create(
title='Naive Event',
start_time=datetime.now(),
end_time=datetime.now() + timedelta(hours=1),
organizer=self.user
)
# Times should be naive
self.assertIsNone(event.start_time.tzinfo)
Proper timezone support is essential for global applications. Store all times in UTC, convert to user timezones for display, and provide intuitive timezone selection interfaces. Django's timezone framework handles the complexity of timezone conversion, daylight saving time transitions, and user preferences while maintaining data integrity and excellent user experience across all time zones.
Translating Text in Code and Templates
Marking strings for translation is the core of Django's internationalization system. This chapter covers comprehensive techniques for translating text in Python code and Django templates, including advanced patterns for pluralization, context-specific translations, and dynamic content localization.
Locale Middleware
Django's locale middleware automatically detects and activates the appropriate language for each request, providing seamless internationalization without requiring manual language management. This chapter covers configuring, customizing, and optimizing locale middleware for sophisticated multilingual applications with advanced language detection and user preference management.