SWMM DateTime and Python datetime interop#
Note
Engine: OpenSWMM 6 — refactored.
SWMM’s native date/time representation is a double whose integer
part is the number of days since 1899-12-30 (the OLE Automation /
Delphi TDateTime epoch) and whose fractional part is the
time-of-day fraction (0.5 = noon).
This is not an astronomical Julian Date. The epoch difference is ~2.4 million days and the precision characteristics are different. Using “Julian” loosely is a long-standing source of confusion in SWMM codebases; OpenSWMM 6 uses “SWMM DateTime” everywhere.
What you actually use#
99% of users never see the raw double:
from openswmm.engine import Solver
with Solver("model.inp") as s:
s.start_datetime # datetime.datetime, not a float
s.report_start_datetime # likewise
s.end_datetime # likewise
The conversion goes through helpers in openswmm.engine:
These two functions are calendar-arithmetic only — sub-second precision
is handled in Python so a round-trip through them is lossless to ≤ 1
microsecond (verified by scripts/verify_dates_python.py in the
repo).
from datetime import datetime
from openswmm.engine import datetime_to_oadate, oadate_to_datetime
dt = datetime(2024, 6, 15, 13, 30, 45, 123_456)
assert oadate_to_datetime(datetime_to_oadate(dt)) == dt # within 1 µs
When you might use the raw double#
The low-level C API exposes encode / decode primitives that mirror the
legacy datetime_* C functions bit-for-bit (whole-second precision).
They are reachable from Python through openswmm.engine.datetime_api:
from openswmm.engine import datetime_api
d = datetime_api.encode_date(2024, 6, 15) # 45458.0 (integer days)
t = datetime_api.encode_time(13, 30, 45) # 0.563...
dt_value = d + t # combined SWMM DateTime
y, m, day = datetime_api.decode_date(dt_value)
h, mi, s = datetime_api.decode_time(dt_value)
The full signature set is:
Function |
Returns |
|---|---|
|
Integer-valued SWMM DateTime |
|
Fractional-day |
|
|
|
|
|
SWMM DateTime |
|
|
When to reach for which:
You have a Python
datetimeand want a SWMM DateTime:datetime_to_oadate()(preserves microseconds).You have a SWMM DateTime and want a Python
datetime:oadate_to_datetime()(preserves microseconds).You’re driving a C API that takes the double directly (e.g., hot-start save schedules, custom plugin code): use
openswmm.engine.datetime_apifor the integer-second primitives.
Precision notes#
The double has ~15 significant decimal digits. For dates in the 2000-2100 range that’s a precision floor of ~10 nanoseconds — well below the microsecond resolution Python’s
datetimeexposes.datetime_to_oadate()/oadate_to_datetime()are exact for whole-second values, and lossless to ≤ 1 µs for values with microseconds (verified inscripts/verify_dates_python.py).The C API’s
decode_timerounds viafloor(fracDay * 86400 + 0.5), matching the legacy engine bit-for-bit. Use it when you need parity with a legacy SWMM run; useoadate_to_datetime()when you want sub-second fidelity.
Durations vs. moments#
OpenSWMM 6 distinguishes the two more carefully than legacy SWMM did:
Moments (the start of the simulation, the time of a save) are Python
datetimeobjects throughout the public API.Durations (a timestep, an elapsed time, the report step) are Python
timedeltaobjects.
The conversion is straightforward when you need to drop into the C API yourself:
from datetime import timedelta
dt_seconds = 60.0
routing_step = timedelta(seconds=dt_seconds) # for Python consumers
# ... or back ...
dt_seconds = routing_step.total_seconds()
See also#
Concepts & engine lifecycle — the engine lifecycle and where dates show up.
Error handling, edge cases & debugging — what happens when an out-of-range date is passed to a setter.
Running a simulation — Solver — the
Solverproperties that surface dates.