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 |
|---|---|---|---|
|
|
1 |
Year (1–9999) |
|
|
1 |
Month (1–12) |
|
|
1 |
Day (1–31) |
|
|
0 |
Hour (0–23) |
|
|
0 |
Minute (0–59) |
|
|
0 |
Second (0–59) |
|
|
0 |
Millisecond (0–999) |
|
|
0 |
UTC offset in minutes. Minutes are used because all real-world UTC offsets are whole multiples of minutes (ISO 8601 specifies offsets as |
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 |
|---|---|---|---|
|
|
0 |
Number of days (can be negative for negative durations) |
|
|
0 |
Seconds, always in [0, 86399] after normalization |
|
|
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 |
|---|---|---|
|
|
Add duration to timestamp |
|
|
Commutative form |
|
|
Add two durations |
|
|
Subtract duration |
|
|
Difference between two timestamps |
|
|
Subtract durations |
|
|
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-DDYYYY-MM-DDTHH:MM:SSYYYY-MM-DDTHH:MM:SSZYYYY-MM-DDTHH:MM:SS+HH:MMYYYY-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.:
5msfor sub-second values1.500sor42sfor sub-minute valuesMM:SS[.mmm]when hours are zeroHH: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