-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
index.js
150 lines (135 loc) · 4.76 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import { MIN, MS } from '../../constant'
const typeToPos = {
year: 0,
month: 1,
day: 2,
hour: 3,
minute: 4,
second: 5
}
// Cache time-zone lookups from Intl.DateTimeFormat,
// as it is a *very* slow method.
const dtfCache = {}
const getDateTimeFormat = (timezone, options = {}) => {
const timeZoneName = options.timeZoneName || 'short'
const key = `${timezone}|${timeZoneName}`
let dtf = dtfCache[key]
if (!dtf) {
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',
timeZoneName
})
dtfCache[key] = dtf
}
return dtf
}
export default (o, c, d) => {
let defaultTimezone
const makeFormatParts = (timestamp, timezone, options = {}) => {
const date = new Date(timestamp)
const dtf = getDateTimeFormat(timezone, options)
return dtf.formatToParts(date)
}
const tzOffset = (timestamp, timezone) => {
const formatResult = makeFormatParts(timestamp, timezone)
const filled = []
for (let i = 0; i < formatResult.length; i += 1) {
const { type, value } = formatResult[i]
const pos = typeToPos[type]
if (pos >= 0) {
filled[pos] = parseInt(value, 10)
}
}
const hour = filled[3]
// Workaround for the same behavior in different node version
// https://github.com/nodejs/node/issues/33027
/* istanbul ignore next */
const fixedHour = hour === 24 ? 0 : hour
const utcString = `${filled[0]}-${filled[1]}-${filled[2]} ${fixedHour}:${filled[4]}:${filled[5]}:000`
const utcTs = d.utc(utcString).valueOf()
let asTS = +timestamp
const over = asTS % 1000
asTS -= over
return (utcTs - asTS) / (60 * 1000)
}
// find the right offset a given local time. The o input is our guess, which determines which
// offset we'll pick in ambiguous cases (e.g. there are two 3 AMs b/c Fallback DST)
// https://github.com/moment/luxon/blob/master/src/datetime.js#L76
const fixOffset = (localTS, o0, tz) => {
// Our UTC time is just a guess because our offset is just a guess
let utcGuess = localTS - (o0 * 60 * 1000)
// Test whether the zone matches the offset for this ts
const o2 = tzOffset(utcGuess, tz)
// If so, offset didn't change and we're done
if (o0 === o2) {
return [utcGuess, o0]
}
// If not, change the ts by the difference in the offset
utcGuess -= (o2 - o0) * 60 * 1000
// If that gives us the local time we want, we're done
const o3 = tzOffset(utcGuess, tz)
if (o2 === o3) {
return [utcGuess, o2]
}
// If it's different, we're in a hole time.
// The offset has changed, but the we don't adjust the time
return [localTS - (Math.min(o2, o3) * 60 * 1000), Math.max(o2, o3)]
}
const proto = c.prototype
proto.tz = function (timezone = defaultTimezone, keepLocalTime) {
const oldOffset = this.utcOffset()
const date = this.toDate()
const target = date.toLocaleString('en-US', { timeZone: timezone })
const diff = Math.round((date - new Date(target)) / 1000 / 60)
let ins = d(target).$set(MS, this.$ms)
.utcOffset((-Math.round(date.getTimezoneOffset() / 15) * 15) - diff, true)
if (keepLocalTime) {
const newOffset = ins.utcOffset()
ins = ins.add(oldOffset - newOffset, MIN)
}
ins.$x.$timezone = timezone
return ins
}
proto.offsetName = function (type) {
// type: short(default) / long
const zone = this.$x.$timezone || d.tz.guess()
const result = makeFormatParts(this.valueOf(), zone, { timeZoneName: type }).find(m => m.type.toLowerCase() === 'timezonename')
return result && result.value
}
const oldStartOf = proto.startOf
proto.startOf = function (units, startOf) {
if (!this.$x || !this.$x.$timezone) {
return oldStartOf.call(this, units, startOf)
}
const withoutTz = d(this.format('YYYY-MM-DD HH:mm:ss:SSS'))
const startOfWithoutTz = oldStartOf.call(withoutTz, units, startOf)
return startOfWithoutTz.tz(this.$x.$timezone, true)
}
d.tz = function (input, arg1, arg2) {
const parseFormat = arg2 && arg1
const timezone = arg2 || arg1 || defaultTimezone
const previousOffset = tzOffset(+d(), timezone)
if (typeof input !== 'string') {
// timestamp number || js Date || Day.js
return d(input).tz(timezone)
}
const localTs = d.utc(input, parseFormat).valueOf()
const [targetTs, targetOffset] = fixOffset(localTs, previousOffset, timezone)
const ins = d(targetTs).utcOffset(targetOffset)
ins.$x.$timezone = timezone
return ins
}
d.tz.guess = function () {
return Intl.DateTimeFormat().resolvedOptions().timeZone
}
d.tz.setDefault = function (timezone) {
defaultTimezone = timezone
}
}