RosettaCodeData/Task/Date-manipulation/ALGOL-68/date-manipulation.alg
2025-08-11 18:05:26 -07:00

237 lines
8 KiB
Text

# some char/string utilities: #
OP UC = (CHAR ch) CHAR:
IF ch >= "a" AND ch <= "z" THEN REPR (ABS ch - 32) ELSE ch FI;
OP UC = (STRING s) STRING: (
[UPB s]CHAR res;
FOR i TO UPB s DO res[i] := UC s[i] OD;
res);
OP LC = (CHAR ch) CHAR: IF ch >= "A" AND ch <= "Z" THEN REPR (ABS ch + 32) ELSE ch FI;
OP LC = (STRING s) STRING: (
[UPB s]CHAR res;
FOR i TO UPB s DO res[i] := LC s[i] OD;
res);
OP INDEXOF = ([]STRING ss, STRING s) INT: (
INT idx := LWB ss - 1;
FOR i FROM LWB ss TO UPB ss DO
IF s = ss[i] THEN idx := i; break FI
OD;
break:
idx);
PRIO INDEXOF = 7;
# date manipulation utilities: #
MODE DATE = STRUCT ([6]INT d, STRING tz);
MODE TZOFFSET = STRUCT (STRING tz, [3]INT offset);
# add freely #
[]TZOFFSET tz offsets = (("EST", (-5, 0, 0)), ("", (0,0,0)), ("UTC", (0,0,0)), ("NPT", (5, 45, 0)));
OP INDEXOF = ([]TZOFFSET tzos, STRING s) INT: (
INT idx := LWB tzos - 1;
FOR i FROM LWB tzos TO UPB tzos DO
IF s = tz OF tzos[i] THEN idx := i; break FI
OD;
break:
idx);
PROC is leap year = (INT year) BOOL:
year %* 400 = 0 OR year %* 4 = 0 AND year %* 100 ~= 0;
PROC days in month = (INT month, year) INT:
CASE month IN 31, (is leap year(year) | 29 | 28), 31, 30, 31, 30,
31, 31, 30, 31, 30, 31
ESAC;
# makes sure all numbers are in the right ranges. f.i., to get the last day of a month,
make a date with day 0 of the next month and call this routine #
PROC normalize date = (REF DATE date) VOID:
BEGIN
PROC fix = (REF INT unit, INT limit, REF INT next) VOID:
IF unit < 0 OR unit >= limit THEN
INT adj = (unit < 0 | 1 | 0);
next +:= unit % limit - adj; unit %*:= limit
FI;
INT year = 1, month = 2, day = 3, hour = 4, minute = 5, second = 6;
REF []INT d := (d OF date);
fix(d[second], 60, d[minute]); fix(d[minute], 60, d[hour]);
fix(d[hour], 24, d[day]);
IF d[month] < 1 OR d[month] > 12 THEN
INT adj = (d[month] < 1 | 1 | 0);
d[year] +:= (d[month]-1) % 12 - adj; d[month] := (d[month]-1) %* 12 + 1
FI;
WHILE d[day] > days in month(d[month], d[year]) DO
d[day] -:= days in month(d[month], d[year]);
d[month] +:= 1;
IF d[month] > 12 THEN d[year] +:= 1; d[month] := 1 FI
OD;
WHILE d[day] < 1 DO
IF d[month] = 1 THEN d[month] := 12; d[year] -:= 1 ELSE d[month] -:= 1 FI;
d[day] +:= days in month(d[month], d[year])
OD
END;
# interval: y[, m[, d[, h[, M[, s]]]]] #
PROC date add = (DATE d, []INT interval) DATE: (
DATE result := d;
FOR i FROM LWB interval TO UPB interval DO
(d OF result)[i - LWB interval + 1] +:= interval[i]
OD;
normalize date(result);
result);
# interval: [[[[[y,] m,] d,] h,] M,] s #
PROC time add = (DATE d, []INT interval) DATE: (
DATE result := d;
FOR i FROM LWB interval TO UPB interval DO
(d OF result)[i - UPB interval + 6] +:= interval[i]
OD;
normalize date(result);
result);
# this datetime in a different time zone #
PROC in tz = (DATE d, STRING new tz) DATE:
IF
INT idx current = tz offsets INDEXOF tz OF d;
INT idx new = tz offsets INDEXOF new tz;
idx current < 1 OR idx new < 1
THEN
d
ELSE
[3]INT current offset := offset OF tz offsets[idx current];
[] INT new offset = offset OF tz offsets[idx new];
FOR i TO UPB current offset DO
current offset[i] := -current offset[i]
OD;
DATE result := time add(time add(d, current offset), new offset);
tz OF result := tz OF tz offsets[idx new];
result
FI;
# canonical representation of a date (SQL-style with optional time zone) #
OP REPR = (DATE d) STRING: (
STRING buf; FILE f; associate(f, buf);
putf(f, ($4d,"-"2d,"-"2d," "2d,":"2d,":"2d$, d OF d));
IF tz OF d ~= "" THEN put(f, (" ", tz OF d)) FI;
close(f);
buf);
[]STRING month names = ("January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December");
# default for h:m:s is 0:0:0. there's no default for year, month or day #
PROC parse date = (STRING s, STRING fmt, REF DATE result) BOOL:
BEGIN
INT idx;
PROC at end = BOOL: idx > UPB s;
PROC skip spaces = VOID:
IF at end THEN error
ELIF s[idx] ~= " " THEN error
ELSE WHILE IF at end THEN FALSE ELSE s[idx] = " " FI DO idx +:= 1 OD
FI;
PROC read number = (INT min digits, max digits, min value, max value) INT:
BEGIN
IF at end THEN error
ELSE
INT md = IF prefix = "-" THEN min digits ELSE max digits FI;
INT n := 0, digits := 0;
WHILE IF at end THEN FALSE ELSE s[idx] >= "0" AND s[idx] <= "9" FI DO
n := n * 10 + (ABS s[idx] - ABS "0");
digits +:= 1;
idx +:= 1
OD;
IF n < min value OR n > max value OR digits < md OR digits > max digits THEN error FI;
n
FI
END;
PROC read alpha = STRING:
IF at end THEN error
ELSE
INT start = idx;
WHILE IF at end THEN FALSE ELSE UC s[idx] >= "A" AND UC s[idx] <= "Z" FI DO idx +:= 1 OD;
IF start = idx THEN error ELSE s[start : idx-1] FI
FI;
PROC read month name = INT:
BEGIN
STRING m := read alpha;
m[1] := UC m[1]; m[2:] := LC m[2:];
INT res = month names INDEXOF m;
IF res < 1 THEN error FI;
res
END;
PROC match = (CHAR ch) VOID:
IF at end THEN error
ELIF s[idx] ~= ch THEN error
ELSE idx +:= 1
FI;
PROC no prefix = VOID: IF prefix ~= " " THEN error FI;
INT year = 1, month = 2, day = 3, hour = 4, minute = 5, second = 6;
result := ((-1, -1, -1, 0, 0, 0), "");
BOOL percent := FALSE, skip := FALSE;
CHAR prefix := " ", ampm := " ";
idx := 1;
FOR i FROM LWB fmt TO UPB fmt DO
CHAR ch = fmt[i];
IF percent THEN
IF ch = "-" THEN no prefix; prefix := "-"
ELSE
IF skip THEN skip spaces; skip := FALSE FI;
IF ch = "%" THEN no prefix; match("%")
ELIF ch = "Y" THEN no prefix; (d OF result)[year] := read number(4, 4, 1, 9999)
ELIF ch = "B" THEN no prefix; (d OF result)[month] := read month name
ELIF ch = "m" THEN (d OF result)[month] := read number(1, 2, 1, 12)
ELIF ch = "d" THEN (d OF result)[day] := read number(1, 2, 1, 31)
ELIF ch = "H" THEN (d OF result)[hour] := read number(1, 2, 0, 23)
ELIF ch = "I" THEN (d OF result)[hour] := -read number(1, 2, 1, 12)
ELIF ch = "M" THEN (d OF result)[minute] := read number(1, 2, 0, 59)
ELIF ch = "S" THEN (d OF result)[second] := read number(1, 2, 0, 59)
ELIF ch = "p" THEN no prefix; STRING ap = read alpha;
IF ap = "am" THEN ampm := "a" ELIF ap = "pm" THEN ampm := "p"
ELSE error FI
ELIF ch = "Z" THEN no prefix; (tz OF result) := read alpha
ELSE error
FI;
prefix := " "; percent := FALSE
FI
ELSE
IF ch = " " THEN skip := TRUE
ELIF ch = "%" THEN percent := TRUE
ELSE
IF skip THEN skip spaces; skip := FALSE FI;
match(ch)
FI
FI
OD;
IF skip OR percent THEN error FI;
IF (d OF result)[hour] < 0 THEN
INT h = -(d OF result)[hour];
(d OF result)[hour] := IF ampm = "p" THEN h %* 12 + 12 ELSE h %* 12 FI
ELIF ampm ~= " " THEN error
FI;
FOR i TO 6 DO IF (d OF result)[i] < 0 THEN error FI OD;
normalize date(result);
TRUE EXIT
error: FALSE
END;
# finally, the task: #
STRING string = "March 7 2009 7:30pm EST";
DATE date;
BOOL ok = parse date(string, "%B %-d %Y %-I:%M%p %Z", date);
print(("The string """, string, """", new line));
IF ok THEN
print((" represents the date ", REPR date, new line));
DATE later = time add(date, (12, 0, 0));
print((" 12 hours later it's ", REPR later, new line));
print(("(the same moment in Nepal: ", REPR in tz(later, "NPT"), ")", new line))
ELSE
print((" doesn't represent any date this program can parse :(", new line))
FI