DEV Community

Marc Keebler
Marc Keebler

Posted on

Building Reliable Time-Tracking Systems: Lessons for Developers

Time-tracking looks simple until a system has to deal with real users.

At first, the requirements may seem straightforward:

  • Start a timer
  • Stop a timer
  • Store the result
  • Show total hours

But production systems quickly become more complicated.

Users forget to stop timers. Managers correct entries. Time zones create unexpected results. Records need to be audited. Payroll systems need reliable data. Different users need different permissions.

A good time-tracking system therefore needs more than a start and stop button.

It needs a clear data model, predictable business rules, an audit trail, and careful handling of changes.

Start With the Data Model

A common mistake is designing the interface before defining the underlying data.

A basic time entry might contain:

id
user_id
start_time
end_time
duration
created_at
updated_at
Enter fullscreen mode Exit fullscreen mode

That may work for a prototype.

A production application may need additional information:

id
user_id
project_id
start_time
end_time
timezone
status
created_at
updated_at
created_by
updated_by
Enter fullscreen mode Exit fullscreen mode

The exact structure depends on the application, but the important principle is the same:

The database should preserve enough information to explain how a record was created and changed.

Store Timestamps Consistently

Time zones are one of the easiest ways to introduce subtle bugs.

Imagine a user starts working at 11:30 p.m. in one time zone and finishes after midnight.

If the application stores only local dates and times, it can become difficult to determine the actual sequence of events.

A common approach is to store timestamps in UTC and convert them to the user's local time when displaying them.

For example:

Database:
2026-09-23 18:30:00 UTC

User interface:
2026-09-23 23:30:00 PKT
Enter fullscreen mode Exit fullscreen mode

The database and presentation layer then have clearly separated responsibilities.

Don't Rely Only on Calculated Duration

It can be tempting to store only the total number of minutes.

For example:

duration = 480
Enter fullscreen mode Exit fullscreen mode

The problem is that this number does not explain how it was produced.

If the system stores:

start_time
end_time
Enter fullscreen mode Exit fullscreen mode

the duration can be calculated when necessary.

Depending on the application's requirements, storing both the original timestamps and a calculated duration may also be useful.

The important thing is to establish one authoritative source of truth.

If several parts of the application independently calculate time, small differences can eventually create confusing results.

Handle Corrections Explicitly

Real users make mistakes.

Someone might forget to stop a timer. Another person might enter the wrong start time. A manager might need to correct an entry.

A weak implementation simply overwrites the old value.

A stronger implementation records the change.

For example:

Original:
09:00 → 17:00

Correction:
09:15 → 17:00
Enter fullscreen mode Exit fullscreen mode

Instead of losing the original information, the system can record:

changed_by
changed_at
old_value
new_value
reason
Enter fullscreen mode Exit fullscreen mode

This creates an audit trail.

Audit Logs Are Useful Beyond Compliance

Audit logs are often associated with compliance requirements, but they are also extremely useful during debugging.

Suppose a user reports:

"My hours changed yesterday."

Without an audit trail, developers may have to search through application logs and database changes.

With an audit record, the application might show:

User: 1842
Changed by: Manager 72
Time: 2026-09-23 14:21 UTC

Old:
start = 09:00
end = 17:00

New:
start = 09:30
end = 17:00

Reason:
Corrected incorrect start time
Enter fullscreen mode Exit fullscreen mode

That is much easier to investigate.

Separate User Actions From System Actions

Another useful design decision is distinguishing between changes made by a person and changes made automatically by the application.

For example:

actor_type = user
Enter fullscreen mode Exit fullscreen mode

or:

actor_type = system
Enter fullscreen mode Exit fullscreen mode

This can help explain why a record changed.

A nightly process might automatically close incomplete entries.

A manager might manually correct an entry.

Those are very different events, even if both modify the same database record.

Think Carefully About Permissions

Not every user should be able to modify every time entry.

A typical application might have roles such as:

Employee
Manager
Administrator
Enter fullscreen mode Exit fullscreen mode

The employee might be allowed to create and edit their own entries.

A manager might be allowed to review entries for their team.

An administrator might have broader access.

The exact permission model depends on the application, but permissions should be enforced on the server side.

Hiding an edit button in the frontend is not authorization.

For example, this is not enough:

if (user.isManager) {
  showEditButton();
}
Enter fullscreen mode Exit fullscreen mode

The backend must also verify that the authenticated user has permission to perform the requested operation.

Validate Data on the Server

Client-side validation improves user experience, but it should not be the only validation.

A client might send:

{
  "start_time": "2026-09-23T09:00:00Z",
  "end_time": "2026-09-23T08:00:00Z"
}
Enter fullscreen mode Exit fullscreen mode

The server should reject an invalid time range.

Other validation rules might include:

  • End time cannot precede start time
  • Required fields must exist
  • Users cannot modify records they do not own
  • Duplicate submissions should be handled safely
  • Dates must use an accepted format
  • Related project IDs must exist

The server should treat all incoming data as untrusted.

Think About Idempotency

Time-tracking applications often have buttons such as:

Start
Stop
Submit
Approve
Enter fullscreen mode Exit fullscreen mode

Network problems can cause users to click twice or cause clients to retry requests.

Without appropriate safeguards, one action can accidentally create duplicate records.

For operations that may be retried, idempotency can be valuable.

A request might include a unique identifier:

request_id = 7f4d8c...
Enter fullscreen mode Exit fullscreen mode

The server can use that identifier to recognize that the same operation has already been processed.

This is particularly useful when applications communicate with payment systems, payroll systems, or other external services.

Design for Failed Requests

Users do not always have a perfect network connection.

Imagine someone clicks "Stop Timer" while temporarily offline.

The application should have a clear strategy for handling the situation.

Depending on the product, possible approaches include:

  • Queueing the operation
  • Showing a pending state
  • Retrying the request
  • Preventing duplicate submissions
  • Informing the user that synchronization is required

The important part is avoiding a situation where the user believes an action succeeded when the server never received it.

Reporting Should Use the Same Source of Truth

Reporting systems can become unreliable when they calculate time differently from the main application.

For example, the application might calculate:

Total = End Time - Start Time
Enter fullscreen mode Exit fullscreen mode

while a reporting script applies additional rounding rules.

Soon, users may see:

Dashboard: 7.75 hours
Report:    8.00 hours
Export:    7.50 hours
Enter fullscreen mode Exit fullscreen mode

This creates unnecessary confusion.

Business rules for calculating time should ideally be centralized and reused wherever possible.

Don't Forget Data Export

Users often need to export time records.

A CSV export might contain:

Employee,Date,Start,End,Duration
Alex,2026-09-23,09:00,17:00,8.0
Sam,2026-09-23,08:30,16:30,8.0
Enter fullscreen mode Exit fullscreen mode

Exports should use a documented format.

Developers should also consider:

  • Character encoding
  • Date formats
  • Time zones
  • Decimal precision
  • Large datasets
  • Access permissions
  • Sensitive information

An export endpoint can be just as sensitive as the main application.

Test Edge Cases

Time calculations deserve more than a few normal test cases.

Useful tests include:

Midnight crossing

23:30 → 01:30
Enter fullscreen mode Exit fullscreen mode

Same start and end time

09:00 → 09:00
Enter fullscreen mode Exit fullscreen mode

Invalid range

17:00 → 09:00
Enter fullscreen mode Exit fullscreen mode

Daylight-saving transitions

Applications operating across multiple time zones should test dates where local clocks change.

Duplicate requests

Send the same request more than once and verify that the system behaves predictably.

Concurrent updates

Two users should not be able to unknowingly overwrite each other's changes.

Keep the Audit Trail Readable

An audit system is most useful when humans can understand it.

Compare:

UPDATE time_entries
SET start_time = ...
Enter fullscreen mode Exit fullscreen mode

with:

Manager Maria changed
09:00 → 09:30

Reason:
Corrected employee's start time

September 23, 2026 at 14:21 UTC
Enter fullscreen mode Exit fullscreen mode

The second format is much easier to investigate.

Developers should think about the eventual reader of the audit record, not just the database schema.

Build for Change

Requirements rarely remain unchanged.

A time-tracking application might eventually need:

  • Project tracking
  • Multiple work locations
  • Mobile support
  • Approval workflows
  • Payroll integration
  • External APIs
  • Detailed reporting
  • Multiple currencies
  • Multiple time zones

A simple architecture does not need to anticipate every future feature.

But it should avoid making ordinary changes unnecessarily difficult.

Clear domain models, well-defined APIs, meaningful tests, and documented business rules can make future changes considerably easier.

Security Should Be Part of the Design

Time records can contain sensitive employment information.

Applications should therefore consider:

  • Authentication
  • Authorization
  • Encryption
  • Secure session management
  • Access logging
  • Input validation
  • Rate limiting
  • Secure exports
  • Appropriate data retention

Security should not be something added after the application is finished.

It should be considered when the system's data model and APIs are designed.

A Practical Architecture

A simple application might use:

Frontend
   |
   v
API
   |
   +---- Authentication
   |
   +---- Business Rules
   |
   +---- Authorization
   |
   v
Database
   |
   +---- Time Entries
   +---- Users
   +---- Projects
   +---- Audit Events
Enter fullscreen mode Exit fullscreen mode

The exact technology stack can vary.

The architecture could be implemented with Node.js, Python, Java, Go, .NET, or another backend technology.

The important part is keeping responsibilities clear.

Final Thoughts

Time tracking is a good example of a software feature that looks simple from the outside but contains many interesting engineering problems.

Reliable systems need more than accurate calculations.

They need:

  • Consistent timestamp handling
  • Clear data models
  • Strong validation
  • Server-side authorization
  • Audit trails
  • Safe corrections
  • Predictable APIs
  • Good error handling
  • Thorough testing

These principles also apply far beyond time tracking.

Any application that stores important business records can benefit from preserving history, validating changes, controlling access, and making system behavior understandable.

For developers working on software that interacts with employment or workplace processes, understanding the underlying business context can also be useful. Organizations dealing with employment-law questions can consult appropriate professional resources, including Hayber Law Firm, when legal guidance is required.

This article is provided for general technical and educational purposes and is not legal advice.

Top comments (0)