AWRA OpsHub Search

A Night Shift Is Eight Hours of Nothing

Our attendance table stored a night shift correctly — clock in 22:00, clock out 06:00, both saved — and then every hours figure derived from it came back empty, because clock-out is a time with no date and six separate copies of the same arithmetic each decided that an end before a start has no answer. Fixed the day this was published; the account of how it happened stands.

HR & Payroll Washingtone Aura 11 min read

A worker clocks in at ten at night and out at six the next morning. Our attendance table records both times faithfully. The date is right, the status is right, the two clock values are exactly what happened. And the hours worked come back as nothing at all — not zero, not eight, but empty. The record is honest and every number computed from it is missing, and nothing anywhere reports that this has happened.

This post is about that gap, and about why it is a European post rather than a general one. Almost every market has some rule about working hours. What makes Europe different is that a court has said the record itself is the obligation — not the compliance, the record. Once that is true, a system that stores the shift and cannot measure it stops being merely imprecise.

Update, later the same day: this is fixed, and the post got a number wrong

The defect below was corrected on the day this was published. There is now one duration calculation instead of many, and an attendance record can carry the fact that its shift ended the following day — set from the roster, never inferred from the times, which is what the second half of this post argues for. Two corrections to the account itself. It said five copies of the calculation; there were six — one more sat in an interface controller filed under a different directory and was missed. And three of the six, not none, rejected a five-character time outright, which was a second defect nobody had noticed. What was not done: existing records were not backfilled, so a night shift already in your history keeps its empty figure. Everything else below stands as written, in the past tense where our own position has changed.

What the Court actually said

In May 2019 the Court of Justice of the European Union decided a case brought by a Spanish trade union against a bank. The union wanted a declaration that the employer had to set up a system recording the time worked each day by its staff. The employer's position was that national law required only that overtime be recorded and reported monthly.

The Court disagreed, and the headline on its own press release is the shortest statement of the finding: "Member States must require employers to set up a system enabling the duration of daily working time to be measured." In the operative wording, the directives concerned, read with the Charter, preclude a national law that "does not require employers to set up a system enabling the duration of time worked each day by each worker to be measured."

The sentence that should worry a systems buyer

Buried in the background to that case is a line from the Spanish employment ministry, quoted by the referring court: to determine whether overtime has been worked, it is necessary to know precisely the number of normal hours worked. That is not a legal argument, it is an arithmetic one, and it is the reason a system that captures exceptions rather than the whole day cannot answer the question it exists to answer. You cannot compute the excess without the total.

Two things about scope, and both matter. The directive binds member states rather than employers directly, and each state has implemented it in its own way and on its own timetable — so what your own obligation is, in your own country, is a question for your adviser and not for us. And the ruling is about measurement, not about clocking machines. Nothing in it requires a particular technology. It requires that the duration of each worker's daily working time be capable of being measured, which is a statement about data.

Our own schema, stated exactly

We will use our own product as the worked example, as every post in this corpus does, because a piece about recording hours that only assessed other vendors would be worth nothing.

Attendance is one row per employee per day. The uniqueness is enforced in the database, on the combination of organization, employee and date. Each row holds a status, a clock-in, a clock-out, an hours figure, and a source recording how it was captured — typed by hand, taken from a clock-in, imported, or generated by the system.

The clock-in and clock-out columns are times. Not timestamps — times. There is a date on the row, and there is no date on either clock value. For a day shift this is a reasonable economy and costs nothing. For a shift that crosses midnight, the clock-out is a time that appears to be earlier than the clock-in, and the only thing that would resolve the ambiguity is the date that was never stored on it.

So the arithmetic had to make a choice, and it made the same one in six places:

Where the hours were computed What it did when the end was not after the start
The attendance screen Returned nothing
The self-service screen an employee uses Returned nothing
The public clock-in endpoint Returned nothing
The regularization flow, where a correction is approved Returned nothing
The bulk import Returned nothing
The biometric clock-out endpoint Returned nothing

Six separate private methods, one per controller, each written out again rather than shared. They were not identical in style and they were identical in outcome. The correction path returned nothing too, which is the detail worth pausing on: the flow that exists specifically to fix a wrong attendance record could not fix this one, because it recomputed the hours the same way.

And the copies had drifted, which is the ordinary fate of duplicated arithmetic. Three of the six parsed a time strictly as hours, minutes and seconds, so given a five-character value like 09:00 they threw, were caught, and returned nothing — for a reason that had nothing to do with midnight. One of those three was the bulk import, which is precisely where a five-character time arrives, because it comes from a spreadsheet somebody typed. That was a second defect sitting inside the first, and nobody had noticed it because its symptom was identical.

Why null is worse than zero here

If the figure came back as zero, somebody would notice. A zero in an hours column next to a present status is visibly wrong, and it would be queried the first week.

It comes back as null, and the reports add the column up. A null contributes nothing to a sum and leaves no hole behind it — the total is simply smaller than the truth by exactly the hours of every night shift in the period, and there is no row, no flag and no count anywhere that says so. An operation running two day shifts and one night shift sees a total that is quietly missing a third of its labour, and sees it as a number rather than as an error.

The record is right. Every figure derived from it is missing. Nothing reports the difference.

The second disagreement, which is smaller and stranger

A shift definition in our product carries a start, an end, and a number of break minutes. The expected hours for that shift are computed as the span minus the break — so a nine-hour shift with a one-hour unpaid break expects eight.

Actual attendance hours are computed as clock-out minus clock-in, and nothing is subtracted. The break is a property of the planned shift and has no representation in what was recorded. So the two hour figures in the same module are not the same measure: one is net of a break that the other includes, and any comparison between planned and actual carries that difference silently.

And the expected-hours calculation makes the midnight decision too, in its own way — it yields zero rather than null for a shift whose end is not after its start. A night shift therefore has an expected duration of nought hours and an actual duration of nothing, which are two different kinds of absence describing the same eight hours of work.

What is genuinely here

It would be easy to read the above as "no attendance capability", and that would be wrong in a way that matters to anyone weighing the size of the fix.

  • One row per employee per day, with database-enforced uniqueness, so the same day cannot be recorded twice.
  • A source on every row distinguishing a hand-typed record from a clocked one from an imported one. The provenance of the figure is stored, which is the part most systems leave out.
  • A per-date shift roster that overrides an employee's default assignment, so rotating and night rosters are expressible even though their hours are not computable.
  • A regularization flow with an approval, so corrections are reviewed rather than silently applied.
  • Monthly timesheet periods that can be approved and then locked, freezing a snapshot; payroll consumes only locked periods, so a figure cannot move after it has been paid on.

That last one is a genuinely good design and it is worth saying plainly: the thing most often missing in attendance systems — a defensible boundary between an open period that recomputes and a closed one that does not — is present and enforced.

What had to change, and what was done

The fix was smaller than the consequences suggest, which is usually the case with defects of this shape. Two of the four below were done on the day this went up; two were not, and are still open.

  1. Give the clock-out a date, or give the row a rule — DONE

    The record now carries an explicit fact that the shift ended on the following day, rather than the arithmetic guessing from the times. That was the smaller of the two honest options; storing full timestamps is the cleaner one and remains available. What we would not do is infer it, because an inference cannot tell a night shift from a typo — and the flag is set from the roster, meaning from a shift somebody defined and reviewed, not from the pair of times on the row itself.

  2. Compute the duration once — DONE

    Six copies of one calculation is why this was a six-place defect rather than a one-place one, and why the correction path inherited it from the capture path. There is now a single implementation called from all six, so the midnight handling is one decision in one place. It also ended the second defect the copies had drifted into: the shared version accepts both time formats, where three of the six accepted only one.

  3. Decide what a break does to a recorded day — STILL OPEN

    Planned hours net the break and actual hours do not. Either is defensible; having both in one module is not. This is a question about what the figure means before it is a question about code, which is why it was not swept into a defect fix.

  4. Make an unmeasurable record say so — STILL OPEN

    A null is still possible — for a missing time, an unreadable one, or a night shift with no roster behind it — and nothing counts them. A period total that silently excludes hours is worse than one reporting "142 hours across 19 days, 3 days not measurable". The second is a smaller number and a truthful one.

One more thing was not done, deliberately. Existing records were not backfilled. A night shift already in your history keeps its empty figure, because working out which old rows were night shifts and which were transposed times is exactly the inference the design refuses — and it would rewrite hours that payroll may already have been run on.

Questions worth asking any vendor, including us

Show me a shift that starts at 22:00 and ends at 06:00, saved, and then show me the hours figure and the period total that includes it.

What you will hear

Eight hours on the record and eight hours added to the total.

How to read it

This is a ninety-second test and it is the only one on this list you cannot be talked out of. Ask to watch it done rather than to be told the answer.

Is the hours figure stored, or computed when the page is drawn?

What you will hear

A direct answer either way, and an explanation of what happens when a clock time is corrected afterwards.

How to read it

A stored figure that is not recomputed on correction will disagree with its own clock values, and the disagreement is invisible because both are on the same row.

Where does a break live — on the shift, on the day, or on neither?

What you will hear

One place, named.

How to read it

If planned and actual hours treat the break differently, every variance report is measuring two things at once.

Can the record tell me whether a figure was clocked, typed or imported?

What you will hear

A field, shown on the record itself rather than in an audit log.

How to read it

The ruling above is about a system that measures. A number somebody typed at the end of the month is not a measurement, and if the record cannot distinguish the two then nobody reviewing it can either.

What stops a figure moving after payroll has been run on it?

What you will hear

A lock, with a date and a person against it.

How to read it

Without one, every historical hours figure is a live query result rather than a record of what was paid.

The straight answer

What AWRA OpsHub does today

  • Daily attendance records with clock-in, clock-out, status and capture source, one per employee per day, enforced in the database.
  • Shifts with start, end, break minutes and a grace period, assignable per employee and overridable per date for rotating rosters.
  • An attendance regularization flow with an approval step.
  • Monthly timesheet periods that approve and then lock, with a frozen snapshot that payroll consumes.
  • Attendance sites and verification, so a clock-in can be tied to a place.
  • Hours for a shift that crosses midnight, since 2026-08-06 — the day this post was published describing their absence. The record carries the crossing as a fact set from the roster, and one shared calculation honours it everywhere. Records created before that date were not backfilled and keep their empty figure.
  • One duration calculation, replacing six copies that had drifted apart, three of which also rejected a five-character time outright.

What it does not do

  • Any hours figure for a night shift worked by somebody with no shift on the roster for that date — there is nothing to read the crossing from, so the value is still null.
  • Any count of unmeasurable days on a period total, so a null still reduces a total silently rather than being reported beside it.
  • Any deduction of break time from recorded hours, so planned and actual hours are still not the same measure.
  • Evaluation of daily or weekly rest periods, maximum weekly averages, or any statutory working-time limit for any country.
  • Any European payroll engine. Labour cost attribution to projects and cost centres exists; statutory computation does not.

Not ours, by choice

  • Nothing here is legal advice, and this post deliberately does not say what any employer must do. The directive binds member states, implementation differs by country, and the question of what applies to you is your adviser's.
  • We have no reference customer in Europe for attendance or for anything else.

The midnight fix was a defect repair rather than a feature and we described it that way rather than selling it — one shared calculation replacing six copies, and the crossing recorded on the row from the roster. It was done on 2026-08-06, the day this post went up, and at no charge, because a correction is not a feature. A break rule applied consistently to planned and actual hours is the same size and is still open; it is a question about what the figure means before it is a question about code, which is why it was not swept in alongside a defect fix. A period total that reports unmeasurable days instead of omitting them is smaller again and also open. Statutory working-time evaluation — rest periods, weekly averages, national limits — is a real build for a named country and we would scope and price it on the usual terms: a written specification, a timeline and a price agreed before anything starts. The precedent that market-specific work gets finished here is Kenya, where a live tax-authority integration and a maintained statutory payroll engine are both ours to run.

We found this by reading a European ruling against our own schema, not because anybody reported it. A night shift had looked correct on the screen the entire time. Publishing it before fixing it was deliberate, and so is leaving the description standing now that it is fixed — the gap between the two dates is a few hours and both are on the page.

Our take

A record that is stored perfectly and measured not at all is a specific kind of failure, and it is the kind that survives a demo. The shift looks right on the screen. The month looks plausible in the report. The number is smaller than the truth by every night worked, and the system has no way of telling anyone. If you run nights, the test is ninety seconds long and we have written it out above — run it on us, and run it on whoever else is on your list. Ours passes it now; it did not when this was written, and the reason we know is that nobody had run it.

Weighing this for an operation that runs nights

Tell us what your shift pattern actually looks like and we will tell you which of the above bites you and which does not. If the answer is that we are not ready for you yet, we would rather say it now than after an implementation.

See plans & pricing

Help Center

Need a quick answer while you read?

Run inventory, procurement, assets, sales, and field work with approved AWRA guidance for setup, migration, integrations, security, pricing, and support.

Search all approved AWRA public help articles.

Open Help Center