{
  "id": "P106",
  "slug": "date-reader",
  "key": "P106-date-reader",
  "title": "Date reader",
  "summary": "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.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P106 - Date reader\n\nA date written in three parts can be read as day/month/year,\nmonth/day/year or year/month/day. This program tries all three and shows\nwhich readings give a real date and how far apart they fall. It also\ncounts how many days of a year are ambiguous, and writes a date so that\nit cannot be misread.\n\n- `main.eml` - the menu, the readings table, the yearly count and the\n  unambiguous form\n- `readings.eml` - splitting the fields, month names, years, each\n  reading, day numbers, weekdays and the yearly count\n\nHow each part works:\n\n- Fields are separated by /, -, ., commas or spaces. A month can be a\n  number or a name, or its first three letters or more.\n- A one- or two-digit year follows strptime's %y: 00 to 68 are 2000 to\n  2068, and 69 to 99 are 1969 to 1999. A four-digit year runs from 1000 to\n  9999.\n- Each reading is kept or refused with its reason: no month 13, no day 31\n  in April, no 29 February in 2027, or a month name where the day or the\n  year goes.\n- As in the corpus case `date-parse-ambiguity`, two readings can both be\n  real dates: 03/04/2026 is 3 April or 4 March, 30 days apart, and\n  03/04/05 has three readings, up to 729 days apart. Nothing in the\n  string says which was meant.\n- Day numbers count from 1970-01-01 in the Gregorian calendar (Howard\n  Hinnant's days_from_civil), and give the weekday and the distances.\n- The yearly count takes each day written dd/mm. A day above 12 cannot be\n  read as mm/dd at all, so a reader who takes it as mm/dd gets an error.\n  When the day equals the month, the date is the same either way. On the\n  other 132 days the reader gets a different, valid date and no error.\n\nWhat is checked: menu choices 1 to 4; three fields; each field in its\nplace as described; a year from 1000 to 9999 for the yearly count; the\nyear first, in four digits, for the unambiguous form. An empty answer\ncancels.\n\nSessions: `sessions/basic.in` gives:\n- 03/04/2026 and 12/01/2026, read two ways;\n- 03/04/05, read three ways;\n- 25/12/2026, read one way;\n- 31/04/2026, read no way;\n- 2026-04-03, 3 Apr 2026, April 3, 2026, and 05 Apr 03;\n- 29/02/2027, and 07/07/2026, which reads the same both ways;\n- the yearly counts for 2026 and 2024;\n- 2026-04-03 and 1999-12-31 written unmistakably.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- a cancel, one field, four fields;\n- a month Foo, a month Ma (too short to tell March from May), 32/13/2026,\n  0/0/0, years of three digits and with a leading zero, and a month name\n  last;\n- yearly counts with a cancel, 99, abcd and 0999;\n- the unambiguous form with a cancel, a date not year-first, 30 February\n  and month 13.\n\nBuilt on the verified corpus case `date-parse-ambiguity` (the order of\nday and month is information that is not in the string).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P106 date reader: every way a written date can be read, how far apart\n# the readings fall, how many days of a year are ambiguous, and the date\n# written so that it cannot be misread.\nimport readings\n\ndef plain(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef pad(s, n):\n    while len(s) < n:\n        s + \" \" => s\n    return s\n\ndef grouped(n):\n    str(n) => s\n    \"\" => out\n    0 => k\n    for i in [0:len(s) - 1]:\n        s[len(s) - 1 - i] + out => out\n        k + 1 => k\n        if k % 3 == 0 and i < len(s) - 1:\n            \",\" + out => out\n    return out\n\ndef read_date():\n    plain(input(\"date> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return\n    readings.tokens(t) => f\n    if len(f) != 3:\n        \"Type a date of three parts, such as 03/04/05, 2026-04-03 or 3 Apr 2026.\" ^0\n        return\n    [\"DMY\", \"MDY\", \"YMD\"] => orders\n    [\"day/month/year\", \"month/day/year\", \"year/month/day\"] => names\n    [] => good\n    False => short_year\n    for k in [0:2]:\n        readings.reading(f, orders[k]) => r\n        if r[0]:\n            if r[4]:\n                True => short_year\n            (\"  \" + pad(names[k], 17) + readings.iso(r[1], r[2], r[3]) + \"  \" + readings.weekday(r[1], r[2], r[3])) ^0\n            True => dup\n            for g in good:\n                if g[0] == r[1] and g[1] == r[2] and g[2] == r[3]:\n                    False => dup\n            if dup:\n                good + [[r[1], r[2], r[3]]] => good\n        else:\n            (\"  \" + pad(names[k], 17) + \"no: \" + r[1]) ^0\n    if short_year:\n        \"  (a one- or two-digit year: 00 to 68 mean 2000 to 2068, 69 to 99 mean 1969 to 1999)\" ^0\n    if len(good) == 0:\n        \"No reading gives a date.\" ^0\n    elif len(good) == 1:\n        (\"One date only: \" + readings.words(good[0][0], good[0][1], good[0][2]) + \".\") ^0\n    else:\n        0 => far\n        for a in good:\n            for b in good:\n                readings.day_number(a[0], a[1], a[2]) - readings.day_number(b[0], b[1], b[2]) => x\n                if x > far:\n                    x => far\n        (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\n\ndef ambiguous():\n    plain(input(\"year (1000 to 9999)> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return\n    if not readings.is_digits(t) or len(t) != 4 or t[0] == \"0\":\n        \"Type a year from 1000 to 9999.\" ^0\n        return\n    int(t) => y\n    readings.ambiguous_days(y) => a\n    a[0] - a[1] - a[2] => one\n    (\"Of the \" + str(a[0]) + \" days of \" + t + \", written dd/mm:\") ^0\n    (\"  \" + str(one) + \" have a day above 12, so mm/dd cannot read them at all;\") ^0\n    (\"  \" + str(a[2]) + \" have the day equal to the month (01/01, 02/02 ...), the same date either way;\") ^0\n    (\"  \" + str(a[1]) + \" also read as a different, valid date mm/dd.\") ^0\n    (\"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\n\ndef unambiguous():\n    plain(input(\"date as year-month-day, such as 2026-04-03> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return\n    readings.tokens(t) => f\n    if len(f) != 3 or len(f[0]) != 4:\n        \"Type the year first, in four digits, then the month and the day.\" ^0\n        return\n    readings.reading(f, \"YMD\") => r\n    if not r[0]:\n        (\"Not a date: \" + r[1] + \".\") ^0\n        return\n    (\"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\n    \"A four-digit year first, then the month and the day, or the month as a word, leaves nothing to guess.\" ^0\n\n\"== Date reader ==\" ^0\n\"A date in three parts can be read as day/month/year, month/day/year or year/month/day; this tries all three.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        read_date()\n    elif choice == \"2\":\n        ambiguous()\n    elif choice == \"3\":\n        unambiguous()\n    elif choice == \"4\":\n        False => running\n    else:\n        \"Pick a number from 1 to 4.\" ^0\n\"Bye.\" ^0\n",
      "python": "import readings\n\ndef plain(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef pad(s, n):\n    while len(s) < n:\n        s = s + \" \"\n    return s\n\ndef grouped(n):\n    s = str(n)\n    out = \"\"\n    k = 0\n    for i in range(0, len(s)):\n        out = s[len(s) - 1 - i] + out\n        k = k + 1\n        if k % 3 == 0 and i < len(s) - 1:\n            out = \",\" + out\n    return out\n\ndef read_date():\n    t = plain(input(\"date> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return\n    f = readings.tokens(t)\n    if len(f) != 3:\n        print(\"Type a date of three parts, such as 03/04/05, 2026-04-03 or 3 Apr 2026.\")\n        return\n    orders = [\"DMY\", \"MDY\", \"YMD\"]\n    names = [\"day/month/year\", \"month/day/year\", \"year/month/day\"]\n    good = []\n    short_year = False\n    for k in range(0, 3):\n        r = readings.reading(f, orders[k])\n        if r[0]:\n            if r[4]:\n                short_year = True\n            print(\"  \" + pad(names[k], 17) + readings.iso(r[1], r[2], r[3]) + \"  \" + readings.weekday(r[1], r[2], r[3]))\n            dup = True\n            for g in good:\n                if g[0] == r[1] and g[1] == r[2] and g[2] == r[3]:\n                    dup = False\n            if dup:\n                good = good + [[r[1], r[2], r[3]]]\n        else:\n            print(\"  \" + pad(names[k], 17) + \"no: \" + r[1])\n    if short_year:\n        print(\"  (a one- or two-digit year: 00 to 68 mean 2000 to 2068, 69 to 99 mean 1969 to 1999)\")\n    if len(good) == 0:\n        print(\"No reading gives a date.\")\n    elif len(good) == 1:\n        print(\"One date only: \" + readings.words(good[0][0], good[0][1], good[0][2]) + \".\")\n    else:\n        far = 0\n        for a in good:\n            for b in good:\n                x = readings.day_number(a[0], a[1], a[2]) - readings.day_number(b[0], b[1], b[2])\n                if x > far:\n                    far = x\n        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.\")\n\ndef ambiguous():\n    t = plain(input(\"year (1000 to 9999)> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return\n    if not readings.is_digits(t) or len(t) != 4 or t[0] == \"0\":\n        print(\"Type a year from 1000 to 9999.\")\n        return\n    y = int(t)\n    a = readings.ambiguous_days(y)\n    one = a[0] - a[1] - a[2]\n    print(\"Of the \" + str(a[0]) + \" days of \" + t + \", written dd/mm:\")\n    print(\"  \" + str(one) + \" have a day above 12, so mm/dd cannot read them at all;\")\n    print(\"  \" + str(a[2]) + \" have the day equal to the month (01/01, 02/02 ...), the same date either way;\")\n    print(\"  \" + str(a[1]) + \" also read as a different, valid date mm/dd.\")\n    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.\")\n\ndef unambiguous():\n    t = plain(input(\"date as year-month-day, such as 2026-04-03> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return\n    f = readings.tokens(t)\n    if len(f) != 3 or len(f[0]) != 4:\n        print(\"Type the year first, in four digits, then the month and the day.\")\n        return\n    r = readings.reading(f, \"YMD\")\n    if not r[0]:\n        print(\"Not a date: \" + r[1] + \".\")\n        return\n    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]) + \".\")\n    print(\"A four-digit year first, then the month and the day, or the month as a word, leaves nothing to guess.\")\n\nprint(\"== Date reader ==\")\nprint(\"A date in three parts can be read as day/month/year, month/day/year or year/month/day; this tries all three.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        read_date()\n    elif choice == \"2\":\n        ambiguous()\n    elif choice == \"3\":\n        unambiguous()\n    elif choice == \"4\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 4.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "readings.eml",
      "eml": "# P106 date reader - a date written as three fields can mean different\n# days, and nothing in the string says which (the corpus case\n# date-parse-ambiguity). Every reading of three fields - day/month/year,\n# month/day/year, year/month/day - is tried and kept or refused with its\n# reason. Two-digit years follow strptime's %y: 00-68 are 2000-2068 and\n# 69-99 are 1969-1999. A month can also be written as a name.\n\ndef month_names():\n    return [\"january\", \"february\", \"march\", \"april\", \"may\", \"june\", \"july\", \"august\", \"september\", \"october\", \"november\", \"december\"]\n\ndef lower(s):\n    \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\" => up\n    \"abcdefghijklmnopqrstuvwxyz\" => low\n    \"\" => out\n    for c in s:\n        c => x\n        for i in [0:25]:\n            if c == up[i]:\n                low[i] => x\n        out + x => out\n    return out\n\ndef is_digits(s):\n    if len(s) == 0:\n        return False\n    for c in s:\n        if not (c >= \"0\" and c <= \"9\"):\n            return False\n    return True\n\ndef month_of(t):\n    # 1-12 for a month name or its first three letters or more, else 0\n    lower(t) => s\n    if len(s) < 3:\n        return 0\n    month_names() => names\n    for i in [0:11]:\n        if len(s) <= len(names[i]) and names[i][0:len(s)] == s:\n            return i + 1\n    return 0\n\ndef tokens(text):\n    # the fields between / - . , and spaces\n    [] => out\n    \"\" => w\n    for c in text + \" \":\n        if c == \"/\" or c == \"-\" or c == \".\" or c == \",\" or c == \" \":\n            if w != \"\":\n                out + [w] => out\n            \"\" => w\n        else:\n            w + c => w\n    return out\n\ndef leap(y):\n    return (y % 4 == 0 and y % 100 != 0) or y % 400 == 0\n\ndef month_days(y, m):\n    [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] => d\n    if m == 2 and leap(y):\n        return 29\n    return d[m - 1]\n\ndef year_of(t):\n    # [year] from 1-2 digits (%y) or 4 digits (1000-9999), else []\n    if not is_digits(t):\n        return []\n    if len(t) <= 2:\n        int(t) => y\n        if y < 69:\n            return [2000 + y]\n        return [1900 + y]\n    if len(t) == 4 and t[0] != \"0\":\n        return [int(t)]\n    return []\n\ndef reading(fields, order):\n    # order \"DMY\", \"MDY\" or \"YMD\": [True, y, m, d, short year] or\n    # [False, why]\n    False => short\n    -1 => y\n    -1 => m\n    -1 => d\n    for k in [0:2]:\n        fields[k] => t\n        order[k] => role\n        if role == \"M\":\n            if is_digits(t) and len(t) <= 2:\n                int(t) => m\n                if m < 1 or m > 12:\n                    return [False, \"there is no month \" + t]\n            else:\n                month_of(t) => m\n                if m == 0:\n                    return [False, t + \" is not a month\"]\n        elif month_of(t) > 0 and not is_digits(t):\n            return [False, \"the month name \" + t + \" is not where the month goes\"]\n        elif role == \"D\":\n            if not is_digits(t) or len(t) > 2:\n                return [False, t + \" is not a day\"]\n            int(t) => d\n        else:\n            year_of(t) => yy\n            if len(yy) == 0:\n                return [False, t + \" is not a year (1 or 2 digits, or 4 from 1000)\"]\n            yy[0] => y\n            (len(t) <= 2) => short\n    if d < 1 or d > month_days(y, m):\n        return [False, title(month_names()[m - 1]) + \" \" + str(y) + \" has no day \" + str(d)]\n    return [True, y, m, d, short]\n\ndef day_number(y, m, d):\n    # days from 1970-01-01, proleptic Gregorian (Hinnant's days_from_civil)\n    y => yy\n    if m <= 2:\n        yy - 1 => yy\n    yy % 400 => yoe\n    int((yy - yoe) / 400) => era\n    m + 9 => mp\n    if m > 2:\n        m - 3 => mp\n    int((153 * mp + 2 - (153 * mp + 2) % 5) / 5) + d - 1 => doy\n    yoe * 365 + int((yoe - yoe % 4) / 4) - int((yoe - yoe % 100) / 100) + doy => doe\n    return era * 146097 + doe - 719468\n\ndef weekday(y, m, d):\n    [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"] => names\n    return names[(day_number(y, m, d) + 4) % 7]\n\ndef iso(y, m, d):\n    str(y) => ys\n    while len(ys) < 4:\n        \"0\" + ys => ys\n    str(m) => ms\n    if m < 10:\n        \"0\" + ms => ms\n    str(d) => ds\n    if d < 10:\n        \"0\" + ds => ds\n    return ys + \"-\" + ms + \"-\" + ds\n\ndef title(n):\n    \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\" => up\n    \"abcdefghijklmnopqrstuvwxyz\" => low\n    for i in [0:25]:\n        if n[0] == low[i]:\n            return up[i] + n[1:len(n)]\n    return n\n\ndef words(y, m, d):\n    return str(d) + \" \" + title(month_names()[m - 1]) + \" \" + str(y)\n\ndef ambiguous_days(y):\n    # [days in the year, days dd/mm reads as another valid date mm/dd,\n    #  days that read the same both ways]\n    0 => total\n    0 => other\n    0 => same\n    for m in [1:12]:\n        for d in [1:month_days(y, m)]:\n            total + 1 => total\n            if d <= 12:\n                if d == m:\n                    same + 1 => same\n                elif m <= month_days(y, d):\n                    other + 1 => other\n    return [total, other, same]\n",
      "python": "def month_names():\n    return [\"january\", \"february\", \"march\", \"april\", \"may\", \"june\", \"july\", \"august\", \"september\", \"october\", \"november\", \"december\"]\n\ndef lower(s):\n    up = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n    low = \"abcdefghijklmnopqrstuvwxyz\"\n    out = \"\"\n    for c in s:\n        x = c\n        for i in range(0, 26):\n            if c == up[i]:\n                x = low[i]\n        out = out + x\n    return out\n\ndef is_digits(s):\n    if len(s) == 0:\n        return False\n    for c in s:\n        if not (c >= \"0\" and c <= \"9\"):\n            return False\n    return True\n\ndef month_of(t):\n    s = lower(t)\n    if len(s) < 3:\n        return 0\n    names = month_names()\n    for i in range(0, 12):\n        if len(s) <= len(names[i]) and names[i][0:len(s)] == s:\n            return i + 1\n    return 0\n\ndef tokens(text):\n    out = []\n    w = \"\"\n    for c in text + \" \":\n        if c == \"/\" or c == \"-\" or c == \".\" or c == \",\" or c == \" \":\n            if w != \"\":\n                out = out + [w]\n            w = \"\"\n        else:\n            w = w + c\n    return out\n\ndef leap(y):\n    return y % 4 == 0 and y % 100 != 0 or y % 400 == 0\n\ndef month_days(y, m):\n    d = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]\n    if m == 2 and leap(y):\n        return 29\n    return d[m - 1]\n\ndef year_of(t):\n    if not is_digits(t):\n        return []\n    if len(t) <= 2:\n        y = int(t)\n        if y < 69:\n            return [2000 + y]\n        return [1900 + y]\n    if len(t) == 4 and t[0] != \"0\":\n        return [int(t)]\n    return []\n\ndef reading(fields, order):\n    short = False\n    y = -1\n    m = -1\n    d = -1\n    for k in range(0, 3):\n        t = fields[k]\n        role = order[k]\n        if role == \"M\":\n            if is_digits(t) and len(t) <= 2:\n                m = int(t)\n                if m < 1 or m > 12:\n                    return [False, \"there is no month \" + t]\n            else:\n                m = month_of(t)\n                if m == 0:\n                    return [False, t + \" is not a month\"]\n        elif month_of(t) > 0 and not is_digits(t):\n            return [False, \"the month name \" + t + \" is not where the month goes\"]\n        elif role == \"D\":\n            if not is_digits(t) or len(t) > 2:\n                return [False, t + \" is not a day\"]\n            d = int(t)\n        else:\n            yy = year_of(t)\n            if len(yy) == 0:\n                return [False, t + \" is not a year (1 or 2 digits, or 4 from 1000)\"]\n            y = yy[0]\n            short = len(t) <= 2\n    if d < 1 or d > month_days(y, m):\n        return [False, title(month_names()[m - 1]) + \" \" + str(y) + \" has no day \" + str(d)]\n    return [True, y, m, d, short]\n\ndef day_number(y, m, d):\n    yy = y\n    if m <= 2:\n        yy = yy - 1\n    yoe = yy % 400\n    era = int((yy - yoe) / 400)\n    mp = m + 9\n    if m > 2:\n        mp = m - 3\n    doy = int((153 * mp + 2 - (153 * mp + 2) % 5) / 5) + d - 1\n    doe = yoe * 365 + int((yoe - yoe % 4) / 4) - int((yoe - yoe % 100) / 100) + doy\n    return era * 146097 + doe - 719468\n\ndef weekday(y, m, d):\n    names = [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"]\n    return names[(day_number(y, m, d) + 4) % 7]\n\ndef iso(y, m, d):\n    ys = str(y)\n    while len(ys) < 4:\n        ys = \"0\" + ys\n    ms = str(m)\n    if m < 10:\n        ms = \"0\" + ms\n    ds = str(d)\n    if d < 10:\n        ds = \"0\" + ds\n    return ys + \"-\" + ms + \"-\" + ds\n\ndef title(n):\n    up = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n    low = \"abcdefghijklmnopqrstuvwxyz\"\n    for i in range(0, 26):\n        if n[0] == low[i]:\n            return up[i] + n[1:len(n)]\n    return n\n\ndef words(y, m, d):\n    return str(d) + \" \" + title(month_names()[m - 1]) + \" \" + str(y)\n\ndef ambiguous_days(y):\n    total = 0\n    other = 0\n    same = 0\n    for m in range(1, 13):\n        for d in range(1, month_days(y, m)+1):\n            total = total + 1\n            if d <= 12:\n                if d == m:\n                    same = same + 1\n                elif m <= month_days(y, d):\n                    other = other + 1\n    return [total, other, same]\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n1\n\n1\n2026\n1\n1/2/3/4\n1\n3 Foo 2026\n1\n3 Ma 2026\n1\n32/13/2026\n1\n0/0/0\n1\n1/1/123\n1\n1/1/0999\n1\n2026 3 Apr\n2\n\n2\n99\n2\nabcd\n2\n0999\n3\n\n3\n04/03/2026\n3\n2026-02-30\n3\n2026-13-01\n4\n",
      "screen": "== Date reader ==\nA date in three parts can be read as day/month/year, month/day/year or year/month/day; this tries all three.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 0\nPick a number from 1 to 4.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> x\nPick a number from 1 to 4.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> \nCancelled.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 2026\nType a date of three parts, such as 03/04/05, 2026-04-03 or 3 Apr 2026.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 1/2/3/4\nType a date of three parts, such as 03/04/05, 2026-04-03 or 3 Apr 2026.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 3 Foo 2026\n  day/month/year   no: Foo is not a month\n  month/day/year   no: Foo is not a day\n  year/month/day   no: Foo is not a month\nNo reading gives a date.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 3 Ma 2026\n  day/month/year   no: Ma is not a month\n  month/day/year   no: Ma is not a day\n  year/month/day   no: Ma is not a month\nNo reading gives a date.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 32/13/2026\n  day/month/year   no: there is no month 13\n  month/day/year   no: there is no month 32\n  year/month/day   no: there is no month 13\nNo reading gives a date.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 0/0/0\n  day/month/year   no: there is no month 0\n  month/day/year   no: there is no month 0\n  year/month/day   no: there is no month 0\nNo reading gives a date.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 1/1/123\n  day/month/year   no: 123 is not a year (1 or 2 digits, or 4 from 1000)\n  month/day/year   no: 123 is not a year (1 or 2 digits, or 4 from 1000)\n  year/month/day   no: 123 is not a day\nNo reading gives a date.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 1/1/0999\n  day/month/year   no: 0999 is not a year (1 or 2 digits, or 4 from 1000)\n  month/day/year   no: 0999 is not a year (1 or 2 digits, or 4 from 1000)\n  year/month/day   no: 0999 is not a day\nNo reading gives a date.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 2026 3 Apr\n  day/month/year   no: 2026 is not a day\n  month/day/year   no: 2026 is not a month\n  year/month/day   no: the month name Apr is not where the month goes\nNo reading gives a date.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 2\nyear (1000 to 9999)> \nCancelled.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 2\nyear (1000 to 9999)> 99\nType a year from 1000 to 9999.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 2\nyear (1000 to 9999)> abcd\nType a year from 1000 to 9999.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 2\nyear (1000 to 9999)> 0999\nType a year from 1000 to 9999.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 3\ndate as year-month-day, such as 2026-04-03> \nCancelled.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 3\ndate as year-month-day, such as 2026-04-03> 04/03/2026\nType the year first, in four digits, then the month and the day.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 3\ndate as year-month-day, such as 2026-04-03> 2026-02-30\nNot a date: February 2026 has no day 30.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 3\ndate as year-month-day, such as 2026-04-03> 2026-13-01\nNot a date: there is no month 13.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 4\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n03/04/2026\n1\n12/01/2026\n1\n03/04/05\n1\n25/12/2026\n1\n31/04/2026\n1\n2026-04-03\n1\n3 Apr 2026\n1\nApril 3, 2026\n1\n05 Apr 03\n1\n29/02/2027\n1\n07/07/2026\n2\n2026\n2\n2024\n3\n2026-04-03\n3\n1999-12-31\n4\n",
      "screen": "== Date reader ==\nA date in three parts can be read as day/month/year, month/day/year or year/month/day; this tries all three.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 03/04/2026\n  day/month/year   2026-04-03  Friday\n  month/day/year   2026-03-04  Wednesday\n  year/month/day   no: 2026 is not a day\n2 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.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 12/01/2026\n  day/month/year   2026-01-12  Monday\n  month/day/year   2026-12-01  Tuesday\n  year/month/day   no: 2026 is not a day\n2 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.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 03/04/05\n  day/month/year   2005-04-03  Sunday\n  month/day/year   2005-03-04  Friday\n  year/month/day   2003-04-05  Saturday\n  (a one- or two-digit year: 00 to 68 mean 2000 to 2068, 69 to 99 mean 1969 to 1999)\n3 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.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 25/12/2026\n  day/month/year   2026-12-25  Friday\n  month/day/year   no: there is no month 25\n  year/month/day   no: 2026 is not a day\nOne date only: 25 December 2026.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 31/04/2026\n  day/month/year   no: April 2026 has no day 31\n  month/day/year   no: there is no month 31\n  year/month/day   no: 2026 is not a day\nNo reading gives a date.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 2026-04-03\n  day/month/year   no: 2026 is not a day\n  month/day/year   no: 2026 is not a month\n  year/month/day   2026-04-03  Friday\nOne date only: 3 April 2026.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 3 Apr 2026\n  day/month/year   2026-04-03  Friday\n  month/day/year   no: the month name Apr is not where the month goes\n  year/month/day   no: 2026 is not a day\nOne date only: 3 April 2026.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> April 3, 2026\n  day/month/year   no: the month name April is not where the month goes\n  month/day/year   2026-04-03  Friday\n  year/month/day   no: the month name April is not where the month goes\nOne date only: 3 April 2026.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 05 Apr 03\n  day/month/year   2003-04-05  Saturday\n  month/day/year   no: the month name Apr is not where the month goes\n  year/month/day   2005-04-03  Sunday\n  (a one- or two-digit year: 00 to 68 mean 2000 to 2068, 69 to 99 mean 1969 to 1999)\n2 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.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 29/02/2027\n  day/month/year   no: February 2027 has no day 29\n  month/day/year   no: there is no month 29\n  year/month/day   no: 2027 is not a day\nNo reading gives a date.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 1\ndate> 07/07/2026\n  day/month/year   2026-07-07  Tuesday\n  month/day/year   2026-07-07  Tuesday\n  year/month/day   no: 2026 is not a day\nOne date only: 7 July 2026.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 2\nyear (1000 to 9999)> 2026\nOf the 365 days of 2026, written dd/mm:\n  221 have a day above 12, so mm/dd cannot read them at all;\n  12 have the day equal to the month (01/01, 02/02 ...), the same date either way;\n  132 also read as a different, valid date mm/dd.\nA 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.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 2\nyear (1000 to 9999)> 2024\nOf the 366 days of 2024, written dd/mm:\n  222 have a day above 12, so mm/dd cannot read them at all;\n  12 have the day equal to the month (01/01, 02/02 ...), the same date either way;\n  132 also read as a different, valid date mm/dd.\nA 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.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 3\ndate as year-month-day, such as 2026-04-03> 2026-04-03\nISO 8601: 2026-04-03. In words: Friday, 3 April 2026.\nA four-digit year first, then the month and the day, or the month as a word, leaves nothing to guess.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 3\ndate as year-month-day, such as 2026-04-03> 1999-12-31\nISO 8601: 1999-12-31. In words: Friday, 31 December 1999.\nA four-digit year first, then the month and the day, or the month as a word, leaves nothing to guess.\n\n1) read a date  2) ambiguous days in a year  3) write a date unmistakably  4) quit\nchoice> 4\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "date-parse-ambiguity",
      "caseId": "250-date-parse-ambiguity",
      "title": "Date parse ambiguity — information that is not in the string"
    }
  ],
  "updated": "2026-10-16"
}
