sourcelibs/Dates/src/Dates.xtl
1⍝# Dates: dates -- day numbers from calendar dates and back, weekdays,
2⍝# leap years, month lengths, ISO text, month calendars.
3⍝# Import it with an alias of your choice: "d:" u_se< "Dates".
4⍝# Put libs/Dates/src on XETAL_PATH ("just path"); the reference is libs/Dates/docs.
5⍝# Names with l: are exported; those under h: are private to this file.
6⍝#
7⍝# A date is three numbers, year month day (2026 10 3), or a 3-row
8⍝# matrix of them, one date per column; a day number counts days from
9⍝# 1970-01-01 (day 0), so dates subtract and add as numbers. The
10⍝# calendar is the proleptic Gregorian one, for any year.
11
12ᵗ⁼u̲se< "Strings"
13
14⍝## Day numbers
15
16⍝# d_ays ymd: the day number of each date (Howard Hinnant's
17⍝# days_from_civil, whole-array).
18ˡd̲ays ← { ymd →
19 m ← f̲irst 1 d̲rop ymd
20 d ← f̲irst 2 d̲rop ymd
21 y ← (f̲irst ymd) − m ≤ 2 ⍝ March-based years
22 era ← y d̲iv 400
23 yoe ← y − era × 400
24 doy ← (d − 1) + (2 + 153 × m + 9 − 12 × m > 2) d̲iv 5
25 doe ← (yoe × 365) + (yoe d̲iv 4) − (yoe d̲iv 100) − doy ⍝ read right to left: + doy
26 (era × 146097) + doe − 719468
27}
28
29⍝# c_ivil n: the date (year month day) of each day number: three
30⍝# numbers for one, a 3-row matrix for several (civil_from_days).
31ˡc̲ivil ← { n →
32 z ← n + 719468
33 era ← z d̲iv 146097
34 doe ← z − era × 146097
35 yoe ← (doe − (doe d̲iv 1460) − (doe d̲iv 36524) − doe d̲iv 146096) d̲iv 365
36 doy ← doe − (365 × yoe) + (yoe d̲iv 4) − yoe d̲iv 100
37 mp ← (2 + 5 × doy) d̲iv 153
38 d ← 1 + doy − (2 + 153 × mp) d̲iv 5
39 m ← mp + 3 − 12 × mp ≥ 10
40 y ← (yoe + era × 400) + m ≤ 2
41 (3 c̲at s̲hape n) r̲eshape y c̲at m c̲at d
42}
43
44⍝# t_oday @: today's day number, from the clock ([]TS: year, month,
45⍝# day, ... of the local time stamp). Read each time it is called.
46ˡt̲oday ← { @ → ˡd̲ays 3 t̲ake ⎕TS }
47
48⍝## Calendar facts
49
50⍝# w_eekday n: the day of the week of each day number, 1 Monday to 7
51⍝# Sunday (ISO 8601; day 0, 1970-01-01, was a Thursday).
52ˡw̲eekday ← { n → 1 + (n + 3) m̲od 7 }
53
54⍝# l_eap? y: whether each year is a leap year.
55ˡl̲eap? ← { y → (0 = y m̲od 400) ∨ (0 = y m̲od 4) ∧ 0 ≠ y m̲od 100 }
56
57⍝# y d_aysIn m: the number of days in month m of year y.
58ˡd̲aysIn ← { y m →
59 '∨ r̲/ r̲avel (m < 1) ∨ m > 12 ? @ p̲anic< "months run 1 to 12, not {m}"
60 (m s̲elect 31 28 31 30 31 30 31 31 30 31 30 31) + (m = 2) ∧ ˡl̲eap? y
61}
62
63⍝## Text
64
65⍝# A number of one or two digits as two, 03.
66ʰt̲wo ← { x → " " "0" ᵗr̲eplace 2 ᵗp̲adLeft f̲ormat x }
67
68⍝# i_so n: each day number as ISO text, 2026-10-03 (a list for several).
69ˡi̲so ← { n →
70 c ← ˡc̲ivil r̲avel n
71 '{ i →
72 v ← i s̲elect₂ c
73 (f̲ormat f̲irst v) c̲at "-" c̲at (ʰt̲wo f̲irst 1 d̲rop v) c̲at "-" c̲at ʰt̲wo f̲irst 2 d̲rop v
74 } m̲ap r̲ange t̲ally r̲avel n
75}
76
77⍝# y c_alendar m: month m of year y as a 6 by 7 matrix of days, Monday
78⍝# first, 0 where a cell has no day.
79ˡc̲alendar ← { y m →
80 first ← ˡw̲eekday ˡd̲ays y c̲at m c̲at 1
81 n ← y ˡd̲aysIn m
82 cells ← (o̲ffsets 42) − first − 2
83 6 7 r̲eshape cells × (cells ≥ 1) ∧ cells ≤ n
84}
85
86⍝# y m_onth m: the month as text, a header and a line per week.
87ˡm̲onth ← { y m →
88 c ← y ˡc̲alendar m
89 weeks ← '{ i → " " ᵗj̲oin '{ d → d = 0 ? " "◆ 2 ᵗp̲adLeft f̲ormat d } m̲ap i s̲elect c } m̲ap r̲ange 6
90 ᵗm̲ix (e̲nclose "Mo Tu We Th Fr Sa Su") c̲at weeks
91}