parquet_temporal Module

Independent, self-contained module providing the element-level Parquet date/time value types.

Provides three public types, each holding ONE element (not a whole column -- unlike parquet_string_column in the parquet_strings module):

  • parquet_date -- a calendar date, stored as days since 1970-01-01 (identical to the physical value of Parquet's DATE / Arrow's date32 type).
  • parquet_time -- a time of day, stored as nanoseconds since midnight (holds any Parquet TIME unit -- seconds/millis/micros/nanos -- exactly).
  • parquet_timestamp -- an instant, stored losslessly as seconds since 1970-01-01T00:00:00 plus a nanosecond-of-second part (holds any Parquet TIMESTAMP unit, including legacy INT96 files, exactly).

All three are plain value types with an internal null state; a default-initialized element is null ("no value yet"). Element access follows a two-tier rule: semantic accessors (get, year, to_string, to_mjd, to_unix, comparison operators, ...) abort on a null element (guard with is_null), interop accessors (raw/set_raw/get_raw) never abort (they return 0 for a null element -- validity travels separately via is_null), and type-to-type conversions (get_date/get_time, set(date, time)) propagate null instead of aborting. Most procedures are elemental, so they apply directly to whole arrays (mask = ts%is_null(), call dates%set(years, months, days)).

Calendar math uses the proleptic Gregorian calendar (Howard Hinnant's exact-integer days_from_civil/civil_from_days algorithms). Timezone interpretation is deliberately out of scope: a parquet_timestamp always holds the stored epoch offset verbatim, and a column's timezone metadata is a column-level property of the read/write layer, not of the element. This module depends only on intrinsic modules (iso_fortran_env, ieee_arithmetic); it has no dependency on any other module in this library (the read/write integration layer depends on it, never the reverse).

Why the setters take intent(inout) rather than intent(out), and the obligation that creates. A POLYMORPHIC intent(out) dummy is not free: the compiler default-initialises the element through the runtime on entry, and a type-bound procedure's passed-object dummy has to be polymorphic, so an elemental setter pays that per element. Measured on a 4M-row whole-column read: the construction loop cost 12.3 ms with intent(out) and 3.4 ms with intent(inout) (3.6x), taking the whole date-column read from 23.5 ms to 14.5 ms. The same change on the timestamp setters took its loop from 17.2 ms to 5.7 ms.

The cost of that is an obligation moved from the compiler to this source: intent(out) reset EVERY component for free, whereas under intent(inout) a component a setter does not assign keeps whatever the element held before. So every intent(inout) setter here must assign every component of its type, and adding a component to one of these types means revisiting all of them. tools/check_source_conventions.py's check_temporal_setters_assign_all enforces exactly that, deriving both the component list and the setter list from this file so a new component or a new setter is covered without editing the check.

The converse is equally load-bearing and is enforced too: a setter with a caught-failure path that RETURNS without assigning self -- %parse with a success argument, and ts_set_date_time's null propagation -- must KEEP intent(out), because that is precisely what makes a failed or null-propagating call yield a null element rather than a stale one.



Variables

Type Visibility Attributes Name Initial
integer, public, parameter :: parquet_unit_seconds = 1

Time-unit selectors for set_unix/to_unix (and reused by the read/write layer). Note: parquet_unit_seconds is meaningful only for set_unix/to_unix (Unix-time interop) -- it can never be a MAML-declared TIME/TIMESTAMP column's own stored unit, since Parquet's physical format has no seconds-resolution TIME/TIMESTAMP encoding at all (only MILLIS/MICROS/NANOS); a MAML time[s]/timestamp[s] token is rejected at parse time for exactly this reason. whole seconds since the epoch.

integer, public, parameter :: parquet_unit_millis = 2

milliseconds since the epoch.

integer, public, parameter :: parquet_unit_micros = 3

microseconds since the epoch.

integer, public, parameter :: parquet_unit_nanos = 4

nanoseconds since the epoch.

integer(kind=int64), public, parameter :: parquet_ns_per_sec = NS_PER_SECOND

Public unit-conversion convenience constants for the raw nanosecond values the difference/offset operators traffic in. The _per_X pair are exact integer(int64) divisors (for a whole-unit result, e.g. ns_value/parquet_ns_per_day for a whole day count); the _to_X pair are real(real64) multiplicative factors (for a fractional result, e.g. a fractional day count) -- see feature_temporal.md's "Design: unit-conversion convenience constants" for the naming rationale. exact; ns in one second.

integer(kind=int64), public, parameter :: parquet_ns_per_day = NS_PER_DAY

exact; ns in one day.

real(kind=real64), public, parameter :: parquet_ns_to_sec = 1.0e-9_real64

convenience; ns -> fractional seconds.

real(kind=real64), public, parameter :: parquet_ns_to_day = 1.0_real64/86400.0e9_real64

convenience; ns -> fractional days.


Interfaces

public interface parquet_date

Constructs a valid parquet_date from (year, month, day); aborts on an invalid civil date (the structure constructor itself is unavailable outside this module -- components are private -- so this generic takes its place).

  • private impure elemental function date_new(year, month, day) result(res)

    Constructor specific for the parquet_date generic: builds a valid date element from (year, month, day); aborts on an invalid civil date.

    Arguments

    Type IntentOptional Attributes Name
    integer(kind=int32), intent(in) :: year

    calendar year.

    integer(kind=int32), intent(in) :: month

    month, 1..12.

    integer(kind=int32), intent(in) :: day

    day of month.

    Return Value type(parquet_date)

    the constructed element (valid).

public interface parquet_time

Constructs a valid parquet_time from (hour, minute, second[, nanosecond]); aborts on invalid fields.

  • private impure elemental function time_new(hour, minute, second, nanosecond) result(res)

    Constructor specific for the parquet_time generic: builds a valid time element from (hour, minute, second[, nanosecond]); aborts on invalid fields.

    Arguments

    Type IntentOptional Attributes Name
    integer(kind=int32), intent(in) :: hour

    hour, 0..23.

    integer(kind=int32), intent(in) :: minute

    minute, 0..59.

    integer(kind=int32), intent(in) :: second

    second, 0..59.

    integer(kind=int32), intent(in), optional :: nanosecond

    sub-second part, 0..999999999 (default 0).

    Return Value type(parquet_time)

    the constructed element (valid).

public interface parquet_timestamp

Constructs a valid parquet_timestamp, either from full civil fields (year, month, day, hour, minute, second[, nanosecond]) -- aborting on invalid fields -- or from a (parquet_date, parquet_time) pair, where a null input propagates to a null result.

  • private impure elemental function ts_new_civil(year, month, day, hour, minute, second, nanosecond) result(res)

    Civil-fields constructor specific for the parquet_timestamp generic; aborts on invalid fields.

    Arguments

    Type IntentOptional Attributes Name
    integer(kind=int32), intent(in) :: year

    calendar year.

    integer(kind=int32), intent(in) :: month

    month, 1..12.

    integer(kind=int32), intent(in) :: day

    day of month.

    integer(kind=int32), intent(in) :: hour

    hour, 0..23.

    integer(kind=int32), intent(in) :: minute

    minute, 0..59.

    integer(kind=int32), intent(in) :: second

    second, 0..59.

    integer(kind=int32), intent(in), optional :: nanosecond

    sub-second part, 0..999999999 (default 0).

    Return Value type(parquet_timestamp)

    the constructed element (valid).

  • private impure elemental function ts_new_date_time(date, time) result(res)

    Date-plus-time constructor specific for the parquet_timestamp generic; a null input propagates to a null result (never aborts).

    Arguments

    Type IntentOptional Attributes Name
    type(parquet_date), intent(in) :: date

    the calendar-date part.

    type(parquet_time), intent(in) :: time

    the time-of-day part.

    Return Value type(parquet_timestamp)

    the constructed element (null if an input is null).


Derived Types

type, public ::  parquet_date

A calendar date (proleptic Gregorian), stored as days since 1970-01-01 -- identical to the physical value of a Parquet DATE / Arrow date32 column. A default-initialized element is null. Range: about +-5.8 million years.

Constructor

Constructs a valid parquet_date from (year, month, day); aborts on an invalid civil date (the structure constructor itself is unavailable outside this module -- components are private -- so this generic takes its place).

private impure, elemental function date_new (year, month, day)

Constructor specific for the parquet_date generic: builds a valid date element from (year, month, day); aborts on an invalid civil date.

Type-Bound Procedures

procedure, public :: set => date_set

Sets from a validated (year, month, day).

procedure, public :: get => date_get

Returns year, month, day (aborts on null).

procedure, public :: year => date_year

Calendar year (aborts on null).

procedure, public :: month => date_month

Calendar month 1..12 (aborts on null).

procedure, public :: day => date_day

Day of month 1..31 (aborts on null).

procedure, public :: is_null => date_is_null

Whether the element is null (never aborts).

procedure, public :: set_null => date_set_null

Marks the element null.

procedure, public :: set_raw => date_set_raw

Sets the raw day count (interop; marks valid).

procedure, public :: raw => date_raw

Raw day count; 0 for a null element (interop).

generic, public :: set_mjd => date_set_mjd_i32, date_set_mjd_i64

Sets from an integer Modified Julian Date.

procedure, public :: to_mjd => date_to_mjd

Integer Modified Julian Date (aborts on null).

procedure, public :: to_string => date_to_string

ISO-8601 "YYYY-MM-DD" (aborts on null).

procedure, public :: parse => date_parse

Sets from an ISO-8601 date string.

generic, public :: operator(==) => date_eq

Equality (aborts on a null operand).

generic, public :: operator(/=) => date_ne

Inequality (aborts on a null operand).

generic, public :: operator(<) => date_lt

Ordering (aborts on a null operand).

generic, public :: operator(<=) => date_le

Ordering (aborts on a null operand).

generic, public :: operator(>) => date_gt

Ordering (aborts on a null operand).

generic, public :: operator(>=) => date_ge

Ordering (aborts on a null operand).

generic, public :: operator(-) => date_diff, date_sub_days_i32, date_sub_days_i64

Difference or day offset (aborts on a null operand / out-of-range result).

generic, public :: operator(+) => date_add_days_i32, date_add_days_i64

Day offset (aborts on a null operand / out-of-range result).

type, public ::  parquet_time

A time of day, stored as nanoseconds since midnight, [0, 86400e9 - 1]. Holds any Parquet TIME unit (seconds/millis/micros/nanos) exactly. A default-initialized element is null.

Constructor

Constructs a valid parquet_time from (hour, minute, second[, nanosecond]); aborts on invalid fields.

private impure, elemental function time_new (hour, minute, second, nanosecond)

Constructor specific for the parquet_time generic: builds a valid time element from (hour, minute, second[, nanosecond]); aborts on invalid fields.

Type-Bound Procedures

procedure, public :: set => time_set

Sets from validated (hour, minute, second[, nanosecond]).

procedure, public :: get => time_get

Returns hour, minute, second[, nanosecond] (aborts on null).

procedure, public :: hour => time_hour

Hour 0..23 (aborts on null).

procedure, public :: minute => time_minute

Minute 0..59 (aborts on null).

procedure, public :: second => time_second

Second 0..59 (aborts on null).

procedure, public :: nanosecond => time_nanosecond

Sub-second part 0..999999999 (aborts on null).

procedure, public :: is_null => time_is_null

Whether the element is null (never aborts).

procedure, public :: set_null => time_set_null

Marks the element null.

procedure, public :: set_raw => time_set_raw

Sets raw ns-since-midnight (interop; marks valid).

procedure, public :: raw => time_raw

Raw ns-since-midnight; 0 for a null element (interop).

procedure, public :: to_string => time_to_string

ISO-8601 "HH:MM:SS[.fraction]" (aborts on null).

procedure, public :: parse => time_parse

Sets from an ISO-8601 time string.

generic, public :: operator(==) => time_eq

Equality (aborts on a null operand).

generic, public :: operator(/=) => time_ne

Inequality (aborts on a null operand).

generic, public :: operator(<) => time_lt

Ordering (aborts on a null operand).

generic, public :: operator(<=) => time_le

Ordering (aborts on a null operand).

generic, public :: operator(>) => time_gt

Ordering (aborts on a null operand).

generic, public :: operator(>=) => time_ge

Ordering (aborts on a null operand).

generic, public :: operator(-) => time_diff, time_sub_ns_i32, time_sub_ns_i64

Difference or ns offset (wraps; aborts on a null operand or a >24h offset magnitude).

generic, public :: operator(+) => time_add_ns_i32, time_add_ns_i64

Ns offset (wraps; aborts on a null operand or a >24h offset magnitude).

type, public ::  parquet_timestamp

An instant, stored losslessly as whole seconds since 1970-01-01T00:00:00 plus a normalized nanosecond-of-second part (always 0..999999999, also for pre-epoch instants). Holds any Parquet TIMESTAMP unit exactly over the full int64 range of the stored value. Timezone-agnostic: holds the stored epoch offset verbatim. A default-initialized element is null.

Constructor

Constructs a valid parquet_timestamp, either from full civil fields (year, month, day, hour, minute, second[, nanosecond]) -- aborting on invalid fields -- or from a (parquet_date, parquet_time) pair, where a null input propagates to a null result.

private impure, elemental function ts_new_civil (year, month, day, hour, minute, second, nanosecond)

Civil-fields constructor specific for the parquet_timestamp generic; aborts on invalid fields.

private impure, elemental function ts_new_date_time (date, time)

Date-plus-time constructor specific for the parquet_timestamp generic; a null input propagates to a null result (never aborts).

Type-Bound Procedures

generic, public :: set => ts_set_civil, ts_set_date_time

Sets from civil fields or a date + time pair.

procedure, public :: get => ts_get

Returns all civil fields (aborts on null).

procedure, public :: get_date => ts_get_date

The date part; null propagates (never aborts).

procedure, public :: get_time => ts_get_time

The time-of-day part; null propagates (never aborts).

procedure, public :: is_null => ts_is_null

Whether the element is null (never aborts).

procedure, public :: set_null => ts_set_null

Marks the element null.

procedure, public :: set_raw => ts_set_raw

Sets the raw (seconds, nanoseconds) pair (interop).

procedure, public :: get_raw => ts_get_raw

Raw (seconds, nanoseconds); zeros when null (interop).

generic, public :: set_unix => ts_set_unix_i32, ts_set_unix_i64

Sets from a Unix-time value in a given unit.

procedure, public :: to_unix => ts_to_unix

Unix-time value in a given unit (aborts on null/loss).

procedure, public :: set_mjd => ts_set_mjd

Sets from a real64 Modified Julian Date.

procedure, public :: to_mjd => ts_to_mjd

real64 Modified Julian Date (aborts on null).

procedure, public :: set_jd => ts_set_jd

Sets from a real64 Julian Date.

procedure, public :: to_jd => ts_to_jd

real64 Julian Date (aborts on null).

procedure, public :: to_string => ts_to_string

ISO-8601 "YYYY-MM-DDTHH:MM:SS[.fraction]" (aborts on null).

procedure, public :: parse => ts_parse

Sets from an ISO-8601 date-time string.

generic, public :: operator(==) => ts_eq

Equality (aborts on a null operand).

generic, public :: operator(/=) => ts_ne

Inequality (aborts on a null operand).

generic, public :: operator(<) => ts_lt

Ordering (aborts on a null operand).

generic, public :: operator(<=) => ts_le

Ordering (aborts on a null operand).

generic, public :: operator(>) => ts_gt

Ordering (aborts on a null operand).

generic, public :: operator(>=) => ts_ge

Ordering (aborts on a null operand).

procedure, public :: diff_seconds => ts_diff_seconds

Real64-seconds difference (ts, ts); never aborts on magnitude.

generic, public :: operator(-) => ts_diff_ns, ts_sub_ns_i32, ts_sub_ns_i64

Difference or ns offset (aborts on a null operand or int64 overflow).

generic, public :: operator(+) => ts_add_ns_i32, ts_add_ns_i64

Ns offset (aborts on a null operand or int64 overflow).