Skip to content

Commit e69caba

Browse files
authored
fix: Add Experimental Timezone Plugin (#974)
1 parent 6969e89 commit e69caba

File tree

5 files changed

+220
-1
lines changed

5 files changed

+220
-1
lines changed

.travis.yml

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ node_js:
77
- '12'
88
- '10'
99
- '8'
10-
- '6'
1110
install:
1211
- npm install -g codecov
1312
- npm install

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
"karma-sauce-launcher": "^1.1.0",
8888
"mockdate": "^2.0.2",
8989
"moment": "2.27.0",
90+
"moment-timezone": "0.5.31",
9091
"ncp": "^2.0.0",
9192
"pre-commit": "^1.2.2",
9293
"prettier": "^1.16.1",

src/plugin/timezone/index.js

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
const typeToPos = {
2+
year: 0,
3+
month: 1,
4+
day: 2,
5+
hour: 3,
6+
minute: 4,
7+
second: 5
8+
}
9+
10+
export default (o, c, d) => {
11+
const localUtcOffset = d().utcOffset()
12+
const tzOffset = (timestamp, timezone) => {
13+
const date = new Date(timestamp)
14+
const dtf = new Intl.DateTimeFormat('en-US', {
15+
hour12: false,
16+
timeZone: timezone,
17+
year: 'numeric',
18+
month: '2-digit',
19+
day: '2-digit',
20+
hour: '2-digit',
21+
minute: '2-digit',
22+
second: '2-digit'
23+
})
24+
const fotmatResult = dtf.formatToParts(date)
25+
const filled = []
26+
for (let i = 0; i < fotmatResult.length; i += 1) {
27+
const { type, value } = fotmatResult[i]
28+
const pos = typeToPos[type]
29+
30+
if (pos >= 0) {
31+
filled[pos] = parseInt(value, 10)
32+
}
33+
}
34+
const utcString = `${filled[0]}-${filled[1]}-${filled[2]} ${filled[3]}:${filled[4]}:${filled[5]}:000`
35+
const utcTs = d.utc(utcString).valueOf()
36+
let asTS = +date
37+
const over = asTS % 1000
38+
asTS -= over >= 0 ? over : 1000 + over
39+
return (utcTs - asTS) / (60 * 1000)
40+
}
41+
const fixOffset = (localTS, o0, tz) => {
42+
let utcGuess = localTS - (o0 * 60 * 1000)
43+
const o2 = tzOffset(utcGuess, tz)
44+
if (o0 === o2) {
45+
return [utcGuess, o0]
46+
}
47+
utcGuess -= (o2 - o0) * 60 * 1000
48+
const o3 = tzOffset(utcGuess, tz)
49+
if (o2 === o3) {
50+
return [utcGuess, o2]
51+
}
52+
return [localTS - (Math.min(o2, o3) * 60 * 1000), Math.max(o2, o3)]
53+
}
54+
const proto = c.prototype
55+
proto.tz = function (timezone) {
56+
const target = this.toDate().toLocaleString('en-US', { timeZone: timezone })
57+
const diff = (this.toDate() - new Date(target)) / 1000 / 60
58+
return d(target).utcOffset(localUtcOffset - diff, true)
59+
}
60+
d.tz = function (input, timezone) {
61+
const previousoffset = tzOffset(+d(), timezone)
62+
const localTs = d.utc(input).valueOf()
63+
const [targetTs, targetOffset] = fixOffset(localTs, previousoffset, timezone)
64+
const ins = d(targetTs).utcOffset(targetOffset)
65+
return ins
66+
}
67+
68+
d.tz.guess = function () {
69+
return Intl.DateTimeFormat().resolvedOptions().timeZone
70+
}
71+
}

test/plugin/timezone.test.js

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import MockDate from 'mockdate'
2+
import moment from 'moment-timezone'
3+
import dayjs from '../../src'
4+
import utc from '../../src/plugin/utc'
5+
import timezone from '../../src/plugin/timezone'
6+
7+
dayjs.extend(utc)
8+
dayjs.extend(timezone)
9+
10+
beforeEach(() => {
11+
MockDate.set(new Date())
12+
})
13+
14+
afterEach(() => {
15+
MockDate.reset()
16+
})
17+
18+
const NY = 'America/New_York'
19+
20+
describe('Guess', () => {
21+
it('return string', () => {
22+
expect(typeof dayjs.tz.guess()).toBe('string')
23+
})
24+
})
25+
26+
27+
describe('Parse', () => {
28+
it('parse target time string', () => {
29+
const newYork = dayjs.tz('2014-06-01 12:00', NY)
30+
const MnewYork = moment.tz('2014-06-01 12:00', NY)
31+
expect(newYork.format()).toBe('2014-06-01T12:00:00-04:00')
32+
expect(newYork.format()).toBe(MnewYork.format())
33+
expect(newYork.utcOffset()).toBe(-240)
34+
expect(newYork.utcOffset()).toBe(MnewYork.utcOffset())
35+
expect(newYork.valueOf()).toBe(1401638400000)
36+
expect(newYork.valueOf()).toBe(MnewYork.valueOf())
37+
})
38+
39+
it('parse and convert between timezones', () => {
40+
const newYork = dayjs.tz('2014-06-01 12:00', NY)
41+
expect(newYork.tz('America/Los_Angeles').format()).toBe('2014-06-01T09:00:00-07:00')
42+
expect(newYork.tz('Europe/London').format()).toBe('2014-06-01T17:00:00+01:00')
43+
})
44+
})
45+
46+
describe('Convert', () => {
47+
it('convert to target time', () => {
48+
const losAngeles = dayjs('2014-06-01T12:00:00Z').tz('America/Los_Angeles')
49+
const MlosAngeles = dayjs('2014-06-01T12:00:00Z').tz('America/Los_Angeles')
50+
expect(losAngeles.format()).toBe('2014-06-01T05:00:00-07:00')
51+
expect(losAngeles.format()).toBe(MlosAngeles.format())
52+
expect(losAngeles.valueOf()).toBe(1401624000000)
53+
expect(losAngeles.valueOf()).toBe(MlosAngeles.valueOf())
54+
expect(losAngeles.utcOffset()).toBe(-420)
55+
expect(losAngeles.utcOffset()).toBe(MlosAngeles.utcOffset())
56+
})
57+
58+
it('convert to target time', () => {
59+
const losAngeles = dayjs('2014-06-01T12:00:00Z').tz('America/Los_Angeles')
60+
expect(losAngeles.format()).toBe('2014-06-01T05:00:00-07:00')
61+
expect(losAngeles.valueOf()).toBe(1401624000000)
62+
})
63+
64+
it('DST', () => {
65+
const jun = dayjs('2014-06-01T12:00:00Z')
66+
const dec = dayjs('2014-12-01T12:00:00Z')
67+
68+
expect(jun.tz('America/Los_Angeles').format('ha')).toBe('5am')
69+
expect(dec.tz('America/Los_Angeles').format('ha')).toBe('4am')
70+
expect(jun.tz(NY).format('ha')).toBe('8am')
71+
expect(dec.tz(NY).format('ha')).toBe('7am')
72+
expect(jun.tz('Asia/Tokyo').format('ha')).toBe('9pm')
73+
expect(dec.tz('Asia/Tokyo').format('ha')).toBe('9pm')
74+
expect(jun.tz('Australia/Sydney').format('ha')).toBe('10pm')
75+
expect(dec.tz('Australia/Sydney').format('ha')).toBe('11pm')
76+
})
77+
})
78+
79+
80+
describe('DST, a time that never existed', () => {
81+
// 11 March 2012, 02:00:00 clocks were
82+
// turned forward 1 hour to 11 March 2012, 03:00:00 local
83+
// daylight time instead.
84+
// 02:00 -> 03:00
85+
// 02:59 -> 03:59
86+
87+
it('2012-03-11 01:59:59', () => {
88+
const s = '2012-03-11 01:59:59'
89+
const d = dayjs.tz(s, NY)
90+
const m = moment.tz(s, NY)
91+
expect(d.format()).toBe('2012-03-11T01:59:59-05:00')
92+
expect(d.format()).toBe(m.format())
93+
expect(d.utcOffset()).toBe(-300)
94+
expect(d.utcOffset()).toBe(m.utcOffset())
95+
expect(d.valueOf()).toBe(1331449199000)
96+
expect(d.valueOf()).toBe(m.valueOf())
97+
})
98+
it('2012-03-11 02:00:00', () => {
99+
const s = '2012-03-11 02:00:00'
100+
const d = dayjs.tz(s, NY)
101+
const m = moment.tz(s, NY)
102+
expect(d.format()).toBe('2012-03-11T03:00:00-04:00')
103+
expect(d.format()).toBe(m.format())
104+
expect(d.valueOf()).toBe(m.valueOf())
105+
expect(d.valueOf()).toBe(1331449200000)
106+
expect(d.utcOffset()).toBe(-240)
107+
expect(d.utcOffset()).toBe(m.utcOffset())
108+
})
109+
it('2012-03-11 02:59:59', () => {
110+
const s = '2012-03-11 02:59:59'
111+
const d = dayjs.tz(s, NY)
112+
const m = moment.tz(s, NY)
113+
expect(d.format()).toBe('2012-03-11T03:59:59-04:00')
114+
expect(d.format()).toBe(m.format())
115+
expect(d.valueOf()).toBe(m.valueOf())
116+
expect(d.valueOf()).toBe(1331452799000)
117+
expect(d.utcOffset()).toBe(-240)
118+
expect(d.utcOffset()).toBe(m.utcOffset())
119+
})
120+
it('2012-03-11 03:00:00', () => {
121+
const s = '2012-03-11 03:00:00'
122+
const d = dayjs.tz(s, NY)
123+
const m = moment.tz(s, NY)
124+
expect(d.format()).toBe('2012-03-11T03:00:00-04:00')
125+
expect(d.format()).toBe(m.format())
126+
expect(d.valueOf()).toBe(m.valueOf())
127+
expect(d.valueOf()).toBe(1331449200000)
128+
expect(d.utcOffset()).toBe(-240)
129+
expect(d.utcOffset()).toBe(m.utcOffset())
130+
})
131+
})

types/plugin/timezone.d.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { PluginFunc } from 'dayjs'
2+
3+
declare const plugin: PluginFunc
4+
export = plugin
5+
6+
interface DayjsTimezone {
7+
(): Dayjs
8+
guess(): string
9+
}
10+
11+
declare module 'dayjs' {
12+
interface Dayjs {
13+
tz(): Dayjs
14+
}
15+
16+
const tz: DayjsTimezone
17+
}

0 commit comments

Comments
 (0)