Date calculator
Days between two dates, the day of the week, a date plus or minus some days, whether a year is a leap year and why, and someone's age on a date - including the two rules for a 29 February birthday - on the Gregorian calendar from 1583 to 9999, from a text menu.
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
Five things people ask a calendar: how many days lie between two dates, what day of the week a date is (and which day of its year), what date comes some days before or after another, whether a year is a leap year - and why - and how old someone is on a given date. Everything is on the Gregorian calendar, from 1583 (the first full year after the calendar began in October 1582) to 9999. A text menu.
main.eml- the menu and its questions, with their checks, and what the screen showsdates.eml- the calendar: the leap-year rule and its reason, the days in a month, counting days, turning a count back into a date, the day of the week, and reading and writing YYYY-MM-DDtext.eml- trimming, whole numbers with or without a sign, grouped digits and counts
How it counts: a date becomes the number of days since 0001-01-01 by a closed formula - 365 a year, plus one for every leap year before it, which is every 4th, less every 100th, plus every 400th - so the days between two dates are a subtraction however far apart they are (3,074,245 days from 1583-01-01 to 9999-12-31), with no loop over the years. A count turns back into a date by taking off whole blocks of 400 years (146,097 days), 100 years (36,524), 4 years (1,461) and single years; the last day of a long block would count as a fifth short block, so that count is held at 3. The day of the week is Zeller's congruence, which treats January and February as months 13 and 14 of the year before so that the leap day falls at the end of the year.
Age is a comparison, not a division: the difference in years, less one if the birthday has not come yet. A 29 February birthday has no date in other years; here it falls on 1 March, so on 28 February 2025 someone born on 2004-02-29 is 20 - and the screen adds that where the birthday is kept on 28 February instead, they are 21. Both rules are used in real law and software.
What is checked: a date is written YYYY-MM-DD with a year from 1583 to 9999, a month that exists and a day that month has (2023-02-29 is refused, 2024-02-29 is not); a number of days is whole, may start with + or -, is at most 3,000,000 either way, and must not lead outside the calendar's range; a year is one from 1583 to 9999; a birth date may not come after the date of the age. Anything else asks again; nothing cancels.
Sessions: sessions/basic.in counts the days across the end of February in a leap year, an ordinary year and 1900 (divisible by 100, so not a leap year), and back from 2026-10-03 to 2025-01-01; names the weekday of 2024-02-29 and of 1969-07-20; adds 30 days to 2024-01-31 and takes 1,000 from 2026-10-03; asks about 2000, 1900, 2024 and 2023; and gives three ages, two of them for a 29 February birthday, on 28 February and on 1 March; sessions/bad-input.in types dates in the wrong shape, out of range or that do not exist, numbers of days that are words, doubly signed, too large or that lead out of range, years out of range, a birth date after the age date, and then counts the whole range.
Built on the verified corpus cases days-between-dates (days between two dates, with the full leap-year rule), day-of-week-zeller (Zeller's congruence) and age-at-date-leap-day (age as a comparison, and the two rules for a 29 February birthday).
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 calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 0
Pick a number from 1 to 6.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 1
first date> 2024/02/28
Write the date as YYYY-MM-DD, for example 2024-02-29.
first date> 2023-02-29
2023-02-29 is not a date: February 2023 has 28 days.
first date> 1582-12-31
The year must be from 1583 to 9999 (the Gregorian calendar).
first date> 2024-13-01
There is no month 13.
first date>
Cancelled.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 3
date> 2024-01-31
days (like 30 or -30)> abc
Type a whole number of days like 30 or -30, or nothing to cancel.
days (like 30 or -30)> +-5
Type a whole number of days like 30 or -30, or nothing to cancel.
days (like 30 or -30)> 9999999
At most 3,000,000 days either way.
days (like 30 or -30)> -200000
That goes before 1583-01-01.
days (like 30 or -30)>
Cancelled.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 3
date> 9999-12-31
days (like 30 or -30)> 1
That goes past 9999-12-31.
days (like 30 or -30)> +0
9999-12-31 + 0 days = 9999-12-31, a Friday.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 4
year> abc
Type a year from 1583 to 9999, or nothing to cancel.
year> 1582
Type a year from 1583 to 9999, or nothing to cancel.
year> 10000
Type a year from 1583 to 9999, or nothing to cancel.
year>
Cancelled.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 5
born on> 2030-01-01
age on> 2026-10-03
The birth date comes after that date.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 2
date>
Cancelled.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 1
first date> 1583-01-01
second date> 9999-12-31
9999-12-31 is 3,074,245 days (439,177 weeks and 6 days) after 1583-01-01.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 6
Bye.
What was typed (32 lines)
0
1
2024/02/28
2023-02-29
1582-12-31
2024-13-01
3
2024-01-31
abc
+-5
9999999
-200000
3
9999-12-31
1
+0
4
abc
1582
10000
5
2030-01-01
2026-10-03
2
1
1583-01-01
9999-12-31
6
basic
interpreter: byte-equal
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 1
first date> 2024-02-28
second date> 2024-03-01
2024-03-01 is 2 days after 2024-02-28.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 1
first date> 2023-02-28
second date> 2023-03-01
2023-03-01 is 1 day after 2023-02-28.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 1
first date> 1900-02-28
second date> 1900-03-01
1900-03-01 is 1 day after 1900-02-28.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 1
first date> 2026-10-03
second date> 2025-01-01
2025-01-01 is 640 days (91 weeks and 3 days) before 2026-10-03.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 2
date> 2024-02-29
2024-02-29 is a Thursday, day 60 of 366.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 2
date> 1969-07-20
1969-07-20 is a Sunday, day 201 of 365.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 3
date> 2024-01-31
days (like 30 or -30)> 30
2024-01-31 + 30 days = 2024-03-01, a Friday.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 3
date> 2026-10-03
days (like 30 or -30)> -1000
2026-10-03 - 1,000 days = 2024-01-07, a Sunday.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 4
year> 2000
2000 is a leap year: divisible by 400.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 4
year> 1900
1900 is not a leap year: divisible by 100 but not by 400.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 4
year> 2024
2024 is a leap year: divisible by 4 and not by 100.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 4
year> 2023
2023 is not a leap year: not divisible by 4.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 5
born on> 2004-02-29
age on> 2025-02-28
On 2025-02-28 someone born 2004-02-29 is 20 years old (7,670 days).
Where a 29 February birthday is kept on 28 February in other years, they are 21.
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 5
born on> 2004-02-29
age on> 2025-03-01
On 2025-03-01 someone born 2004-02-29 is 21 years old (7,671 days).
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 5
born on> 1990-05-17
age on> 2026-10-03
On 2026-10-03 someone born 1990-05-17 is 36 years old (13,288 days).
== Date calculator ==
1) days between 2) day of the week 3) add or take away days
4) leap year 5) age on a date 6) quit
choice> 6
Bye.
What was typed (40 lines)
1
2024-02-28
2024-03-01
1
2023-02-28
2023-03-01
1
1900-02-28
1900-03-01
1
2026-10-03
2025-01-01
2
2024-02-29
2
1969-07-20
3
2024-01-31
30
3
2026-10-03
-1000
4
2000
4
1900
4
2024
4
2023
5
2004-02-29
2025-02-28
5
2004-02-29
2025-03-01
5
1990-05-17
2026-10-03
6
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# P015 date calculator: days between two dates, the day of the week, a date
# plus or minus some days, whether a year is a leap year and why, and
# someone's age on a date - all on the Gregorian calendar from 1583 to 9999.
import dates
import text
3000000 => most_days
def ask_date(prompt):
# A date typed as YYYY-MM-DD, asked again until one is typed; None if the
# answer is empty, which cancels.
while True:
text.trim(input(prompt)) => answer
if answer == "":
return None
dates.parse(answer) => r
if r[0] == None:
r[1] ^0
else:
return r[0]
def ask_number(prompt, low, high, message):
# A whole number from low to high, asked again until one is typed; -1 if
# the answer is empty, which cancels.
while True:
text.trim(input(prompt)) => answer
if answer == "":
return 0 - 1
text.number(answer) => n
if n >= low and n <= high:
return n
message ^0
def gap(n):
# "2 days", "400 days (57 weeks and 1 day)".
text.count(n, "day") => s
if n >= 7:
s + " (" + text.count(dates.div(n, 7), "week") => s
if n % 7 > 0:
s + " and " + text.count(n % 7, "day") => s
s + ")" => s
return s
dates.day_number([dates.first_year, 1, 1]) => first_day
dates.day_number([dates.last_year, 12, 31]) => last_day
True => running
while running:
"" ^0
"== Date calculator ==" ^0
"1) days between 2) day of the week 3) add or take away days" ^0
"4) leap year 5) age on a date 6) quit" ^0
text.trim(input("choice> ")) => choice
if choice == "1":
ask_date("first date> ") => a
None => b
if a != None:
ask_date("second date> ") => b
if b == None:
"Cancelled." ^0
else:
dates.day_number(b) - dates.day_number(a) => d
if d == 0:
(dates.show(b) + " is the same day as " + dates.show(a) + ".") ^0
elif d > 0:
(dates.show(b) + " is " + gap(d) + " after " + dates.show(a) + ".") ^0
else:
(dates.show(b) + " is " + gap(0 - d) + " before " + dates.show(a) + ".") ^0
elif choice == "2":
ask_date("date> ") => a
if a == None:
"Cancelled." ^0
else:
dates.day_number(a) - dates.day_number([a[0], 1, 1]) + 1 => day_of_year
365 => year_length
if dates.is_leap(a[0]):
366 => year_length
(dates.show(a) + " is a " + dates.weekday(a) + ", day " + str(day_of_year) + " of " + str(year_length) + ".") ^0
elif choice == "3":
ask_date("date> ") => a
None => result
while a != None and result == None:
text.trim(input("days (like 30 or -30)> ")) => answer
text.signed(answer) => s
if answer == "":
None => a
elif not s[1]:
"Type a whole number of days like 30 or -30, or nothing to cancel." ^0
elif s[0] > most_days or s[0] < 0 - most_days:
"At most 3,000,000 days either way." ^0
elif dates.day_number(a) + s[0] < first_day:
"That goes before 1583-01-01." ^0
elif dates.day_number(a) + s[0] > last_day:
"That goes past 9999-12-31." ^0
else:
dates.from_day_number(dates.day_number(a) + s[0]) => result
if a == None:
"Cancelled." ^0
else:
" + " + text.count(s[0], "day") => step
if s[0] < 0:
" - " + text.count(0 - s[0], "day") => step
(dates.show(a) + step + " = " + dates.show(result) + ", a " + dates.weekday(result) + ".") ^0
elif choice == "4":
ask_number("year> ", dates.first_year, dates.last_year, "Type a year from 1583 to 9999, or nothing to cancel.") => y
if y == 0 - 1:
"Cancelled." ^0
elif dates.is_leap(y):
(str(y) + " is a leap year: " + dates.leap_reason(y) + ".") ^0
else:
(str(y) + " is not a leap year: " + dates.leap_reason(y) + ".") ^0
elif choice == "5":
ask_date("born on> ") => born
None => on
if born != None:
ask_date("age on> ") => on
if on == None:
"Cancelled." ^0
elif dates.day_number(on) < dates.day_number(born):
"The birth date comes after that date." ^0
else:
on[0] - born[0] => years
if on[1] < born[1] or (on[1] == born[1] and on[2] < born[2]):
years - 1 => years
dates.day_number(on) - dates.day_number(born) => lived
("On " + dates.show(on) + " someone born " + dates.show(born) + " is " + text.count(years, "year") + " old (" + text.count(lived, "day") + ").") ^0
if born[1] == 2 and born[2] == 29 and on[1] == 2 and on[2] == 28 and on[0] > born[0] and not dates.is_leap(on[0]):
("Where a 29 February birthday is kept on 28 February in other years, they are " + str(years + 1) + ".") ^0
elif choice == "6":
False => running
else:
"Pick a number from 1 to 6." ^0
"Bye." ^0
Python projection (main.py)
import dates
import text
most_days = 3000000
def ask_date(prompt):
while True:
answer = text.trim(input(prompt))
if answer == "":
return None
r = dates.parse(answer)
if r[0] == None:
print(r[1])
else:
return r[0]
def ask_number(prompt, low, high, message):
while True:
answer = text.trim(input(prompt))
if answer == "":
return 0 - 1
n = text.number(answer)
if n >= low and n <= high:
return n
print(message)
def gap(n):
s = text.count(n, "day")
if n >= 7:
s = s + " (" + text.count(dates.div(n, 7), "week")
if n % 7 > 0:
s = s + " and " + text.count(n % 7, "day")
s = s + ")"
return s
first_day = dates.day_number([dates.first_year, 1, 1])
last_day = dates.day_number([dates.last_year, 12, 31])
running = True
while running:
print("")
print("== Date calculator ==")
print("1) days between 2) day of the week 3) add or take away days")
print("4) leap year 5) age on a date 6) quit")
choice = text.trim(input("choice> "))
if choice == "1":
a = ask_date("first date> ")
b = None
if a != None:
b = ask_date("second date> ")
if b == None:
print("Cancelled.")
else:
d = dates.day_number(b) - dates.day_number(a)
if d == 0:
print(dates.show(b) + " is the same day as " + dates.show(a) + ".")
elif d > 0:
print(dates.show(b) + " is " + gap(d) + " after " + dates.show(a) + ".")
else:
print(dates.show(b) + " is " + gap(0 - d) + " before " + dates.show(a) + ".")
elif choice == "2":
a = ask_date("date> ")
if a == None:
print("Cancelled.")
else:
day_of_year = dates.day_number(a) - dates.day_number([a[0], 1, 1]) + 1
year_length = 365
if dates.is_leap(a[0]):
year_length = 366
print(dates.show(a) + " is a " + dates.weekday(a) + ", day " + str(day_of_year) + " of " + str(year_length) + ".")
elif choice == "3":
a = ask_date("date> ")
result = None
while a != None and result == None:
answer = text.trim(input("days (like 30 or -30)> "))
s = text.signed(answer)
if answer == "":
a = None
elif not s[1]:
print("Type a whole number of days like 30 or -30, or nothing to cancel.")
elif s[0] > most_days or s[0] < 0 - most_days:
print("At most 3,000,000 days either way.")
elif dates.day_number(a) + s[0] < first_day:
print("That goes before 1583-01-01.")
elif dates.day_number(a) + s[0] > last_day:
print("That goes past 9999-12-31.")
else:
result = dates.from_day_number(dates.day_number(a) + s[0])
if a == None:
print("Cancelled.")
else:
step = " + " + text.count(s[0], "day")
if s[0] < 0:
step = " - " + text.count(0 - s[0], "day")
print(dates.show(a) + step + " = " + dates.show(result) + ", a " + dates.weekday(result) + ".")
elif choice == "4":
y = ask_number("year> ", dates.first_year, dates.last_year, "Type a year from 1583 to 9999, or nothing to cancel.")
if y == 0 - 1:
print("Cancelled.")
elif dates.is_leap(y):
print(str(y) + " is a leap year: " + dates.leap_reason(y) + ".")
else:
print(str(y) + " is not a leap year: " + dates.leap_reason(y) + ".")
elif choice == "5":
born = ask_date("born on> ")
on = None
if born != None:
on = ask_date("age on> ")
if on == None:
print("Cancelled.")
elif dates.day_number(on) < dates.day_number(born):
print("The birth date comes after that date.")
else:
years = on[0] - born[0]
if on[1] < born[1] or on[1] == born[1] and on[2] < born[2]:
years = years - 1
lived = dates.day_number(on) - dates.day_number(born)
print("On " + dates.show(on) + " someone born " + dates.show(born) + " is " + text.count(years, "year") + " old (" + text.count(lived, "day") + ").")
if born[1] == 2 and born[2] == 29 and on[1] == 2 and on[2] == 28 and on[0] > born[0] and not dates.is_leap(on[0]):
print("Where a 29 February birthday is kept on 28 February in other years, they are " + str(years + 1) + ".")
elif choice == "6":
running = False
else:
print("Pick a number from 1 to 6.")
print("Bye.")
dates.eml
eml# P015 date calculator - dates on the Gregorian calendar. A date is [year, month, day]
# for the years 1583 to 9999 (the calendar began in October 1582). Days are
# counted from 0001-01-01 by a closed formula, so the gap between two dates is
# a subtraction however far apart they are, and the count turns back into a
# date by taking off whole 400-, 100-, 4- and 1-year blocks.
["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] => month_names
["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] => day_names
1583 => first_year
9999 => last_year
def div(a, b):
# a // b for whole numbers a >= 0 and b > 0 (small enough to stay exact).
return int((a - a % b) / b)
def is_leap(year):
# Divisible by 4 is a leap year, except divisible by 100, except
# divisible by 400 after all.
if year % 400 == 0:
return True
if year % 100 == 0:
return False
return year % 4 == 0
def leap_reason(year):
# Why year is or is not a leap year, naming the rule that decides it.
if year % 400 == 0:
return "divisible by 400"
if year % 100 == 0:
return "divisible by 100 but not by 400"
if year % 4 == 0:
return "divisible by 4 and not by 100"
return "not divisible by 4"
def days_in_month(year, month):
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] => lengths
if month == 2 and is_leap(year):
return 29
return lengths[month - 1]
def day_number(d):
# Days from 0001-01-01 (day 0) to d: 365 a year, plus one for every leap
# year before it - every 4th, less every 100th, plus every 400th.
d[0] - 1 => p
365 * p + div(p, 4) - div(p, 100) + div(p, 400) => n
for m in [1:d[1] - 1]:
n + days_in_month(d[0], m) => n
return n + d[2] - 1
def from_day_number(n):
# The date n days after 0001-01-01. A 400-year block has 146097 days, a
# 100-year block 36524, a 4-year block 1461; the last day of a long block
# would count as a fifth short one, so that count is held at 3.
div(n, 146097) => n400
n % 146097 => n
div(n, 36524) => n100
if n100 == 4:
3 => n100
n - 36524 * n100 => n
div(n, 1461) => n4
n % 1461 => n
div(n, 365) => n1
if n1 == 4:
3 => n1
n - 365 * n1 => n
400 * n400 + 100 * n100 + 4 * n4 + n1 + 1 => year
1 => month
while n >= days_in_month(year, month):
n - days_in_month(year, month) => n
month + 1 => month
return [year, month, n + 1]
def weekday(d):
# The day of the week by Zeller's congruence: January and February count
# as months 13 and 14 of the year before, which puts the leap day at the
# end of the year.
d[0] => y
d[1] => m
if m < 3:
m + 12 => m
y - 1 => y
y % 100 => k
div(y, 100) => j
return day_names[(d[2] + div(13 * (m + 1), 5) + k + div(k, 4) + div(j, 4) + 5 * j) % 7]
def show(d):
return str(d[0]) + "-" + ("%02d" % d[1]) + "-" + ("%02d" % d[2])
def parse(s):
# [date, ""] if s is a date written YYYY-MM-DD, otherwise [None, what is
# wrong with it].
"Write the date as YYYY-MM-DD, for example 2024-02-29." => shape
if len(s) != 10 or s[4] != "-" or s[7] != "-":
return [None, shape]
for i in [0, 1, 2, 3, 5, 6, 8, 9]:
if not (s[i] in "0123456789"):
return [None, shape]
int(s[0:4]) => y
int(s[5:7]) => m
int(s[8:10]) => day
if y < first_year or y > last_year:
return [None, "The year must be from 1583 to 9999 (the Gregorian calendar)."]
if m < 1 or m > 12:
return [None, "There is no month " + str(m) + "."]
if day < 1 or day > days_in_month(y, m):
return [None, s + " is not a date: " + month_names[m - 1] + " " + str(y) + " has " + str(days_in_month(y, m)) + " days."]
return [[y, m, day], ""]
Python projection (dates.py)
month_names = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
day_names = ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
first_year = 1583
last_year = 9999
def div(a, b):
return int((a - a % b) / b)
def is_leap(year):
if year % 400 == 0:
return True
if year % 100 == 0:
return False
return year % 4 == 0
def leap_reason(year):
if year % 400 == 0:
return "divisible by 400"
if year % 100 == 0:
return "divisible by 100 but not by 400"
if year % 4 == 0:
return "divisible by 4 and not by 100"
return "not divisible by 4"
def days_in_month(year, month):
lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if month == 2 and is_leap(year):
return 29
return lengths[month - 1]
def day_number(d):
p = d[0] - 1
n = 365 * p + div(p, 4) - div(p, 100) + div(p, 400)
for m in range(1, d[1]):
n = n + days_in_month(d[0], m)
return n + d[2] - 1
def from_day_number(n):
n400 = div(n, 146097)
n = n % 146097
n100 = div(n, 36524)
if n100 == 4:
n100 = 3
n = n - 36524 * n100
n4 = div(n, 1461)
n = n % 1461
n1 = div(n, 365)
if n1 == 4:
n1 = 3
n = n - 365 * n1
year = 400 * n400 + 100 * n100 + 4 * n4 + n1 + 1
month = 1
while n >= days_in_month(year, month):
n = n - days_in_month(year, month)
month = month + 1
return [year, month, n + 1]
def weekday(d):
y = d[0]
m = d[1]
if m < 3:
m = m + 12
y = y - 1
k = y % 100
j = div(y, 100)
return day_names[(d[2] + div(13 * (m + 1), 5) + k + div(k, 4) + div(j, 4) + 5 * j) % 7]
def show(d):
return str(d[0]) + "-" + "%02d" % d[1] + "-" + "%02d" % d[2]
def parse(s):
shape = "Write the date as YYYY-MM-DD, for example 2024-02-29."
if len(s) != 10 or s[4] != "-" or s[7] != "-":
return [None, shape]
for i in [0, 1, 2, 3, 5, 6, 8, 9]:
if not s[i] in "0123456789":
return [None, shape]
y = int(s[0:4])
m = int(s[5:7])
day = int(s[8:10])
if y < first_year or y > last_year:
return [None, "The year must be from 1583 to 9999 (the Gregorian calendar)."]
if m < 1 or m > 12:
return [None, "There is no month " + str(m) + "."]
if day < 1 or day > days_in_month(y, m):
return [None, s + " is not a date: " + month_names[m - 1] + " " + str(y) + " has " + str(days_in_month(y, m)) + " days."]
return [[y, m, day], ""]
text.eml
eml# P015 date calculator - reading what is typed and writing counts. The
# interpreter that checks every session does not run string methods yet, so
# the text handling is written out here.
def trim(s):
# s without the spaces at either end.
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 number(s):
# The value of s if it is digits only (at least one), otherwise -1.
if s == "":
return 0 - 1
0 => n
for c in s:
if not (c in "0123456789"):
return 0 - 1
n * 10 + int(c) => n
return n
def signed(s):
# [value, True] for digits with an optional + or - in front, otherwise
# [0, False].
1 => sign
s => rest
if len(s) > 0 and (s[0] == "+" or s[0] == "-"):
if s[0] == "-":
0 - 1 => sign
s[1:len(s)] => rest
number(rest) => n
if n == 0 - 1:
return [0, False]
return [sign * n, True]
def grouped(n):
# A whole number n >= 0 as 1,234,567.
str(n) => digits
"" => out
len(digits) => i
while i > 3:
"," + digits[i - 3:i] + out => out
i - 3 => i
return digits[0:i] + out
def count(n, word):
# "1 day", "1,000 days".
if n == 1:
return "1 " + word
return grouped(n) + " " + word + "s"
Python projection (text.py)
def trim(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 number(s):
if s == "":
return 0 - 1
n = 0
for c in s:
if not c in "0123456789":
return 0 - 1
n = n * 10 + int(c)
return n
def signed(s):
sign = 1
rest = s
if len(s) > 0 and (s[0] == "+" or s[0] == "-"):
if s[0] == "-":
sign = 0 - 1
rest = s[1:len(s)]
n = number(rest)
if n == 0 - 1:
return [0, False]
return [sign * n, True]
def grouped(n):
digits = str(n)
out = ""
i = len(digits)
while i > 3:
out = "," + digits[i - 3:i] + out
i = i - 3
return digits[0:i] + out
def count(n, word):
if n == 1:
return "1 " + word
return grouped(n) + " " + word + "s"