Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Dates and datetimes

Temporal columns have logical types, not merely integer labels. Their construction and extraction use explicit physical units at the Plenty boundary; native parsing and calendar expressions handle ordinary analytical workflows. All operations return Result[..., DataFrameError].

import std.dataframe as df

def main() -> Result[(), df.DataFrameError | AllocError | IoError]:
    source = df.frame([df.column_str("text", ["2024-02-29", "1970-01-02"]?)?]?)?
    day = df.col("day")?
    result = source.lazy()?.select([
        df.col("text")?.str_to_date()?.alias("day")?
    ]?)?.with_columns([
        day.dt_year()?.alias("year")?,
        day.dt_format("%d/%m")?.alias("label")?
    ]?)?.collect()?
    print(result.values_date_days("day")?)?
    print(result.values_i64("year")?)?
    print(result.values_str("label")?)?
    Ok(())
[19782, 1]
[2024, 1970]
["29/02", "02/01"]

Units, timezones and the boundary

Logical typeConstructionExtraction
Datecolumn_date(name, days: &list[i32])values_date_days(name) / get_date_days(name, row)
Datetimecolumn_datetime(name, ticks: &list[i64], unit, utc=False)values_datetime_ticks(name, unit, utc=False) / get_datetime_ticks(name, row, unit, utc=False)

Dates count proleptic Gregorian calendar days since 1970-01-01. Datetimes count ticks from 1970-01-01 00:00:00. TimeUnit.Milliseconds, Microseconds and Nanoseconds mean 1,000, 1,000,000 and 1,000,000,000 ticks per second. Negative values precede the epoch. No unit is inferred or converted by a getter. Date extraction returns i32; datetime extraction returns i64.

utc=False describes a timezone-free civil datetime. utc=True describes UTC; it does not convert a local wall-clock value into UTC. No host timezone is consulted. Named-zone localization, timezone conversion, daylight-saving ambiguities and leap seconds are not supported in this foundation. std.time.Timestamp uses seconds plus nanoseconds and is not implicitly converted to these column representations.

Each constructor also has a _nullable form accepting list[Option[T]]. Each bulk getter has a _nullable form returning list[Option[T]]; the plain getter rejects any null with DataFrameError.Null. Scalar getters return Option[T] inside Result. Empty and all-null columns retain their dtype. Transfers copy whole buffers; nullable values use a separate validity buffer.

Construction and typed extraction validate the engine’s representable calendar range. Not every i32 day or i64 millisecond/microsecond value is a valid calendar date. The full i64 nanosecond range is representable. Invalid values produce errors rather than wrapping; null payload bits are ignored. Wrong dtype, unit or UTC metadata is an error, not an implicit conversion.

Schemas report DType.Date or DType.Datetime(unit, timezone: Option[str]). Nothing is naive and Some("UTC") is UTC. Other timezone labels from files are preserved honestly, but the typed temporal getters and calendar operations reject them. Schema inspection is not a promise that every reported type or value can be extracted. Generic get_i64 does not discard datetime metadata.

Parse strings and extract calendar fields

Expression methodOutput
str_to_date(format="%Y-%m-%d", strict=True)Date
str_to_datetime(format, unit, utc=False, strict=True)Datetime with the specified unit/zone
dt_year(), dt_month(), dt_day()I64 calendar component
dt_hour(), dt_minute(), dt_second()I64 time component of a Datetime
dt_date()Date portion of a Datetime
dt_format(format)String

Formats use Chrono directives such as %Y-%m-%d, %H:%M:%S and %.f (optional fractional seconds). Parsing consumes the whole string. Omitted clock fields default to zero. Invalid values and out-of-range conversions fail with Query in strict mode or become null with strict=False. Existing nulls remain null. Parsing leap-second :60 is rejected.

Fractional precision beyond the selected unit is discarded toward the earlier instant: -1 nanosecond becomes -1 millisecond, not zero. Choose nanoseconds when that precision matters; their calendar range is much smaller than that of milliseconds or microseconds. Nanosecond overflow is checked explicitly.

Invalid formats and timezone directives (%z, %Z, %+, including modified forms) are rejected. A literal Z is just text; use it only when the data is explicitly UTC. Date-only values cannot supply time-of-day formatting fields. Unicode literals in formats are preserved. Calendar components use the stored naive/UTC interpretation and return null for null inputs.

Parsing and range checks execute as checked native column expressions inside Polars plans, with no Plenty callback or per-cell FFI. These validation/parsing nodes are opaque to the optimizer; calendar kernels remain native Polars operations. The checked parser avoids overflow and leap-second normalization in the pinned native parser. Imported invalid calendar values fail explicitly before calendar extraction or formatting.

Parquet preserves temporal dtypes and metadata. CSV does not: format columns explicitly before writing and parse the resulting strings after reading. CSV schema overrides and Expr.cast accept numeric, boolean and string target types; they do not construct temporal types. As-of joins remain integer-key operations. See the runnable lesson and signature index.

Acknowledgements

  • Date parsing, calendar validation and format directives use Chrono 0.4.45, built into the dataframe engine (MIT OR Apache-2.0).