Project P106

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.

2 modules · 2 recorded sessionstext-menu UI in the terminalupdated 2026-10-16

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 form
  • readings.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]

Built on these corpus cases

ΣEML 2026

Efficient New Language — a high-density semantic overlay for humans and AI agents.

This site’s demos run on the real EML toolchain, in your browser.

© 2026 EveMissLab (一言諾科技有限公司) / Neo.K. All rights reserved.