datetime

Introduction

The stdlib_datetime module provides types and procedures for date, time, and duration handling. It defines two primary derived types — datetime_type for representing specific points in time and timedelta_type for representing durations — along with arithmetic operators, comparison operators, ISO 8601 parsing/formatting, and calendar utilities.

No C-bindings or parameterized derived types are required. The module uses only standard Fortran intrinsics, calendar arithmetic, and string manipulation.

Status

Experimental

Derived Types

datetime_type

Represents a specific point in time.

Component Type Default Description
year integer 1 Year (1–9999)
month integer 1 Month (1–12)
day integer 1 Day (1–31)
hour integer 0 Hour (0–23)
minute integer 0 Minute (0–59)
second integer 0 Second (0–59)
millisecond integer 0 Millisecond (0–999)
utc_offset_minutes integer 0 UTC offset in minutes. Minutes are used because all real-world UTC offsets are whole multiples of minutes (ISO 8601 specifies offsets as ±HH:MM).

timedelta_type

Represents a duration or interval. After normalization, seconds is always in [0, 86399] and milliseconds is always in [0, 999] (i.e., they are never negative). For negative durations, only the days component is negative while seconds and milliseconds remain non-negative.

Component Type Default Description
days integer 0 Number of days (can be negative for negative durations)
seconds integer 0 Seconds, always in [0, 86399] after normalization
milliseconds integer 0 Milliseconds, always in [0, 999] after normalization

Constructors

datetime — Create from components

Status

Experimental

Class

Pure function.

Description

Creates a datetime_type from individual components. All arguments are optional; if omitted, they default to the type's initial values: year=1, month=1, day=1, hour=0, minute=0, second=0, millisecond=0, utc_offset_minutes=0.

Note: Unlike parse_datetime, this constructor does not perform strict bounds checking on the provided values (e.g., passing month=13 is not checked) to remain highly efficient in tight loops. The caller is responsible for ensuring the values form a valid date block.

Syntax

dt = datetime ([year] [, month] [, day] [, hour] [, minute] [, second] [, millisecond] [, utc_offset_minutes])

Arguments

All arguments are optional with intent(in) and type integer. If no arguments are provided, all components default to the type's initial values (year=1, month=1, day=1, hour=0, minute=0, second=0, millisecond=0, utc_offset_minutes=0).

Return value

A datetime_type value with components equal to those provided, or to the default ones.

timedelta — Create from mixed units

Status

Experimental

Class

Pure function.

Description

Creates a normalized timedelta_type. Accepts mixed units (days, hours, minutes, seconds, milliseconds) and normalizes them. If no arguments are provided, all components default to 0.

Syntax

td = timedelta ([days] [, hours] [, minutes] [, seconds] [, milliseconds])

Arguments

All arguments are optional with intent(in) and type integer. If no arguments are provided, all components default to 0.

Return value

A normalized timedelta_type value with seconds in [0, 86399] and milliseconds in [0, 999].

now — Current local time

Status

Experimental

Class

Function (not pure; calls the intrinsic date_and_time which is impure).

Description

Returns the current local date and time from the system clock.

Syntax

dt = now ()

now_utc — Current UTC time

Status

Experimental

Class

Function (not pure; calls now() internally).

Description

Returns the current UTC date and time.

Syntax

dt = now_utc ()

epoch — Unix epoch

Status

Experimental

Class

Pure function.

Description

Returns the Unix epoch: 1970-01-01T00:00:00Z. This is a mathematical constant (a fixed date) and does not depend on the operating system.

Syntax

dt = epoch ()

Operators

Arithmetic

Expression Result Type Description
datetime + timedelta datetime_type Add duration to timestamp
timedelta + datetime datetime_type Commutative form
timedelta + timedelta timedelta_type Add two durations
datetime - timedelta datetime_type Subtract duration
datetime - datetime timedelta_type Difference between two timestamps
timedelta - timedelta timedelta_type Subtract durations
-timedelta timedelta_type Negate duration

Comparison

All six comparison operators are provided for both datetime_type and timedelta_type: ==, /=, <, <=, >, >=.

Important: datetime_type comparisons convert both operands to UTC internally, so comparing across timezones works correctly.

Parsing and Formatting

parse_datetime — Parse ISO 8601 string

Status

Experimental

Class

Function (not pure; dummy arguments in a pure function must be intent(in), which contradicts the intent(out) status argument stat).

Description

Parses an ISO 8601 date/time string into a datetime_type.

Syntax

dt = parse_datetime (str [, stat])

Arguments

str: character(len=*), intent(in). The ISO 8601 string to parse.

stat (optional): integer, intent(out). Returns 0 on success, non-zero on error.

Supported formats

  • YYYY-MM-DD
  • YYYY-MM-DDTHH:MM:SS
  • YYYY-MM-DDTHH:MM:SSZ
  • YYYY-MM-DDTHH:MM:SS+HH:MM
  • YYYY-MM-DDTHH:MM:SS.fZ (variable precision fractional seconds up to milliseconds)
  • YYYY-MM-DDTHH:MM:SS.f+HH:MM

For the YYYY-MM-DDTHH:MM:SS form without a timezone designator, the value is interpreted as UTC, and utc_offset_minutes is set to 0. There is currently no cross-platform standard way to get a time zone offset for arbitrary past/future dates, so defaulting to UTC serves as an absolute, safe coordinate. Forms with Z or an explicit offset use the specified UTC offset.

format_datetime — Format as ISO 8601

Status

Experimental

Class

Pure function.

Description

Formats a datetime_type as an ISO 8601 string.

Syntax

str = format_datetime (dt)

Return value

character(:), allocatable — e.g. "2026-03-17T12:00:00Z" or "2026-03-17T23:05:15+05:30".

format_timedelta — Format duration

Status

Experimental

Class

Pure function.

Description

Formats a timedelta_type as a human-readable string.

Syntax

str = format_timedelta (td)

Return value

character(:), allocatable — e.g. "30 days, 01:30:00".

Utility Functions

is_leap_year

Status

Experimental

Class

Pure elemental function / interface.

Description

Returns .true. if the given year (or datetime's year) is a leap year.

Syntax

result = is_leap_year (year) or (dt)

days_in_month

Status

Experimental

Class

Pure function.

Description

Returns the number of days in a given month and year.

Syntax

d = days_in_month (month, year)

days_in_year

Status

Experimental

Class

Pure function.

Description

Returns 365 or 366.

Syntax

d = days_in_year (year)

day_of_year

Status

Experimental

Class

Pure function.

Description

Returns the ordinal day (1–366).

Syntax

doy = day_of_year (dt)

day_of_week

Status

Experimental

Class

Pure function.

Description

Returns the ISO weekday (1=Monday, ..., 7=Sunday).

Syntax

dow = day_of_week (dt)

to_utc

Status

Experimental

Class

Pure function.

Description

Converts a datetime_type to UTC.

Syntax

utc_dt = to_utc (dt)

total_seconds

Status

Experimental

Class

Pure function.

Description

Returns the total duration as real(dp).

Syntax

secs = total_seconds (td)

Example

program example_datetime
    !! Demonstrate the stdlib_datetime module functionality.
    use stdlib_datetime
    implicit none

    type(datetime_type)  :: t1, t2, t3
    type(timedelta_type) :: duration
    integer :: stat

    print '(A)', '=== stdlib_datetime Example ==='
    print *

    ! 1. Get the current local time
    t1 = now()
    print '(A,A)', 'Current local time: ', format_datetime(t1)

    ! 2. Get the current UTC time
    t2 = now_utc()
    print '(A,A)', 'Current UTC time:   ', format_datetime(t2)

    ! 3. Parse an ISO 8601 string
    t2 = parse_datetime('2026-03-17T12:00:00Z', stat)
    if (stat /= 0) then
        print '(A)', 'ERROR: Failed to parse date string!'
        stop 1
    end if
    print '(A,A)', 'Parsed datetime:    ', format_datetime(t2)

    ! 4. Calculate the difference between two datetimes
    duration = t1 - t2
    print '(A,A)', 'Duration (t1-t2):   ', &
        format_timedelta(duration)

    ! 5. Add 30 days to a date
    t3 = t2 + timedelta_type(30, 0, 0)
    print '(A,A)', 'After adding 30d:   ', format_datetime(t3)

    ! 6. Add mixed units (1 day, 6 hours, 30 minutes)
    t3 = t2 + timedelta(days=1, hours=6, minutes=30)
    print '(A,A)', 'After +1d 6h 30m:   ', format_datetime(t3)

    ! 7. Calendar utilities
    print *
    print '(A)', '=== Calendar Utilities ==='
    print '(A,L1)', 'Is 2024 leap year?  ', is_leap_year(2024)
    print '(A,L1)', 'Is 2026 leap year?  ', is_leap_year(2026)
    print '(A,I0)', 'Days in Feb 2024:   ', &
        days_in_month(2, 2024)
    print '(A,I0)', 'Days in Feb 2026:   ', &
        days_in_month(2, 2026)
    print '(A,I0)', 'Day of year (t2):   ', day_of_year(t2)
    print '(A,I0)', 'Day of week (t2):   ', day_of_week(t2)
    print '(A)', '  (1=Mon, 2=Tue, ..., 7=Sun)'

    ! 8. Comparison operators
    print *
    print '(A)', '=== Comparisons ==='
    t2 = parse_datetime('2026-03-17T12:00:00Z')
    t3 = parse_datetime('2026-03-18T12:00:00Z')
    print '(A,L1)', 'Mar17 < Mar18?      ', t2 < t3
    print '(A,L1)', 'Mar17 == Mar17?     ', t2 == t2

    ! Cross-timezone equality: 12:00 UTC == 17:30 IST
    t2 = datetime_type(2026, 3, 17, 12, 0, 0, 0, 0)
    t3 = datetime_type(2026, 3, 17, 17, 30, 0, 0, 330)
    print '(A,L1)', '12:00Z == 17:30+05:30? ', t2 == t3

    ! 9. Unix epoch
    print *
    print '(A,A)', 'Unix epoch:         ', &
        format_datetime(epoch())

    print *
    print '(A)', 'Done!'

end program example_datetime