- Python 3.10+
- Rust - Install via rustup:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source ~/.cargo/env
- Poetry - Install via pipx:
pipx install poetry
- Maturin - Install via pip:
pip install maturin
git clone https://github.com/bissli/opendate.git
cd opendate
make dev # Install dependencies and build native extension
make test # Run tests| Command | Description |
|---|---|
make dev |
Install dependencies and build native extension |
make build |
Build native extension only |
make test |
Run all tests |
make lint |
Run all linters (Python + Rust) |
make lint-rust |
Check Rust formatting and clippy |
make format-rust |
Auto-format Rust code |
make clean |
Remove build artifacts |
# Clone the repository
git clone https://github.com/bissli/opendate.git
cd opendate
# Option 1: Use make (recommended)
make dev
# Option 2: Manual steps
poetry install --extras test
maturin develop --release
# Verify installation
python -c "from opendate import Date; print(Date.today())"# Edit Python files in src/date/
# Then run tests
make test# Edit Rust files in rust/src/
# Rebuild and test
make build
make test# Run all tests (recommended)
make test
# Or use pytest directly for more options:
pytest tests/ # Run all tests
pytest tests/test_date.py # Run specific test file
pytest tests/ -v # Verbose output
pytest tests/ --cov=date # With coverage# Check Rust formatting and lints
make lint-rust
# Auto-format Rust code
make format-rust# Recommended
make build
# Or manually
maturin develop --release# Build a wheel for your current platform only
maturin build --release
# Wheel is created in rust/target/wheels/
ls rust/target/wheels/Note: For releases, GitHub Actions automatically builds wheels for all platforms (Linux, macOS, Windows) and Python versions (3.10-3.13). Do not manually upload wheels to PyPI—use the release process below instead.
make cleanThe project uses bump2version for version management:
# Bump patch version (0.1.23 → 0.1.24)
bump2version patch
# Bump minor version (0.1.24 → 0.2.0)
bump2version minor
# Bump major version (0.2.0 → 1.0.0)
bump2version majorThis automatically:
- Updates version in
pyproject.toml - Creates a git commit with message "Bump version: X.Y.Z → X.Y.W"
- Creates a git tag
X.Y.W
-
Ensure all changes are committed and tests pass:
git status # Should be clean make test # Should pass make lint # Should pass
-
Bump the version:
bump2version patch # or minor/major -
Push commit and tag to GitHub:
git push origin master --tags
-
GitHub Actions automatically:
- Verifies tag matches pyproject.toml version
- Checks if version already exists on PyPI (skips if exists)
- Builds wheels for all platforms:
- Linux (x86_64, aarch64) - glibc and musl
- macOS (x86_64, Apple Silicon)
- Windows (x86_64)
- Builds for Python 3.10, 3.11, 3.12, 3.13
- Creates a GitHub Release with all wheel artifacts
- Publishes to PyPI
-
Verify the release:
- Check GitHub Actions for build status
- Check PyPI for the new version
- Test installation:
pip install opendate==X.Y.Z
-
Create a "release" environment in your GitHub repository:
- Go to Settings → Environments → New environment
- Name it
release
-
Configure PyPI Trusted Publishing:
- Go to pypi.org → Your Project → Publishing
- Add a new trusted publisher:
- Owner:
bissli - Repository:
opendate - Workflow:
release.yml - Environment:
release
- Owner:
opendate/
├── pyproject.toml # Project config (maturin build-backend)
├── Makefile # Development shortcuts
├── rust/
│ ├── Cargo.toml # Rust package config
│ └── src/
│ ├── lib.rs # Module exports
│ ├── calendar.rs # BusinessCalendar implementation
│ ├── parser/ # Dateutil-compatible parser (Rust port)
│ │ ├── mod.rs # Module exports
│ │ ├── core.rs # Main Parser implementation
│ │ ├── iso.rs # ISO-8601 parser (IsoParser)
│ │ ├── parserinfo.rs # Parser configuration
│ │ ├── tokenizer.rs # String tokenization
│ │ ├── ymd.rs # Year/Month/Day resolution
│ │ ├── result.rs # ParseResult type
│ │ └── errors.rs # Error types
│ └── python.rs # PyO3 bindings
├── src/
│ └── date/
│ ├── __init__.py # Public API and factory functions
│ ├── constants.py # Timezone instances, WeekDay enum
│ ├── helpers.py # Utility functions, Rust parser bridge
│ ├── decorators.py # Type conversion decorators
│ ├── calendars.py # Calendar classes (NYSE, custom)
│ ├── date_.py # Date class
│ ├── time_.py # Time class
│ ├── datetime_.py # DateTime class
│ ├── interval.py # Interval class
│ ├── extras.py # Legacy compatibility functions
│ └── mixins/ # Shared behavior mixins
│ ├── business.py # Business day calculations
│ └── extras_.py # Additional date utilities
├── tests/
├── docs/
│ └── developer-guide.md # This file
└── .github/
└── workflows/
├── release.yml # Release pipeline
└── tests.yml # CI tests
The Python code follows a modular architecture inspired by Pendulum:
- Core classes in separate files (
date_.py,datetime_.py, etc.) - Shared behavior via mixins
- Pendulum methods automatically wrapped via metaclass to preserve calendar context
- Circular imports avoided using
import dateat module level
The _opendate module provides high-performance native implementations:
Efficient business day calculations using ordinal-based lookups:
is_business_day(ordinal)- Check if date is a business dayadd_business_days(ordinal, n)- Add/subtract business daysnext_business_day(ordinal)/prev_business_day(ordinal)- Find adjacent business dayscount_business_days(start, end)- Count business days in range
A Rust port of python-dateutil's parser for fast datetime parsing:
Parser- Parse arbitrary datetime strings (dateutil-compatible)IsoParser- Parse ISO-8601 datetime stringsTimeParser- Parse standalone time strings
The parser supports the same formats as dateutil including fuzzy parsing, dayfirst/yearfirst options, and AM/PM handling.
Check in (source files):
rust/
├── Cargo.toml # ✓ Rust package config
└── src/
├── lib.rs # ✓ Module exports
├── calendar.rs # ✓ BusinessCalendar implementation
├── parser/ # ✓ Dateutil-compatible parser
│ ├── mod.rs
│ ├── core.rs
│ ├── iso.rs
│ ├── parserinfo.rs
│ ├── tokenizer.rs
│ ├── ymd.rs
│ ├── result.rs
│ └── errors.rs
└── python.rs # ✓ PyO3 bindings
Ignored (build artifacts in .gitignore):
rust/
├── Cargo.lock # ✗ Dependency lock (regenerated on build)
└── target/ # ✗ Build output directory
*.so # ✗ Compiled shared libraries
The Cargo.lock is ignored because this is a library crate. For libraries, the lock file
is regenerated when building to use the latest compatible dependencies.
Rust is not installed or not in PATH:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/envInstall maturin:
pip install maturinThe native extension is not built. Run:
make buildRebuild the extension:
make build
make testThe version was already published. Bump to a new version:
bump2version patch
git push origin master --tags