lacing.time#
Rational time and half-open intervals — the foundations.
Time in lacing is always rational, never float. See
.claude/skills/lacing-time-and-intervals/SKILL.md and BACK-DOC §2.1.
Wire format: {"v": int, "r": int}. Python uses fractions.Fraction
for arithmetic. to_seconds() is a one-way escape hatch for display
and third-party libs that demand a float.
Module Attributes
Default rate (ticks/second). |
|
JSON Schema for the |
|
JSON Schema for the |
Classes
|
A point in time as |
|
A half-open interval |
Exceptions
Raised when a rate or seconds conversion would lose precision. |
- lacing.time.DEFAULT_RATE: int = 24000#
Default rate (ticks/second). LCM 1008000 covers all common video rates exactly.
- exception lacing.time.LossyTimeConversionError[source]#
Bases:
ValueErrorRaised when a rate or seconds conversion would lose precision.
- lacing.time.RATIONAL_TIME_JSON_SCHEMA: dict[str, Any] = {'additionalProperties': False, 'description': 'A point in time as v/r seconds. Both members are integers — never a float, never a JSON number with a fractional part.', 'properties': {'r': {'description': 'Rate in ticks per second. Strictly positive.', 'exclusiveMinimum': 0, 'type': 'integer'}, 'v': {'description': 'Tick count (may be negative).', 'type': 'integer'}}, 'required': ['v', 'r'], 'title': 'RationalTime', 'type': 'object'}#
JSON Schema for the
RationalTimewire shape{"v": int, "r": int}.Non-negotiable 1 lives here as much as in the constructor:
integer, notnumber. A JSON Schema validator must reject{"v": 0.5, "r": 48000}.
- class lacing.time.RationalTime(value, rate=24000)[source]#
Bases:
objectA point in time as
value / rateseconds.Immutable. Two
RationalTimevalues with different rates compare via their rational value, soRationalTime(24, 24) == RationalTime(1, 1).Examples
>>> RationalTime(24000) == RationalTime(1, 1) True >>> RationalTime.from_seconds("1.5", rate=2).value 3
- classmethod from_seconds(seconds, rate=24000)[source]#
Build from seconds. Quantizes to
rate; raises if lossy.secondsmay be astrlike"1.001"to avoid float ingestion.- Return type:
- classmethod from_seconds_lossy(seconds, *, rate=24000, mode='round')[source]#
Build from seconds, quantizing to the nearest sample at
rate.Unlike
from_seconds()— which raisesLossyTimeConversionErrorwhen the value cannot be represented exactly — this method always succeeds by quantizing.modeselects the rounding rule:"round"— nearest sample, ties to even (default)"floor"— largest sample <=seconds"ceil"— smallest sample >=seconds
Use this when sample-level quantization is knowingly acceptable — the common case for user-supplied durations. Use
from_seconds()when exactness matters and a lossy conversion should be an error.Examples
>>> RationalTime.from_seconds_lossy("0.1", rate=3).value 0 >>> RationalTime.from_seconds_lossy("0.1", rate=3, mode="ceil").value 1
- Return type:
- classmethod from_wire(d)[source]#
Build from the wire form. Unknown keys are an error, not ignored.
RATIONAL_TIME_JSON_SCHEMAsetsadditionalProperties: falseand lacing-ui’s Zod mirror is.strict(); accepting extras here would make Python the one lax end of a contract both other ends enforce, so a{"v": 0, "r": 1, "seconds": 0.0}payload would round-trip through Python and then be rejected by the frontend.- Return type:
- classmethod now(rate=24000)[source]#
Wall-clock time as a
RationalTime, quantized torate.Uses
time.time_ns()and builds the value directly, sidestepping the float-quantization landmine offrom_seconds(float). Every producer of an annotation or artifact needs this forProvenance.generated_at_time— the only value that field reads as a known time. Tick 0 there is the UNKNOWN sentinel (lacing.model.UNKNOWN_GENERATED_AT), never the epoch.- Return type:
- to_rate(new_rate)[source]#
Re-express at
new_rate. RaisesLossyTimeConversionErroron loss.- Return type:
- lacing.time.TIME_INTERVAL_JSON_SCHEMA: dict[str, Any] = {'additionalProperties': False, 'description': 'A half-open interval [start, end). start == end is a point.', 'properties': {'end': {'additionalProperties': False, 'description': 'A point in time as v/r seconds. Both members are integers — never a float, never a JSON number with a fractional part.', 'properties': {'r': {'description': 'Rate in ticks per second. Strictly positive.', 'exclusiveMinimum': 0, 'type': 'integer'}, 'v': {'description': 'Tick count (may be negative).', 'type': 'integer'}}, 'required': ['v', 'r'], 'title': 'RationalTime', 'type': 'object'}, 'start': {'additionalProperties': False, 'description': 'A point in time as v/r seconds. Both members are integers — never a float, never a JSON number with a fractional part.', 'properties': {'r': {'description': 'Rate in ticks per second. Strictly positive.', 'exclusiveMinimum': 0, 'type': 'integer'}, 'v': {'description': 'Tick count (may be negative).', 'type': 'integer'}}, 'required': ['v', 'r'], 'title': 'RationalTime', 'type': 'object'}}, 'required': ['start', 'end'], 'title': 'TimeInterval', 'type': 'object'}#
JSON Schema for the
TimeIntervalwire shape{"start": ..., "end": ...}.start <= endis a constructor invariant that JSON Schema cannot express; Pydantic validation still enforces it.
- class lacing.time.TimeInterval(start, end)[source]#
Bases:
objectA half-open interval
[start, end).start == endis a valid point annotation, not a degenerate case. Alwaysstart <= end; constructor raisesValueErrorotherwise.- property duration: RationalTime#
end - startat the same rate asstart.
- classmethod from_wire(d)[source]#
Build from the wire form. See
RationalTime.from_wire()on extras.- Return type: