Universal timezone conversion

Universal timezone conversion

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

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

  • To check 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 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 server. Use toClientDate(date).toLocaleString(). For example server passes 27/11/2018 3:00PM in UTC+9, client receives this date object and want to display 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 client (for e.g. 27/11/2018 3:00PM in UTC+7), and want to send to server the date 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 a 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 statements but it is correct in a dynamic environment.

In server mode (absolute time, modify 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?