Project P162

Timesheet

A week of shifts and hours over 8 in a day paid at time and a half, with the day read as the shift, the shop's calendar day and the payroll server's: every midnight that falls inside a long shift pays less.

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

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 shop's week of shifts and the rule "hours over 8 in a day are paid at time and a half", with the day read three ways.

  • main.eml - the menu, the shifts, overtime by each reading, one worker's days, and a shift of your own
  • shifts.eml - the workers and their shifts, the hours on each day of the shop or of the payroll server, and the hours over 8

How each part works:

  • Five workers, in the shop's time (UTC+9): day shifts, night shifts, evening shifts, a double shift, and 12-hour nights. 20.00 an hour, 30.00 for each hour over 8.
  • As in the corpus case the-counter-reset-at-midnight-and-the-shift-crossed-it, a count that returns to zero at midnight starts again in the middle of every shift that crosses it. Eve's three 12-hour nights are 12 hours over 8 by the shift and none by the shop's day: midnight splits each into 4 and 8. By the shop's day Ben, Cleo and Eve lose 15 hours of overtime.
  • As in the-day-boundary-was-the-servers-and-the-user-was-elsewhere, the payroll server's day is UTC, which ends at 09:00 in the shop: it keeps the night shifts whole and cuts Dan's 07:00 starts instead, 2 hours.
  • Pay for the week: 5,020.00 by the shift, 4,870.00 by the shop's day, 5,000.00 by the server's day. A shift of 8 hours or less pays the same under every reading; a longer one pays less under any reading whose midnight falls inside it.

What is checked: menu choices 1 to 5; a worker from 1 to 5; a start hour from 0 to 23 and 1 to 16 hours. An empty answer cancels.

Sessions: sessions/basic.in gives: - the shifts and the overtime; - Eve's days and Dan's days; - your own shifts from 22:00 for 10 hours, from 23:00 for 11 and from 10:00 for 10.

sessions/bad-input.in gives: - menu choices 0 and x; - workers: a cancel, 0, 6 and x; - start hours: a cancel, 24 and x; then hours with a cancel, 0, 17 and x; - shifts from 02:00 for 12 hours, which only the server's midnight cuts, and from 09:00 for 6.

Built on the verified corpus cases the-counter-reset-at-midnight-and-the-shift-crossed-it (a daily count reset at midnight, two hours into the night shift) and the-day-boundary-was-the-servers-and-the-user-was-elsewhere (a daily count by the server's midnight, with the users nine hours ahead).

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
== Timesheet ==
A week of shifts, and hours over 8 in a day paid at time and a half - by which day?

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 0
Pick a number from 1 to 5.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> x
Pick a number from 1 to 5.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 3
worker (1 Ana, 2 Ben, 3 Cleo, 4 Dan, 5 Eve)> 
Cancelled.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 3
worker (1 Ana, 2 Ben, 3 Cleo, 4 Dan, 5 Eve)> 0
Type a number from 1 to 5.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 3
worker (1 Ana, 2 Ben, 3 Cleo, 4 Dan, 5 Eve)> 6
Type a number from 1 to 5.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 3
worker (1 Ana, 2 Ben, 3 Cleo, 4 Dan, 5 Eve)> x
Type a number from 1 to 5.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 4
start hour (0 to 23)> 
Cancelled.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 4
start hour (0 to 23)> 24
Type a whole number from 0 to 23.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 4
start hour (0 to 23)> x
Type a whole number from 0 to 23.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 4
start hour (0 to 23)> 9
hours (1 to 16)> 
Cancelled.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 4
start hour (0 to 23)> 9
hours (1 to 16)> 0
Type a whole number from 1 to 16.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 4
start hour (0 to 23)> 9
hours (1 to 16)> 17
Type a whole number from 1 to 16.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 4
start hour (0 to 23)> 9
hours (1 to 16)> x
Type a whole number from 1 to 16.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 4
start hour (0 to 23)> 2
hours (1 to 16)> 12
A Wednesday shift 02:00-14:00, 12 hours:
  by the shift:        4 over 8
  by the shop's day:   Wed 12 - 4 over 8
  by the server's day: Tue 7, Wed 5 - 0 over 8
The server's midnight, 09:00 here, cuts it in two.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 4
start hour (0 to 23)> 9
hours (1 to 16)> 6
A Wednesday shift 09:00-15:00, 6 hours:
  by the shift:        0 over 8
  by the shop's day:   Wed 6 - 0 over 8
  by the server's day: Wed 6 - 0 over 8
No more than 8 hours: no reading pays any at time and a half.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 5
Bye.
What was typed (35 lines)
0
x
3

3
0
3
6
3
x
4

4
24
4
x
4
9

4
9
0
4
9
17
4
9
x
4
2
12
4
9
6
5

basic

interpreter: byte-equal
== Timesheet ==
A week of shifts, and hours over 8 in a day paid at time and a half - by which day?

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 1
The week in the shop's time, UTC+9. The payroll server keeps UTC, so its midnight is 09:00 here.
Hours over 8 in a day are paid at time and a half: 20.00 an hour, 30.00 for each hour over 8.
  Ana   Mon Tue Wed Thu Fri 09:00-19:00, 10 hours
  Ben   Mon Tue Wed Thu Fri 22:00-08:00, 10 hours
  Cleo  Mon Tue Wed Thu Fri 15:00-01:00, 10 hours
  Dan   Mon 07:00-23:00, 16 hours; Wed Thu Fri 07:00-15:00, 8 hours
  Eve   Mon Wed Fri 20:00-08:00, 12 hours

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 2
Hours over 8 in a day, with the day read three ways:
  worker  hours  by shift  shop day  server day
  Ana        50        10        10          10
  Ben        50        10         8          10
  Cleo       50        10         9          10
  Dan        40         8         8           6
  Eve        36        12         0          12
  all       226        50        35          48
Pay for the week: 5,020.00 by the shift, 4,870.00 by the shop's day, 5,000.00 by the server's day.
Hours over 8 paid as plain hours by the shop's day: Ben 2, Cleo 1 and Eve 12. A shift that crosses midnight starts its count again at 0.
Hours over 8 paid as plain hours by the server's day: Dan 2. The server's midnight is 09:00 here, and it cuts the shifts that cross it.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 3
worker (1 Ana, 2 Ben, 3 Cleo, 4 Dan, 5 Eve)> 5
Eve by the shift:        Mon 12, Wed 12, Fri 12 - 12 hours over 8
Eve by the shop's day:   Mon 4, Tue 8, Wed 4, Thu 8, Fri 4, Sat 8 - 0 hours over 8
Eve by the server's day: Mon 12, Wed 12, Fri 12 - 12 hours over 8

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 3
worker (1 Ana, 2 Ben, 3 Cleo, 4 Dan, 5 Eve)> 4
Dan by the shift:        Mon 16, Wed 8, Thu 8, Fri 8 - 8 hours over 8
Dan by the shop's day:   Mon 16, Wed 8, Thu 8, Fri 8 - 8 hours over 8
Dan by the server's day: Sun 2, Mon 14, Tue 2, Wed 8, Thu 8, Fri 6 - 6 hours over 8

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 4
start hour (0 to 23)> 22
hours (1 to 16)> 10
A Wednesday shift 22:00-08:00, 10 hours:
  by the shift:        2 over 8
  by the shop's day:   Wed 2, Thu 8 - 0 over 8
  by the server's day: Wed 10 - 2 over 8
Midnight in the shop cuts it in two, and each part counts from 0.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 4
start hour (0 to 23)> 23
hours (1 to 16)> 11
A Wednesday shift 23:00-10:00, 11 hours:
  by the shift:        3 over 8
  by the shop's day:   Wed 1, Thu 10 - 2 over 8
  by the server's day: Wed 10, Thu 1 - 2 over 8
Midnight in the shop cuts it in two, and each part counts from 0.
The server's midnight, 09:00 here, cuts it in two.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 4
start hour (0 to 23)> 10
hours (1 to 16)> 10
A Wednesday shift 10:00-20:00, 10 hours:
  by the shift:        2 over 8
  by the shop's day:   Wed 10 - 2 over 8
  by the server's day: Wed 10 - 2 over 8
No midnight falls inside it: every reading pays the same.

1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit
choice> 5
Bye.
What was typed (16 lines)
1
2
3
5
3
4
4
22
10
4
23
11
4
10
10
5

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
# P162 timesheet: a week of shifts and "hours over 8 in a day are paid at
# time and a half" - the day read as the shift, the shop's calendar day and
# the payroll server's day; one worker's days, and a shift of your own.
import shifts

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):
    s => out
    while len(out) < n:
        out + " " => out
    return out

def lpad(s, n):
    s => out
    while len(out) < n:
        " " + out => out
    return out

def digits(t):
    for c in t:
        if c < "0" or c > "9":
            return False
    return len(t) > 0

def money(cents):
    cents % 100 => c
    int((cents - c) / 100) => w
    str(w) => s
    if w >= 1000:
        str(int((w - w % 1000) / 1000)) + "," + str(w % 1000 + 1000)[1:] => s
    if c < 10:
        return s + ".0" + str(c)
    return s + "." + str(c)

def clock(h):
    h % 24 => h
    if h < 10:
        return "0" + str(h) + ":00"
    return str(h) + ":00"

def span(s):
    return clock(s[1]) + "-" + clock(s[1] + s[2]) + ", " + str(s[2]) + " hours"

def show_shifts():
    "The week in the shop's time, UTC+9. The payroll server keeps UTC, so its midnight is 09:00 here." ^0
    "Hours over 8 in a day are paid at time and a half: 20.00 an hour, 30.00 for each hour over 8." ^0
    for w in shifts.workers():
        # shifts with the same hours, grouped: [start, hours, days]
        [] => groups
        for s in w[1]:
            False => found
            for g in groups:
                if g[0] == s[1] and g[1] == s[2]:
                    g[2] + " " + shifts.day_name(s[0]) => g[2]
                    True => found
            if not found:
                groups + [[s[1], s[2], shifts.day_name(s[0])]] => groups
        "" => line
        for g in groups:
            if line != "":
                line + "; " => line
            line + g[2] + " " + span([0, g[0], g[1]]) => line
        ("  " + pad(w[0], 6) + line) ^0

def readings(w):
    # [hours, over by shift, over by the shop's day, over by the server's day]
    return [shifts.total(w[1]), shifts.over_shifts(w[1]), shifts.over_days(shifts.days(w[1], False)), shifts.over_days(shifts.days(w[1], True))]

def names(list):
    # "Ben 2, Cleo 1 and Eve 12"
    "" => out
    0 => i
    while i < len(list):
        if i > 0 and i == len(list) - 1:
            out + " and " => out
        elif i > 0:
            out + ", " => out
        out + list[i] => out
        i + 1 => i
    return out

def show_overtime():
    "Hours over 8 in a day, with the day read three ways:" ^0
    ("  " + pad("worker", 8) + lpad("hours", 5) + lpad("by shift", 10) + lpad("shop day", 10) + lpad("server day", 12)) ^0
    [0, 0, 0, 0] => sums
    [] => lost_shop
    [] => lost_server
    for w in shifts.workers():
        readings(w) => r
        ("  " + pad(w[0], 8) + lpad(str(r[0]), 5) + lpad(str(r[1]), 10) + lpad(str(r[2]), 10) + lpad(str(r[3]), 12)) ^0
        0 => k
        while k < 4:
            sums[k] + r[k] => sums[k]
            k + 1 => k
        if r[2] < r[1]:
            lost_shop + [w[0] + " " + str(r[1] - r[2])] => lost_shop
        if r[3] < r[1]:
            lost_server + [w[0] + " " + str(r[1] - r[3])] => lost_server
    ("  " + pad("all", 8) + lpad(str(sums[0]), 5) + lpad(str(sums[1]), 10) + lpad(str(sums[2]), 10) + lpad(str(sums[3]), 12)) ^0
    ("Pay for the week: " + money(shifts.pay(sums[0], sums[1])) + " by the shift, " + money(shifts.pay(sums[0], sums[2])) + " by the shop's day, " + money(shifts.pay(sums[0], sums[3])) + " by the server's day.") ^0
    if len(lost_shop) > 0:
        ("Hours over 8 paid as plain hours by the shop's day: " + names(lost_shop) + ". A shift that crosses midnight starts its count again at 0.") ^0
    if len(lost_server) > 0:
        ("Hours over 8 paid as plain hours by the server's day: " + names(lost_server) + ". The server's midnight is 09:00 here, and it cuts the shifts that cross it.") ^0

def day_list(ds):
    "" => out
    for e in ds:
        if out != "":
            out + ", " => out
        out + shifts.day_name(e[0]) + " " + str(e[1]) => out
    return out

def show_worker():
    plain(input("worker (1 Ana, 2 Ben, 3 Cleo, 4 Dan, 5 Eve)> ")) => t
    if t == "":
        "Cancelled." ^0
        return
    if len(t) != 1 or not digits(t) or int(t) < 1 or int(t) > 5:
        "Type a number from 1 to 5." ^0
        return
    shifts.workers()[int(t) - 1] => w
    "" => line
    for s in w[1]:
        if line != "":
            line + ", " => line
        line + shifts.day_name(s[0]) + " " + str(s[2]) => line
    (w[0] + " by the shift:        " + line + " - " + str(shifts.over_shifts(w[1])) + " hours over 8") ^0
    shifts.days(w[1], False) => a
    (w[0] + " by the shop's day:   " + day_list(a) + " - " + str(shifts.over_days(a)) + " hours over 8") ^0
    shifts.days(w[1], True) => b
    (w[0] + " by the server's day: " + day_list(b) + " - " + str(shifts.over_days(b)) + " hours over 8") ^0

def own():
    plain(input("start hour (0 to 23)> ")) => t
    if t == "":
        "Cancelled." ^0
        return
    if len(t) > 2 or not digits(t) or int(t) > 23:
        "Type a whole number from 0 to 23." ^0
        return
    plain(input("hours (1 to 16)> ")) => n
    if n == "":
        "Cancelled." ^0
        return
    if len(n) > 2 or not digits(n) or int(n) < 1 or int(n) > 16:
        "Type a whole number from 1 to 16." ^0
        return
    [[2, int(t), int(n)]] => one
    readings(["yours", one]) => r
    shifts.days(one, False) => a
    shifts.days(one, True) => b
    ("A Wednesday shift " + span(one[0]) + ":") ^0
    ("  by the shift:        " + str(r[1]) + " over 8") ^0
    ("  by the shop's day:   " + day_list(a) + " - " + str(r[2]) + " over 8") ^0
    ("  by the server's day: " + day_list(b) + " - " + str(r[3]) + " over 8") ^0
    if r[1] == 0:
        "No more than 8 hours: no reading pays any at time and a half." ^0
        return
    if r[2] < r[1]:
        "Midnight in the shop cuts it in two, and each part counts from 0." ^0
    if r[3] < r[1]:
        "The server's midnight, 09:00 here, cuts it in two." ^0
    if r[2] == r[1] and r[3] == r[1]:
        "No midnight falls inside it: every reading pays the same." ^0

"== Timesheet ==" ^0
"A week of shifts, and hours over 8 in a day paid at time and a half - by which day?" ^0
True => running
while running:
    "" ^0
    "1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit" ^0
    plain(input("choice> ")) => choice
    if choice == "1":
        show_shifts()
    elif choice == "2":
        show_overtime()
    elif choice == "3":
        show_worker()
    elif choice == "4":
        own()
    elif choice == "5":
        False => running
    else:
        "Pick a number from 1 to 5." ^0
"Bye." ^0
Python projection (main.py)
import shifts

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):
    out = s
    while len(out) < n:
        out = out + " "
    return out

def lpad(s, n):
    out = s
    while len(out) < n:
        out = " " + out
    return out

def digits(t):
    for c in t:
        if c < "0" or c > "9":
            return False
    return len(t) > 0

def money(cents):
    c = cents % 100
    w = int((cents - c) / 100)
    s = str(w)
    if w >= 1000:
        s = str(int((w - w % 1000) / 1000)) + "," + str(w % 1000 + 1000)[1:]
    if c < 10:
        return s + ".0" + str(c)
    return s + "." + str(c)

def clock(h):
    h = h % 24
    if h < 10:
        return "0" + str(h) + ":00"
    return str(h) + ":00"

def span(s):
    return clock(s[1]) + "-" + clock(s[1] + s[2]) + ", " + str(s[2]) + " hours"

def show_shifts():
    print("The week in the shop's time, UTC+9. The payroll server keeps UTC, so its midnight is 09:00 here.")
    print("Hours over 8 in a day are paid at time and a half: 20.00 an hour, 30.00 for each hour over 8.")
    for w in shifts.workers():
        groups = []
        for s in w[1]:
            found = False
            for g in groups:
                if g[0] == s[1] and g[1] == s[2]:
                    g[2] = g[2] + " " + shifts.day_name(s[0])
                    found = True
            if not found:
                groups = groups + [[s[1], s[2], shifts.day_name(s[0])]]
        line = ""
        for g in groups:
            if line != "":
                line = line + "; "
            line = line + g[2] + " " + span([0, g[0], g[1]])
        print("  " + pad(w[0], 6) + line)

def readings(w):
    return [shifts.total(w[1]), shifts.over_shifts(w[1]), shifts.over_days(shifts.days(w[1], False)), shifts.over_days(shifts.days(w[1], True))]

def names(lst):
    out = ""
    i = 0
    while i < len(lst):
        if i > 0 and i == len(lst) - 1:
            out = out + " and "
        elif i > 0:
            out = out + ", "
        out = out + lst[i]
        i = i + 1
    return out

def show_overtime():
    print("Hours over 8 in a day, with the day read three ways:")
    print("  " + pad("worker", 8) + lpad("hours", 5) + lpad("by shift", 10) + lpad("shop day", 10) + lpad("server day", 12))
    sums = [0, 0, 0, 0]
    lost_shop = []
    lost_server = []
    for w in shifts.workers():
        r = readings(w)
        print("  " + pad(w[0], 8) + lpad(str(r[0]), 5) + lpad(str(r[1]), 10) + lpad(str(r[2]), 10) + lpad(str(r[3]), 12))
        k = 0
        while k < 4:
            sums[k] = sums[k] + r[k]
            k = k + 1
        if r[2] < r[1]:
            lost_shop = lost_shop + [w[0] + " " + str(r[1] - r[2])]
        if r[3] < r[1]:
            lost_server = lost_server + [w[0] + " " + str(r[1] - r[3])]
    print("  " + pad("all", 8) + lpad(str(sums[0]), 5) + lpad(str(sums[1]), 10) + lpad(str(sums[2]), 10) + lpad(str(sums[3]), 12))
    print("Pay for the week: " + money(shifts.pay(sums[0], sums[1])) + " by the shift, " + money(shifts.pay(sums[0], sums[2])) + " by the shop's day, " + money(shifts.pay(sums[0], sums[3])) + " by the server's day.")
    if len(lost_shop) > 0:
        print("Hours over 8 paid as plain hours by the shop's day: " + names(lost_shop) + ". A shift that crosses midnight starts its count again at 0.")
    if len(lost_server) > 0:
        print("Hours over 8 paid as plain hours by the server's day: " + names(lost_server) + ". The server's midnight is 09:00 here, and it cuts the shifts that cross it.")

def day_list(ds):
    out = ""
    for e in ds:
        if out != "":
            out = out + ", "
        out = out + shifts.day_name(e[0]) + " " + str(e[1])
    return out

def show_worker():
    t = plain(input("worker (1 Ana, 2 Ben, 3 Cleo, 4 Dan, 5 Eve)> "))
    if t == "":
        print("Cancelled.")
        return
    if len(t) != 1 or not digits(t) or int(t) < 1 or int(t) > 5:
        print("Type a number from 1 to 5.")
        return
    w = shifts.workers()[int(t) - 1]
    line = ""
    for s in w[1]:
        if line != "":
            line = line + ", "
        line = line + shifts.day_name(s[0]) + " " + str(s[2])
    print(w[0] + " by the shift:        " + line + " - " + str(shifts.over_shifts(w[1])) + " hours over 8")
    a = shifts.days(w[1], False)
    print(w[0] + " by the shop's day:   " + day_list(a) + " - " + str(shifts.over_days(a)) + " hours over 8")
    b = shifts.days(w[1], True)
    print(w[0] + " by the server's day: " + day_list(b) + " - " + str(shifts.over_days(b)) + " hours over 8")

def own():
    t = plain(input("start hour (0 to 23)> "))
    if t == "":
        print("Cancelled.")
        return
    if len(t) > 2 or not digits(t) or int(t) > 23:
        print("Type a whole number from 0 to 23.")
        return
    n = plain(input("hours (1 to 16)> "))
    if n == "":
        print("Cancelled.")
        return
    if len(n) > 2 or not digits(n) or int(n) < 1 or int(n) > 16:
        print("Type a whole number from 1 to 16.")
        return
    one = [[2, int(t), int(n)]]
    r = readings(["yours", one])
    a = shifts.days(one, False)
    b = shifts.days(one, True)
    print("A Wednesday shift " + span(one[0]) + ":")
    print("  by the shift:        " + str(r[1]) + " over 8")
    print("  by the shop's day:   " + day_list(a) + " - " + str(r[2]) + " over 8")
    print("  by the server's day: " + day_list(b) + " - " + str(r[3]) + " over 8")
    if r[1] == 0:
        print("No more than 8 hours: no reading pays any at time and a half.")
        return
    if r[2] < r[1]:
        print("Midnight in the shop cuts it in two, and each part counts from 0.")
    if r[3] < r[1]:
        print("The server's midnight, 09:00 here, cuts it in two.")
    if r[2] == r[1] and r[3] == r[1]:
        print("No midnight falls inside it: every reading pays the same.")

print("== Timesheet ==")
print("A week of shifts, and hours over 8 in a day paid at time and a half - by which day?")
running = True
while running:
    print("")
    print("1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit")
    choice = plain(input("choice> "))
    if choice == "1":
        show_shifts()
    elif choice == "2":
        show_overtime()
    elif choice == "3":
        show_worker()
    elif choice == "4":
        own()
    elif choice == "5":
        running = False
    else:
        print("Pick a number from 1 to 5.")
print("Bye.")

shifts.eml

eml
# P162 timesheet - a shop's week of shifts and the rule "hours over 8 in a
# day are paid at time and a half", read three ways. As in the corpus case
# the-counter-reset-at-midnight-and-the-shift-crossed-it, a count that
# returns to zero at midnight starts again in the middle of any shift that
# crosses it, so a long night shift is two short days. And as in
# the-day-boundary-was-the-servers-and-the-user-was-elsewhere, the payroll
# server's midnight is UTC, nine hours behind the shop: its day ends at 09:00
# here. Times are whole hours from Monday 00:00 in the shop.

def zone():
    # the shop is UTC+9
    return 9

def rate():
    # cents an hour, and for each hour over 8
    return 2000

def over_rate():
    return 3000

def day_name(k):
    # k = -1 is the Sunday before
    return ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"][k + 1]

def workers():
    # [name, shifts]; a shift is [day (0 = Monday), start hour, hours]
    return [["Ana", [[0, 9, 10], [1, 9, 10], [2, 9, 10], [3, 9, 10], [4, 9, 10]]],
            ["Ben", [[0, 22, 10], [1, 22, 10], [2, 22, 10], [3, 22, 10], [4, 22, 10]]],
            ["Cleo", [[0, 15, 10], [1, 15, 10], [2, 15, 10], [3, 15, 10], [4, 15, 10]]],
            ["Dan", [[0, 7, 16], [2, 7, 8], [3, 7, 8], [4, 7, 8]]],
            ["Eve", [[0, 20, 12], [2, 20, 12], [4, 20, 12]]]]

def day_of(h):
    # the whole day hour h falls in, h >= 0
    return int((h - h % 24) / 24)

def days(shifts, server):
    # hours worked on each day, as [[day, hours], ...] in order: the shop's
    # days, or the server's (UTC) days, where day -1 is the Sunday before
    [] => out
    for s in shifts:
        0 => k
        while k < s[2]:
            s[0] * 24 + s[1] + k => h
            if server:
                day_of(h + 24 - zone()) - 1 => d
            else:
                day_of(h) => d
            False => found
            for e in out:
                if e[0] == d:
                    e[1] + 1 => e[1]
                    True => found
            if not found:
                out + [[d, 1]] => out
            k + 1 => k
    return out

def over_days(ds):
    # hours over 8 in each day, summed
    0 => t
    for e in ds:
        if e[1] > 8:
            t + e[1] - 8 => t
    return t

def over_shifts(shifts):
    0 => t
    for s in shifts:
        if s[2] > 8:
            t + s[2] - 8 => t
    return t

def total(shifts):
    0 => t
    for s in shifts:
        t + s[2] => t
    return t

def pay(hours, over):
    # cents for a week of hours with that many over 8
    return (hours - over) * rate() + over * over_rate()
Python projection (shifts.py)
def zone():
    return 9

def rate():
    return 2000

def over_rate():
    return 3000

def day_name(k):
    return ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"][k + 1]

def workers():
    return [["Ana", [[0, 9, 10], [1, 9, 10], [2, 9, 10], [3, 9, 10], [4, 9, 10]]], ["Ben", [[0, 22, 10], [1, 22, 10], [2, 22, 10], [3, 22, 10], [4, 22, 10]]], ["Cleo", [[0, 15, 10], [1, 15, 10], [2, 15, 10], [3, 15, 10], [4, 15, 10]]], ["Dan", [[0, 7, 16], [2, 7, 8], [3, 7, 8], [4, 7, 8]]], ["Eve", [[0, 20, 12], [2, 20, 12], [4, 20, 12]]]]

def day_of(h):
    return int((h - h % 24) / 24)

def days(shifts, server):
    out = []
    for s in shifts:
        k = 0
        while k < s[2]:
            h = s[0] * 24 + s[1] + k
            if server:
                d = day_of(h + 24 - zone()) - 1
            else:
                d = day_of(h)
            found = False
            for e in out:
                if e[0] == d:
                    e[1] = e[1] + 1
                    found = True
            if not found:
                out = out + [[d, 1]]
            k = k + 1
    return out

def over_days(ds):
    t = 0
    for e in ds:
        if e[1] > 8:
            t = t + e[1] - 8
    return t

def over_shifts(shifts):
    t = 0
    for s in shifts:
        if s[2] > 8:
            t = t + s[2] - 8
    return t

def total(shifts):
    t = 0
    for s in shifts:
        t = t + s[2]
    return t

def pay(hours, over):
    return (hours - over) * rate() + over * over_rate()

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.