Skip to content

Commit

Permalink
fix: Add Experimental Timezone Plugin (#974)
Browse files Browse the repository at this point in the history
  • Loading branch information
zazzaz committed Jul 30, 2020
1 parent 6969e89 commit e69caba
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 1 deletion.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ node_js:
- '12'
- '10'
- '8'
- '6'
install:
- npm install -g codecov
- npm install
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"karma-sauce-launcher": "^1.1.0",
"mockdate": "^2.0.2",
"moment": "2.27.0",
"moment-timezone": "0.5.31",
"ncp": "^2.0.0",
"pre-commit": "^1.2.2",
"prettier": "^1.16.1",
Expand Down
71 changes: 71 additions & 0 deletions src/plugin/timezone/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
const typeToPos = {
year: 0,
month: 1,
day: 2,
hour: 3,
minute: 4,
second: 5
}

export default (o, c, d) => {
const localUtcOffset = d().utcOffset()
const tzOffset = (timestamp, timezone) => {
const date = new Date(timestamp)
const dtf = new Intl.DateTimeFormat('en-US', {
hour12: false,
timeZone: timezone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
const fotmatResult = dtf.formatToParts(date)
const filled = []
for (let i = 0; i < fotmatResult.length; i += 1) {
const { type, value } = fotmatResult[i]
const pos = typeToPos[type]

if (pos >= 0) {
filled[pos] = parseInt(value, 10)
}
}
const utcString = `${filled[0]}-${filled[1]}-${filled[2]} ${filled[3]}:${filled[4]}:${filled[5]}:000`
const utcTs = d.utc(utcString).valueOf()
let asTS = +date
const over = asTS % 1000
asTS -= over >= 0 ? over : 1000 + over
return (utcTs - asTS) / (60 * 1000)
}
const fixOffset = (localTS, o0, tz) => {
let utcGuess = localTS - (o0 * 60 * 1000)
const o2 = tzOffset(utcGuess, tz)
if (o0 === o2) {
return [utcGuess, o0]
}
utcGuess -= (o2 - o0) * 60 * 1000
const o3 = tzOffset(utcGuess, tz)
if (o2 === o3) {
return [utcGuess, o2]
}
return [localTS - (Math.min(o2, o3) * 60 * 1000), Math.max(o2, o3)]
}
const proto = c.prototype
proto.tz = function (timezone) {
const target = this.toDate().toLocaleString('en-US', { timeZone: timezone })
const diff = (this.toDate() - new Date(target)) / 1000 / 60
return d(target).utcOffset(localUtcOffset - diff, true)
}
d.tz = function (input, timezone) {
const previousoffset = tzOffset(+d(), timezone)
const localTs = d.utc(input).valueOf()
const [targetTs, targetOffset] = fixOffset(localTs, previousoffset, timezone)
const ins = d(targetTs).utcOffset(targetOffset)
return ins
}

d.tz.guess = function () {
return Intl.DateTimeFormat().resolvedOptions().timeZone
}
}
131 changes: 131 additions & 0 deletions test/plugin/timezone.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import MockDate from 'mockdate'
import moment from 'moment-timezone'
import dayjs from '../../src'
import utc from '../../src/plugin/utc'
import timezone from '../../src/plugin/timezone'

dayjs.extend(utc)
dayjs.extend(timezone)

beforeEach(() => {
MockDate.set(new Date())
})

afterEach(() => {
MockDate.reset()
})

const NY = 'America/New_York'

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 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')
})
})

describe('Convert', () => {
it('convert to target time', () => {
const losAngeles = dayjs('2014-06-01T12:00:00Z').tz('America/Los_Angeles')
const MlosAngeles = dayjs('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', () => {
const losAngeles = dayjs('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('DST', () => {
const jun = dayjs('2014-06-01T12:00:00Z')
const dec = dayjs('2014-12-01T12:00:00Z')

expect(jun.tz('America/Los_Angeles').format('ha')).toBe('5am')
expect(dec.tz('America/Los_Angeles').format('ha')).toBe('4am')
expect(jun.tz(NY).format('ha')).toBe('8am')
expect(dec.tz(NY).format('ha')).toBe('7am')
expect(jun.tz('Asia/Tokyo').format('ha')).toBe('9pm')
expect(dec.tz('Asia/Tokyo').format('ha')).toBe('9pm')
expect(jun.tz('Australia/Sydney').format('ha')).toBe('10pm')
expect(dec.tz('Australia/Sydney').format('ha')).toBe('11pm')
})
})


describe('DST, a time that never existed', () => {
// 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())
})
})
17 changes: 17 additions & 0 deletions types/plugin/timezone.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { PluginFunc } from 'dayjs'

declare const plugin: PluginFunc
export = plugin

interface DayjsTimezone {
(): Dayjs
guess(): string
}

declare module 'dayjs' {
interface Dayjs {
tz(): Dayjs
}

const tz: DayjsTimezone
}

0 comments on commit e69caba

Please sign in to comment.