Custom Timezone FDG
A reusable Python package for working with a configurable business time zone, including daylight-saving-time (DST) edge cases. It can be used in regular Python programs, background workers and web applications, with optional Django app and middleware integration.
- Distribution:
custom_timezone_fdg - Python import:
custom_timezone - Version:
0.1.1(set insrc/custom_timezone/__init__.pybefore publishing this release) - License: MIT
The core uses Python's datetime and zoneinfo; Django is not required to use the core helpers. The distribution also depends on tzdata, which supplies IANA time-zone data on platforms that do not provide it themselves.
Features
- Configurable business time zone using an explicit override, process configuration, Django setting or environment variable.
- Parsing and formatting datetimes with IANA time-zone names, including
America/Toronto. - Explicit handling of nonexistent and ambiguous local times around DST transitions.
- Thread- and asyncio-task-local temporary time-zone overrides.
- Optional Django middleware that activates the configured time zone per request and restores the previous one afterward.
- Django system checks for invalid time-zone settings and disabled timezone awareness.
- Type information via
py.typedand compatibility imports throughcustom_timezone.timezone_utils.
Requirements and installation
The package declares Python 3.9+. Optional Django support declares Django 4.2 through 6.x, subject to the Python versions supported by each Django release. The GitHub Actions test matrix covers Python 3.9–3.13, including supported Django 4.2, 5.2 and 6.x combinations. Not every possible Python/Django combination is included.
Install the core utilities:
python -m pip install custom_timezone_fdg
Install with Django integration dependencies:
python -m pip install "custom_timezone_fdg[django]"
The distribution name contains _fdg, but imports use custom_timezone:
import custom_timezone as ct
If you're contributing from this repository, install it in editable mode:
python -m pip install -e ".[test,django]"
Quick start (without Django)
import custom_timezone as ct
# Set the business time zone for this Python process.
ct.configure("America/Toronto")
# Interpret a wall-clock time entered without an offset.
appointment = ct.parse_local_datetime("2026-09-26T14:30")
print(appointment) # 2026-09-26 14:30:00-04:00
print(ct.format_datetime(appointment)) # 2026-09-26 14:30:00 EDT
# Convert a UTC instant to the configured business time zone.
from datetime import datetime, timezone
stored_utc = datetime(2026, 1, 1, 17, 0, tzinfo=timezone.utc)
print(ct.to_custom_timezone(stored_utc)) # 2026-01-01 12:00:00-05:00
print(ct.format_datetime_local(stored_utc)) # 2026-01-01T12:00
# Use another zone temporarily (also works in async tasks).
with ct.override("Asia/Shanghai"):
print(ct.now())
Choose the business time zone
The first available value is used:
| Priority | Configuration | Scope |
|---|---|---|
| 1 | with ct.override("Asia/Shanghai") |
Current thread/asyncio task |
| 2 | ct.configure("America/Toronto") |
Python process |
| 3 | Django CUSTOM_TIME_ZONE |
If Django settings are already loaded |
| 4 | CUSTOM_TIME_ZONE environment variable |
Python process |
| 5 | UTC |
Default fallback |
Configure a non-Django application using an environment variable:
export CUSTOM_TIME_ZONE=America/Toronto
Or in Python:
ct.configure("America/Toronto")
ct.configure(None) # Clear the process override; use other configuration sources.
An invalid time-zone name raises TimezoneConfigurationError, which subclasses ValueError.
Core API
| Function | Purpose |
|---|---|
get_timezone() |
Return the active zoneinfo.ZoneInfo |
configure(tz) |
Set or clear the process-wide time zone |
override(tz) |
Temporarily use another time zone |
parse_local_datetime(text, ...) |
Parse ISO 8601 datetime text into an aware datetime |
localize_datetime(dt, ...) |
Attach the selected time zone to a naive datetime |
to_custom_timezone(dt, ...) |
Convert a datetime to the configured time zone |
format_datetime(dt, ...) |
Format a datetime in the configured time zone |
format_datetime_local(dt, ...) |
Format for HTML datetime-local input (YYYY-MM-DDTHH:MM) |
now() |
Return the current time in the configured time zone |
Most helpers also accept tz="Europe/Paris" to select a zone for a single call.
Parsing rules
parse_local_datetime() accepts ISO-style date and time strings with a T or space separator, optionally including an offset. An explicit offset is retained. None or blank text returns None; invalid input raises ValueError.
A date without a time is intentionally rejected by default:
ct.parse_local_datetime("2026-09-26") # ValueError
ct.parse_local_datetime("2026-09-26", allow_date_only=True) # Local midnight
Daylight-saving transitions
For America/Toronto in 2026, March 8 at 02:30 does not exist because clocks move forward. November 1 at 01:30 occurs twice when clocks move back.
from datetime import datetime
missing = datetime(2026, 3, 8, 2, 30)
ct.localize_datetime(missing, nonexistent="raise") # Default: NonExistentTimeError
ct.localize_datetime(missing, nonexistent="shift_forward") # 03:30 EDT
ct.localize_datetime(missing, nonexistent="shift_backward")# 01:30 EST
repeated = datetime(2026, 11, 1, 1, 30)
ct.localize_datetime(repeated, ambiguous="earliest") # First occurrence (EDT)
ct.localize_datetime(repeated, ambiguous="latest") # Second occurrence (EST)
ct.localize_datetime(repeated, ambiguous="raise") # AmbiguousTimeError
Both exceptions subclass ValueError; the same options are available in parse_local_datetime().
Naive datetimes
With to_custom_timezone(), use the naive= parameter to specify how an unaware datetime should be interpreted: "local" (default), "utc", or "raise".
Optional Django integration
Install with the django extra and configure your new or existing Django project:
# settings.py
import os
INSTALLED_APPS = [
# Django's standard and project apps...
"custom_timezone.apps.CustomTimezoneConfig",
]
MIDDLEWARE = [
# Normal Django middleware...
"custom_timezone.middleware.CustomTimezoneMiddleware",
]
TIME_ZONE = "UTC"
USE_TZ = True
CUSTOM_TIME_ZONE = os.getenv("CUSTOM_TIME_ZONE", "America/Toronto")
Add these entries to your existing INSTALLED_APPS and MIDDLEWARE lists; do not replace the other middleware that Django requires. The shorter app entry "custom_timezone" is also supported.
- The middleware activates the selected business time zone for each request, including Django templates, forms and Admin; the previous active zone is restored afterward.
- Django can keep storing timestamps in UTC when
USE_TZ=True. python manage.py checkreportscustom_timezone.E001for an invalid configured zone andcustom_timezone.W001ifUSE_TZ=False.- This Django app has no models or migrations.
Verify with:
python manage.py check
python manage.py test
Using in other frameworks
No framework-specific integration is required for regular scripts, FastAPI, Flask or task workers. To select a time zone for a single request or task, use with ct.override("Area/City") and ensure the context spans only the intended operation.
Replacing an existing in-project custom_timezone app
The import paths custom_timezone and custom_timezone.timezone_utils are retained for common usage. However, the standalone package intentionally changes certain behaviours, so test before replacing an existing application-specific implementation.
- Remove or rename the old in-project
custom_timezone/module so it cannot shadow the installed distribution. - Install
custom_timezone_fdg[django]in your project's environment. - Retain or adapt your existing
INSTALLED_APPS, middleware, imports andCUSTOM_TIME_ZONEsetting. - Run tests for datetime parsing, API validation and DST transitions before deploying.
| Input/behaviour | Older in-project app | Package behaviour |
|---|---|---|
| Nonexistent local time | Could silently shift forward | Raises NonExistentTimeError by default |
| Date-only string | Could be treated as midnight | Rejected unless allow_date_only=True |
| Invalid non-string input | Could raise TypeError or act as empty |
Raises ValueError |
| Invalid business time zone | Could raise ImproperlyConfigured |
Raises TimezoneConfigurationError |
| Middleware cleanup | Could reset to Django default | Restores previously active time zone |
Development, testing and publishing
This repository uses the src/ layout: distributable code lives in src/custom_timezone/, while tests and packaging files are at the repository root. Consumers still import custom_timezone—never src.custom_timezone.
python3.13 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[test,django]"
python -m pytest
The package version is defined in src/custom_timezone/__init__.py through __version__. For the v0.1.1 release, set __version__ = "0.1.1" and commit the change before creating the Git tag. The repository's CHANGELOG.md records release details. GitHub Actions runs the supported test matrix on pushes and pull requests; the main branch passed the matrix following the CI compatibility fixes.
Build and inspect the distributions:
python -m pip install --upgrade build twine
python -m build
python -m twine check dist/*
unzip -l dist/*.whl
Before publishing, install the built wheel in a separate clean virtual environment and run its import and (if relevant) Django integration checks. Test on TestPyPI before publishing to PyPI; new uploads cannot overwrite an existing version's distribution filename.
Project links
- Source repository: GitHub
- Issues and feature requests: GitHub Issues
These links assume the repository is published under the shown name; update them if you choose a different repository name.
Contact
Email: huachenlihx@gmail.com
License
This project is licensed under the MIT License. See the LICENSE file for the full license text.
Metadata
Release files for custom-timezone-fdg 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| custom_timezone_fdg-0.1.1.tar.gz | 22.2 kB | Without provenance | Details for custom_timezone_fdg-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| custom_timezone_fdg-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for custom_timezone_fdg-0.1.1-py3-none-any.whl |
Total release size: 37.3 kB
Release files / custom_timezone_fdg-0.1.1.tar.gz
| Download URL | custom_timezone_fdg-0.1.1.tar.gz |
|---|---|
| Size | 22.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
753c2e9ca770df336535335a38b04d34048d4fed96726d4fb8375ba4da2ccbcb
|
|
BLAKE2b-256 checksum How to use checksums |
e0cccd09c8eb81389f128b79e7a3800a730ad96dd68f215351df77ebaed4f366
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.15.2
|
Release files / custom_timezone_fdg-0.1.1-py3-none-any.whl
| Download URL | custom_timezone_fdg-0.1.1-py3-none-any.whl |
|---|---|
| Size | 15.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
34a94f3e2659a58f160f5ec212b1d13e1ba1962da3b76948ef11045c291fdbd9
|
|
BLAKE2b-256 checksum How to use checksums |
9ec736a0b75ded59107238ff3acb7b2995ed56249c54355f63a736f824b1e702
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.15.2
|
Security for custom-timezone-fdg 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.