How to Handle Date and Time Correctly to Avoid Timezone Bugs
Working with date and time in software applications can be surprisingly tricky. A date that makes perfect sense in one timezone may be completely incorrect in another. Timezone-related bugs are a nightmare for developers. They often lead to various issues that result in confused, frustrated users and even potential financial losses. In this guide, I’ll share the learnings I’ve gathered while navigating these challenges over the years and show you how you can avoid common pitfalls when working with date and time in your applications. What We Will Cover ✅ Why Do Timezone Issues Occur ✅ Real-world examples of timezone-related problems ✅ How to test timezone differences in your browser ✅ Best practices for handling date-time correctly ✅ Best practices displaying date-time in the front-end ✅ Conclusion Why Do Timezone Issues Occur? Storing Only Dates Can Cause Issues One of the most common sources of timezone-related issues is the way dates are stored. Many systems store dates in the format YYYY-MM-DD (e.g., 2025-01-01). However, this format does not contain all the information necessary to correctly interpret the date, especially information about time zones. For example, consider an event management system: A user in London schedules an online event for 2025-01-01. The system stores just 2025-01-01 without specifying a timezone. A user in Toronto sees 2025-01-01 but assumes it is their local date. Due to timezone differences, when it's January 1 in London, it is still December 31 in Toronto. This could lead to the Toronto user missing the event entirely because they interpreted the date incorrectly. Storing Only Time Can Also Be a Problem Similarly, many systems store time separately, using formats like HH:MM (e.g., 10:00) or as seconds since midnight (eg. 36000). However, without the date and timezone context, this can cause serious issues. For example, consider a global meeting scheduling app: A user in London schedules a meeting at 10:00 (i.e 10 am). The system stores just 10:00 without specifying the timezone. A user in Toronto sees 10:00 but assumes it is their local time, which is incorrect. The meeting happens at the wrong time for the Toronto user, leading to confusion. This demonstrates why both date and time should always be stored with timezone information. Real-world examples of timezone-related problems Let's look at some more examples to understand the timezone issues better. Example 1: Server Misinterprets the Date Let's say you have a SaaS app with a subscription-based service. A user in Toronto (UTC-5) signs up to your app and sets a subscription renewal date as Jan 1, 2025. So, the value 2025-01-01 is sent to the server and stored in the database. The user expects their subscription to renew at midnight in Toronto on 2025-01-01. However, the server is in London (UTC) and thus, the server interprets 2025-01-01 in UTC timezone, which is "5 hours" earlier than Toronto time—meaning it's still 2024-12-31 in Toronto. As a result, the user will think they were charged before the set date and may lose trust in the service. Code Illustration: Server-Side (Ruby): # Server stores renewal date as "2025-01-01" in UTC renewal_date = Date.parse('2025-01-01') puts "Renewal Date (UTC): #{renewal_date}" # Output: "Renewal Date (UTC): 2025-01-01" Client-Side (JavaScript): // Server sends renewal date as "2025-01-01" (UTC) const renewalDate = "2025-01-01"; // Browser parses it in the user's local time zone (Toronto, UTC-5) const renewalDateLocal = new Date(renewalDate); console.log("Local Renewal Date:", renewalDateLocal); // Output: Tue Dec 31 2024 19:00:00 GMT-0500 (Eastern Standard Time) Example 2: Client Misinterprets the Date Let's say you have an app where students can register for an examination online. A registration deadline of 2025-01-01 is scheduled for an examination. The server, which is in London (UTC), processes the deadline as midnight UTC. A student in Toronto (UTC-5), which is 5 hours behind UTC, sees the deadline and assumes that it is midnight in Toronto time. However, the backend will stop accepting applications at midnight UTC, which is 7:00 PM on Dec 31, 2024, in Toronto. Therefore, when the student tries to register for the exam at 11:00 PM on Dec 31, 2024 (Toronto time), thinking they’re within the deadline, the system rejects the registration since the deadline has already passed in UTC. Code Illustration: Server-Side (Ruby): # Server stores deadline as "2025-01-01" in UTC deadline = Date.parse('2025-01-01') puts "Deadline (UTC): #{deadline}" # Output: "Deadline (UTC): 2025-01-01" Client-Side (JavaScript): // Server sends deadline as "2025-01-01" (UTC) const deadline = "2025-01-01"; // Browser parses it in the student's local time zone (Toronto, UTC-5) console.log(new Date(deadline)); // Output: Tue Dec 31 2024 19:00:00 GMT-0500 (Eastern Standard Time)

Working with date and time in software applications can be surprisingly tricky. A date that makes perfect sense in one timezone may be completely incorrect in another. Timezone-related bugs are a nightmare for developers. They often lead to various issues that result in confused, frustrated users and even potential financial losses.
In this guide, I’ll share the learnings I’ve gathered while navigating these challenges over the years and show you how you can avoid common pitfalls when working with date and time in your applications.
What We Will Cover
✅ Why Do Timezone Issues Occur
✅ Real-world examples of timezone-related problems
✅ How to test timezone differences in your browser
✅ Best practices for handling date-time correctly
✅ Best practices displaying date-time in the front-end
✅ Conclusion
Why Do Timezone Issues Occur?
Storing Only Dates Can Cause Issues
One of the most common sources of timezone-related issues is the way dates are stored. Many systems store dates in the format YYYY-MM-DD
(e.g., 2025-01-01
). However, this format does not contain all the information necessary to correctly interpret the date, especially information about time zones.
For example, consider an event management system:
- A user in London schedules an online event for
2025-01-01
. - The system stores just
2025-01-01
without specifying a timezone. - A user in Toronto sees
2025-01-01
but assumes it is their local date. - Due to timezone differences, when it's
January 1
in London, it is stillDecember 31
in Toronto. - This could lead to the Toronto user missing the event entirely because they interpreted the date incorrectly.
Storing Only Time Can Also Be a Problem
Similarly, many systems store time separately, using formats like HH:MM
(e.g., 10:00
) or as seconds since midnight (eg. 36000
). However, without the date and timezone context, this can cause serious issues.
For example, consider a global meeting scheduling app:
- A user in London schedules a meeting at
10:00
(i.e10 am
). - The system stores just
10:00
without specifying the timezone. - A user in Toronto sees
10:00
but assumes it is their local time, which is incorrect. - The meeting happens at the wrong time for the Toronto user, leading to confusion.
This demonstrates why both date and time should always be stored with timezone information.
Real-world examples of timezone-related problems
Let's look at some more examples to understand the timezone issues better.
Example 1: Server Misinterprets the Date
- Let's say you have a SaaS app with a subscription-based service.
- A user in
Toronto (UTC-5)
signs up to your app and sets a subscription renewal date asJan 1, 2025
. - So, the value
2025-01-01
is sent to the server and stored in the database. - The user expects their subscription to renew at midnight in Toronto on
2025-01-01
. - However, the server is in
London (UTC)
and thus, the server interprets2025-01-01
in UTC timezone, which is "5 hours" earlier than Toronto time—meaning it's still2024-12-31
in Toronto. - As a result, the user will think they were charged before the set date and may lose trust in the service.
Code Illustration:
Server-Side (Ruby):
# Server stores renewal date as "2025-01-01" in UTC
renewal_date = Date.parse('2025-01-01')
puts "Renewal Date (UTC): #{renewal_date}"
# Output: "Renewal Date (UTC): 2025-01-01"
Client-Side (JavaScript):
// Server sends renewal date as "2025-01-01" (UTC)
const renewalDate = "2025-01-01";
// Browser parses it in the user's local time zone (Toronto, UTC-5)
const renewalDateLocal = new Date(renewalDate);
console.log("Local Renewal Date:", renewalDateLocal);
// Output: Tue Dec 31 2024 19:00:00 GMT-0500 (Eastern Standard Time)
Example 2: Client Misinterprets the Date
- Let's say you have an app where students can register for an examination online.
- A registration deadline of
2025-01-01
is scheduled for an examination. - The server, which is in
London (UTC)
, processes the deadline as midnight UTC. - A student in
Toronto (UTC-5)
, which is 5 hours behind UTC, sees the deadline and assumes that it is midnight in Toronto time. - However, the backend will stop accepting applications at midnight UTC, which is
7:00 PM on Dec 31, 2024
, in Toronto. - Therefore, when the student tries to register for the exam at
11:00 PM on Dec 31, 2024 (Toronto time)
, thinking they’re within the deadline, the system rejects the registration since the deadline has already passed in UTC.
Code Illustration:
Server-Side (Ruby):
# Server stores deadline as "2025-01-01" in UTC
deadline = Date.parse('2025-01-01')
puts "Deadline (UTC): #{deadline}"
# Output: "Deadline (UTC): 2025-01-01"
Client-Side (JavaScript):
// Server sends deadline as "2025-01-01" (UTC)
const deadline = "2025-01-01";
// Browser parses it in the student's local time zone (Toronto, UTC-5)
console.log(new Date(deadline));
// Output: Tue Dec 31 2024 19:00:00 GMT-0500 (Eastern Standard Time)
How to test timezone differences in your browser
You can change the timezone for a specific tab in your browser using Chrome DevTools.
- Open Developer Tools (
F12
orCtrl + Shift + I
on Windows/Linux,Cmd + Option + I
on macOS). - Go to the Sensors:
DevTools → More tools → Sensors
. - Under the Timezone dropdown, select a different location (e.g., San Francisco or Tokyo).
- Run the following JavaScript snippet in the browser console:
console.log("Current Local Time:", new Date());
console.log("2025-01-01 in local timezone:", new Date('2025-01-01'));
Try these same values with different timezones and see what outputs you get.
Best practices for handling date-time correctly
The golden rule for handling either date or time is to always store date and time in UTC
in ISO8601
format. ISO8601
is a standard date-time format that ensures consistency across different systems and timezones. By using this format, your application will be able to represent dates and times accurately, regardless of where your users are located.
The full ISO8601
format looks like this: YYYY-MM-DDTHH:mm:ssZ
-
YYYY
: Year (4 digits) -
MM
: Month (2 digits, from 01 to 12) -
DD
: Day of the month (2 digits, from 01 to 31) -
T
: Separator between date and time -
HH
: Hour (2 digits, from 00 to 23, in 24-hour format) -
mm
: Minute (2 digits, from 00 to 59) -
ss
: Second (2 digits, from 00 to 59) -
Z
: UTC time (indicates zero timezone offset) or a specific timezone offset like +09:00 for Tokyo, or -05:00 for Toronto.
In the format YYYY-MM-DDTHH:mm:ssZ
, the Z indicates that the time is in UTC (Coordinated Universal Time)
. However, if you want to store the timestamp for a different timezone, you would replace Z with the appropriate timezone offset.
Examples of ISO8601 with Different Timezones:
- UTC (Coordinated Universal Time):
2025-01-01T00:00:00Z
- The Z at the end indicates that this time is in UTC (i.e., zero offset).
- Tokyo (Japan Standard Time - JST, UTC +9 hours):
2025-01-01T00:00:00+09:00
- The +09:00 offset indicates that this time is 9 hours ahead of UTC (Japan Standard Time).
- Toronto (Eastern Standard Time - EST, UTC -5 hours):
2025-01-01T00:00:00-05:00
- The -05:00 offset indicates that this time is 5 hours behind UTC (Eastern Standard Time).
Converting date to ISO format:
On the Server Side (Ruby):
Always use timestamps (for both date and time fields) instead of just dates or times. In Ruby, it's best to use the Time
(instead of Date
or DateTime
) class for precise timestamps, as it includes both the date and the time, along with timezone support.
birthday = Time.new # Create a time object with a specific date and time
iso_string = birthday.iso8601 # Convert to ISO8601 format
puts iso_string # Output: "2025-01-01T00:00:00+00:00"
On the Client Side (JavaScript):
Again, always use timestamps. When sending date or time to the back-end, use ISO8601
format. In JavaScript, the Date
object has a .toISOString()
method that converts it to an ISO8601
string.
Example:
const deadline = new Date(); // Get current date and time
const isoString = deadline.toISOString(); // Convert to ISO8601 format
console.log(isoString);
// Output: "2025-01-01T00:00:00.000Z"
A great feature of modern browsers is that when you send a Date
object in an HTTP request (like in a JSON payload), the browser automatically converts it to ISO8601
format using .toISOString
.
Example with HTTP Request:
const birthDay = new Date();
fetch('https://dummyjson.com/users/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
firstName: 'Anil',
lastName: 'Devkota',
age: 25,
birthDay // automatically converted to ISO format
})
})
.then(res => res.json())
.then(console.log);
If you open the browser’s network tab, you'll see that the following payload that looks something like this.
{
age: 25
birthDay: "2025-01-01T00:00:00.000Z"
firstName: "Anil"
lastName: "Devkota"
}
NOTE: The
.toISOString()
method always converts the time toUTC
, regardless of the local timezone of the user. This means that if the user is in a different timezone (e.g., Toronto, Eastern Standard Time,UTC-5
), the output of.toISOString()
will be inUTC
rather than their local time.Example:
Let's say the local time is2025-01-01T05:00:00
in Toronto (Eastern Standard Time). When you use.toISOString()
, the time will be converted to UTC (which will be2025-01-01T10:00:00.000Z
).const localTime = new Date('2025-01-01T05:00:00') const isoString = localTime.toISOString() console.log(isoString) // Output: `2025-01-01T10:00:00.000Z`
This is perfectly fine. This is what we want, a UTC timestamp. Don't get confused.
Best practices for displaying date-time in the front-end
There's no single best way to display date-time in the front-end as it largely depends on product requirements and team consensus. Different applications have different needs, whether it's prioritizing localization, clarity, or usability.
That said, I really like how Stripe
handles this. Instead of just showing a raw timestamp, Stripe displays a text value, and when you hover over it, a tooltip appears with the same timestamp in three different time zones:
Local Time – The user's browser time zone.
UTC – A universal reference, useful for consistency.
Organization Time Zone – A user-selected time zone, typically set during sign-up or in account settings.
This approach strikes a great balance between readability and flexibility, making it easy for users across different time zones to interpret the date-time correctly.
Conclusion
Handling timezones correctly is essential to avoid errors and inconsistencies in applications. By following these best practices—storing timestamps in UTC, using timezone-aware objects, and converting for display—you can ensure a smooth experience for users worldwide.