The regulation is a reducer
Dutyclock turns the drivers' hours rules into a pure function over a log of taps, with golden tests built from the regulator's own worked examples. What it shows is advisory, and it says so.
Dutyclock is an Android app for lorry, coach and van drivers that counts down the hours the drivers' hours rules allow: the break due after 4.5 hours of driving, the 9-hour day, the 56-hour week, the 90-hour fortnight and the rest deadlines. The driver taps one of four modes and everything on screen is derived from the taps; no counter is stored, and an edit to the log recomputes the lot. The part of Regulation 561/2006 the app covers is one function from a list of segments to a state, a reducer, and the lesson carries to any software in a regulated trade: encode the rule as a pure function, use the regulator's own worked examples as golden tests, and say plainly that the output is advisory, not a record.
The numbers the regulator publishes
The GOV.UK guide to drivers' hours for goods vehicles, read on 12 October 2026 and showing an update date of 1 July 2026, states the rules in a page; the chart has the six limits to one scale. The 45-minute break may be split into at least 15 minutes followed by at least 30, in that order, and a break under 15 minutes contributes nothing. A fixed week runs from 00:00 Monday to 24:00 the following Sunday. The guide also records that from 1 July 2026 the assimilated rules, its name for Regulations 561/2006 and 165/2014 as they apply in the UK, reach light goods vehicles over 2.5 tonnes on international work, cabotage included.
Two other rule sets ride along. The GB domestic rules allow 10 hours of driving and 11 of duty in a working day, the 24 hours from the start of duty. The working time rules in the guide's Annex 2 add an average of 48 hours a week over a reference period of normally 17 weeks, or 26 under a collective or workforce agreement, a ceiling of 60 hours in any single week, and breaks of their own: no more than 6 consecutive hours of work without one, 30 minutes of breaks across 6 to 9 hours of work and 45 beyond, each at least 15 minutes.
Taps become segments
The vocabulary in Model.kt is the tachograph's: driving, other work, availability, and break or rest. A tap is an Entry, an instant and a mode that lasts until the next tap, and segmentsOf turns the entries into half-open segments, merging back-to-back stretches of one mode because a rest is one rest however many times it was tapped; duty merges only within one rule set. Of the four rule sets, EU 561/2006, AETR, the van set the model labels as in scope from 1 July 2026, and GB domestic goods, the first three share the 561/2006 limits and run the working time rules alongside. A Seed is the catch-up sheet, the state of a week already under way as the driver reads it off the tachograph. DayChoices holds the two things the rules let a driver elect, a 10-hour day and a reduced 9-hour rest, and its comment is the design in a sentence: nothing is assumed, the default is the stricter limit. The day's driving limit is 9 hours until the driver chooses otherwise, or passes nine hours with a 10-hour day unused, which limits() spends rather than call the day over; the rest owed is 11 hours, or 9 after a reduced-rest choice or a finished first part of a split rest, 3 hours or more.
The replay
Replay.kt is the reducer. It takes the seed and the segments in order and keeps the running totals every rule needs, from driving since the last qualifying break to the debts for reduced weekly rests, each due by the end of the third week after the one it began in. Its comment says what that buys: the same log always produces the same state, so any edit simply replays. RuleEngine.replay clips the history to the 20 weeks before now; the longest window any rule needs is an 18-week working time period.
From the replayed state, limits() builds a Limits record, which the code describes as every number the counters, the planner and the alerts are built from. FixedWeek.kt anchors the working time reference period on the Monday on or after 1 April, 1 August and 1 December, 17 or 18 weeks at 48 hours a week, citing section 3.6 of the Department for Transport's guidance, and counts weeks in UTC, the clock the tachograph records.
Twelve kinds of counter come out the far end: break, daily driving, daily rest, weekly rest, the 56-hour week, the 90-hour fortnight, compensation, the working time break, the 60-hour week, the 48-hour average, and GB driving and duty. Each carries the time left, the clock that moves it, driving, work, duty or the wall, and the article, regulation or Act it cites, with the guidance section. The alert planner arms at most three alarms from the list, and the drive-home planner appends a proposed drive as synthetic entries and runs the replay again to find which limit runs out first. Every write to the log re-arms the alerts, and the writes and reads together are the whole seam between the pure part and the phone.
No optional derogation is applied, not the ferry interruption, multi-manning, the coach 12-day rule or consecutive reduced weekly rests abroad, and the replay's comment calls this conservative by construction: every driving minute counts toward every EU counter, and a rest in progress never clears a debt or spends a reduced rest until it ends. The app can overcount, and what it knowingly leaves out, other work before a catch-up, is written down where the seed is read; for a countdown that ends at a roadside check, overcounting is the only acceptable error.
The regulator's examples are the tests
The engine is plain Kotlin with no Android imports, so it runs under JUnit; BreakRulesTest.kt cites section 1.4 of the GOV.UK guide and Article 7 of the regulation at the top. Five of its six tests are the guide's sentences and diagrams turned into logs; the sixth replays the product blueprint's split break, moves one tap and checks the totals follow. Fifteen minutes of work, 270 of driving and a 45-minute break leave 4 hours 30 available and no late-break finding. Two hours of driving, a 15-minute rest, two more hours and a 30 reset the clock to 4 hours 30; with the 30 first, the same minutes leave 30, because a 15 cannot be the second part. A 14-minute break counts for nothing, and 275 minutes of driving with no break produces a BREAK_LATE finding stamped at 4 hours 30 after the first tap, not at the end of the drive.
The other files follow, as the repository stood on 12 October 2026: nine tests on weekly rest, compensation and the 56 and 90, six on daily driving and rest, ten on working time and the GB rules, thirteen edge cases, eleven from a review, five each for the planner and the records, three for the alert plan, 68 under core/, plus one smoke test outside it. Most are a scenario a driver could tap and a figure the regulator printed; others cover PDF text, backups, CSV and bad input.
Advisory, and said so
The README says Dutyclock is advisory, not a tachograph, not an ELD and not a legal record, and the app repeats it at first run, in Settings, on the countdown notification and in the header of every PDF, which reads "Personal record. Not a tachograph record." The manifest declares four permissions, notifications, exact alarms, boot completion and vibration, and no network permission, so the app itself sends nothing. What leaves the phone is the PDF, CSV or backup the driver chooses to export or share, and whatever Android's own backup copies, since allowBackup is on with the default rules. The tachograph is the record the law recognises; the honest claim is that the arithmetic is right and the inputs are the driver's.
It is the shape I would use for any rule a regulator publishes with worked examples: a pure function over an append-only log, replayed rather than updated in place, the stricter reading as the default until the user elects otherwise, and the examples as golden tests. Then label the output for what it is. The code is on GitHub.
Get new posts by email
Occasional essays on engineering, AI, and building for the people technology leaves behind.
Subscribe with RSS