When Time Travel Breaks Your Clock-In UI
It was early. Too early. One of those mornings where the coffee machine feels passive-aggressive and your inbox is already judging you.
I opened a WhatsApp message from one of our night shift staff:
Adeen: “I can’t clock out. The system thinks I never clocked in.”
My response?
“That’s weird. Wait… actually… oh no.”
Because I knew immediately what had happened: my clock-in system, the one I built from scratch, had never learned to think across the date boundary.
The Great Midnight Betrayal
Here’s what was happening:
-
Staff clocked in at 11 PM on June 7.
-
Came back to clock out at 7 AM on June 8.
-
The system looked at June 8 records and confidently said: “Nothing to see here. You should clock in now!”
The SQL query I had written in /attendance/status
was the culprit:
// Old logic: completely blind to yesterday
SELECT * FROM attendance
WHERE user_id = ? AND DATE(clock_in_time) = ?
My system was acting like a bouncer who checks today’s guest list and refuses to acknowledge anyone still partying from the night before.
Operation: Remember Yesterday
I rewired the logic to handle reality better—by making it check both today and yesterday:
// New logic: with actual awareness of how night shifts work
const yesterday = moment(date).subtract(1, 'day').format('YYYY-MM-DD');
const [records] = await pool.execute(
`SELECT * FROM attendance
WHERE user_id = ? AND (DATE(clock_in_time) = ? OR DATE(clock_in_time) = ?)
ORDER BY clock_in_time DESC`,
[userId, date, yesterday]
);
It was one small OR
for SQL, one giant leap for night shift workers everywhere.
Preventing Accidental Double Clock-Ins
Next up: the /attendance/clock-in
endpoint.
It used to only check if you’d clocked in today, which meant people still clocked in from yesterday could double dip.
Now, it checks if you’re clocked in—period:
const [activeClockInCheck] = await pool.execute(
`SELECT id, status, clock_in_time FROM attendance
WHERE user_id = ? AND status = 'clocked_in'
ORDER BY clock_in_time DESC LIMIT 1`
);
if (activeClockInCheck.length > 0) {
const clockInDate = moment(activeClockInCheck[0].clock_in_time).format('YYYY-MM-DD');
return res.status(400).json({
success: false,
message: clockInDate === today
? 'You are already clocked in for today'
: `You are still clocked in from ${clockInDate}. Please clock out first.`
});
}
Now the system gently but firmly stops people from creating quantum duplicates of themselves.
Long Shifts? I See You (And I’m Concerned)
Some folks work long hours—really long. So I added a friendly panic button:
if (hoursWorked > 16) {
console.warn(`WARNING: User ${userId} has been clocked in for ${hoursWorked.toFixed(2)} hours since ${activeRecord.clock_in_time}`);
}
It’s not a hard block, but it logs a big red flag in the console—because no one should be clocked in longer than an international flight.
The Today-Shift Endpoint Gets a Brain
Previously, the /attendance/today-shift
endpoint was totally confused if your shift started yesterday. I gave it a moment of clarity:
if (clockInDate === yesterday) {
relevantDate = yesterday;
isActiveNightShift = true;
}
Now it understands: if your clock-in started yesterday and you’re still clocked in, it should show you yesterday’s shift details—not today’s blank slate.
Testing Time Travel Scenarios
I ran through every twisted timeline imaginable:
-
Clocking in yesterday, clocking out today? ✅ Works.
-
Trying to double clock-in while still clocked in? ✅ Blocked.
-
Shifts longer than 16 hours? ✅ Logs a warning.
-
Regular day shift? ✅ Still works like before.
Testing it felt like building a temporal simulator. But hey—no paradoxes. So I’ll call that a win.
The Deployment: Surprisingly Smooth (?!?)
Pushed to production during a quiet hour. Held my breath.
Then came the magic moment: a security staff member clocked out after a night shift—and the button actually appeared. No drama. No confusion.
They messaged me with a single word:
“Finally.”
Yes. Finally.
Lessons From Debugging Time
-
Your system needs to understand “yesterday” just as much as “today.”
-
Night shifts aren’t an edge case—they’re half your operations.
-
Always test shifts that span two days. Or three. Or more.
-
Logging saves your sanity.
-
Even time-travel bugs can be squashed—with a well-placed SQL
OR
.
In case you missed my previous post where i began this system development: Building a Web-Based Clocking System: A 48-Hour Sprint (and a Latte or Two)