Fortran date and time
last modified November 14, 2026
Almost every useful program needs a clock at some point: to stamp a log entry,
to date a file, to compute how long a calculation took or to decide whether a
cached value has expired. Fortran has three ways to ask for the time. The
subroutine date_and_time returns the calendar date and the time of
day as text and as numbers, system_clock returns a monotonic
counter for measuring intervals, and cpu_time returns the
processor time the program itself has consumed.
This article reads the clock and formats its answer as an ISO 8601 timestamp, explains the eight numbers behind the date, handles the time zone, measures durations with nanosecond resolution, decides which years are leap years, converts dates to Julian day numbers so that they can be subtracted, adds days to a date, and finally times a loop to estimate the cost of one iteration.
Reading the clock
call date_and_time(date, time, zone, values) fills the four
arguments it is given: date is an eight-character string
ccyymmdd, time is hhmmss.sss,
zone is the offset from Coordinated Universal Time written as
+hhmm, and values is an integer array of eight
elements. All four arguments are optional, so a program that wants only the
date calls date_and_time(date). The character lengths must be at
least eight, ten and five; if a variable is shorter, the value is truncated
rather than rejected.
program d1
implicit none
character(len=8) :: date
character(len=5) :: zone
character(len=10) :: time
character(len=25) :: iso
integer :: values(8)
call date_and_time(date, time, zone, values)
print '(a, a)', 'date: ', date
print '(a, a)', 'time: ', time
print '(a, a)', 'zone: ', zone
print '(a, i0)', 'year: ', values(1)
print '(a, i0)', 'month: ', values(2)
print '(a, i0)', 'day: ', values(3)
print '(a, i0)', 'hour: ', values(5)
print '(a, i0)', 'minute: ', values(6)
print '(a, i0)', 'second: ', values(7)
print '(a, i0)', 'millisecond: ', values(8)
iso = date(1:4)//'-'//date(5:6)//'-'//date(7:8)//'T'// &
time(1:2)//':'//time(3:4)//':'//time(5:6)
print '(a, a)', 'iso 8601: ', trim(iso)
end program d1
The program prints the same instant in four ways: the two compact strings, the zone, and the individual fields from the array. The last line assembles an ISO 8601 timestamp from slices of the strings, which is the form to store in a file or send over a network, because it sorts correctly as text. The date shown is the day this article was written, and the time fields hold the moment the program ran; both come from the operating system, not from the program.
$ gfortran d1.f90 -o d1 $ ./d1 date: 20261114 time: 172035.302 zone: +0100 year: 2026 month: 11 day: 14 hour: 17 minute: 20 second: 35 millisecond: 302 iso 8601: 2026-11-14T17:20:35
The eight elements and the time zone
The array is the machine-readable form of the same information, and its order is worth memorising: year, month, day, the offset from UTC in minutes, hour, minute, second and millisecond. Note that the offset sits in the middle, at position four, between the date and the time, and that it is measured in minutes rather than hours, because a few zones differ from UTC by a quarter or a half hour. A negative value means west of Greenwich.
program d2
implicit none
character(len=8) :: date
character(len=10) :: time
character(len=5) :: zone
character(len=8) :: clock
character(len=16), parameter :: names(8) = [character(len=16) :: &
'year', 'month', 'day', 'utc offset', 'hour', 'minute', 'second', &
'millisecond']
integer :: values(8), i, offset, utc_hour, utc_minute, day_shift
call date_and_time(date, time, zone, values)
print '(a, a)', 'local time: ', time(1:2)//':'//time(3:4)//':'// &
time(5:6)//'.'//time(8:10)
print '(a)', 'the values array:'
do i = 1, 8
print '(i2, 4x, a, 4x, i0)', i, names(i), values(i)
end do
offset = values(4)
print '(a, i0, a)', 'offset from utc: ', offset, ' minutes'
print '(a, f5.1, a)', 'that is ', real(offset) / 60.0, ' hours'
utc_minute = values(6) - mod(offset, 60)
utc_hour = values(5) - offset / 60
day_shift = 0
if (utc_minute < 0) then
utc_minute = utc_minute + 60
utc_hour = utc_hour - 1
end if
if (utc_hour < 0) then
utc_hour = utc_hour + 24
day_shift = -1
else if (utc_hour >= 24) then
utc_hour = utc_hour - 24
day_shift = 1
end if
write (clock, '(i2.2, a, i2.2, a, i2.2)') utc_hour, ':', utc_minute, &
':', values(7)
print '(a, a, a, a)', 'utc time: ', clock, ' on day ', date
print '(a, i0)', 'utc day minus local day: ', day_shift
end program d2
The offset here is 60 minutes, one hour, so central European time is ahead of
UTC. Subtracting it is not simply a subtraction of one from the hour: the
minutes may wrap below zero, and the hour may wrap below zero or above
twenty-three, in which case the date moves by a day. The program handles all
three cases and prints the UTC clock and the day shift, which is zero for this
run because the local time is not near midnight. The millisecond field is the
only part of the timestamp with sub-second resolution, and it is what makes
date_and_time usable for coarse timing.
$ gfortran d2.f90 -o d2 $ ./d2 local time: 17:20:35.347 the values array: 1 year 2026 2 month 11 3 day 14 4 utc offset 60 5 hour 17 6 minute 20 7 second 35 8 millisecond 347 offset from utc: 60 minutes that is 1.0 hours utc time: 16:20:35 on day 20261114 utc day minus local day: 0
Measuring durations with system_clock
date_and_time answers "what time is it", while
system_clock answers "how much time has passed". It returns a
count and, on request, the number of counts per second and the largest value
the count can reach. Only differences of counts are meaningful; the zero point
is arbitrary and differs between machines. The kinds matter: with
integer(int64) arguments gfortran counts nanoseconds in a 64-bit
integer, while a default integer argument gives a coarser clock with a small
range.
program d3
use iso_fortran_env, only: int64, real64
implicit none
integer(int64) :: start, finish, rate, top
real(real64) :: elapsed, cpu_start, cpu_finish, used
real :: x
integer :: i
call system_clock(start, rate, top)
print '(a, g0)', 'counts per second: ', rate
print '(a, g0)', 'largest count: ', top
print '(a, f10.1, a)', 'a full cycle is ', real(top, real64) / &
real(rate, real64) / 86400.0, ' days'
call cpu_time(cpu_start)
call system_clock(start)
x = 0.0
do i = 1, 20000000
x = x + real(mod(i, 7)) * 1.0e-7
end do
call system_clock(finish)
call cpu_time(cpu_finish)
elapsed = real(finish - start, real64) / real(rate, real64)
used = cpu_finish - cpu_start
print '(a, f8.4, a)', 'wall clock: ', elapsed, ' s'
print '(a, f8.4, a)', 'cpu time: ', used, ' s'
print '(a, f0.9)', 'the sum: ', x
call system_clock(start)
call system_clock(finish)
print '(a, g0, a)', 'two calls apart: ', finish - start, ' counts'
print '(a, g0, a)', 'a count is ', 1000000000_int64 / rate, ' ns'
end program d3
A billion counts per second means one count is a nanosecond, and the largest
count covers about 292 years, so the counter will not wrap during any plausible
run. The loop adds twenty million small terms and the two clocks agree to the
four decimals printed: the processor was busy the whole time, with no waiting
for input or for other processes. cpu_time measures exactly that,
the processor time charged to the program, and it advances more slowly than the
wall clock when the program sleeps or waits. Two consecutive
system_clock calls are a few dozen nanoseconds apart, which is the
resolution of the timer itself.
$ gfortran d3.f90 -o d3 $ ./d3 counts per second: 1000000000 largest count: 9223372036854775807 a full cycle is 106752.0 days wall clock: 0.0461 s cpu time: 0.0461 s the sum: 5.870213032 two calls apart: 22 counts a count is 1 ns
Leap years and the length of a month
Calendar arithmetic starts with the length of a month, which is constant except
for February. A year is a leap year when it is divisible by four, but
not when it is divisible by a hundred, unless it is also divisible by four
hundred: 2024 is a leap year, 1900 is not, 2000 is. Encoding that rule as a
pure function makes it usable in array expressions, and a table of
month lengths turns the day of the month into the day of the year.
module calendar
implicit none
contains
pure logical function is_leap(year) result(leap)
integer, intent(in) :: year
leap = (mod(year, 4) == 0 .and. mod(year, 100) /= 0) .or. &
mod(year, 400) == 0
end function is_leap
pure integer function days_in_month(year, month) result(days)
integer, intent(in) :: year, month
integer, parameter :: length(12) = [31, 28, 31, 30, 31, 30, &
31, 31, 30, 31, 30, 31]
days = length(month)
if (month == 2 .and. is_leap(year)) days = 29
end function days_in_month
pure integer function day_of_year(year, month, day) result(number)
integer, intent(in) :: year, month, day
integer :: m
number = day
do m = 1, month - 1
number = number + days_in_month(year, m)
end do
end function day_of_year
end module calendar
program d4
use calendar
implicit none
integer, parameter :: years(6) = [1900, 2000, 2023, 2024, 2100, 2400]
integer :: i
print '(a)', ' year leap february days'
do i = 1, size(years)
print '(i5, 4x, l1, 7x, i2, 6x, i3)', years(i), is_leap(years(i)), &
days_in_month(years(i), 2), merge(366, 365, is_leap(years(i)))
end do
print '(a, i0)', 'day of year 2026-11-14: ', day_of_year(2026, 11, 14)
print '(a, i0)', 'day of year 2024-12-31: ', day_of_year(2024, 12, 31)
print '(a, i0)', 'day of year 2023-12-31: ', day_of_year(2023, 12, 31)
print '(a, i0)', 'days left in 2026: ', 365 - day_of_year(2026, 11, 14)
end program d4
The table shows the century rule doing its work: 1900 and 2100 have 28 days in
February, while 2000 and 2400, divisible by 400, have 29. The day of the year
for the fourteenth of November 2026 is 318, because the ten months before it
hold 304 days, and 47 days remain in the year. The merge call in
the table picks 366 or 365 from the same leap-year test, which keeps the two
columns consistent by construction.
$ gfortran d4.f90 -o d4 $ ./d4 year leap february days 1900 F 28 365 2000 T 29 366 2023 F 28 365 2024 T 29 366 2100 F 28 365 2400 T 29 366 day of year 2026-11-14: 318 day of year 2024-12-31: 366 day of year 2023-12-31: 365 days left in 2026: 47
Julian day numbers and the day of the week
Subtracting two dates is awkward while they are years, months and days. The
standard trick is to convert each date to a Julian day number, an
integer that counts days continuously from a fixed origin in 4713 BC, and to
subtract the numbers instead. The same conversion gives the day of the week for
free, because the numbers repeat every seven days. The module below holds the
conversion in both directions, the weekday, and a small formatting function,
and it is compiled from its own file, which produces a dates.mod
that the programs read.
module dates
use iso_fortran_env, only: int64
implicit none
contains
pure integer(int64) function julian_day(year, month, day) result(jd)
integer, intent(in) :: year, month, day
integer :: a, y, m
a = (14 - month) / 12
y = year + 4800 - a
m = month + 12 * a - 3
jd = int(day, int64) + (153_int64 * m + 2) / 5 + 365_int64 * y + &
y / 4 - y / 100 + y / 400 - 32045
end function julian_day
pure function date_from_julian(jd) result(parts)
integer(int64), intent(in) :: jd
integer :: parts(3)
integer(int64) :: a, b, c, d, e, m
a = jd + 32044
b = (4 * a + 3) / 146097
c = a - 146097 * b / 4
d = (4 * c + 3) / 1461
e = c - 1461 * d / 4
m = (5 * e + 2) / 153
parts(3) = int(e - (153 * m + 2) / 5 + 1)
parts(2) = int(m + 3 - 12 * (m / 10))
parts(1) = int(100 * b + d - 4800 + m / 10)
end function date_from_julian
pure integer function weekday(jd) result(n)
integer(int64), intent(in) :: jd
n = int(mod(jd + 1, 7_int64))
end function weekday
pure function iso_date(year, month, day) result(text)
integer, intent(in) :: year, month, day
character(len=10) :: text
write (text, '(i4.4, a, i2.2, a, i2.2)') year, '-', month, '-', day
end function iso_date
end module dates
The forward conversion shifts the year so that March is the first month, which places the extra day of a leap year at the end of the year and makes the month-length formula a single polynomial. The inverse repeats the idea with integer divisions by 146097, the number of days in four hundred years, and by 1461, the number of days in four years. The weekday is the day number plus one taken modulo seven, because day zero fell on a Monday, so zero means Sunday in the table below.
program d5
use dates
use iso_fortran_env, only: int64
implicit none
character(len=9), parameter :: names(0:6) = [character(len=9) :: &
'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', &
'Saturday']
integer, parameter :: years(5) = [2000, 2026, 1969, 1900, 1582]
integer, parameter :: months(5) = [1, 11, 7, 1, 10]
integer, parameter :: days(5) = [1, 14, 20, 1, 15]
integer :: i, parts(3)
integer(int64) :: jd, epoch
print '(a)', 'date julian day weekday'
do i = 1, 5
jd = julian_day(years(i), months(i), days(i))
print '(a, 4x, i7, 4x, a)', iso_date(years(i), months(i), days(i)), &
jd, trim(names(weekday(jd)))
end do
jd = julian_day(2026, 11, 14)
parts = date_from_julian(jd)
print '(a, i0, a, a)', 'back from ', jd, ': ', iso_date(parts(1), &
parts(2), parts(3))
print '(a, i0)', 'the year is: ', parts(1)
print '(a, i0)', 'the month is: ', parts(2)
print '(a, i0)', 'the day is: ', parts(3)
epoch = julian_day(1970, 1, 1)
print '(a, i0)', 'the unix epoch is julian day ', epoch
print '(a, i0)', 'days from the epoch to 2026-11-14: ', jd - epoch
end program d5
The table checks the conversion against dates whose weekday is known: the first of January 2000 was a Saturday, the moon landing on the twentieth of July 1969 was a Sunday, and the fifteenth of October 1582, the first day of the Gregorian calendar, was a Friday. The inverse conversion returns the same date it started from, field by field. The Unix epoch, the first of January 1970, is Julian day 2440588, so the fourteenth of November 2026 is exactly 20771 days later, and that single subtraction replaces a month-by-month calculation.
$ gfortran dates.f90 d5.f90 -o d5 $ ./d5 date julian day weekday 2000-01-01 2451545 Saturday 2026-11-14 2461359 Saturday 1969-07-20 2440423 Sunday 1900-01-01 2415021 Monday 1582-10-15 2299161 Friday back from 2461359: 2026-11-14 the year is: 2026 the month is: 11 the day is: 14 the unix epoch is julian day 2440588 days from the epoch to 2026-11-14: 20771
Date arithmetic
With the two conversions in hand, adding a number of days to a date is three steps: convert to a day number, add, convert back. The program below shifts the fourteenth of November 2026 by a week, a month, a year and a day, and also backwards, and prints the weekday of every result. The last lines compare a step of 365 days with a step of one calendar year.
program d6
use dates
use iso_fortran_env, only: int64
implicit none
integer, parameter :: shifts(8) = [0, 1, 7, 30, 100, 365, -1, -366]
character(len=9), parameter :: names(0:6) = [character(len=9) :: &
'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', &
'Saturday']
integer :: i, parts(3)
integer(int64) :: base, other
base = julian_day(2026, 11, 14)
print '(a)', 'days added new date weekday'
do i = 1, size(shifts)
parts = date_from_julian(base + int(shifts(i), int64))
print '(i7, 6x, a, 4x, a)', shifts(i), iso_date(parts(1), parts(2), &
parts(3)), trim(names(weekday(base + int(shifts(i), int64))))
end do
other = julian_day(2027, 3, 1)
print '(a, i0, a)', 'from 2026-11-14 to 2027-03-01: ', other - base, &
' days'
print '(a, f0.1, a)', 'that is ', real(other - base) / 7.0, ' weeks'
print '(a, a)', 'a year later: ', iso_date(2027, 11, 14)
print '(a, i0)', 'the same day in 2027 is julian day ', &
julian_day(2027, 11, 14)
print '(a, i0)', 'a plain 365 day step lands on day ', &
julian_day(2027, 11, 14) - base
end program d6
A week later is the same weekday, a month later is the fourteenth of December, and 365 days later is Sunday rather than Saturday, because 2027 is not a leap year and 365 is one day more than 52 weeks. The span from the fourteenth of November to the first of March is 107 days, which the program also expresses as 15.3 weeks. Because the shift is applied to a number, it handles month ends and leap days without a single special case; a shift applied to the day field alone would produce the thirty-second of November.
$ gfortran dates.f90 d6.f90 -o d6
$ ./d6
days added new date weekday
0 2026-11-14 Saturday
1 2026-11-15 Sunday
7 2026-11-21 Saturday
30 2026-12-14 Monday
100 2027-02-22 Monday
365 2027-11-14 Sunday
-1 2026-11-13 Friday
-366 2025-11-13 Thursday
from 2026-11-14 to 2027-03-01: 107 days
that is 15.3 weeks
a year later: 2027-11-14
the same day in 2027 is julian day 2461724
a plain 365 day step lands on day 365
Timing a loop
The usual reason to read a clock in a numerical program is to find out where
the time goes. Timing the same operation at four sizes shows both the cost of
one iteration and how the total grows with the amount of work, and comparing
the result with a mathematical estimate of the sum confirms that the loop
computed what it was asked to. The sum of the square roots from one to
n grows like n**1.5, so every doubling of
n must multiply the sum by about 2.83.
program d7
use iso_fortran_env, only: int64, real64
implicit none
integer, parameter :: sizes(4) = [1000000, 2000000, 4000000, 8000000]
integer(int64) :: tick_start, tick_stop, rate
real(real64) :: cpu_start, cpu_stop, total, sums(4)
integer :: i, k, n
call system_clock(count_rate=rate)
print '(a)', ' n cpu s ns/item sum'
do k = 1, size(sizes)
n = sizes(k)
call cpu_time(cpu_start)
total = 0.0_real64
do i = 1, n
total = total + sqrt(real(i, real64))
end do
call cpu_time(cpu_stop)
sums(k) = total
print '(i7, f9.4, f11.2, es14.6)', n, cpu_stop - cpu_start, &
(cpu_stop - cpu_start) * 1.0e9 / real(n, real64), total
end do
call system_clock(tick_start)
total = 0.0_real64
do i = 1, 4000000
total = total + sqrt(real(i, real64))
end do
call system_clock(tick_stop)
print '(a, f0.4, a)', 'wall clock for four million: ', &
real(tick_stop - tick_start, real64) / real(rate, real64), ' s'
print '(a, es14.6)', 'the same sum again: ', total
do k = 2, 4
print '(a, i0, a, f0.4)', 'sum ratio, ', sizes(k), ' over half: ', &
sums(k) / sums(k - 1)
end do
end program d7
The four sizes take a few milliseconds each, and the time per iteration sits a
little above two nanoseconds and does not grow with n, which is
what linear cost looks like. The wall clock measured separately for four
million iterations agrees with the processor time, since the loop neither reads
nor writes anything outside memory. The three sum ratios are all 2.8284, the
value of 2**1.5 to four decimals, so the totals scale exactly as
the mathematics predicts.
$ gfortran d7.f90 -o d7
$ ./d7
n cpu s ns/item sum
1000000 0.0023 2.31 6.666672E+08
2000000 0.0049 2.43 1.885619E+09
4000000 0.0095 2.38 5.333334E+09
8000000 0.0179 2.24 1.508495E+10
wall clock for four million: .0090 s
the same sum again: 5.333334E+09
sum ratio, 2000000 over half: 2.8284
sum ratio, 4000000 over half: 2.8284
sum ratio, 8000000 over half: 2.8284
Next steps
The strings article explains the slicing and
concatenation that turn the date and time strings into an ISO 8601 timestamp,
and the internal files article covers the
write to a character variable used for the UTC clock. The
modules article describes the separate
compilation and the .mod file that dates.f90
produces, the intrinsic numeric
functions article covers mod, merge and
sqrt, and the floating-point
article explains why a measured time should be printed with a fixed number of
decimals rather than compared for equality.
Source
Fortran 2018 standard (J3/18-007r1)
GNU Fortran manual: DATE_AND_TIME
GNU Fortran manual: SYSTEM_CLOCK
In this article we have read the calendar clock with date_and_time,
formatted an ISO 8601 timestamp, decoded the eight-element array and its time
zone offset, measured intervals with system_clock at nanosecond
resolution and compared them with cpu_time, tested the leap-year
rule, converted dates to Julian day numbers and back, found the day of the week
for five known dates, added and subtracted days across a year boundary, and
timed a loop at four sizes to see linear cost and the n**1.5
growth of a sum of roots. The previous article,
Fortran random numbers, uses timing and
seeding together in simulations.
Author
List all Fortran tutorials.