Metadata-Version: 2.4
Name: django-usertz-localize
Version: 0.1.1
Summary: A lightweight Django template tags and middleware library for automatic client-site timezone conversion
Author-email: Reitumetse Mosesi <maholamofokeng@gmail.com>
License-File: LICENSE
Classifier: Framework :: Django
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.9
Requires-Dist: django>=3.2
Description-Content-Type: text/markdown

# 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

```bash
pip install django-usertz-localize
```

### Manual Installation

1. Clone the repository
2. Copy the `django-usertz-localize` directory to your Django project

## Quick Start

### 1. Add to Django Settings

Add `django-usertz-localize` to your `INSTALLED_APPS` in `settings.py`:

```python
INSTALLED_APPS = [
    # ... other apps
    'django_usertz_localize',
]
```

### 2. Add Middleware

Add the timezone middleware to your `MIDDLEWARE` list in `settings.py`:

```python
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' %}):

```html
{% load usertz %}
```

### 4. Initialize Timezone Detection

Include the timezone loader script in your base template (typically in `<head>` or before closing `</body>`):

```html
{% usertz %}
{{ usertz_lib.get }}
```

This injects a JavaScript snippet that:
- Detects the user's timezone using the browser's `Intl` API
- 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)

```html
{{ item.created_at|tz }}
```

**Output:** `2026-06-11 14:30:45+02:00` (in user's timezone)

#### Date Only

```html
{{ item.created_at|tz:"date" }}
```

**Output:** `2026-06-11`

#### Time Only

```html
{{ item.created_at|tz:"time" }}
```

**Output:** `14:30:45`

#### Year Only

```html
{{ item.created_at|tz:"year" }}
```

**Output:** `2026`

#### Month Name

```html
{{ item.created_at|tz:"month" }}
```

**Output:** `June`

#### Custom Format

Use Python's `strftime` format codes with the `custom:` prefix:

```html
{{ item.created_at|tz:"custom:%B %d, %Y" }}
```

**Output:** `June 11, 2026`

```html
{{ item.created_at|tz:"custom:%A, %b %d at %I:%M %p" }}
```

**Output:** `Wednesday, Jun 11 at 02:30 PM`

### Common Format Examples

```html
<!-- 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:

```javascript
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:

```python
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:

```python
localized = timezone.localtime(value)  # Convert to user's timezone
return localized.strftime(format_code)  # Format as requested
```

## Real-World Example

### View

```python
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

```html
{% 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

1. Ensure `{% usertz %}` is included in your template
2. Check that `TimezoneMiddleware` is added to `MIDDLEWARE` in settings
3. Verify cookies are enabled in the browser
4. 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:

```python
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:

```python
# 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.

```python
# 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:

```python
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.
