ZetCode

Algol 68 date and time

last modified November 14, 2026

Algol 68 Genie does not turn a date into text or parse one back. It provides the current date and time as eight integers, and it measures processor time as a real. Calendar work such as finding the difference between two dates or adding a hundred days is written with ordinary integer arithmetic, which is the subject of this article.

The programs read the clock, read the local time, format a date, convert a calendar date to a day number, and turn a day number back into a date. The transcripts that show the current date were made on the day the article was written, so a later run prints a different date.

Processor time

clock returns the processor time used so far as a REAL number of seconds, and seconds returns the same kind of reading. Neither is a wall clock: a program that sleeps for a second uses almost no processor time. Two readings taken around a loop therefore measure the work of the loop and not the time a person waited.

cputime.a68
BEGIN
  REAL start = clock;
  INT total := 0;
  FOR i TO 1000000 DO total +:= i MOD 7 OD;
  REAL finish = clock;
  REAL t0 = clock;
  sleep(1);
  REAL t1 = clock;
  print(("total ", whole(total, 0), newline));
  print(("the loop used more processor time than sleep: ",
         (finish - start) > (t1 - t0), newline))
END

The four declarations hold snapshots of the clock, and the difference between the first pair is compared with the difference around sleep. The exact numbers depend on the machine, so the program prints only the result of the comparison, which is stable. The sum itself is deterministic and shows that the loop really ran.

$ a68g cputime.a68
total 2999998
the loop used more processor time than sleep: T

The current date and time

localtime returns a row of eight integers: the year, the month from 1 to 12, the day of the month, the hour, the minute, the second, the day of the week with 1 for Sunday and 7 for Saturday, and a flag that is 1 while daylight saving time is in effect. The row is a value, so it can be subscripted like any other row.

now.a68
BEGIN
  []INT t = localtime;
  print(("year     ", whole(t[1], 0), newline));
  print(("month    ", whole(t[2], 0), newline));
  print(("day      ", whole(t[3], 0), newline));
  print(("hour     ", whole(t[4], 0), newline));
  print(("minute   ", whole(t[5], 0), newline));
  print(("second   ", whole(t[6], 0), newline));
  print(("weekday  ", whole(t[7], 0), newline));
  print(("dst      ", whole(t[8], 0), newline))
END

The bounds of the row are 1 and 8, so a loop over it would visit every field in the order above. The weekday 7 belongs to Saturday, and the last field is 0 outside the summer time period. Reading localtime once into the row keeps all eight fields from the same instant; calling it again for each print could cross a second boundary and mix two readings.

$ a68g now.a68
year     2026
month    11
day      14
hour     8
minute   56
second   18
weekday  7
dst      0

Formatting a date

A date is formatted by padding the month and the day to two digits and by choosing the month name from a row. The procedure two returns a string of exactly two characters, which keeps the fields of the ISO form and the fields of a time of day aligned.

format.a68
PROC two = (INT n) STRING:
  IF n < 10 THEN "0" + whole(n, 0) ELSE whole(n, 0) FI;

[]STRING months = ("January", "February", "March", "April", "May", "June",
                   "July", "August", "September", "October", "November",
                   "December");

BEGIN
  []INT t = localtime;
  print((whole(t[1], 0), "-", two(t[2]), "-", two(t[3]), newline));
  print((months[t[2]], " ", whole(t[3], 0), ", ", whole(t[1], 0), newline));
  print((two(t[4]), ":", two(t[5]), ":", two(t[6]), newline))
END

The row months is indexed directly by the month field, because January is at subscript 1 in both. The year is printed without padding, the day of the month in the long form has no leading zero, and the time uses the same two-digit rule as the ISO date. The names of different lengths are all widened to STRING when the row is built.

$ a68g format.a68
2026-11-14
November 14, 2026
08:56:18

Converting a duration

A number of seconds becomes hours, minutes, and seconds with two remainders: the whole hours are the quotient by 3600, and the minutes are the quotient of what is left by 60. The last remainder is the number of seconds, because a minute has 60 of them.

duration.a68
PROC two = (INT n) STRING:
  IF n < 10 THEN "0" + whole(n, 0) ELSE whole(n, 0) FI;

PROC show duration = (INT total) VOID:
(
  INT h = total OVER 3600;
  INT m = (total MOD 3600) OVER 60;
  INT s = total MOD 60;
  print((two(h), ":", two(m), ":", two(s), newline))
);

BEGIN
  show duration(0);
  show duration(59);
  show duration(3661);
  show duration(86399)
END

The parentheses around total MOD 3600 make the grouping explicit; OVER and MOD have the same priority, so the expression is read left to right. The procedure prints the fields with two digits, so a duration of zero is 00:00:00 and the last second of a day is 23:59:59. Durations longer than a day keep counting hours past 23.

$ a68g duration.a68
00:00:00
00:00:59
01:01:01
23:59:59

Day numbers and differences

Calendar arithmetic becomes simple when a date is converted to the number of days since 1 January 1970. The conversion counts whole 400-year eras, the years inside the era, and the days inside the year, with the leap day placed at the end of the shifted year. The result for 1970-01-01 is 0.

days.a68
PROC days from civil = (INT y, m, d) INT:
(
  INT yy := y;
  IF m <= 2 THEN yy -:= 1 FI;
  INT era = yy OVER 400;
  INT yoe = yy - era * 400;
  INT mp := m - 3;
  IF m <= 2 THEN mp := m + 9 FI;
  INT doy = (153 * mp + 2) OVER 5 + d - 1;
  INT doe = yoe * 365 + yoe OVER 4 - yoe OVER 100 + doy;
  era * 146097 + doe - 719468
);

[]STRING weekdays = ("Sunday", "Monday", "Tuesday", "Wednesday",
                     "Thursday", "Friday", "Saturday");

BEGIN
  INT a = days from civil(2000, 1, 1);
  INT b = days from civil(2024, 2, 29);
  print((whole(b - a, 0), " days between the two dates", newline));
  print(("2000-01-01 was a ", weekdays[(a + 4) MOD 7 + 1], newline));
  print(("2024-02-29 was a ", weekdays[(b + 4) MOD 7 + 1], newline))
END

The two IF statements shift January and February into the previous year, so the leap day is the last day of the shifted year and the month arithmetic is uniform. The constant 719468 moves the epoch from 1 March 0000 to 1 January 1970. The difference of two day numbers is the number of days between the dates, and the weekday follows from the fact that day 0, 1 January 1970, was a Thursday: adding 4 and taking the remainder modulo 7 gives an index into a row that starts with Sunday.

$ a68g days.a68
8825 days between the two dates
2000-01-01 was a Saturday
2024-02-29 was a Thursday

Adding and subtracting days

The reverse conversion splits the shifted day number into eras, years, and days again. It is the usual companion of days from civil, and the two together let a program add a number of days to a date and print the result.

adddays.a68
MODE DATE = STRUCT(INT year, month, day);

PROC civil from days = (INT days) DATE:
(
  INT z := days + 719468;
  INT era = z OVER 146097;
  INT doe = z - era * 146097;
  INT yoe = (doe - doe OVER 1460 + doe OVER 36524 - doe OVER 146096) OVER 365;
  INT y := yoe + era * 400;
  INT doy = doe - (365 * yoe + yoe OVER 4 - yoe OVER 100);
  INT mp = (5 * doy + 2) OVER 153;
  INT d = doy - (153 * mp + 2) OVER 5 + 1;
  INT m := mp + 3;
  IF mp >= 10 THEN m := mp - 9 FI;
  IF m <= 2 THEN y +:= 1 FI;
  DATE(y, m, d)
);

PROC days from civil = (INT y, m, d) INT:
(
  INT yy := y;
  IF m <= 2 THEN yy -:= 1 FI;
  INT era = yy OVER 400;
  INT yoe = yy - era * 400;
  INT mp := m - 3;
  IF m <= 2 THEN mp := m + 9 FI;
  INT doy = (153 * mp + 2) OVER 5 + d - 1;
  INT doe = yoe * 365 + yoe OVER 4 - yoe OVER 100 + doy;
  era * 146097 + doe - 719468
);

PROC two = (INT n) STRING:
  IF n < 10 THEN "0" + whole(n, 0) ELSE whole(n, 0) FI;

PROC show = (DATE dt) VOID:
  print((whole(year OF dt, 0), "-", two(month OF dt), "-",
         two(day OF dt), newline));

BEGIN
  INT today = days from civil(2026, 11, 14);
  show(civil from days(today + 100));
  show(civil from days(today - 1000));
  print((whole(days from civil(2024, 2, 29) - days from civil(2000, 1, 1), 0),
         " days between 2000-01-01 and 2024-02-29", newline))
END

The reverse conversion undoes each step of the forward one: the era is the quotient by 146097, the year of the era comes from a division that corrects for the leap years, and the month is found with the same 153-day pattern. The result is a DATE structure, which show prints with the same two-digit padding as before. Adding 100 days to 14 November 2026 gives 22 February 2027, and subtracting 1000 days gives 18 February 2024, which crosses the leap day of 2024.

$ a68g adddays.a68
2027-02-22
2024-02-18
8825 days between 2000-01-01 and 2024-02-29

Next steps

The environment and processes article uses clock to time a command, and the number formatting article explains the widths used by whole and fixed. The random numbers article uses the same kind of integer arithmetic in its simulations.

Source

Revised Report on the Algorithmic Language Algol 68

The Algol 68 Genie project

In this article we have measured processor time, read the local date and time, formatted a date, converted dates to day numbers, computed differences and weekdays, and added and subtracted days.

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 Algol 68 tutorials.