Skip to content

LiteralTimestamp #110

@xmnlab

Description

@xmnlab

This is an initial idea from gpt, please feel free to use your own approach (maybe this idea is wrong):


Here’s a visit for LiteralTimestamp you can add to LLVMLiteIRVisitor.
It parses an ISO-style timestamp (YYYY-MM-DDTHH:MM:SS[.fffffffff] or with a space instead of T) and emits a constant struct:

{ i32 year, i32 month, i32 day, i32 hour, i32 minute, i32 second, i32 nanos }

Fractional seconds are parsed into nanoseconds (0–999,999,999). Timezones (Z or ±HH:MM) are not handled in this basic lowering and will raise.

from llvmlite import ir
from plum import dispatch

@dispatch  # type: ignore[no-redef]
def visit(self, node: astx.LiteralTimestamp) -> None:
    """
    Lower a LiteralTimestamp to LLVM IR.

    Representation:
      { i32 year, i32 month, i32 day, i32 hour, i32 minute, i32 second, i32 nanos }

    Accepted formats (no timezone):
      YYYY-MM-DDTHH:MM:SS
      YYYY-MM-DDTHH:MM:SS.sssssssss
      YYYY-MM-DD HH:MM:SS
      YYYY-MM-DD HH:MM:SS.sssssssss

    Notes:
      - Fractional seconds are parsed to nanoseconds (0..999,999,999).
      - Timezones ('Z' or ±HH:MM) are not supported here.
    """
    s = node.value.strip()

    # Split date and time by 'T' or space.
    if "T" in s:
        date_part, time_part = s.split("T", 1)
    elif " " in s:
        date_part, time_part = s.split(" ", 1)
    else:
        raise Exception(
            f"LiteralTimestamp: invalid format '{node.value}'. "
            "Expected 'YYYY-MM-DDTHH:MM:SS[.fffffffff]' (or space instead of 'T')."
        )

    # Reject timezone suffixes for now.
    if time_part.endswith("Z") or "+" in time_part or "-" in time_part[2:]:
        # (the [- in time_part[2:]] avoids the '-' of HH if someone wrote '-1', but
        # standard times won't have that anyway)
        raise Exception(
            f"LiteralTimestamp: timezone offsets not supported in '{node.value}'."
        )

    # Parse date: YYYY-MM-DD
    try:
        y_str, m_str, d_str = date_part.split("-")
        year = int(y_str)
        month = int(m_str)
        day = int(d_str)
    except Exception as exc:
        raise Exception(
            f"LiteralTimestamp: invalid date part in '{node.value}'. "
            "Expected 'YYYY-MM-DD'."
        ) from exc

    # Basic range checks (not validating month/day combinations here)
    if not (1 <= month <= 12):
        raise Exception(f"LiteralTimestamp: month out of range in '{node.value}'.")
    if not (1 <= day <= 31):
        raise Exception(f"LiteralTimestamp: day out of range in '{node.value}'.")

    # Parse time: HH:MM:SS(.fffffffff)?
    frac_ns = 0
    try:
        if "." in time_part:
            hms, frac = time_part.split(".", 1)
            # Keep only digits for fraction; cap to 9 digits (nanoseconds).
            if not frac.isdigit():
                raise ValueError("fractional seconds must be digits")
            if len(frac) > 9:
                frac = frac[:9]  # truncate extra precision
            # Scale to nanoseconds (right-pad with zeros).
            frac_ns = int(frac.ljust(9, "0"))
        else:
            hms = time_part

        h_str, m_str, s_str = hms.split(":")
        hour = int(h_str)
        minute = int(m_str)
        second = int(s_str)
    except Exception as exc:
        raise Exception(
            f"LiteralTimestamp: invalid time part in '{node.value}'. "
            "Expected 'HH:MM:SS' (optionally with '.fffffffff')."
        ) from exc

    if not (0 <= hour <= 23):
        raise Exception(f"LiteralTimestamp: hour out of range in '{node.value}'.")
    if not (0 <= minute <= 59):
        raise Exception(f"LiteralTimestamp: minute out of range in '{node.value}'.")
    if not (0 <= second <= 59):
        raise Exception(f"LiteralTimestamp: second out of range in '{node.value}'.")

    # Build constant struct
    i32 = self._llvm.INT32_TYPE
    ts_ty = ir.LiteralStructType([i32, i32, i32, i32, i32, i32, i32])

    const_ts = ir.Constant(
        ts_ty,
        [
            ir.Constant(i32, year),
            ir.Constant(i32, month),
            ir.Constant(i32, day),
            ir.Constant(i32, hour),
            ir.Constant(i32, minute),
            ir.Constant(i32, second),
            ir.Constant(i32, frac_ns),
        ],
    )

    self.result_stack.append(const_ts)

Adjustments you might want later

  • Timezone support: Add fields for UTC offset minutes (e.g., an extra i32 offset_minutes) and parse Z/±HH:MM.
  • Stricter date validation: Enforce month/day combinations and leap years.
  • Different ABI: If you prefer a scalar representation (e.g., UNIX epoch i64 + i32 nanos), compute it here and return { i64 secs, i32 nanos } instead.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions