-
Notifications
You must be signed in to change notification settings - Fork 292
/
DateHelper.ts
247 lines (216 loc) · 7.18 KB
/
DateHelper.ts
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
import dayjs from 'dayjs';
import weekOfYear from 'dayjs/plugin/weekOfYear';
import dayOfYear from 'dayjs/plugin/dayOfYear';
import weekday from 'dayjs/plugin/weekday';
import minMax from 'dayjs/plugin/minMax';
import isoWeeksInYear from 'dayjs/plugin/isoWeeksInYear';
import isoWeek from 'dayjs/plugin/isoWeek';
import isLeapYear from 'dayjs/plugin/isLeapYear';
import advancedFormat from 'dayjs/plugin/advancedFormat';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import localeData from 'dayjs/plugin/localeData';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import updateLocale from 'dayjs/plugin/updateLocale';
import type { ManipulateType, PluginFunc, Ls } from 'dayjs';
import type { OptionsType } from '../options/Options';
import type { Timestamp, DomainType } from '../index';
dayjs.extend(weekOfYear);
dayjs.extend(isoWeeksInYear);
dayjs.extend(isoWeek);
dayjs.extend(isLeapYear);
dayjs.extend(dayOfYear);
dayjs.extend(weekday);
dayjs.extend(minMax);
dayjs.extend(advancedFormat);
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(localeData);
dayjs.extend(localizedFormat);
dayjs.extend(updateLocale);
const DEFAULT_LOCALE = 'en';
export default class DateHelper {
locale: OptionsType['date']['locale'];
timezone: string;
constructor() {
this.locale = DEFAULT_LOCALE;
this.timezone = dayjs.tz.guess();
if (typeof window === 'object') {
(window as any).dayjs ||= dayjs;
}
}
async setup({ options }: { options: OptionsType }) {
this.timezone = options.date.timezone || dayjs.tz.guess();
const userLocale = options.date.locale;
if (typeof userLocale === 'string' && userLocale !== DEFAULT_LOCALE) {
let locale;
if (typeof window === 'object') {
locale =
(window as any)[`dayjs_locale_${userLocale}`] ||
(await this.loadBrowserLocale(userLocale));
} else {
locale = await this.loadNodeLocale(userLocale);
}
dayjs.locale(userLocale);
this.locale = locale;
}
if (typeof userLocale === 'object') {
if (userLocale.hasOwnProperty('name')) {
dayjs.locale(userLocale.name, userLocale);
this.locale = userLocale;
} else {
this.locale = dayjs.updateLocale(DEFAULT_LOCALE, userLocale);
}
}
}
// eslint-disable-next-line class-methods-use-this
extend(dayjsPlugin: PluginFunc): dayjs.Dayjs {
return dayjs.extend(dayjsPlugin);
}
/**
* Return the week number, relative to its month
*
* @param {number|Date} d Date or timestamp in milliseconds
* @returns {number} The week number, relative to the month [0-5]
*/
getMonthWeekNumber(d: Timestamp | dayjs.Dayjs): number {
const dayjsDate = this.date(d);
const date = dayjsDate.startOf('day');
const endOfWeek = dayjsDate.startOf('month').endOf('week');
if (date <= endOfWeek) {
return 1;
}
return Math.ceil(date.diff(endOfWeek, 'weeks', true)) + 1;
}
/**
* Return the number of weeks in the given month
*
* As there is no fixed standard to specify which month a partial week should
* belongs to, the ISO week date standard is used, where:
* - the first week of the month should have at least 4 days
*
* @see https://en.wikipedia.org/wiki/ISO_week_date
*
* @param {Timestamp | dayjs.Dayjs} d Datejs object or timestamp
* @return {number} The number of weeks
*/
getWeeksCountInMonth(d: Timestamp | dayjs.Dayjs): number {
const pivotDate = this.date(d);
return (
this.getLastWeekOfMonth(pivotDate).diff(
this.getFirstWeekOfMonth(pivotDate),
'week',
) + 1
);
}
/**
* Return the start of the first week of the month
*
* @see getWeeksCountInMonth() about standard warning
* @return {dayjs.Dayjs} A dayjs object representing the start of the
* first week
*/
getFirstWeekOfMonth(d: Timestamp | dayjs.Dayjs): dayjs.Dayjs {
const startOfMonth = this.date(d).startOf('month');
let startOfFirstWeek = startOfMonth.startOf('week');
if (startOfMonth.weekday() > 4) {
startOfFirstWeek = startOfFirstWeek.add(1, 'week');
}
return startOfFirstWeek;
}
/**
* Return the end of the last week of the month
*
* @see getWeeksCountInMonth() about standard warning
* @return {dayjs.Dayjs} A dayjs object representing the end of the last week
*/
getLastWeekOfMonth(d: Timestamp | dayjs.Dayjs): dayjs.Dayjs {
const endOfMonth = this.date(d).endOf('month');
let endOfLastWeek = endOfMonth.endOf('week');
if (endOfMonth.weekday() < 4) {
endOfLastWeek = endOfLastWeek.subtract(1, 'week');
}
return endOfLastWeek;
}
date(d: Timestamp | Date | dayjs.Dayjs | string = new Date()): dayjs.Dayjs {
if (dayjs.isDayjs(d)) {
return d;
}
return dayjs(d)
.tz(this.timezone)
.utcOffset(0)
.locale(this.locale as (typeof Ls)[0] | string);
}
format(
timestamp: Timestamp,
formatter: null | string | Function,
...args: any
): string | null {
if (typeof formatter === 'function') {
return formatter(timestamp, ...args);
}
if (typeof formatter === 'string') {
return this.date(timestamp).format(formatter);
}
return null;
}
/**
* Return an array of time interval
*
* @param {number|Date} date A random date included in the wanted interval
* @param {number|Date} range Length of the wanted interval, or a stop date.
* @param {boolean} range Whether the end date should be excluded
* from the result
* @returns {Array<number>} Array of unix timestamp, in milliseconds
*/
intervals(
interval: DomainType,
date: Timestamp | Date | dayjs.Dayjs,
range: number | Date | dayjs.Dayjs,
excludeEnd: boolean = true,
): Timestamp[] {
let start = this.date(date);
let end: dayjs.Dayjs;
if (typeof range === 'number') {
end = start.add(range, interval as ManipulateType);
} else if (dayjs.isDayjs(range)) {
end = range;
} else {
end = this.date(range);
}
start = start.startOf(interval as ManipulateType);
end = end.startOf(interval as ManipulateType);
let pivot = dayjs.min(start, end);
end = dayjs.max(start, end);
const result: Timestamp[] = [];
if (!excludeEnd) {
end = end.add(1, 'second');
}
do {
result.push(+pivot);
pivot = pivot.add(1, interval as ManipulateType);
} while (pivot < end);
return result;
}
// this function will work cross-browser for loading scripts asynchronously
// eslint-disable-next-line class-methods-use-this
loadBrowserLocale(userLocale: string): Promise<any> {
return new Promise((resolve, reject) => {
const s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = `https://cdn.jsdelivr.net/npm/dayjs@1/locale/${userLocale}.js`;
s.onerror = (err) => {
reject(err);
};
s.onload = () => {
resolve((window as any)[`dayjs_locale_${userLocale}`]);
};
document.head.appendChild(s);
});
}
// eslint-disable-next-line class-methods-use-this
loadNodeLocale(userLocale: string): Promise<any> {
return import(`dayjs/locale/${userLocale}.js`);
}
}