Date reader
A date in three parts read as day/month/year, month/day/year and year/month/day, each reading kept or refused with its reason and the real ones set apart in days; how many days of a year read as another date the other way; and a date written so it cannot be misread.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
A date written in three parts can be read as day/month/year, month/day/year or year/month/day. This program tries all three and shows which readings give a real date and how far apart they fall. It also counts how many days of a year are ambiguous, and writes a date so that it cannot be misread.
main.eml- the menu, the readings table, the yearly count and the unambiguous formreadings.eml- splitting the fields, month names, years, each reading, day numbers, weekdays and the yearly count
How each part works:
- Fields are separated by /, -, ., commas or spaces. A month can be a number or a name, or its first three letters or more.
- A one- or two-digit year follows strptime's %y: 00 to 68 are 2000 to 2068, and 69 to 99 are 1969 to 1999. A four-digit year runs from 1000 to 9999.
- Each reading is kept or refused with its reason: no month 13, no day 31 in April, no 29 February in 2027, or a month name where the day or the year goes.
- As in the corpus case
date-parse-ambiguity, two readings can both be real dates: 03/04/2026 is 3 April or 4 March, 30 days apart, and 03/04/05 has three readings, up to 729 days apart. Nothing in the string says which was meant. - Day numbers count from 1970-01-01 in the Gregorian calendar (Howard Hinnant's days_from_civil), and give the weekday and the distances.
- The yearly count takes each day written dd/mm. A day above 12 cannot be read as mm/dd at all, so a reader who takes it as mm/dd gets an error. When the day equals the month, the date is the same either way. On the other 132 days the reader gets a different, valid date and no error.
What is checked: menu choices 1 to 4; three fields; each field in its place as described; a year from 1000 to 9999 for the yearly count; the year first, in four digits, for the unambiguous form. An empty answer cancels.
Sessions: sessions/basic.in gives: - 03/04/2026 and 12/01/2026, read two ways; - 03/04/05, read three ways; - 25/12/2026, read one way; - 31/04/2026, read no way; - 2026-04-03, 3 Apr 2026, April 3, 2026, and 05 Apr 03; - 29/02/2027, and 07/07/2026, which reads the same both ways; - the yearly counts for 2026 and 2024; - 2026-04-03 and 1999-12-31 written unmistakably.
sessions/bad-input.in gives: - menu choices 0 and x; - a cancel, one field, four fields; - a month Foo, a month Ma (too short to tell March from May), 32/13/2026, 0/0/0, years of three digits and with a leading zero, and a month name last; - yearly counts with a cancel, 99, abcd and 0999; - the unambiguous form with a cancel, a date not year-first, 30 February and month 13.
Built on the verified corpus case date-parse-ambiguity (the order of day and month is information that is not in the string).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal== Date reader ==
A date in three parts can be read as day/month/year, month/day/year or year/month/day; this tries all three.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 0
Pick a number from 1 to 4.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> x
Pick a number from 1 to 4.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date>
Cancelled.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 2026
Type a date of three parts, such as 03/04/05, 2026-04-03 or 3 Apr 2026.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 1/2/3/4
Type a date of three parts, such as 03/04/05, 2026-04-03 or 3 Apr 2026.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 3 Foo 2026
day/month/year no: Foo is not a month
month/day/year no: Foo is not a day
year/month/day no: Foo is not a month
No reading gives a date.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 3 Ma 2026
day/month/year no: Ma is not a month
month/day/year no: Ma is not a day
year/month/day no: Ma is not a month
No reading gives a date.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 32/13/2026
day/month/year no: there is no month 13
month/day/year no: there is no month 32
year/month/day no: there is no month 13
No reading gives a date.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 0/0/0
day/month/year no: there is no month 0
month/day/year no: there is no month 0
year/month/day no: there is no month 0
No reading gives a date.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 1/1/123
day/month/year no: 123 is not a year (1 or 2 digits, or 4 from 1000)
month/day/year no: 123 is not a year (1 or 2 digits, or 4 from 1000)
year/month/day no: 123 is not a day
No reading gives a date.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 1/1/0999
day/month/year no: 0999 is not a year (1 or 2 digits, or 4 from 1000)
month/day/year no: 0999 is not a year (1 or 2 digits, or 4 from 1000)
year/month/day no: 0999 is not a day
No reading gives a date.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 2026 3 Apr
day/month/year no: 2026 is not a day
month/day/year no: 2026 is not a month
year/month/day no: the month name Apr is not where the month goes
No reading gives a date.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 2
year (1000 to 9999)>
Cancelled.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 2
year (1000 to 9999)> 99
Type a year from 1000 to 9999.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 2
year (1000 to 9999)> abcd
Type a year from 1000 to 9999.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 2
year (1000 to 9999)> 0999
Type a year from 1000 to 9999.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 3
date as year-month-day, such as 2026-04-03>
Cancelled.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 3
date as year-month-day, such as 2026-04-03> 04/03/2026
Type the year first, in four digits, then the month and the day.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 3
date as year-month-day, such as 2026-04-03> 2026-02-30
Not a date: February 2026 has no day 30.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 3
date as year-month-day, such as 2026-04-03> 2026-13-01
Not a date: there is no month 13.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 4
Bye.
What was typed (39 lines)
0
x
1
1
2026
1
1/2/3/4
1
3 Foo 2026
1
3 Ma 2026
1
32/13/2026
1
0/0/0
1
1/1/123
1
1/1/0999
1
2026 3 Apr
2
2
99
2
abcd
2
0999
3
3
04/03/2026
3
2026-02-30
3
2026-13-01
4
basic
interpreter: byte-equal== Date reader ==
A date in three parts can be read as day/month/year, month/day/year or year/month/day; this tries all three.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 03/04/2026
day/month/year 2026-04-03 Friday
month/day/year 2026-03-04 Wednesday
year/month/day no: 2026 is not a day
2 different dates, up to 30 days apart. Every one of them is a well-formed date, so no check of the string can say which was meant.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 12/01/2026
day/month/year 2026-01-12 Monday
month/day/year 2026-12-01 Tuesday
year/month/day no: 2026 is not a day
2 different dates, up to 323 days apart. Every one of them is a well-formed date, so no check of the string can say which was meant.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 03/04/05
day/month/year 2005-04-03 Sunday
month/day/year 2005-03-04 Friday
year/month/day 2003-04-05 Saturday
(a one- or two-digit year: 00 to 68 mean 2000 to 2068, 69 to 99 mean 1969 to 1999)
3 different dates, up to 729 days apart. Every one of them is a well-formed date, so no check of the string can say which was meant.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 25/12/2026
day/month/year 2026-12-25 Friday
month/day/year no: there is no month 25
year/month/day no: 2026 is not a day
One date only: 25 December 2026.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 31/04/2026
day/month/year no: April 2026 has no day 31
month/day/year no: there is no month 31
year/month/day no: 2026 is not a day
No reading gives a date.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 2026-04-03
day/month/year no: 2026 is not a day
month/day/year no: 2026 is not a month
year/month/day 2026-04-03 Friday
One date only: 3 April 2026.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 3 Apr 2026
day/month/year 2026-04-03 Friday
month/day/year no: the month name Apr is not where the month goes
year/month/day no: 2026 is not a day
One date only: 3 April 2026.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> April 3, 2026
day/month/year no: the month name April is not where the month goes
month/day/year 2026-04-03 Friday
year/month/day no: the month name April is not where the month goes
One date only: 3 April 2026.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 05 Apr 03
day/month/year 2003-04-05 Saturday
month/day/year no: the month name Apr is not where the month goes
year/month/day 2005-04-03 Sunday
(a one- or two-digit year: 00 to 68 mean 2000 to 2068, 69 to 99 mean 1969 to 1999)
2 different dates, up to 729 days apart. Every one of them is a well-formed date, so no check of the string can say which was meant.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 29/02/2027
day/month/year no: February 2027 has no day 29
month/day/year no: there is no month 29
year/month/day no: 2027 is not a day
No reading gives a date.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 1
date> 07/07/2026
day/month/year 2026-07-07 Tuesday
month/day/year 2026-07-07 Tuesday
year/month/day no: 2026 is not a day
One date only: 7 July 2026.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 2
year (1000 to 9999)> 2026
Of the 365 days of 2026, written dd/mm:
221 have a day above 12, so mm/dd cannot read them at all;
12 have the day equal to the month (01/01, 02/02 ...), the same date either way;
132 also read as a different, valid date mm/dd.
A reader who takes them as mm/dd gets an error on the 221, the right date on the 12, and on the other 132 a wrong date with no error at all.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 2
year (1000 to 9999)> 2024
Of the 366 days of 2024, written dd/mm:
222 have a day above 12, so mm/dd cannot read them at all;
12 have the day equal to the month (01/01, 02/02 ...), the same date either way;
132 also read as a different, valid date mm/dd.
A reader who takes them as mm/dd gets an error on the 222, the right date on the 12, and on the other 132 a wrong date with no error at all.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 3
date as year-month-day, such as 2026-04-03> 2026-04-03
ISO 8601: 2026-04-03. In words: Friday, 3 April 2026.
A four-digit year first, then the month and the day, or the month as a word, leaves nothing to guess.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 3
date as year-month-day, such as 2026-04-03> 1999-12-31
ISO 8601: 1999-12-31. In words: Friday, 31 December 1999.
A four-digit year first, then the month and the day, or the month as a word, leaves nothing to guess.
1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit
choice> 4
Bye.
What was typed (31 lines)
1
03/04/2026
1
12/01/2026
1
03/04/05
1
25/12/2026
1
31/04/2026
1
2026-04-03
1
3 Apr 2026
1
April 3, 2026
1
05 Apr 03
1
29/02/2027
1
07/07/2026
2
2026
2
2024
3
2026-04-03
3
1999-12-31
4
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P106 date reader: every way a written date can be read, how far apart
# the readings fall, how many days of a year are ambiguous, and the date
# written so that it cannot be misread.
import readings
def plain(s):
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def pad(s, n):
while len(s) < n:
s + " " => s
return s
def grouped(n):
str(n) => s
"" => out
0 => k
for i in [0:len(s) - 1]:
s[len(s) - 1 - i] + out => out
k + 1 => k
if k % 3 == 0 and i < len(s) - 1:
"," + out => out
return out
def read_date():
plain(input("date> ")) => t
if t == "":
"Cancelled." ^0
return
readings.tokens(t) => f
if len(f) != 3:
"Type a date of three parts, such as 03/04/05, 2026-04-03 or 3 Apr 2026." ^0
return
["DMY", "MDY", "YMD"] => orders
["day/month/year", "month/day/year", "year/month/day"] => names
[] => good
False => short_year
for k in [0:2]:
readings.reading(f, orders[k]) => r
if r[0]:
if r[4]:
True => short_year
(" " + pad(names[k], 17) + readings.iso(r[1], r[2], r[3]) + " " + readings.weekday(r[1], r[2], r[3])) ^0
True => dup
for g in good:
if g[0] == r[1] and g[1] == r[2] and g[2] == r[3]:
False => dup
if dup:
good + [[r[1], r[2], r[3]]] => good
else:
(" " + pad(names[k], 17) + "no: " + r[1]) ^0
if short_year:
" (a one- or two-digit year: 00 to 68 mean 2000 to 2068, 69 to 99 mean 1969 to 1999)" ^0
if len(good) == 0:
"No reading gives a date." ^0
elif len(good) == 1:
("One date only: " + readings.words(good[0][0], good[0][1], good[0][2]) + ".") ^0
else:
0 => far
for a in good:
for b in good:
readings.day_number(a[0], a[1], a[2]) - readings.day_number(b[0], b[1], b[2]) => x
if x > far:
x => far
(str(len(good)) + " different dates, up to " + grouped(far) + " days apart. Every one of them is a well-formed date, so no check of the string can say which was meant.") ^0
def ambiguous():
plain(input("year (1000 to 9999)> ")) => t
if t == "":
"Cancelled." ^0
return
if not readings.is_digits(t) or len(t) != 4 or t[0] == "0":
"Type a year from 1000 to 9999." ^0
return
int(t) => y
readings.ambiguous_days(y) => a
a[0] - a[1] - a[2] => one
("Of the " + str(a[0]) + " days of " + t + ", written dd/mm:") ^0
(" " + str(one) + " have a day above 12, so mm/dd cannot read them at all;") ^0
(" " + str(a[2]) + " have the day equal to the month (01/01, 02/02 ...), the same date either way;") ^0
(" " + str(a[1]) + " also read as a different, valid date mm/dd.") ^0
("A reader who takes them as mm/dd gets an error on the " + str(one) + ", the right date on the " + str(a[2]) + ", and on the other " + str(a[1]) + " a wrong date with no error at all.") ^0
def unambiguous():
plain(input("date as year-month-day, such as 2026-04-03> ")) => t
if t == "":
"Cancelled." ^0
return
readings.tokens(t) => f
if len(f) != 3 or len(f[0]) != 4:
"Type the year first, in four digits, then the month and the day." ^0
return
readings.reading(f, "YMD") => r
if not r[0]:
("Not a date: " + r[1] + ".") ^0
return
("ISO 8601: " + readings.iso(r[1], r[2], r[3]) + ". In words: " + readings.weekday(r[1], r[2], r[3]) + ", " + readings.words(r[1], r[2], r[3]) + ".") ^0
"A four-digit year first, then the month and the day, or the month as a word, leaves nothing to guess." ^0
"== Date reader ==" ^0
"A date in three parts can be read as day/month/year, month/day/year or year/month/day; this tries all three." ^0
True => running
while running:
"" ^0
"1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit" ^0
plain(input("choice> ")) => choice
if choice == "1":
read_date()
elif choice == "2":
ambiguous()
elif choice == "3":
unambiguous()
elif choice == "4":
False => running
else:
"Pick a number from 1 to 4." ^0
"Bye." ^0
Python projection (main.py)
import readings
def plain(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def pad(s, n):
while len(s) < n:
s = s + " "
return s
def grouped(n):
s = str(n)
out = ""
k = 0
for i in range(0, len(s)):
out = s[len(s) - 1 - i] + out
k = k + 1
if k % 3 == 0 and i < len(s) - 1:
out = "," + out
return out
def read_date():
t = plain(input("date> "))
if t == "":
print("Cancelled.")
return
f = readings.tokens(t)
if len(f) != 3:
print("Type a date of three parts, such as 03/04/05, 2026-04-03 or 3 Apr 2026.")
return
orders = ["DMY", "MDY", "YMD"]
names = ["day/month/year", "month/day/year", "year/month/day"]
good = []
short_year = False
for k in range(0, 3):
r = readings.reading(f, orders[k])
if r[0]:
if r[4]:
short_year = True
print(" " + pad(names[k], 17) + readings.iso(r[1], r[2], r[3]) + " " + readings.weekday(r[1], r[2], r[3]))
dup = True
for g in good:
if g[0] == r[1] and g[1] == r[2] and g[2] == r[3]:
dup = False
if dup:
good = good + [[r[1], r[2], r[3]]]
else:
print(" " + pad(names[k], 17) + "no: " + r[1])
if short_year:
print(" (a one- or two-digit year: 00 to 68 mean 2000 to 2068, 69 to 99 mean 1969 to 1999)")
if len(good) == 0:
print("No reading gives a date.")
elif len(good) == 1:
print("One date only: " + readings.words(good[0][0], good[0][1], good[0][2]) + ".")
else:
far = 0
for a in good:
for b in good:
x = readings.day_number(a[0], a[1], a[2]) - readings.day_number(b[0], b[1], b[2])
if x > far:
far = x
print(str(len(good)) + " different dates, up to " + grouped(far) + " days apart. Every one of them is a well-formed date, so no check of the string can say which was meant.")
def ambiguous():
t = plain(input("year (1000 to 9999)> "))
if t == "":
print("Cancelled.")
return
if not readings.is_digits(t) or len(t) != 4 or t[0] == "0":
print("Type a year from 1000 to 9999.")
return
y = int(t)
a = readings.ambiguous_days(y)
one = a[0] - a[1] - a[2]
print("Of the " + str(a[0]) + " days of " + t + ", written dd/mm:")
print(" " + str(one) + " have a day above 12, so mm/dd cannot read them at all;")
print(" " + str(a[2]) + " have the day equal to the month (01/01, 02/02 ...), the same date either way;")
print(" " + str(a[1]) + " also read as a different, valid date mm/dd.")
print("A reader who takes them as mm/dd gets an error on the " + str(one) + ", the right date on the " + str(a[2]) + ", and on the other " + str(a[1]) + " a wrong date with no error at all.")
def unambiguous():
t = plain(input("date as year-month-day, such as 2026-04-03> "))
if t == "":
print("Cancelled.")
return
f = readings.tokens(t)
if len(f) != 3 or len(f[0]) != 4:
print("Type the year first, in four digits, then the month and the day.")
return
r = readings.reading(f, "YMD")
if not r[0]:
print("Not a date: " + r[1] + ".")
return
print("ISO 8601: " + readings.iso(r[1], r[2], r[3]) + ". In words: " + readings.weekday(r[1], r[2], r[3]) + ", " + readings.words(r[1], r[2], r[3]) + ".")
print("A four-digit year first, then the month and the day, or the month as a word, leaves nothing to guess.")
print("== Date reader ==")
print("A date in three parts can be read as day/month/year, month/day/year or year/month/day; this tries all three.")
running = True
while running:
print("")
print("1) read a date 2) ambiguous days in a year 3) write a date unmistakably 4) quit")
choice = plain(input("choice> "))
if choice == "1":
read_date()
elif choice == "2":
ambiguous()
elif choice == "3":
unambiguous()
elif choice == "4":
running = False
else:
print("Pick a number from 1 to 4.")
print("Bye.")
readings.eml
eml# P106 date reader - a date written as three fields can mean different
# days, and nothing in the string says which (the corpus case
# date-parse-ambiguity). Every reading of three fields - day/month/year,
# month/day/year, year/month/day - is tried and kept or refused with its
# reason. Two-digit years follow strptime's %y: 00-68 are 2000-2068 and
# 69-99 are 1969-1999. A month can also be written as a name.
def month_names():
return ["january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"]
def lower(s):
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" => up
"abcdefghijklmnopqrstuvwxyz" => low
"" => out
for c in s:
c => x
for i in [0:25]:
if c == up[i]:
low[i] => x
out + x => out
return out
def is_digits(s):
if len(s) == 0:
return False
for c in s:
if not (c >= "0" and c <= "9"):
return False
return True
def month_of(t):
# 1-12 for a month name or its first three letters or more, else 0
lower(t) => s
if len(s) < 3:
return 0
month_names() => names
for i in [0:11]:
if len(s) <= len(names[i]) and names[i][0:len(s)] == s:
return i + 1
return 0
def tokens(text):
# the fields between / - . , and spaces
[] => out
"" => w
for c in text + " ":
if c == "/" or c == "-" or c == "." or c == "," or c == " ":
if w != "":
out + [w] => out
"" => w
else:
w + c => w
return out
def leap(y):
return (y % 4 == 0 and y % 100 != 0) or y % 400 == 0
def month_days(y, m):
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] => d
if m == 2 and leap(y):
return 29
return d[m - 1]
def year_of(t):
# [year] from 1-2 digits (%y) or 4 digits (1000-9999), else []
if not is_digits(t):
return []
if len(t) <= 2:
int(t) => y
if y < 69:
return [2000 + y]
return [1900 + y]
if len(t) == 4 and t[0] != "0":
return [int(t)]
return []
def reading(fields, order):
# order "DMY", "MDY" or "YMD": [True, y, m, d, short year] or
# [False, why]
False => short
-1 => y
-1 => m
-1 => d
for k in [0:2]:
fields[k] => t
order[k] => role
if role == "M":
if is_digits(t) and len(t) <= 2:
int(t) => m
if m < 1 or m > 12:
return [False, "there is no month " + t]
else:
month_of(t) => m
if m == 0:
return [False, t + " is not a month"]
elif month_of(t) > 0 and not is_digits(t):
return [False, "the month name " + t + " is not where the month goes"]
elif role == "D":
if not is_digits(t) or len(t) > 2:
return [False, t + " is not a day"]
int(t) => d
else:
year_of(t) => yy
if len(yy) == 0:
return [False, t + " is not a year (1 or 2 digits, or 4 from 1000)"]
yy[0] => y
(len(t) <= 2) => short
if d < 1 or d > month_days(y, m):
return [False, title(month_names()[m - 1]) + " " + str(y) + " has no day " + str(d)]
return [True, y, m, d, short]
def day_number(y, m, d):
# days from 1970-01-01, proleptic Gregorian (Hinnant's days_from_civil)
y => yy
if m <= 2:
yy - 1 => yy
yy % 400 => yoe
int((yy - yoe) / 400) => era
m + 9 => mp
if m > 2:
m - 3 => mp
int((153 * mp + 2 - (153 * mp + 2) % 5) / 5) + d - 1 => doy
yoe * 365 + int((yoe - yoe % 4) / 4) - int((yoe - yoe % 100) / 100) + doy => doe
return era * 146097 + doe - 719468
def weekday(y, m, d):
["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] => names
return names[(day_number(y, m, d) + 4) % 7]
def iso(y, m, d):
str(y) => ys
while len(ys) < 4:
"0" + ys => ys
str(m) => ms
if m < 10:
"0" + ms => ms
str(d) => ds
if d < 10:
"0" + ds => ds
return ys + "-" + ms + "-" + ds
def title(n):
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" => up
"abcdefghijklmnopqrstuvwxyz" => low
for i in [0:25]:
if n[0] == low[i]:
return up[i] + n[1:len(n)]
return n
def words(y, m, d):
return str(d) + " " + title(month_names()[m - 1]) + " " + str(y)
def ambiguous_days(y):
# [days in the year, days dd/mm reads as another valid date mm/dd,
# days that read the same both ways]
0 => total
0 => other
0 => same
for m in [1:12]:
for d in [1:month_days(y, m)]:
total + 1 => total
if d <= 12:
if d == m:
same + 1 => same
elif m <= month_days(y, d):
other + 1 => other
return [total, other, same]
Python projection (readings.py)
def month_names():
return ["january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"]
def lower(s):
up = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
low = "abcdefghijklmnopqrstuvwxyz"
out = ""
for c in s:
x = c
for i in range(0, 26):
if c == up[i]:
x = low[i]
out = out + x
return out
def is_digits(s):
if len(s) == 0:
return False
for c in s:
if not (c >= "0" and c <= "9"):
return False
return True
def month_of(t):
s = lower(t)
if len(s) < 3:
return 0
names = month_names()
for i in range(0, 12):
if len(s) <= len(names[i]) and names[i][0:len(s)] == s:
return i + 1
return 0
def tokens(text):
out = []
w = ""
for c in text + " ":
if c == "/" or c == "-" or c == "." or c == "," or c == " ":
if w != "":
out = out + [w]
w = ""
else:
w = w + c
return out
def leap(y):
return y % 4 == 0 and y % 100 != 0 or y % 400 == 0
def month_days(y, m):
d = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if m == 2 and leap(y):
return 29
return d[m - 1]
def year_of(t):
if not is_digits(t):
return []
if len(t) <= 2:
y = int(t)
if y < 69:
return [2000 + y]
return [1900 + y]
if len(t) == 4 and t[0] != "0":
return [int(t)]
return []
def reading(fields, order):
short = False
y = -1
m = -1
d = -1
for k in range(0, 3):
t = fields[k]
role = order[k]
if role == "M":
if is_digits(t) and len(t) <= 2:
m = int(t)
if m < 1 or m > 12:
return [False, "there is no month " + t]
else:
m = month_of(t)
if m == 0:
return [False, t + " is not a month"]
elif month_of(t) > 0 and not is_digits(t):
return [False, "the month name " + t + " is not where the month goes"]
elif role == "D":
if not is_digits(t) or len(t) > 2:
return [False, t + " is not a day"]
d = int(t)
else:
yy = year_of(t)
if len(yy) == 0:
return [False, t + " is not a year (1 or 2 digits, or 4 from 1000)"]
y = yy[0]
short = len(t) <= 2
if d < 1 or d > month_days(y, m):
return [False, title(month_names()[m - 1]) + " " + str(y) + " has no day " + str(d)]
return [True, y, m, d, short]
def day_number(y, m, d):
yy = y
if m <= 2:
yy = yy - 1
yoe = yy % 400
era = int((yy - yoe) / 400)
mp = m + 9
if m > 2:
mp = m - 3
doy = int((153 * mp + 2 - (153 * mp + 2) % 5) / 5) + d - 1
doe = yoe * 365 + int((yoe - yoe % 4) / 4) - int((yoe - yoe % 100) / 100) + doy
return era * 146097 + doe - 719468
def weekday(y, m, d):
names = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
return names[(day_number(y, m, d) + 4) % 7]
def iso(y, m, d):
ys = str(y)
while len(ys) < 4:
ys = "0" + ys
ms = str(m)
if m < 10:
ms = "0" + ms
ds = str(d)
if d < 10:
ds = "0" + ds
return ys + "-" + ms + "-" + ds
def title(n):
up = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
low = "abcdefghijklmnopqrstuvwxyz"
for i in range(0, 26):
if n[0] == low[i]:
return up[i] + n[1:len(n)]
return n
def words(y, m, d):
return str(d) + " " + title(month_names()[m - 1]) + " " + str(y)
def ambiguous_days(y):
total = 0
other = 0
same = 0
for m in range(1, 13):
for d in range(1, month_days(y, m)+1):
total = total + 1
if d <= 12:
if d == m:
same = same + 1
elif m <= month_days(y, d):
other = other + 1
return [total, other, same]