Skip to main content

A Python datetime rule engine for working with recurring events and time patterns

Project description

Zerotime

CI PyPI version Python License: MIT Status Typed Dependencies Code style: ruff

A Python datetime rule engine for defining and working with recurring time patterns. Zerotime lets you express complex scheduling rules declaratively using a simple DSL (Domain-Specific Language), then query for matching datetimes, generate sequences, or combine rules using set operations.

Why Zerotime?

Working with recurring events in datetime is surprisingly complex. You might need "every Monday at 9 AM", "the last day of each month", or "business hours except lunch break". Traditional approaches involve writing custom logic for each pattern, handling edge cases like leap years, varying month lengths, and weekday calculations.

Zerotime solves this by providing a unified Rule abstraction. Rules can be atomic (based on month, day, hour constraints) or combined using operators (+ for union, & for intersection, - for difference). The library handles all the complexity of calendar math internally, provides memory-efficient generation of large date ranges, and includes JSON serialization for persistence.

Technical Design

Zerotime uses only the Python standard library (no external dependencies). The core design principles:

  • Declarative DSL: Each temporal field (months, days, weekdays, hours, minutes, seconds) accepts string expressions like "1..5" (range), "/15" (step), "1,15,-1" (list with last-day), or "1..12,!7,!8" (exclusions)
  • Composable Rules: Rules combine via Python operators, enabling complex schedules from simple building blocks
  • Lazy Generation: The generate() method yields datetimes on demand, suitable for large date ranges
  • Immutable Rules: All with_* methods return new rule instances; original rules are never modified
  • Timezone Support: Rules optionally bind to a timezone, with proper DST handling and validation of timezone-aware vs naive datetimes
  • Thread Safety: Parsed expressions are cached with double-checked locking for safe concurrent use

Components Overview

The library consists of three main components:

  • AtomicRule: The fundamental building block. Defines temporal constraints using DSL expressions for each datetime field. All constraints must match (AND logic).

  • CombinedRule: Created by combining two rules with an operator. Supports union (either matches), intersection (both match), and difference (first matches but not second).

  • RuleConfig: Global configuration controlling search limits, generation caps, JSON size limits, and batch sizes for memory-efficient processing.

Usage Example

Here's a realistic example showing how to define business working hours excluding lunch breaks and holidays:

from datetime import datetime, UTC
from zerotime import AtomicRule

# Business hours: weekdays 9-17, on the hour
business_hours = AtomicRule(
    weekdays="1..5",        # Monday to Friday
    hours="9..17",          # 9 AM to 5 PM
    minutes="0",            # On the hour
    seconds="0",
    timezone=UTC
)

# Lunch break: 12-13
lunch_break = AtomicRule(
    weekdays="1..5",
    hours="12,13",
    minutes="0",
    seconds="0",
    timezone=UTC
)

# Working hours = business hours minus lunch
working_hours = business_hours - lunch_break

# Find the next working hour from now
now = datetime(2025, 1, 15, 10, 30, 0, tzinfo=UTC)
next_slot = working_hours.get_next(now)
print(f"Next working hour: {next_slot}")  # 2025-01-15 11:00:00+00:00

# Generate all working hours for a day
start = datetime(2025, 1, 15, 0, 0, 0, tzinfo=UTC)
end = datetime(2025, 1, 15, 23, 59, 59, tzinfo=UTC)
for dt in working_hours.generate(start, end):
    print(dt)
# Output: 09:00, 10:00, 11:00, 14:00, 15:00, 16:00, 17:00

# Serialize for storage
json_str = working_hours.to_json()

# Restore later
from zerotime import Rule
restored = Rule.from_json(json_str)

Main APIs

AtomicRule

Creates rules from temporal constraints. Each field uses DSL syntax.

Simple usage - every day at noon:

rule = AtomicRule(hours="12", minutes="0", seconds="0")

Complex usage - quarterly reports on the last business day:

rule = AtomicRule(
    months="3,6,9,12",      # End of quarter
    days="-1,-2,-3",        # Last 3 days (will pick based on weekday)
    weekdays="1..5",        # Must be a weekday
    hours="17",
    minutes="0",
    seconds="0"
)

Rule Operators

Combine rules using Python operators:

# Union: matches if either rule matches
holidays = rule1 + rule2

# Intersection: matches only if both rules match
overlap = rule1 & rule2

# Difference: matches first rule but not second
working_days = all_days - holidays

Temporal Navigation

Find next/previous matching datetime:

# Simple - find next match
next_match = rule.get_next(datetime.now())

# Complex - search up to 10 years ahead
next_match = rule.get_next(base_date, max_years=10)

Generation

Generate all matching datetimes in a range:

# Simple - iterate all matches
for dt in rule.generate(start, end):
    process(dt)

# Memory-efficient - process in batches
for batch in rule.generate_batch(start, end, batch_size=1000):
    bulk_process(batch)

Configuration

Adjust global limits:

from zerotime import RuleConfig, set_global_config

config = RuleConfig(
    max_years_search=10,        # How far to search in get_next/get_prev
    max_generate_items=100000,  # Cap on generated items (None = unlimited)
    default_batch_size=5000     # Batch size for generate_batch
)
set_global_config(config)

DSL Syntax Reference

Syntax Meaning Example
"N" Single value "15" - day 15
"N..M" Range (inclusive) "1..5" - Mon-Fri
"N..M/S" Range with step "0..59/15" - 0,15,30,45
"/S" Global step (multiples of S) "/15" - 0,15,30,45 for minutes
"A,B,C" List "1,15,-1" - 1st, 15th, last
"!N" Exclusion "1..12,!7,!8" - all except Jul,Aug
"-N" Negative (days only) "-1" - last day of month

Installation

pip install zerotime

Requirements:

  • Python 3.11+
  • No external dependencies (standard library only)

Running Tests

uv run pytest

With coverage:

uv run pytest --cov=zerotime --cov-report=term-missing

Examples

The examples/ directory contains comprehensive, runnable examples covering all features:

File Description
01_basic_usage.py Introduction to AtomicRule, get_next, get_prev
02_dsl_syntax.py Complete DSL reference with all syntax patterns
03_rule_combination.py Combining rules with +, &, - operators
04_generation_methods.py generate(), generate_reverse(), generate_batch()
05_navigation.py Temporal navigation and error handling
06_timezones.py Timezone handling including DST transitions
07_configuration.py RuleConfig and global settings
08_json_serialization.py Saving and restoring rules with JSON
09_builder_methods.py Immutable rule modification with with_* methods
10_real_world_examples.py Practical use cases: billing, scheduling, SLA, maintenance windows

Run any example directly:

python examples/01_basic_usage.py

Further Documentation

License

MIT License - Copyright (c) 2025 Francesco Favi

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zerotime-0.1.3.tar.gz (51.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zerotime-0.1.3-py3-none-any.whl (15.9 kB view details)

Uploaded Python 3

File details

Details for the file zerotime-0.1.3.tar.gz.

File metadata

  • Download URL: zerotime-0.1.3.tar.gz
  • Upload date:
  • Size: 51.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for zerotime-0.1.3.tar.gz
Algorithm Hash digest
SHA256 f48ccccd15112c5670ddab138546420de55df980e84dd7f36ded21cbac8c0060
MD5 8cf4aa946dd2ebbd472481ddd65bad75
BLAKE2b-256 5da18fe7bc3b31d6d185b3dce8f81e63f75ed3dcdea6ce5dfc163276d5b07d6a

See more details on using hashes here.

Provenance

The following attestation bundles were made for zerotime-0.1.3.tar.gz:

Publisher: publish.yml on francescofavi/zerotime

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zerotime-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: zerotime-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 15.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for zerotime-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 d472873d026a756ddbd8730793ae5f49ebd71d0011c97c60c36ce2ea339aa64c
MD5 644cf07f41eb93aec08b68be03363aa2
BLAKE2b-256 11e8b595eee5fa61afec46f196287d8d817edd959342c966ffc83b76fa49304f

See more details on using hashes here.

Provenance

The following attestation bundles were made for zerotime-0.1.3-py3-none-any.whl:

Publisher: publish.yml on francescofavi/zerotime

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page