🕒 Fixing Time zones at Fyle — Making Time Make Sense for Everyone
From bug reports to a seamless experience: Solving timezone display issues the right way.
Hello, I’m Devendra 👋🏼 from the engineering team at Fyle. Over the past few months, I’ve been on a mission to fix one of the trickiest problems in software — time zones. Our goal? Make sure every timestamp in Fyle shows the right time for each user, based on their preferred timezone, no matter where they are in the world.
If you’ve ever tried scheduling a global meeting where everyone’s clock is different, you know how messy time zones can be. At Fyle, this challenge was real: we needed to ensure every timestamp — across expenses, reports, and comments — stayed perfectly aligned with the user’s chosen timezone, not their device clock or UTC. Here’s how we approached it, why it mattered, and how we made it work seamlessly in both Angular and legacy AngularJS.
⏳ The Challenge: A Timezone Tangle
Imagine you’re in London, but your finance team is in India. You set your Fyle app to “Asia/Kolkata” to sync with them, but the app shows timestamps in “Europe/London” or even UTC (Coordinated Universal Time — the primary global time standard used to regulate clocks and time worldwide, unaffected by time zones or daylight saving changes). Confusing, right? That’s exactly what our users faced.
Here’s why it happened:
Database: We store timestamps in UTC (standard practice for consistency).
Angular App: JavaScript’s Date object auto-converts UTC to the device’s local timezone when parsing Application Programming Interface (API) responses.
AngularJS App: Some components displayed raw UTC strings without conversion.
User Preference: Users can set a preferred time zone in “My Settings > Preferences,” which might differ from their device’s timezone.
The result? A single screen could show timestamps in local time, UTC, or a mix—causing chaos for expense comments, report comments, and action timelines.
Example: A London-based employee sets their timezone to “Asia/Kolkata.” An expense timestamp shows “10:00 AM Europe/London” in one component (Angular) but “3:30 PM UTC” in another (AngularJS). Confusion guaranteed.
📢 Customer Feedback
We received multiple customer requests to fix this — the consistent ask was:
“Show me all times in my preferred timezone from settings, not my device time or UTC.”
This was not just about aesthetics; it was about workflow accuracy.
Reviewing reports, reviewing expenses, or tracking actions all depend on precise and contextually correct timestamps.
Users expected all timestamps, everywhere — including expenses, reports, and comments — to follow that setting, regardless of where they appear in the app.
🧠 Designing the Fix
When we dug into the problem, one key design decision emerged:
Should the timestamp be shown in the timezone of:
The expense owner?
The viewer?
We went with the viewer’s timezone — because the person reading the timestamp needs the context in their preferred time, not someone else’s.
That led us to three possible approaches:
Approach 1 — Service-Level Conversion (API Layer)
Convert UTC to the employee’s preferred timezone before sending from the API response.
✅ Consistent across platforms
✅ No UI changes
❌ Any backend error affects all clients
❌ Requires backend deployments for formatting changes
Approach 2 — Component-Level Conversion (Frontend Layer)
Keep UTC from the API and convert it into each component before rendering.
✅ UI flexibility
❌ Repeated logic across components
❌ Easy to miss places → inconsistency
Approach 3 — String-Based Handling + Day.js + Framework-Specific Injection (Final Approach) ✅
Both previous approaches still risked accidental system-local conversions when using Date
.
Our breakthrough:
Keep timestamps as raw UTC strings until they are displayed.
Convert only at the last step using Day.js with the user’s preferred timezone.
Use Angular InjectionToken for global timezone access in Angular and service-level configuration in AngularJS.
Performance Impact:
The Day.js conversion runs once per timestamp at render time (centralised via a pipe/filter), making it lightweight.
Even for high-traffic views like expense timelines with hundreds of timestamps, the performance cost was negligible (<1–2ms per conversion on average in our profiling).
By centralising conversion in a pipe/filter, we avoided repetitive conversions across multiple components, further reducing unnecessary overhead.
We ran a simple benchmark by converting timestamps using Day.js with timezone in different list sizes:
List Size Conversion Calls Average Time (ms) Per-Item Avg (ms)
10 items 10 0.87 ms 0.09 ms
100 items 100 3.99 ms 0.04 ms
1,000 items 1,000 37.16 ms 0.04 ms
Benchmark Methodology
All benchmarks were run in a controlled environment to ensure reproducibility. Tests were executed using MacBook Air (Apple M3, 16 GB RAM, macOS Sequoia 15.6)
Benchmarks were conducted using Node.js v22.14.0 and Chrome 116 (headless mode). Each result represents the average of 30 consecutive runs to minimise variance. For fairness, all libraries were tested with equivalent functionality and without unnecessary imports. Day.js was benchmarked both with and without plugins, depending on the use case, with plugin inclusion explicitly noted where applicable. No external caching or precompilation was used; all measurements reflect pure runtime performance.
✅ Observation: Even for 1,000 conversions, the delay is under 200ms, which is generally unnoticeable for most viewers.
✅ Centralising conversion in a pipe/filter avoids multiple re-parsings and keeps overhead low.
You can reproduce the benchmark using this open-source script:
👉 View benchmark script on GitHub
Does Converting on Display Cause Performance Issues in Large Lists?
In most real-world Fyle views (like expense timelines or report histories), we render well under 500 timestamps at a time, so the performance hit is negligible.
For extensive lists:
We recommend combining this approach with Angular’s OnPush change detection or virtual scrolling to ensure that conversions run only for visible items.
This ensures smooth rendering even for thousands of timestamps.
🚀 Results
Unified Experience: Timestamps are consistent across expenses, reports, and comments in both Angular and AngularJS.
No More Confusion: Eliminated mismatches between local time and UTC.
Happier Users: Zero timezone-related support tickets post-fix.
Developer-Friendly: One pipe/filter reduces manual timezone handling.
📚 Key Takeaways
Avoid JavaScript’s Date Object: It auto-converts to local time, causing unexpected behaviour.
Use Strings Until Display: Keep timestamps as UTC strings to prevent premature conversion.
Centralised Logic: A single pipe/filter saves time and ensures consistency.
Hybrid Stacks Need Extra Care: Angular and AngularJS require tailored solutions.
🖼 Before vs After
💬 Final Thoughts
This wasn’t just about fixing a bug—it was about building trust. By unifying timestamps across two frameworks, we streamlined workflows, eliminated confusion, and made Fyle’s app more reliable for global teams. Next time you’re wrestling with time zones, remember: keep it simple, keep it string-based, and let Day.js do the heavy lifting.
Have a timezone horror story? Share it below! ⬇️