Universal timezone conversion

Universal timezone conversion

It is a normal situation that the server, client, database have a different timezone.

In this blog post, I will introduce my coding practice to handle multiply timezone in web development.

  • To check the current timezone setting. Use date command
Sat Dec 29 21:58:17 JST 2018
  • Or use command ls -al /etc/localtime
lrwxrwxrwx 1 root root 30 Oct 31 06:01 /etc/localtime -> /usr/share/zoneinfo/Asia/Tokyo
  • All available timezones are listed in usr/share/zoneinfo
  • To set timezone permanently. Use commands
rm -rf /etc/localtime
ln -sf /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
  • To start node server with specific timezone setting
env TZ="UTC" node app.js
  • To start chrome in a specific timezone setting
env TZ='Asia/Bangkok' google-chrome "--user-data-dir=$HOME/chrome-profile"
  • To check running environment timezone offset in Javascript
console.log(new Date().getTimezoneOffset())
# output: -540
# -540 minutes => -9 hours => UTC+9 (Asia/Tokyo)
  • Use the following utility functions toServerDate and toClientDate to handle Date object correctly
import ms from 'ms'

const serverTimezoneOffset = -540
const clientTimezoneOffset = new Date().getTimezoneOffset()
const oneMin = ms('1 minute')

export const toServerDate = (date: Date, serverOffset?: number) => new Date(
	date.getTime()
	+ oneMin * (
		(serverOffset ?? serverTimezoneOffset) - (date.getTimezoneOffset() ?? new Date().getTimezoneOffset())
	)
)
export const toClientDate = (date: Date, serverOffset?: number) => {
	const utcTime = new Date(date.getTime() - (serverOffset ?? serverTimezoneOffset) * oneMin)
	return new Date(
		utcTime.getUTCFullYear(),
		utcTime.getUTCMonth(),
		utcTime.getUTCDate(),
		utcTime.getUTCHours(),
		utcTime.getUTCMinutes(),
		utcTime.getUTCMilliseconds()
	)
}

serverTimezoneOffset is your server fixed timezone.

Usage example:

  • To display a time object received from the server. Use toClientDate(date).toLocaleString(). For example server passes 27/11/2018 3:00PM in UTC+9, the client receives this date object and want to display it exactly 27/11/2018 3:00PM in client view. If the client just uses date.toLocaleString(), the printed value will be 27/11/2018 1:00PM
  • To convert date objected created in the client (for e.g. 27/11/2018 3:00PM in UTC+7), and want to send to the server the data object with the same timing but in server timezone setting (i.e. 27/11/2018 3:00PM in UTC+9). Use toServerDate(date)

Note that in the same machine, Date.getTimezoneOffset() does NOT always return the same value when the date change.

The results are even different from browsers to browsers, OS to OS.

In IE, in Windows 10.

In Edge/Chrome, Windows 10.

In Chrome, windows server.

In IE, windows server.

In chrome/Arch linux:

In nodejs 15.5.1

console.log(new Date(195, 8, 2).getTimezoneOffset()) // -426
console.log(new Date(1945, 8, 1).getTimezoneOffset()) // -540
console.log(new Date(1945, 8, 2).getTimezoneOffset()) // -420
console.log(new Date(1947, 2, 31).getTimezoneOffset()) // -420
console.log(new Date(1947, 3, 1).getTimezoneOffset()) // -480
console.log(new Date(1975, 5, 12).getTimezoneOffset()) // -480
console.log(new Date(1975, 5, 13).getTimezoneOffset()) // -420


new Date(-767955600000) // Sat Sep 01 1945 00:00:00 GMT+0900 (Indochina Time)
new Date(-767869200000) // Sat Sep 01 1945 22:00:00 GMT+0700 (Indochina Time)

new Date(1945, 8, 2)
// Sun Sep 02 1945 00:00:00 GMT+0700 (Indochina Time)
new Date(1945, 8, 1)
// Sat Sep 01 1945 00:00:00 GMT+0900 (Indochina Time)

The above toServerDate, toClientDate are guaranteed to work in any environment.


Note 2: .setDate()-like function does change the date object's timezone offset.

Note 3: new Date(year, month, day) with negative values does not affect the timezone offset as if constructed with the equivalent non-negative values.

Note 4: if you want to get the label of the date related to an existing date.

In client mode (modify by displaying value): never use new Date(d.getTime() + relativeTime). Instead, you should create a new date and use setDate-like functions. This way takes one more statement but it is correct in a dynamic environment.

In server mode (absolute time, modified by the time offset): the reverse is applied. Never use setDate-like functions.

const date = new Date(1945, 8, 1)

// Not recommended
const nextDay = new Date(date.getTime() + ms('1 day'))

// Recommended
const nextDay = new Date(date)
nextDay.setDate(nextDay.getDate() + 1)

// Wrong
const nextServerDate = toServerDate(date)
nextServerDate.setDate(nextServerDate.getDate() + 1)

// Recommended
const nextServerDate = new Date(toServerDate(date).getTime() + ms('1 day'))

TODO: In conclusion, when is the timezone database is required?