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 daily reports

Parse a timestamp column once, then keep calendar calculations inside the query. The example below groups events by their UTC calendar date. This is different from splitting timestamp strings or assuming every input has an identical textual layout.

These examples need the dataframe engine, which Linux releases include. utc=True declares that the input text already represents UTC; it does not convert another timezone.

import std.dataframe as df

def main() -> Result[(), df.DataFrameError | AllocError | IoError]:
    events = df.frame([
        df.column_str("text", ["2024-02-28 09:00:00", "2024-02-29 12:30:00", "2024-02-29 14:00:00"]?)?,
        df.column_i64("amount", [10, 20, 5]?)?
    ]?)?
    stamp = df.col("text")?.str_to_datetime(
        "%Y-%m-%d %H:%M:%S", df.TimeUnit.Microseconds, utc=True
    )?.alias("stamp")?
    daily = events.lazy()?.with_columns([stamp]?)?.group_by(
        [df.col("stamp")?.dt_date()?.alias("day")?]?,
        [df.col("amount")?.sum()?.alias("total")?]?
    )?.sort([df.SortKey(df.col("day")?)]?)?.select([
        df.col("day")?.dt_format("%Y-%m-%d")?.alias("day")?,
        df.col("total")?
    ]?)?.collect()?
    print(daily.values_str("day")?)?
    print(daily.values_i64("total")?)?
    Ok(())
["2024-02-28", "2024-02-29"]
[10, 25]

dt_date() retains a logical Date while grouping and sorting; formatting happens only for presentation. The chosen unit controls stored precision. Microseconds keep six fractional decimal places; milliseconds keep three and nanoseconds keep nine. Extra fractional precision is discarded toward the earlier instant, so select the unit intentionally.

Keep invalid input visible as missing data

Parsing is strict by default. Use strict=False when invalid input should become null and your application has a policy for missing data.

import std.dataframe as df

def main() -> Result[(), df.DataFrameError | AllocError | IoError]:
    source = df.frame([
        df.column_str("text", ["1970-01-01", "1969-12-31", "2001-02-29"]?)?
    ]?)?
    dates = source.lazy()?.select([
        df.col("text")?.str_to_date(strict=False)?.alias("day")?
    ]?)?.collect()?
    print(dates.values_date_days_nullable("day")?)?
    Ok(())
[Option[i32].Some(0), Option[i32].Some(-1), Option[i32].Nothing]

The raw boundary representation is days since 1970-01-01; datetimes use integer ticks with an explicit unit. In most reports, prefer calendar expressions and formatting to manipulating these representations yourself. Getters check the logical dtype and, for datetimes, the requested unit and UTC metadata. They do not silently rescale timestamps.

Use Parquet when temporal metadata should survive file round-trips. For CSV, choose an explicit text format and parse it again on input. Named timezones, offset parsing and leap seconds are outside this foundation. See the temporal contracts for nulls, calendar bounds, overflow and conversion costs.