Skip to main content

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 in src/custom_timezone/__init__.py before 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.typed and compatibility imports through custom_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 check reports custom_timezone.E001 for an invalid configured zone and custom_timezone.W001 if USE_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.

  1. Remove or rename the old in-project custom_timezone/ module so it cannot shadow the installed distribution.
  2. Install custom_timezone_fdg[django] in your project's environment.
  3. Retain or adapt your existing INSTALLED_APPS, middleware, imports and CUSTOM_TIME_ZONE setting.
  4. 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.

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)

Source distribution for custom-timezone-fdg 0.1.1
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)

Table of built distributions (wheels) for custom-timezone-fdg 0.1.1
File Interpreter ABI Platform Actions
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.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page