Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,25 @@ env:
CARGO_TERM_COLOR: always

jobs:
python-quality:
name: Ruff and Pyright
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
with:
python-version: "3.10"
- name: Install Python quality tools
run: |
python -m pip install --upgrade pip
python -m pip install --group dev
- name: Lint Python
run: ruff check python
- name: Check Python formatting
run: ruff format --check python
- name: Check Python types
run: pyright

test:
runs-on: ubuntu-latest

Expand Down Expand Up @@ -63,3 +82,9 @@ jobs:

- name: Run Python tests
run: python -m pytest python/tests

- name: Run Python examples
run: |
for example in python/examples/*.py; do
python "$example" > /dev/null
done
18 changes: 18 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,21 @@ Documentation = "https://github.com/RustedBytes/fasttime/blob/main/python/README
features = ["pyo3/extension-module", "python", "std"]
python-source = "python"
module-name = "fasttime"

[dependency-groups]
dev = ["pytest>=8", "ruff==0.16.10", "pyright==1.1.414"]

[tool.ruff]
target-version = "py310"
line-length = 88

[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "I", "UP", "B"]

[tool.pyright]
include = ["python"]
extraPaths = ["python"]
pythonVersion = "3.10"
typeCheckingMode = "standard"
# The implementation is a Rust extension; its API is declared in fasttime.pyi.
reportMissingModuleSource = "none"
41 changes: 41 additions & 0 deletions python/benchmarks/string_formatting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Compare installed release wheels in separate virtual environments.

Run with each environment's Python. Results are median nanoseconds per call,
including Python dispatch, over seven repetitions of 200,000 calls.
"""

import json
import statistics
import timeit
from collections.abc import Callable

import fasttime

values = {
"date": fasttime.Date(2024, 6, 15),
"time": fasttime.Time(12, 30, 45, nanosecond=123456789),
"datetime": fasttime.DateTime.parse("2024-06-15T12:30:45.123456789Z"),
"offset": fasttime.OffsetDateTime.parse("2024-06-15T12:30:45.123456789+05:30"),
"duration": fasttime.Duration.nanoseconds(123456789),
"weekday": fasttime.Weekday.MONDAY,
}


def measure(op: Callable[[object], str], value: object) -> float:
return round(
statistics.median(timeit.repeat(lambda: op(value), number=200000, repeat=7))
* 1e9
/ 200000,
1,
)


print(
json.dumps(
{
f"{name}/{op.__name__}": measure(op, value)
for name, value in values.items()
for op in [str, repr]
}
)
)
20 changes: 10 additions & 10 deletions python/examples/basic_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def main():
print(f"Year: {date.year}, Month: {date.month}, Day: {date.day}")
print(f"Weekday: {date.weekday()}")
print(f"Day of year: {date.ordinal()}")

# Add/subtract days
tomorrow = date.add_days(1)
yesterday = date.add_days(-1)
Expand All @@ -45,7 +45,7 @@ def main():
print(f"DateTime: {dt}")
print(f"Date component: {dt.date}")
print(f"Time component: {dt.time}")

# Get current time
now = fasttime.DateTime.now_utc()
print(f"Current UTC time: {now}")
Expand All @@ -54,25 +54,25 @@ def main():
# 4. Working with Durations
print("4. Working with Durations")
print("-" * 40)

# Create durations
one_hour = fasttime.Duration.seconds(3600)
one_day = fasttime.Duration.seconds(86400)
half_second = fasttime.Duration.milliseconds(500)

print(f"One hour: {one_hour.total_seconds()} seconds")
print(f"One day: {one_day.total_seconds()} seconds")
print(f"Half second: {half_second.total_seconds()} seconds")

# Add durations
total = one_hour + half_second
print(f"One hour + 500ms: {total.total_seconds()} seconds")

# Add duration to datetime
later = dt.add_duration(one_hour)
print(f"Original: {dt}")
print(f"One hour later: {later}")

# Calculate difference
diff = later.difference(dt)
print(f"Difference: {diff.total_seconds()} seconds")
Expand All @@ -83,7 +83,7 @@ def main():
print("-" * 40)
date1 = fasttime.Date(2024, 1, 1)
date2 = fasttime.Date(2024, 12, 31)

print(f"{date1} < {date2}: {date1 < date2}")
print(f"{date1} == {date2}: {date1 == date2}")
print(f"{date1} > {date2}: {date1 > date2}")
Expand All @@ -108,10 +108,10 @@ def main():
print("-" * 40)
epoch = fasttime.Date.from_days_since_unix_epoch(0)
print(f"Unix epoch (day 0): {epoch}")

days = date.days_since_unix_epoch()
print(f"{date} is {days} days since epoch")

reconstructed = fasttime.Date.from_days_since_unix_epoch(days)
print(f"Reconstructed: {reconstructed}")
print(f"Match: {date == reconstructed}")
Expand Down
42 changes: 21 additions & 21 deletions python/examples/durations.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ def main():
# 1. Creating Durations
print("1. Creating Durations")
print("-" * 40)

seconds = fasttime.Duration.seconds(60)
millis = fasttime.Duration.milliseconds(500)
micros = fasttime.Duration.microseconds(1000)
nanos = fasttime.Duration.nanoseconds(123_456_789)

print(f"60 seconds: {seconds.total_seconds()} s")
print(f"500 milliseconds: {millis.total_seconds()} s")
print(f"1000 microseconds: {micros.total_seconds()} s")
Expand All @@ -29,14 +29,14 @@ def main():
# 2. Duration Arithmetic
print("2. Duration Arithmetic")
print("-" * 40)

one_hour = fasttime.Duration.seconds(3600)
thirty_minutes = fasttime.Duration.seconds(1800)

total = one_hour + thirty_minutes
difference = one_hour - thirty_minutes
negated = -thirty_minutes

print(f"1 hour + 30 minutes: {total.total_seconds()} seconds")
print(f"1 hour - 30 minutes: {difference.total_seconds()} seconds")
print(f"-(30 minutes): {negated.total_seconds()} seconds")
Expand All @@ -45,15 +45,15 @@ def main():
# 3. Adding Durations to DateTimes
print("3. Adding Durations to DateTimes")
print("-" * 40)

start = fasttime.DateTime.parse("2024-01-01T00:00:00Z")
print(f"Start: {start}")

# Add various durations
one_day = fasttime.Duration.seconds(86400)
one_week = fasttime.Duration.seconds(86400 * 7)
one_year = fasttime.Duration.seconds(86400 * 365)

print(f"+1 day: {start.add_duration(one_day)}")
print(f"+1 week: {start.add_duration(one_week)}")
print(f"+1 year: {start.add_duration(one_year)}")
Expand All @@ -62,10 +62,10 @@ def main():
# 4. Calculating Time Differences
print("4. Calculating Time Differences")
print("-" * 40)

dt1 = fasttime.DateTime.parse("2024-01-01T12:00:00Z")
dt2 = fasttime.DateTime.parse("2024-01-01T18:30:45Z")

diff = dt2.difference(dt1)
print(f"Time 1: {dt1}")
print(f"Time 2: {dt2}")
Expand All @@ -76,10 +76,10 @@ def main():
# 5. Measuring Intervals
print("5. Measuring Intervals")
print("-" * 40)

meeting_start = fasttime.DateTime.parse("2024-06-15T09:00:00Z")
meeting_end = fasttime.DateTime.parse("2024-06-15T10:30:00Z")

duration = meeting_end.difference(meeting_start)
print(f"Meeting start: {meeting_start}")
print(f"Meeting end: {meeting_end}")
Expand All @@ -89,10 +89,10 @@ def main():
# 6. Comparing Durations
print("6. Comparing Durations")
print("-" * 40)

short = fasttime.Duration.seconds(10)
long = fasttime.Duration.seconds(100)

print(f"Short duration: {short.total_seconds()} s")
print(f"Long duration: {long.total_seconds()} s")
print(f"short < long: {short < long}")
Expand All @@ -103,11 +103,11 @@ def main():
# 7. Working with Nanoseconds
print("7. Working with Nanosecond Precision")
print("-" * 40)

# High-precision timestamps
dt_start = fasttime.DateTime.parse("2024-01-01T00:00:00.000000000Z")
dt_end = fasttime.DateTime.parse("2024-01-01T00:00:00.123456789Z")

nano_diff = dt_end.difference(dt_start)
print(f"Start: {dt_start}")
print(f"End: {dt_end}")
Expand All @@ -118,15 +118,15 @@ def main():
# 8. Negative Durations
print("8. Negative Durations")
print("-" * 40)

future = fasttime.DateTime.parse("2024-12-31T23:59:59Z")
past = fasttime.DateTime.parse("2024-01-01T00:00:00Z")

# Future - past = positive
forward = future.difference(past)
# Past - future = negative
backward = past.difference(future)

print(f"Future: {future}")
print(f"Past: {past}")
print(f"Future - Past: {forward.total_seconds()} seconds (positive)")
Expand All @@ -136,7 +136,7 @@ def main():
# 9. Common Duration Constants
print("9. Common Duration Values")
print("-" * 40)

constants = [
("1 millisecond", fasttime.Duration.milliseconds(1)),
("1 second", fasttime.Duration.seconds(1)),
Expand All @@ -145,7 +145,7 @@ def main():
("1 day", fasttime.Duration.seconds(86400)),
("1 week", fasttime.Duration.seconds(86400 * 7)),
]

for name, dur in constants:
print(f"{name:15} = {dur.total_nanos():>20} nanoseconds")
print()
Expand Down
Loading
Loading