stdlib_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. For zero-day durations, compact forms can be enabled by passing compact=.true.:

  • 5ms for sub-second values

  • 1.500s or 42s for sub-minute values

  • MM:SS[.mmm] when hours are zero

  • HH:MM:SS[.mmm] otherwise

If days /= 0, or compact is not present or .false., the verbose form is preserved: X days, HH:MM:SS[.mmm].

Syntax#

str = format_timedelta (td [, compact])

Arguments#

compact (optional): logical, intent(in). Will use the compact form if .true.. The default value is .false..

Return value#

character(:), allocatable — e.g. "5ms", "1.500s", "05:30", "01:02:03.456", or "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. Compact format_timedelta (sub-second / sub-minute)
    print *
    print '(A)', '=== Compact Duration Formatting ==='
    duration = timedelta(milliseconds=5)
    print '(A,T40,A)', 'timedelta(ms=5):', format_timedelta(duration, compact=.true.)
    duration = timedelta(milliseconds=1500)
    print '(A,T40,A)', 'timedelta(ms=1500):', format_timedelta(duration, compact=.true.)
    duration = timedelta(seconds=65)
    print '(A,T40,A)', 'timedelta(s=65):', format_timedelta(duration)
    duration = timedelta(hours=1, minutes=2, seconds=3)
    print '(A,T40,A)', 'timedelta(1h 2m 3s):', format_timedelta(duration)

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

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

end program example_datetime