{
  "id": "P162",
  "slug": "timesheet",
  "key": "P162-timesheet",
  "title": "Timesheet",
  "summary": "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.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P162 - Timesheet\n\nA shop's week of shifts and the rule \"hours over 8 in a day are paid at\ntime and a half\", with the day read three ways.\n\n- `main.eml` - the menu, the shifts, overtime by each reading, one\n  worker's days, and a shift of your own\n- `shifts.eml` - the workers and their shifts, the hours on each day of\n  the shop or of the payroll server, and the hours over 8\n\nHow each part works:\n\n- Five workers, in the shop's time (UTC+9): day shifts, night shifts,\n  evening shifts, a double shift, and 12-hour nights. 20.00 an hour, 30.00\n  for each hour over 8.\n- As in the corpus case `the-counter-reset-at-midnight-and-the-shift-crossed-it`,\n  a count that returns to zero at midnight starts again in the middle of\n  every shift that crosses it. Eve's three 12-hour nights are 12 hours over\n  8 by the shift and none by the shop's day: midnight splits each into 4\n  and 8. By the shop's day Ben, Cleo and Eve lose 15 hours of overtime.\n- As in `the-day-boundary-was-the-servers-and-the-user-was-elsewhere`, the\n  payroll server's day is UTC, which ends at 09:00 in the shop: it keeps\n  the night shifts whole and cuts Dan's 07:00 starts instead, 2 hours.\n- Pay for the week: 5,020.00 by the shift, 4,870.00 by the shop's day,\n  5,000.00 by the server's day. A shift of 8 hours or less pays the same\n  under every reading; a longer one pays less under any reading whose\n  midnight falls inside it.\n\nWhat is checked: menu choices 1 to 5; a worker from 1 to 5; a start hour\nfrom 0 to 23 and 1 to 16 hours. An empty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the shifts and the overtime;\n- Eve's days and Dan's days;\n- your own shifts from 22:00 for 10 hours, from 23:00 for 11 and from\n  10:00 for 10.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- workers: a cancel, 0, 6 and x;\n- start hours: a cancel, 24 and x; then hours with a cancel, 0, 17 and x;\n- shifts from 02:00 for 12 hours, which only the server's midnight cuts,\n  and from 09:00 for 6.\n\nBuilt on the verified corpus cases `the-counter-reset-at-midnight-and-the-shift-crossed-it`\n(a daily count reset at midnight, two hours into the night shift) and\n`the-day-boundary-was-the-servers-and-the-user-was-elsewhere` (a daily count\nby the server's midnight, with the users nine hours ahead).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P162 timesheet: a week of shifts and \"hours over 8 in a day are paid at\n# time and a half\" - the day read as the shift, the shop's calendar day and\n# the payroll server's day; one worker's days, and a shift of your own.\nimport shifts\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    s => out\n    while len(out) < n:\n        out + \" \" => out\n    return out\n\ndef lpad(s, n):\n    s => out\n    while len(out) < n:\n        \" \" + out => out\n    return out\n\ndef digits(t):\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            return False\n    return len(t) > 0\n\ndef money(cents):\n    cents % 100 => c\n    int((cents - c) / 100) => w\n    str(w) => s\n    if w >= 1000:\n        str(int((w - w % 1000) / 1000)) + \",\" + str(w % 1000 + 1000)[1:] => s\n    if c < 10:\n        return s + \".0\" + str(c)\n    return s + \".\" + str(c)\n\ndef clock(h):\n    h % 24 => h\n    if h < 10:\n        return \"0\" + str(h) + \":00\"\n    return str(h) + \":00\"\n\ndef span(s):\n    return clock(s[1]) + \"-\" + clock(s[1] + s[2]) + \", \" + str(s[2]) + \" hours\"\n\ndef show_shifts():\n    \"The week in the shop's time, UTC+9. The payroll server keeps UTC, so its midnight is 09:00 here.\" ^0\n    \"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\n    for w in shifts.workers():\n        # shifts with the same hours, grouped: [start, hours, days]\n        [] => groups\n        for s in w[1]:\n            False => found\n            for g in groups:\n                if g[0] == s[1] and g[1] == s[2]:\n                    g[2] + \" \" + shifts.day_name(s[0]) => g[2]\n                    True => found\n            if not found:\n                groups + [[s[1], s[2], shifts.day_name(s[0])]] => groups\n        \"\" => line\n        for g in groups:\n            if line != \"\":\n                line + \"; \" => line\n            line + g[2] + \" \" + span([0, g[0], g[1]]) => line\n        (\"  \" + pad(w[0], 6) + line) ^0\n\ndef readings(w):\n    # [hours, over by shift, over by the shop's day, over by the server's day]\n    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))]\n\ndef names(list):\n    # \"Ben 2, Cleo 1 and Eve 12\"\n    \"\" => out\n    0 => i\n    while i < len(list):\n        if i > 0 and i == len(list) - 1:\n            out + \" and \" => out\n        elif i > 0:\n            out + \", \" => out\n        out + list[i] => out\n        i + 1 => i\n    return out\n\ndef show_overtime():\n    \"Hours over 8 in a day, with the day read three ways:\" ^0\n    (\"  \" + pad(\"worker\", 8) + lpad(\"hours\", 5) + lpad(\"by shift\", 10) + lpad(\"shop day\", 10) + lpad(\"server day\", 12)) ^0\n    [0, 0, 0, 0] => sums\n    [] => lost_shop\n    [] => lost_server\n    for w in shifts.workers():\n        readings(w) => r\n        (\"  \" + 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\n        0 => k\n        while k < 4:\n            sums[k] + r[k] => sums[k]\n            k + 1 => k\n        if r[2] < r[1]:\n            lost_shop + [w[0] + \" \" + str(r[1] - r[2])] => lost_shop\n        if r[3] < r[1]:\n            lost_server + [w[0] + \" \" + str(r[1] - r[3])] => lost_server\n    (\"  \" + pad(\"all\", 8) + lpad(str(sums[0]), 5) + lpad(str(sums[1]), 10) + lpad(str(sums[2]), 10) + lpad(str(sums[3]), 12)) ^0\n    (\"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\n    if len(lost_shop) > 0:\n        (\"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\n    if len(lost_server) > 0:\n        (\"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\n\ndef day_list(ds):\n    \"\" => out\n    for e in ds:\n        if out != \"\":\n            out + \", \" => out\n        out + shifts.day_name(e[0]) + \" \" + str(e[1]) => out\n    return out\n\ndef show_worker():\n    plain(input(\"worker (1 Ana, 2 Ben, 3 Cleo, 4 Dan, 5 Eve)> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return\n    if len(t) != 1 or not digits(t) or int(t) < 1 or int(t) > 5:\n        \"Type a number from 1 to 5.\" ^0\n        return\n    shifts.workers()[int(t) - 1] => w\n    \"\" => line\n    for s in w[1]:\n        if line != \"\":\n            line + \", \" => line\n        line + shifts.day_name(s[0]) + \" \" + str(s[2]) => line\n    (w[0] + \" by the shift:        \" + line + \" - \" + str(shifts.over_shifts(w[1])) + \" hours over 8\") ^0\n    shifts.days(w[1], False) => a\n    (w[0] + \" by the shop's day:   \" + day_list(a) + \" - \" + str(shifts.over_days(a)) + \" hours over 8\") ^0\n    shifts.days(w[1], True) => b\n    (w[0] + \" by the server's day: \" + day_list(b) + \" - \" + str(shifts.over_days(b)) + \" hours over 8\") ^0\n\ndef own():\n    plain(input(\"start hour (0 to 23)> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return\n    if len(t) > 2 or not digits(t) or int(t) > 23:\n        \"Type a whole number from 0 to 23.\" ^0\n        return\n    plain(input(\"hours (1 to 16)> \")) => n\n    if n == \"\":\n        \"Cancelled.\" ^0\n        return\n    if len(n) > 2 or not digits(n) or int(n) < 1 or int(n) > 16:\n        \"Type a whole number from 1 to 16.\" ^0\n        return\n    [[2, int(t), int(n)]] => one\n    readings([\"yours\", one]) => r\n    shifts.days(one, False) => a\n    shifts.days(one, True) => b\n    (\"A Wednesday shift \" + span(one[0]) + \":\") ^0\n    (\"  by the shift:        \" + str(r[1]) + \" over 8\") ^0\n    (\"  by the shop's day:   \" + day_list(a) + \" - \" + str(r[2]) + \" over 8\") ^0\n    (\"  by the server's day: \" + day_list(b) + \" - \" + str(r[3]) + \" over 8\") ^0\n    if r[1] == 0:\n        \"No more than 8 hours: no reading pays any at time and a half.\" ^0\n        return\n    if r[2] < r[1]:\n        \"Midnight in the shop cuts it in two, and each part counts from 0.\" ^0\n    if r[3] < r[1]:\n        \"The server's midnight, 09:00 here, cuts it in two.\" ^0\n    if r[2] == r[1] and r[3] == r[1]:\n        \"No midnight falls inside it: every reading pays the same.\" ^0\n\n\"== Timesheet ==\" ^0\n\"A week of shifts, and hours over 8 in a day paid at time and a half - by which day?\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show_shifts()\n    elif choice == \"2\":\n        show_overtime()\n    elif choice == \"3\":\n        show_worker()\n    elif choice == \"4\":\n        own()\n    elif choice == \"5\":\n        False => running\n    else:\n        \"Pick a number from 1 to 5.\" ^0\n\"Bye.\" ^0\n",
      "python": "import shifts\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    out = s\n    while len(out) < n:\n        out = out + \" \"\n    return out\n\ndef lpad(s, n):\n    out = s\n    while len(out) < n:\n        out = \" \" + out\n    return out\n\ndef digits(t):\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            return False\n    return len(t) > 0\n\ndef money(cents):\n    c = cents % 100\n    w = int((cents - c) / 100)\n    s = str(w)\n    if w >= 1000:\n        s = str(int((w - w % 1000) / 1000)) + \",\" + str(w % 1000 + 1000)[1:]\n    if c < 10:\n        return s + \".0\" + str(c)\n    return s + \".\" + str(c)\n\ndef clock(h):\n    h = h % 24\n    if h < 10:\n        return \"0\" + str(h) + \":00\"\n    return str(h) + \":00\"\n\ndef span(s):\n    return clock(s[1]) + \"-\" + clock(s[1] + s[2]) + \", \" + str(s[2]) + \" hours\"\n\ndef show_shifts():\n    print(\"The week in the shop's time, UTC+9. The payroll server keeps UTC, so its midnight is 09:00 here.\")\n    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.\")\n    for w in shifts.workers():\n        groups = []\n        for s in w[1]:\n            found = False\n            for g in groups:\n                if g[0] == s[1] and g[1] == s[2]:\n                    g[2] = g[2] + \" \" + shifts.day_name(s[0])\n                    found = True\n            if not found:\n                groups = groups + [[s[1], s[2], shifts.day_name(s[0])]]\n        line = \"\"\n        for g in groups:\n            if line != \"\":\n                line = line + \"; \"\n            line = line + g[2] + \" \" + span([0, g[0], g[1]])\n        print(\"  \" + pad(w[0], 6) + line)\n\ndef readings(w):\n    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))]\n\ndef names(lst):\n    out = \"\"\n    i = 0\n    while i < len(lst):\n        if i > 0 and i == len(lst) - 1:\n            out = out + \" and \"\n        elif i > 0:\n            out = out + \", \"\n        out = out + lst[i]\n        i = i + 1\n    return out\n\ndef show_overtime():\n    print(\"Hours over 8 in a day, with the day read three ways:\")\n    print(\"  \" + pad(\"worker\", 8) + lpad(\"hours\", 5) + lpad(\"by shift\", 10) + lpad(\"shop day\", 10) + lpad(\"server day\", 12))\n    sums = [0, 0, 0, 0]\n    lost_shop = []\n    lost_server = []\n    for w in shifts.workers():\n        r = readings(w)\n        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))\n        k = 0\n        while k < 4:\n            sums[k] = sums[k] + r[k]\n            k = k + 1\n        if r[2] < r[1]:\n            lost_shop = lost_shop + [w[0] + \" \" + str(r[1] - r[2])]\n        if r[3] < r[1]:\n            lost_server = lost_server + [w[0] + \" \" + str(r[1] - r[3])]\n    print(\"  \" + pad(\"all\", 8) + lpad(str(sums[0]), 5) + lpad(str(sums[1]), 10) + lpad(str(sums[2]), 10) + lpad(str(sums[3]), 12))\n    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.\")\n    if len(lost_shop) > 0:\n        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.\")\n    if len(lost_server) > 0:\n        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.\")\n\ndef day_list(ds):\n    out = \"\"\n    for e in ds:\n        if out != \"\":\n            out = out + \", \"\n        out = out + shifts.day_name(e[0]) + \" \" + str(e[1])\n    return out\n\ndef show_worker():\n    t = plain(input(\"worker (1 Ana, 2 Ben, 3 Cleo, 4 Dan, 5 Eve)> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return\n    if len(t) != 1 or not digits(t) or int(t) < 1 or int(t) > 5:\n        print(\"Type a number from 1 to 5.\")\n        return\n    w = shifts.workers()[int(t) - 1]\n    line = \"\"\n    for s in w[1]:\n        if line != \"\":\n            line = line + \", \"\n        line = line + shifts.day_name(s[0]) + \" \" + str(s[2])\n    print(w[0] + \" by the shift:        \" + line + \" - \" + str(shifts.over_shifts(w[1])) + \" hours over 8\")\n    a = shifts.days(w[1], False)\n    print(w[0] + \" by the shop's day:   \" + day_list(a) + \" - \" + str(shifts.over_days(a)) + \" hours over 8\")\n    b = shifts.days(w[1], True)\n    print(w[0] + \" by the server's day: \" + day_list(b) + \" - \" + str(shifts.over_days(b)) + \" hours over 8\")\n\ndef own():\n    t = plain(input(\"start hour (0 to 23)> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return\n    if len(t) > 2 or not digits(t) or int(t) > 23:\n        print(\"Type a whole number from 0 to 23.\")\n        return\n    n = plain(input(\"hours (1 to 16)> \"))\n    if n == \"\":\n        print(\"Cancelled.\")\n        return\n    if len(n) > 2 or not digits(n) or int(n) < 1 or int(n) > 16:\n        print(\"Type a whole number from 1 to 16.\")\n        return\n    one = [[2, int(t), int(n)]]\n    r = readings([\"yours\", one])\n    a = shifts.days(one, False)\n    b = shifts.days(one, True)\n    print(\"A Wednesday shift \" + span(one[0]) + \":\")\n    print(\"  by the shift:        \" + str(r[1]) + \" over 8\")\n    print(\"  by the shop's day:   \" + day_list(a) + \" - \" + str(r[2]) + \" over 8\")\n    print(\"  by the server's day: \" + day_list(b) + \" - \" + str(r[3]) + \" over 8\")\n    if r[1] == 0:\n        print(\"No more than 8 hours: no reading pays any at time and a half.\")\n        return\n    if r[2] < r[1]:\n        print(\"Midnight in the shop cuts it in two, and each part counts from 0.\")\n    if r[3] < r[1]:\n        print(\"The server's midnight, 09:00 here, cuts it in two.\")\n    if r[2] == r[1] and r[3] == r[1]:\n        print(\"No midnight falls inside it: every reading pays the same.\")\n\nprint(\"== Timesheet ==\")\nprint(\"A week of shifts, and hours over 8 in a day paid at time and a half - by which day?\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show_shifts()\n    elif choice == \"2\":\n        show_overtime()\n    elif choice == \"3\":\n        show_worker()\n    elif choice == \"4\":\n        own()\n    elif choice == \"5\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 5.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "shifts.eml",
      "eml": "# P162 timesheet - a shop's week of shifts and the rule \"hours over 8 in a\n# day are paid at time and a half\", read three ways. As in the corpus case\n# the-counter-reset-at-midnight-and-the-shift-crossed-it, a count that\n# returns to zero at midnight starts again in the middle of any shift that\n# crosses it, so a long night shift is two short days. And as in\n# the-day-boundary-was-the-servers-and-the-user-was-elsewhere, the payroll\n# server's midnight is UTC, nine hours behind the shop: its day ends at 09:00\n# here. Times are whole hours from Monday 00:00 in the shop.\n\ndef zone():\n    # the shop is UTC+9\n    return 9\n\ndef rate():\n    # cents an hour, and for each hour over 8\n    return 2000\n\ndef over_rate():\n    return 3000\n\ndef day_name(k):\n    # k = -1 is the Sunday before\n    return [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"][k + 1]\n\ndef workers():\n    # [name, shifts]; a shift is [day (0 = Monday), start hour, hours]\n    return [[\"Ana\", [[0, 9, 10], [1, 9, 10], [2, 9, 10], [3, 9, 10], [4, 9, 10]]],\n            [\"Ben\", [[0, 22, 10], [1, 22, 10], [2, 22, 10], [3, 22, 10], [4, 22, 10]]],\n            [\"Cleo\", [[0, 15, 10], [1, 15, 10], [2, 15, 10], [3, 15, 10], [4, 15, 10]]],\n            [\"Dan\", [[0, 7, 16], [2, 7, 8], [3, 7, 8], [4, 7, 8]]],\n            [\"Eve\", [[0, 20, 12], [2, 20, 12], [4, 20, 12]]]]\n\ndef day_of(h):\n    # the whole day hour h falls in, h >= 0\n    return int((h - h % 24) / 24)\n\ndef days(shifts, server):\n    # hours worked on each day, as [[day, hours], ...] in order: the shop's\n    # days, or the server's (UTC) days, where day -1 is the Sunday before\n    [] => out\n    for s in shifts:\n        0 => k\n        while k < s[2]:\n            s[0] * 24 + s[1] + k => h\n            if server:\n                day_of(h + 24 - zone()) - 1 => d\n            else:\n                day_of(h) => d\n            False => found\n            for e in out:\n                if e[0] == d:\n                    e[1] + 1 => e[1]\n                    True => found\n            if not found:\n                out + [[d, 1]] => out\n            k + 1 => k\n    return out\n\ndef over_days(ds):\n    # hours over 8 in each day, summed\n    0 => t\n    for e in ds:\n        if e[1] > 8:\n            t + e[1] - 8 => t\n    return t\n\ndef over_shifts(shifts):\n    0 => t\n    for s in shifts:\n        if s[2] > 8:\n            t + s[2] - 8 => t\n    return t\n\ndef total(shifts):\n    0 => t\n    for s in shifts:\n        t + s[2] => t\n    return t\n\ndef pay(hours, over):\n    # cents for a week of hours with that many over 8\n    return (hours - over) * rate() + over * over_rate()\n",
      "python": "def zone():\n    return 9\n\ndef rate():\n    return 2000\n\ndef over_rate():\n    return 3000\n\ndef day_name(k):\n    return [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"][k + 1]\n\ndef workers():\n    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]]]]\n\ndef day_of(h):\n    return int((h - h % 24) / 24)\n\ndef days(shifts, server):\n    out = []\n    for s in shifts:\n        k = 0\n        while k < s[2]:\n            h = s[0] * 24 + s[1] + k\n            if server:\n                d = day_of(h + 24 - zone()) - 1\n            else:\n                d = day_of(h)\n            found = False\n            for e in out:\n                if e[0] == d:\n                    e[1] = e[1] + 1\n                    found = True\n            if not found:\n                out = out + [[d, 1]]\n            k = k + 1\n    return out\n\ndef over_days(ds):\n    t = 0\n    for e in ds:\n        if e[1] > 8:\n            t = t + e[1] - 8\n    return t\n\ndef over_shifts(shifts):\n    t = 0\n    for s in shifts:\n        if s[2] > 8:\n            t = t + s[2] - 8\n    return t\n\ndef total(shifts):\n    t = 0\n    for s in shifts:\n        t = t + s[2]\n    return t\n\ndef pay(hours, over):\n    return (hours - over) * rate() + over * over_rate()\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n3\n\n3\n0\n3\n6\n3\nx\n4\n\n4\n24\n4\nx\n4\n9\n\n4\n9\n0\n4\n9\n17\n4\n9\nx\n4\n2\n12\n4\n9\n6\n5\n",
      "screen": "== Timesheet ==\nA week of shifts, and hours over 8 in a day paid at time and a half - by which day?\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 0\nPick a number from 1 to 5.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> x\nPick a number from 1 to 5.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 3\nworker (1 Ana, 2 Ben, 3 Cleo, 4 Dan, 5 Eve)> \nCancelled.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 3\nworker (1 Ana, 2 Ben, 3 Cleo, 4 Dan, 5 Eve)> 0\nType a number from 1 to 5.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 3\nworker (1 Ana, 2 Ben, 3 Cleo, 4 Dan, 5 Eve)> 6\nType a number from 1 to 5.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 3\nworker (1 Ana, 2 Ben, 3 Cleo, 4 Dan, 5 Eve)> x\nType a number from 1 to 5.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 4\nstart hour (0 to 23)> \nCancelled.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 4\nstart hour (0 to 23)> 24\nType a whole number from 0 to 23.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 4\nstart hour (0 to 23)> x\nType a whole number from 0 to 23.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 4\nstart hour (0 to 23)> 9\nhours (1 to 16)> \nCancelled.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 4\nstart hour (0 to 23)> 9\nhours (1 to 16)> 0\nType a whole number from 1 to 16.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 4\nstart hour (0 to 23)> 9\nhours (1 to 16)> 17\nType a whole number from 1 to 16.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 4\nstart hour (0 to 23)> 9\nhours (1 to 16)> x\nType a whole number from 1 to 16.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 4\nstart hour (0 to 23)> 2\nhours (1 to 16)> 12\nA Wednesday shift 02:00-14:00, 12 hours:\n  by the shift:        4 over 8\n  by the shop's day:   Wed 12 - 4 over 8\n  by the server's day: Tue 7, Wed 5 - 0 over 8\nThe server's midnight, 09:00 here, cuts it in two.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 4\nstart hour (0 to 23)> 9\nhours (1 to 16)> 6\nA Wednesday shift 09:00-15:00, 6 hours:\n  by the shift:        0 over 8\n  by the shop's day:   Wed 6 - 0 over 8\n  by the server's day: Wed 6 - 0 over 8\nNo more than 8 hours: no reading pays any at time and a half.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n3\n5\n3\n4\n4\n22\n10\n4\n23\n11\n4\n10\n10\n5\n",
      "screen": "== Timesheet ==\nA week of shifts, and hours over 8 in a day paid at time and a half - by which day?\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 1\nThe week in the shop's time, UTC+9. The payroll server keeps UTC, so its midnight is 09:00 here.\nHours over 8 in a day are paid at time and a half: 20.00 an hour, 30.00 for each hour over 8.\n  Ana   Mon Tue Wed Thu Fri 09:00-19:00, 10 hours\n  Ben   Mon Tue Wed Thu Fri 22:00-08:00, 10 hours\n  Cleo  Mon Tue Wed Thu Fri 15:00-01:00, 10 hours\n  Dan   Mon 07:00-23:00, 16 hours; Wed Thu Fri 07:00-15:00, 8 hours\n  Eve   Mon Wed Fri 20:00-08:00, 12 hours\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 2\nHours over 8 in a day, with the day read three ways:\n  worker  hours  by shift  shop day  server day\n  Ana        50        10        10          10\n  Ben        50        10         8          10\n  Cleo       50        10         9          10\n  Dan        40         8         8           6\n  Eve        36        12         0          12\n  all       226        50        35          48\nPay 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.\nHours 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.\nHours 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.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 3\nworker (1 Ana, 2 Ben, 3 Cleo, 4 Dan, 5 Eve)> 5\nEve by the shift:        Mon 12, Wed 12, Fri 12 - 12 hours over 8\nEve by the shop's day:   Mon 4, Tue 8, Wed 4, Thu 8, Fri 4, Sat 8 - 0 hours over 8\nEve by the server's day: Mon 12, Wed 12, Fri 12 - 12 hours over 8\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 3\nworker (1 Ana, 2 Ben, 3 Cleo, 4 Dan, 5 Eve)> 4\nDan by the shift:        Mon 16, Wed 8, Thu 8, Fri 8 - 8 hours over 8\nDan by the shop's day:   Mon 16, Wed 8, Thu 8, Fri 8 - 8 hours over 8\nDan by the server's day: Sun 2, Mon 14, Tue 2, Wed 8, Thu 8, Fri 6 - 6 hours over 8\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 4\nstart hour (0 to 23)> 22\nhours (1 to 16)> 10\nA Wednesday shift 22:00-08:00, 10 hours:\n  by the shift:        2 over 8\n  by the shop's day:   Wed 2, Thu 8 - 0 over 8\n  by the server's day: Wed 10 - 2 over 8\nMidnight in the shop cuts it in two, and each part counts from 0.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 4\nstart hour (0 to 23)> 23\nhours (1 to 16)> 11\nA Wednesday shift 23:00-10:00, 11 hours:\n  by the shift:        3 over 8\n  by the shop's day:   Wed 1, Thu 10 - 2 over 8\n  by the server's day: Wed 10, Thu 1 - 2 over 8\nMidnight in the shop cuts it in two, and each part counts from 0.\nThe server's midnight, 09:00 here, cuts it in two.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 4\nstart hour (0 to 23)> 10\nhours (1 to 16)> 10\nA Wednesday shift 10:00-20:00, 10 hours:\n  by the shift:        2 over 8\n  by the shop's day:   Wed 10 - 2 over 8\n  by the server's day: Wed 10 - 2 over 8\nNo midnight falls inside it: every reading pays the same.\n\n1) the shifts  2) overtime  3) one worker's days  4) a shift of your own  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "the-counter-reset-at-midnight-and-the-shift-crossed-it",
      "caseId": "645-the-counter-reset-at-midnight-and-the-shift-crossed-it",
      "title": "The counter reset at midnight and the shift crossed it"
    },
    {
      "slug": "the-day-boundary-was-the-servers-and-the-user-was-elsewhere",
      "caseId": "809-the-day-boundary-was-the-servers-and-the-user-was-elsewhere",
      "title": "The day boundary was the servers and the user was elsewhere"
    }
  ],
  "updated": "2026-10-19"
}
