calendar-day.js
2.21 KB
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
/**
* 「某时区的自然日」三件套。每日领取按 America/Los_Angeles 自然日结算,商业化看板按同一口径分桶,
* 两边必须用同一份换算,否则领取日和报表日会差一天。
*/
const DAY_FORMATTERS = new Map()
function dayFormatter(timezone) {
let formatter = DAY_FORMATTERS.get(timezone)
if (formatter === undefined) {
formatter = new Intl.DateTimeFormat('en-CA', { timeZone: timezone, year: 'numeric', month: '2-digit', day: '2-digit' })
DAY_FORMATTERS.set(timezone, formatter)
}
return formatter
}
/**
* 一个时刻落在 `timezone` 的哪个自然日。
* @param {Date} instant
* @param {string} timezone IANA 时区名
* @returns {string} `YYYY-MM-DD`
*/
export function calendarDayOf(instant, timezone) {
const value = Object.fromEntries(dayFormatter(timezone).formatToParts(instant).map(part => [part.type, part.value]))
return `${value.year}-${value.month}-${value.day}`
}
/**
* `timezone` 里某个自然日的零点对应的 UTC 时刻。
* 先按 UTC 零点猜一个时刻,读出该时刻在目标时区的偏移再减回去;夏令时切换日的偏移以那一刻为准。
* @param {string} date `YYYY-MM-DD`
* @param {string} timezone IANA 时区名
* @returns {Date}
*/
export function midnightInTimezone(date, timezone) {
const guess = new Date(`${date}T00:00:00.000Z`)
const offset = new Intl.DateTimeFormat('en-US', { timeZone: timezone, timeZoneName: 'longOffset' })
.formatToParts(guess)
.find(part => part.type === 'timeZoneName')
.value
const match = /^GMT(?:([+-])(\d\d):(\d\d))?$/.exec(offset)
if (match === null) throw new Error(`Cannot resolve timezone offset: ${timezone}`)
if (match[1] === undefined) return guess
const minutes = Number(match[2]) * 60 + Number(match[3])
const signedMinutes = match[1] === '+' ? minutes : -minutes
return new Date(guess.getTime() - signedMinutes * 60 * 1000)
}
/**
* 自然日加减天数,纯日历运算,不涉及时区。
* @param {string} date `YYYY-MM-DD`
* @param {number} days
* @returns {string}
*/
export function shiftCalendarDay(date, days) {
const at = new Date(`${date}T00:00:00.000Z`)
at.setUTCDate(at.getUTCDate() + days)
return at.toISOString().slice(0, 10)
}