Validating Timezone Operations Implementation in dayjs
This test suite validates the timezone plugin functionality in Day.js, ensuring accurate timezone conversions, DST handling, and timezone-aware date operations. The tests compare Day.js implementations against Moment.js to verify consistent behavior across different timezone scenarios.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
iamkun/dayjs
test/plugin/timezone.test.js
import MockDate from 'mockdate'
import moment from 'moment-timezone'
import dayjs from '../../src'
import timezone from '../../src/plugin/timezone'
import customParseFormat from '../../src/plugin/customParseFormat'
import utc from '../../src/plugin/utc'
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(customParseFormat)
beforeEach(() => {
MockDate.set(new Date())
})
afterEach(() => {
MockDate.reset()
})
const NY = 'America/New_York'
const VAN = 'America/Vancouver'
const DEN = 'America/Denver'
const TOKYO = 'Asia/Tokyo'
const PARIS = 'Europe/Paris'
describe('Guess', () => {
it('return string', () => {
expect(typeof dayjs.tz.guess()).toBe('string')
})
})
describe('Parse', () => {
it('parse target time string', () => {
const newYork = dayjs.tz('2014-06-01 12:00', NY)
const MnewYork = moment.tz('2014-06-01 12:00', NY)
expect(newYork.format()).toBe('2014-06-01T12:00:00-04:00')
expect(newYork.format()).toBe(MnewYork.format())
expect(newYork.utcOffset()).toBe(-240)
expect(newYork.utcOffset()).toBe(MnewYork.utcOffset())
expect(newYork.valueOf()).toBe(1401638400000)
expect(newYork.valueOf()).toBe(MnewYork.valueOf())
})
it('parse timestamp, js Date, Day.js object', () => {
const d = new Date('2020-08-07T12:00-07:00')
const result = '2020-08-07T12:00:00-07:00'
const TjsDate = dayjs.tz(d, VAN)
const Tdayjs = dayjs.tz(dayjs(d), VAN)
const Timestamp = dayjs.tz(d.getTime(), VAN)
const Tmoment = moment.tz(d, VAN)
expect(TjsDate.format()).toBe(result)
expect(Tdayjs.format()).toBe(result)
expect(Timestamp.format()).toBe(result)
expect(Tmoment.format()).toBe(result)
})
it('parse and convert between timezones', () => {
const newYork = dayjs.tz('2014-06-01 12:00', NY)
expect(newYork.tz('America/Los_Angeles').format()).toBe('2014-06-01T09:00:00-07:00')
expect(newYork.tz('Europe/London').format()).toBe('2014-06-01T17:00:00+01:00')
})
it('preserve milliseconds', () => {
const d = dayjs(1596735327399)
const oldMs = d.millisecond()
const dTz = d.tz('America/New_York')
const newMs = dTz.millisecond()
expect(oldMs).toEqual(newMs)
})
})
describe('Convert', () => {
it('convert to target time', () => {
const losAngeles = dayjs('2014-06-01T12:00:00Z').tz('America/Los_Angeles')
const MlosAngeles = moment('2014-06-01T12:00:00Z').tz('America/Los_Angeles')
expect(losAngeles.format()).toBe('2014-06-01T05:00:00-07:00')
expect(losAngeles.format()).toBe(MlosAngeles.format())
expect(losAngeles.valueOf()).toBe(1401624000000)
expect(losAngeles.valueOf()).toBe(MlosAngeles.valueOf())
expect(losAngeles.utcOffset()).toBe(-420)
expect(losAngeles.utcOffset()).toBe(MlosAngeles.utcOffset())
})
it('convert to target time', () => {
[dayjs, moment].forEach((_) => {
const losAngeles = _('2014-06-01T12:00:00Z').tz('America/Los_Angeles')
expect(losAngeles.format()).toBe('2014-06-01T05:00:00-07:00')
expect(losAngeles.valueOf()).toBe(1401624000000)
})
})
it('convert from time with timezone to target time', () => {
const losAngelesInUTC = dayjs('2014-06-01T05:00:00-07:00').tz('UTC')
const MlosAngelesInUTC = moment('2014-06-01T05:00:00-07:00').tz('UTC')
expect(losAngelesInUTC.format()).toBe('2014-06-01T12:00:00Z')
expect(losAngelesInUTC.format()).toBe(MlosAngelesInUTC.format())
})
it('DST', () => {
[dayjs, moment].forEach((_) => {
const jun = _('2014-06-01T12:00:00Z')
const dec = _('2014-12-01T12:00:00Z')
expect(jun.tz('America/Los_Angeles').format('ha')).toBe('5am')
expect(jun.tz('America/Los_Angeles').utcOffset()).toBe(-7 * 60)
expect(dec.tz('America/Los_Angeles').format('ha')).toBe('4am')
expect(dec.tz('America/Los_Angeles').utcOffset()).toBe(-8 * 60)
expect(jun.tz(NY).format('ha')).toBe('8am')
expect(dec.tz(NY).format('ha')).toBe('7am')
expect(jun.tz(TOKYO).format('ha')).toBe('9pm')
expect(dec.tz(TOKYO).format('ha')).toBe('9pm')
expect(jun.tz('Australia/Sydney').format('ha')).toBe('10pm')
expect(dec.tz('Australia/Sydney').format('ha')).toBe('11pm')
})
})
it('format Z', () => {
[dayjs, moment].forEach((_) => {
const t = _('2020-08-06T03:48:10.258Z').tz(TOKYO)
expect(t.format('Z')).toBe('+09:00')
})
})
})
describe('DST, a time that never existed Spring Forward', () => {
// 11 March 2012, 02:00:00 clocks were
// turned forward 1 hour to 11 March 2012, 03:00:00 local
// daylight time instead.
// 02:00 -> 03:00
// 02:59 -> 03:59
it('2012-03-11 01:59:59', () => {
const s = '2012-03-11 01:59:59'
const d = dayjs.tz(s, NY)
const m = moment.tz(s, NY)
expect(d.format()).toBe('2012-03-11T01:59:59-05:00')
expect(d.format()).toBe(m.format())
expect(d.utcOffset()).toBe(-300)
expect(d.utcOffset()).toBe(m.utcOffset())
expect(d.valueOf()).toBe(1331449199000)
expect(d.valueOf()).toBe(m.valueOf())
})
it('2012-03-11 02:00:00', () => {
const s = '2012-03-11 02:00:00'
const d = dayjs.tz(s, NY)
const m = moment.tz(s, NY)
expect(d.format()).toBe('2012-03-11T03:00:00-04:00')
expect(d.format()).toBe(m.format())
expect(d.valueOf()).toBe(m.valueOf())
expect(d.valueOf()).toBe(1331449200000)
expect(d.utcOffset()).toBe(-240)
expect(d.utcOffset()).toBe(m.utcOffset())
})
it('2012-03-11 02:59:59', () => {
const s = '2012-03-11 02:59:59'
const d = dayjs.tz(s, NY)
const m = moment.tz(s, NY)
expect(d.format()).toBe('2012-03-11T03:59:59-04:00')
expect(d.format()).toBe(m.format())
expect(d.valueOf()).toBe(m.valueOf())
expect(d.valueOf()).toBe(1331452799000)
expect(d.utcOffset()).toBe(-240)
expect(d.utcOffset()).toBe(m.utcOffset())
})
it('2012-03-11 03:00:00', () => {
const s = '2012-03-11 03:00:00'
const d = dayjs.tz(s, NY)
const m = moment.tz(s, NY)
expect(d.format()).toBe('2012-03-11T03:00:00-04:00')
expect(d.format()).toBe(m.format())
expect(d.valueOf()).toBe(m.valueOf())
expect(d.valueOf()).toBe(1331449200000)
expect(d.utcOffset()).toBe(-240)
expect(d.utcOffset()).toBe(m.utcOffset())
})
})
describe('DST, a time that never existed Fall Back', () => {
// In the fall, at the end of DST
it('2012-11-04 00:59:59', () => {
const s = '2012-11-04 00:59:59';
[dayjs, moment].forEach((_) => {
const d = _.tz(s, NY)
expect(d.format()).toBe('2012-11-04T00:59:59-04:00')
expect(d.utcOffset()).toBe(-240)
expect(d.valueOf()).toBe(1352005199000)
})
})
it('2012-11-04 00:59:59', () => {
const s = '2012-11-04 00:59:59';
[dayjs, moment].forEach((_) => {
const d = _.tz(s, NY)
expect(d.format()).toBe('2012-11-04T00:59:59-04:00')
expect(d.utcOffset()).toBe(-240)
expect(d.valueOf()).toBe(1352005199000)
})
})
// there's no sense to test "2012-11-04 01:59:59 America/New_York"
// cause it's an invalid date and never exist
// and dayjs result it as "2012-11-04T01:59:00-05:00"
it('2012-11-04 02:00:00', () => {
const s = '2012-11-04 02:00:00';
[dayjs, moment].forEach((_) => {
const d = _.tz(s, NY)
expect(d.format()).toBe('2012-11-04T02:00:00-05:00')
expect(d.utcOffset()).toBe(-300)
expect(d.valueOf()).toBe(1352012400000)
})
})
})
it('DST valueOf', () => {
const day1 = '2021-11-17T09:45:00.000Z'
const d1 = dayjs.utc(day1).tz(PARIS)
const m1 = moment.tz(day1, PARIS)
expect(d1.valueOf()).toBe(m1.valueOf())
const day2 = '2021-05-17T09:45:00.000Z'
const d2 = dayjs.utc(day2).tz(PARIS)
const m2 = moment.tz(day2, PARIS)
expect(d2.valueOf()).toBe(m2.valueOf())
})
describe('set Default', () => {
it('default timezone', () => {
const dateStr = '2014-06-01 12:00'
dayjs.tz.setDefault(NY)
const newYork = dayjs.tz(dateStr)
expect(newYork.format()).toBe('2014-06-01T12:00:00-04:00')
expect(newYork.utcOffset()).toBe(-240)
expect(newYork.valueOf()).toBe(1401638400000)
expect(dayjs(dateStr).tz().format()).toBe(dayjs(dateStr).tz(NY).format())
})
it('empty timezone means local timezone', () => {
const LOCAL_TZ = dayjs.tz.guess()
const dateStr = '2014-06-01 12:00'
dayjs.tz.setDefault()
expect(dayjs(dateStr).tz().valueOf()).toBe(dayjs(dateStr).tz(LOCAL_TZ).valueOf())
expect(dayjs.tz(dateStr).valueOf()).toBe(dayjs.tz(dateStr, LOCAL_TZ).valueOf())
})
it('change default timezone', () => {
dayjs.tz.setDefault(NY)
const newYork = dayjs.tz('2014-06-01 12:00')
expect(newYork.utcOffset()).toBe(-240)
dayjs.tz.setDefault(TOKYO)
const tokyo = dayjs.tz('2014-06-01 12:00')
expect(tokyo.format()).toBe('2014-06-01T12:00:00+09:00')
expect(tokyo.format('Z')).toBe('+09:00')
expect(tokyo.valueOf()).toBe(1401591600000)
})
it('override default timezone in proto.tz', () => {
dayjs.tz.setDefault(NY)
const tokyo = dayjs.tz('2014-06-01 12:00', TOKYO)
expect(tokyo.format()).toBe('2014-06-01T12:00:00+09:00')
expect(tokyo.format('Z')).toBe('+09:00')
expect(tokyo.valueOf()).toBe(1401591600000)
})
it('override default timezone in d.tz', () => {
dayjs.tz.setDefault(NY)
const tokyo = dayjs.tz('2014-06-01 12:00', TOKYO)
expect(tokyo.format()).toBe('2014-06-01T12:00:00+09:00')
expect(tokyo.format('Z')).toBe('+09:00')
expect(tokyo.valueOf()).toBe(1401591600000)
})
})
describe('keepLocalTime', () => {
const base = dayjs.tz('2013-11-18 11:55', 'America/Toronto')
it('keepLocalTime', () => {
expect(base.tz('Europe/Berlin').format()).toBe('2013-11-18T17:55:00+01:00')
expect(base.tz('Europe/Berlin', true).format()).toBe('2013-11-18T11:55:00+01:00')
})
})
describe('Get offsetName', () => {
const dtz = dayjs.tz('2012-03-11 01:59:59', NY)
it('short', () => {
const d = dtz.offsetName('short')
const m = moment.tz('2012-03-11 01:59:59', NY).format('z')
expect(d).toBe(m)
expect(d).toBe('EST')
})
it('long', () => {
const d = dtz.offsetName('long')
expect(d).toBe('Eastern Standard Time')
})
})
describe('CustomPraseFormat', () => {
const result = 1602786600
it('normal', () => {
expect(dayjs.tz('2020/10/15 12:30', DEN).unix()).toBe(result)
})
it('custom', () => {
expect(dayjs.tz('10/15/2020 12:30', 'MM/DD/YYYY HH:mm', DEN).unix()).toBe(result)
})
})
describe('startOf and endOf', () => {
it('corrects for timezone offset in startOf', () => {
const originalDay = dayjs.tz('2010-01-01 00:00:00', NY)
const startOfDay = originalDay.startOf('day')
expect(startOfDay.valueOf()).toEqual(originalDay.valueOf())
})
it('corrects for timezone offset in endOf', () => {
const originalDay = dayjs.tz('2009-12-31 23:59:59.999', NY)
const endOfDay = originalDay.endOf('day')
expect(endOfDay.valueOf()).toEqual(originalDay.valueOf())
})
it('preserves locality when tz is called', () => {
const tzWithoutLocality = dayjs.tz('2023-02-17 00:00:00', NY)
const tzWithLocality = dayjs.tz('2023-02-17 00:00:00', NY).locale({
name: 'locale_test',
weekStart: 3
})
expect(tzWithoutLocality.startOf('week').format('YYYY-MM-DD')).toEqual('2023-02-12')
expect(tzWithLocality.startOf('week').format('YYYY-MM-DD')).toEqual('2023-02-15')
})
})
describe('UTC timezone', () => {
it('TZ with UTC with Locale', () => {
const test1 = dayjs('2000-01-01T09:00:00+09:00').tz('Asia/Seoul').locale('en')
expect(test1.hour()).toBe(9)
const test2 = dayjs('2000-01-01T09:00:00+09:00').tz('Asia/Hong_Kong').locale('en')
expect(test2.hour()).toBe(8)
const test3 = dayjs('2000-01-01T09:00:00+09:00').tz('Etc/UTC').locale('en')
expect(test3.hour()).toBe(0)
})
it('TZ with UTC', () => {
const dayjs1 = dayjs('2000-01-01T09:01:00+09:00').tz('Etc/UTC', false)
expect(dayjs1.format()).toBe('2000-01-01T00:01:00Z')
const moment1 = moment('2000-01-01T09:01:00+09:00').tz('Etc/UTC', false)
expect(moment1.format()).toBe('2000-01-01T00:01:00Z')
const dayjs2 = dayjs('2000-01-01T09:01:00+09:00').tz('Etc/UTC', true)
const moment2 = moment('2000-01-01T09:01:00+09:00').tz('Etc/UTC', true)
expect(dayjs2.format()).toBe(moment2.format())
})
})