ZetCode

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.

d1.f90
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.

d2.f90
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.

d3.f90
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.

d4.f90
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.

dates.f90
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.

d5.f90
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.

d6.f90
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.

d7.f90
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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Fortran tutorials.