Here’s how we handle different timezones in Freshservice

Managing date and time fields has always been tricky. For customer engagement platforms, several of which are cloud-based and cater to customers globally, it’s especially imperative to accurately reflect the date and time no matter from which timezone a user logs in.

Consider SLAs, or service level agreements. SLAs bind customer support agents to respond to and resolve user requests and complaints, or tickets, within a specified timeframe. These timelines are usually based on the priority and urgency criteria set by the support teams.

The issue here is the support team and customers need not necessarily be from the same time zone.

Say, a customer in Singapore reports an issue at 11:00AM (GMT+8), categorising it as ‘Urgent’. The SLA for urgent issues is 15 minutes from the time it is reported. The support portal is configured in Chennai, India (GMT +5.30) time.

How should the SLA be estimated for this ticket?

The SLA for this ticket should be calculated with respect to the customer’s timezone 11:00AM + 15 mins = 11:15 AM (GMT+8).

Let this time—11:15 AM—be designated as ‘Due by’ for that ticket.

This ticket gets assigned to a support agent in Chennai timezone (GMT+5:30). [The relative time for 11:15 AM (GMT +8) in Chennai is 8:45 AM (GMT+5:30).]

So, what should be the ‘Due by’ time displayed for the support agent?

Since the agent is in the Chennai timezone when he logins in it should be displayed as ‘due by 8:45AM’.

If the ticket resolution time is not adjusted for time zones, the ‘due by’ time displayed to the agent would be 11:15 AM, which is far later than the actual SLA.

Thus, a simple timezone miss could negatively impact the support team’s performance and, more importantly, customer experience.

To ensure this doesn’t happen, it’s important that during the design phase itself we consider time zones, understand how to parse strings into date/time for persistence in datastore, and display that back to the users.

There’s also the issue of daylight savings time. Let’s look at what the terms timezone and daylight savings mean.

What is a timezone?

Time zones are geographically distributed regions across the globe that are calculated with a central point of reference called Coordinated Universal Time (or UTC).

These timezones are expressed using positive or negative offsets of UTC time. You can find the list here.

For instance, the time zone for India is Indian Standard Time, with an offset of +5.30 UTC.
That is, it is 5 hours and 30 mins ahead of UTC time.

When it is 11:30 AM in India, it is 6:00 AM in UTC time.

What is daylight savings?

Some countries such as the United States and the United Kingdom advance or retard their clocks during certain months to ensure they have more time saved during daylight. This is known as Daylight Savings.

Accordingly, the UTC offset for daylight time zones are adjusted.

The Freshworks approach

As software companies expand globally, timezone handling becomes critical to ensuring data consistency.

At Freshworks, applications such as Freshservice collect date and time information via two basic field types (as required).

Date only field

Fields that have only date without time information. The time portion of such fields will always be 12:00 AM. The value entered by one user is seen without change by other users in different timezones.

(Eg. Joining date, Date of Birth, Date of Purchase)

date only field

Date with time field

Fields that have date along with time information. This is timezone dependent. When a user in a different timezone views that value, they see it converted to their own timezone.

(Eg. Bank transaction timestamp)

date with time field

Ruby on Rails framework has a strong presence at Freshworks.

In this article, we will discuss about different time contexts in a Rails application,
how to parse date and time information, and how to convert between timezones to display date and time back to the user.

Time contexts in Rails

Application time

Every Rails application can operate in a configured timezone. Default is UTC.

In config/application.rb, we can assign the timezone as below

# application.rb
config.time_zone = 'Singapore'

Database time

By default, all time information is stored in the database in UTC. Similarly, when loading data from the database, active record does the reverse way of converting the stored UTC value to application timezone.

This can be configured using this parameter config.active_record.default_timezone in config/application.rb.

It accepts two values:

:local (current rails application timezone)
:utc (default timezone)

NOTE: It is strongly advised to default the database time to UTC.

System time

Ruby has separate libraries for Date and Time.

By default, the Time library operates in the system time and ignores the timezone information.

Here, the system time is set to IST and application time to Singapore.

#Returns the current system time
2.6.5 :007 > Time.now
=> 2019-11-12 18:00:41 +0530
#Enforces the configured timezone and returns the current time
2.6.5 :008 > Time.zone.now
=> Tue, 12 Nov 2019 20:30:44 +08 +08:00
#System time zone
2.6.5 :009 > Time.now.zone
=> "IST"
#Application time zone
2.6.5 :010 > Time.zone
=> #>

As you can see above, to apply the timezone we have to explicitly enforce Time.zone.function.

ActiveSupport library includes methods that handle zone application inline.

For instance,

#https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/time/calculations.rb
2.6.5 :012 > Time.current #Equivalent of Time.zone.now in this scenario
=> Tue, 12 Nov 2019 20:38:27 +08 +08:00
2.6.5 :013 >

Similarly, Date.current is preferable over Date.today.

Sample Run:

#Default application timezone
2.6.5 :007 > Time.zone
=> #>
#Default database timezone
2.6.5 :008 > ActiveRecord::Base.default_timezone
=> :utc
2.6.5 :009 >
#After modifying the application timezone to Singapore

2.6.5 :002 > Time.zone
=> #>
2.6.5 :003 > ActiveRecord::Base.default_timezone
=> :utc
2.6.5 :004 >
#After setting database timezone to match with application timezone
2.6.5 :003 > ActiveRecord::Base.default_timezone
=> :local
2.6.5 :004 >

Daylight savings in Rails

#During parsing, we can check if that date is as per daylight savings

2.6.5 :001 > Time.zone
=> #>
#Checks if current date is adjusted for daylight savings
2.6.5 :002 > Time.current.dst?
=> false
#Adjust the date to daylight savings period
2.6.5 :003 > test = Time.current - 4.months
=> Fri, 12 Jul 2019 09:06:17 ADT -03:00
#Checks if a given date is daylight adjusted
2.6.5 :004 > test.dst?
=> true
2.6.5 :005 >

Dates and APIs

When working with APIs, iso8601 serves as the standard format for sharing date and time information across services or applications.

2.6.5 :016 > Time.current.iso8601
=> "2019-11-12T09:18:40-04:00"

 

Timezone conversions

For our case study here, we will store all information in UTC and while rendering we will convert them to application timezone.

For simplicity, we will consider user timezone equals application timezone. These terms will be used interchangeably in the examples below.

System Time: IST
Application Time: Atlantic Time(Canada)
Database Time: UTC

Ruby supports Time.parse and Date.parse for parsing user input dates but these do not consider timezone.

You have to explicitly use Time.zone.parse/Date.zone.parse to apply timezone.

Parsing daylight savings times

#Current Application Time
2.6.5 :049 > Time.zone
=> #>
#Sample Input date from User in above timezone
2.6.5 :050 > dst_time
=> "Jul 12 2019 9:06 AM"
#Check if that date was daylight saved
2.6.5 :056 > Time.zone.parse(dst_time).dst?
=> true
#Parse the string in application time
2.6.5 :053 > Time.zone.parse(dst_time)
=> Fri, 12 Jul 2019 09:06:00 ADT -03:00 #The offset is Daylight adjusted
#Parse the string in application timezone and convert to UTC
2.6.5 :054 > utc_time = Time.zone.parse(dst_time).utc
=> 2019-07-12 12:06:00 UTC
#Converting the saved UTC date back to user timezone
2.6.5 :056 > Time.zone.parse(utc_time.to_s)
=> Fri, 12 Jul 2019 09:06:00 ADT -03:00 #It is the input date received from user

Parsing non-daylight savings time

#Sample Input date from User in above timezone
2.6.5 :070 > non_dst_time = "Jan 1 2019 9:06 AM"
=> "Jan 1 2019 9:06 AM"
#Current Application Time
2.6.5 :071 > Time.zone
=> #>
#Check if that date was daylight saved in the application timezone
2.6.5 :072 > Time.zone.parse(non_dst_time).dst?
=> false
#Parse the string in application time
2.6.5 :073 > Time.zone.parse(non_dst_time)
=> Tue, 01 Jan 2019 09:06:00 AST -04:00 #The offset is not daylight adjusted
#Parse the string in application timezone and convert to UTC
2.6.5 :074 > Time.zone.parse(non_dst_time).utc
=> 2019-01-01 13:06:00 UTC
#Converting the saved UTC date back to user timezone
2.6.5 :075 > Time.zone.parse(utc_time.to_s)
=> Tue, 01 Jan 2019 09:06:00 AST -04:00 #It is same as the input date received from user
2.6.5 :076 >

Thus, we can process the date and time information from users and render them with ease and consistency.

Happy timezoning!!