What Does This Time Actually Mean?
A few months into working at Rose Rocket, I picked up what looked like a fairly contained bug in one of our telematics integrations. The provider was sending us the correct data, but somewhere between ingestion, transformation, and persistence, part of that information was being written incorrectly.
I fixed the writer and moved on. Then another timezone issue appeared, and then another one. They were not identical bugs, and they did not all come from the same integration, but after a few of them I started to notice something uncomfortable: I was fixing different symptoms of what looked increasingly like the same underlying problem.
That changed the question for me. Instead of asking why one timestamp was wrong, I started asking what the platform actually believed a time was. That turned out to be a much bigger problem.
When Time Became an Operations Problem
Rose Rocket is transportation management software, which means time is not passive metadata. It drives operations. Pickup and delivery windows have times. Integrations ingest and export times. Automations execute based on them. Billing, invoicing, dispatch activity, notifications, and downstream workflows can all depend on when something happened or when something is expected to happen.
So when the time is wrong, the failure does not remain within the field where it was written. Imagine an integration sends a pickup time intended to represent 9:00 AM at a stop in Vancouver. Somewhere in the write path, the timezone context is either stripped or misinterpreted. The value persists under a different assumption. Later, an automation reads that time and treats it as an absolute instant.
The automation may run hours earlier than intended. That can trigger an invoice, update another workflow, send a notification to a device, export information into an accounting system, or change what an operations team believes happened and when. The original bug is no longer simply a timestamp being off. It has become an operations problem.
We had cases where planned pickup times could be interpreted incorrectly, values such as last exported or last dispatched could become misleading across EST and PST, and integrations could propagate incorrect timezone assumptions further downstream. The blast radius eventually touched multiple integrations, several writers, search, boards, UI rendering, automations, and historical data. I identified at least six integrations where some form of the problem needed to be considered.

A bad assumption about time can move from an integration write path into operational workflows.
Finding the Real Problem
At that point, continuing to patch writers one by one felt wrong. I needed to understand why the system allowed these bugs to keep appearing.
So I started going backward to see what happened before me. I reviewed earlier bug reports, PRs, support context, internal Slack conversations, and integration behavior. I traced where datetime values entered the system, where they were transformed, which writers persisted them, and where they were later interpreted or displayed. I also started paying attention whenever timezone related issues appeared anywhere internally because I wanted to know whether these were truly independent bugs or whether the same assumption was being reproduced in different parts of the codebase.
The more I looked, the clearer the pattern became. The problem was not simply incorrect timezone conversion. The platform did not always have a consistent semantic definition of the time being stored.
Two Kinds of Time
That distinction matters because not every datetime means the same thing. Suppose I tell you that an invoice was sent at 9:00 AM. That event happened at one specific instant. Someone in Toronto can view it in Eastern Time and someone in Vancouver can view it in Pacific Time. The displayed value changes, but the event itself does not.
Now suppose I tell you that a driver is expected at a warehouse at 9:00 AM in Vancouver. That is different. The fact that it is 9:00 AM in Vancouver is part of the meaning of the value. If someone in Toronto opens the same shipment, the expected arrival should not suddenly become 12:00 PM simply because the viewer is in another timezone.
One value describes an instant. The other describes local, or wall clock, time. They may look almost identical when represented as datetimes, but semantically they are different. That became the model that helped me make sense of everything else.
Once those concepts are blurred, different parts of a system begin making their own assumptions. One writer may interpret a value as UTC. Another may resolve it against the organization’s timezone. Another may derive timezone information from a stop. A UI surface may use browser context. An integration may send local time without enough information attached to reconstruct the original instant. Each decision can look reasonable in isolation. Together, they produce a system where the meaning of a datetime depends on which code path happened to touch it.

An instant and a local time may look similar in storage, but they carry different semantics.
Why UTC Was Not Enough
The obvious answer to most timezone problems is usually to store everything in UTC. That is good advice when the value represents an instant. It is incomplete when the value represents something whose local context is part of its meaning.
A scheduled pickup is a good example. If the pickup is planned for 9:00 AM at a specific location, the system needs enough context to understand what that 9:00 AM belongs to before it can derive a useful UTC value. Sometimes we had a stop with reliable location information. Sometimes we had an address. Sometimes we had organization context. Sometimes the system simply did not have enough information at that moment to resolve the value confidently.
Making every writer derive an absolute timestamp would have pushed that contextual logic into every call site. That was already part of the problem. Using the organization’s timezone everywhere did not work either because a transportation company may be based in one region while operating loads across several others. Browser timezone was even less reliable as a source of truth because the person viewing the shipment may be nowhere near the physical location represented by the data. Simply adding timezone fields everywhere would have given us more information without answering the more important question: what does this particular time represent?
A Semantic Model for Time
The direction I eventually proposed was more declarative. Instead of allowing each writer to independently determine how to interpret a datetime, the system should describe the semantics of the value and provide the relevant context. Shared primitives could then handle the resolution consistently.
Conceptually, location dependent operational times followed a flow closer to local operational time plus location context, then timezone resolution, then a derived UTC instant. Absolute events followed the opposite direction: begin with the instant and project it into the relevant timezone for display. The difference looks small, but it moved an important decision out of individual call sites and into shared behavior.
If a value represented an instant, we treated it as an instant. If it represented a location dependent operational time, we resolved it against the relevant stop or address. If organization timezone was genuinely the best available fallback, we used it deliberately rather than allowing it to become an invisible assumption. If we did not have enough information to resolve a value confidently, the system should preserve that uncertainty rather than invent precision.
That became the basis of an RFC I authored around datetime behavior across the platform. Importantly, I was not proposing that we redesign every datetime model or attach timezone objects to every field. Part of the investigation showed that the core data shape was not necessarily the problem. The bigger problem was the behavior around it: multiple writers, converters, integration paths, and call sites each implementing their own interpretation.

The semantic model moved interpretation into shared primitives and made context explicit.
Rolling It Out Safely
This was an established production system with historical data and a large number of workflows already depending on existing datetime behavior. A large rewrite would have increased the exact risk I was trying to remove, so the rollout had to be incremental.
The first step was introducing the shared resolution primitives. We used the contexts the platform already understood, including organization, stop, and address based timezone resolution, while relying on established IANA timezone data and existing datetime libraries for regional rules and daylight saving transitions. Once those primitives existed, I could migrate writers incrementally.
The integration paths were some of the biggest sources of inconsistent behavior, so they could be audited and updated separately. Each integration change stayed small enough to review independently instead of hiding the entire migration inside one large PR. Historical values also had to be considered. Fixing future writes was useful, but it did not automatically make previously stored data correct. Where necessary, the rollout included migration and backfill considerations so that the platform did not end up with two different timezone models depending on when a record happened to be created.
Testing had to change too. A simple unit test proving that one timestamp converted into another was not enough. The behavior needed to hold across different regions, daylight saving changes, missing location data, organization fallbacks, and the relationship between wall clock values and the instants derived from them. The more useful test became: given the information available at this point in the system, can we interpret this datetime correctly? That is a different question from asking whether a timezone library can perform a conversion.
Making Time Clear in the UI
The UI became part of the same architecture problem. If the system shows a user 9:00 AM, what exactly is it asking them to believe? Is that their timezone, the organization’s timezone, the pickup location, the delivery location, or the timezone of the person who entered the information? A backend can be technically correct while still presenting ambiguous information to the person using it.
I did not want the product to hide that context. Where we knew the relevant timezone and location, the interface could expose it. Where we did not know enough to resolve a value confidently, we should not manufacture certainty simply because a clean timestamp looks better. That became one of the more important product lessons for me during the project. Correctness is not only about storing the right value. It is also about making the system’s interpretation of that value understandable to the person relying on it.
What Changed
The work stretched across multiple PRs, several revisions to the RFC, integration audits, migration planning, testing, support context, and conversations with senior and staff engineers. It took a few months because the goal was not merely to land a technically cleaner abstraction. It was to change a fundamental behavior in a production platform without destabilizing the operations already depending on it.
After the changes rolled out, the recurring class of timezone issues that originally pulled me into the investigation largely stopped appearing. The shared primitives also outlived the original bugs. Other engineers now had a common path for reasoning about datetime behavior instead of reconstructing the same decisions at every writer. The RFC remained as documentation for both the implementation and, more importantly, the semantics behind it.
The Larger Lesson
I have started thinking about that documentation differently as AI becomes more involved in how we write software. An AI coding agent can discover and reproduce patterns in a codebase extremely quickly. That is useful when the pattern is good. It is dangerous when the pattern encodes an old assumption nobody has questioned in years. If six writers all handle time differently, an agent now has six precedents to choose from. Clear primitives and explicit semantics reduce that ambiguity. They make the correct behavior easier to discover for engineers, and increasingly, for the tools engineers work with.
The most useful thing I learned from the project was therefore not a particular timezone rule. It was knowing when to stop fixing the bug in front of me. I started with one integration writing a timestamp incorrectly. The first fix was necessary. So was the next one. But eventually the repetition itself became the evidence I needed to look outside the immediate bug.
The problem was no longer where one timestamp was being written incorrectly. The real question was what the platform meant when it stored a time at all.
Originally on Substack