@digitalwalletcorp/utils 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/lib/date-time.cjs +2 -0
- package/lib/date-time.cjs.map +1 -0
- package/lib/date-time.d.cts +203 -0
- package/lib/date-time.d.ts +203 -0
- package/lib/date-time.js +289 -0
- package/lib/date-time.js.map +1 -0
- package/lib/date-util-ByW86YIS.cjs +2 -0
- package/lib/date-util-ByW86YIS.cjs.map +1 -0
- package/lib/date-util-C581gVKx.js +140 -0
- package/lib/date-util-C581gVKx.js.map +1 -0
- package/lib/date-util.cjs +1 -1
- package/lib/date-util.d.cts +8 -15
- package/lib/date-util.d.ts +8 -15
- package/lib/date-util.js +15 -16
- package/lib/html-util-BctKzfnH.js +60 -0
- package/lib/html-util-BctKzfnH.js.map +1 -0
- package/lib/html-util-ELyFVsxl.cjs +2 -0
- package/lib/html-util-ELyFVsxl.cjs.map +1 -0
- package/lib/html-util.cjs +1 -1
- package/lib/html-util.d.cts +5 -4
- package/lib/html-util.d.ts +5 -4
- package/lib/html-util.js +2 -2
- package/lib/index.cjs +1 -1
- package/lib/index.d.cts +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +16 -14
- package/lib/index.js.map +1 -1
- package/lib/{json-util-5mq7fsX_.cjs → json-util-CKnsxu1e.cjs} +3 -3
- package/lib/json-util-CKnsxu1e.cjs.map +1 -0
- package/lib/{json-util-CF-O1b7m.js → json-util-C_Pm1BpJ.js} +6 -5
- package/lib/json-util-C_Pm1BpJ.js.map +1 -0
- package/lib/json-util.cjs +1 -1
- package/lib/json-util.d.cts +10 -1
- package/lib/json-util.d.ts +10 -1
- package/lib/json-util.js +1 -1
- package/lib/{object-util-Cc1SmN-8.cjs → object-util-BHg61ifG.cjs} +2 -2
- package/lib/{object-util-Cc1SmN-8.cjs.map → object-util-BHg61ifG.cjs.map} +1 -1
- package/lib/{object-util-BGqczDbJ.js → object-util-wOMJtGK3.js} +2 -2
- package/lib/{object-util-BGqczDbJ.js.map → object-util-wOMJtGK3.js.map} +1 -1
- package/lib/object-util.cjs +1 -1
- package/lib/object-util.js +2 -2
- package/package.json +1 -1
- package/src/date-time.ts +395 -0
- package/src/date-util.ts +29 -103
- package/src/html-util.ts +22 -12
- package/src/index.ts +1 -0
- package/src/json-util.ts +8 -5
- package/lib/date-util-DrfUsv9b.js +0 -181
- package/lib/date-util-DrfUsv9b.js.map +0 -1
- package/lib/date-util-b1Lw7Nyu.cjs +0 -2
- package/lib/date-util-b1Lw7Nyu.cjs.map +0 -1
- package/lib/html-util-CcRJtzod.js +0 -56
- package/lib/html-util-CcRJtzod.js.map +0 -1
- package/lib/html-util-ClGIpzW1.cjs +0 -2
- package/lib/html-util-ClGIpzW1.cjs.map +0 -1
- package/lib/json-util-5mq7fsX_.cjs.map +0 -1
- package/lib/json-util-CF-O1b7m.js.map +0 -1
package/src/date-time.ts
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* このクラス DateTime の役割は、日時演算の実体としての luxon を内部に閉じ込め、利用者側に露出させないことにある
|
|
3
|
+
*/
|
|
4
|
+
import { DateTime as LuxonDateTime } from 'luxon';
|
|
5
|
+
import type { DateInput, DateUnit, Duration, ParseDateOptions, TimeUnit } from './date-util';
|
|
6
|
+
|
|
7
|
+
/** set で変更する日時の要素。month は1〜12 */
|
|
8
|
+
export interface DateTimeFields {
|
|
9
|
+
year?: number;
|
|
10
|
+
month?: number;
|
|
11
|
+
day?: number;
|
|
12
|
+
hour?: number;
|
|
13
|
+
minute?: number;
|
|
14
|
+
second?: number;
|
|
15
|
+
millisecond?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 日時を文字列にするときのオプション
|
|
20
|
+
*/
|
|
21
|
+
export interface DateTimeFormatOptions {
|
|
22
|
+
/** 月名・曜日名に使うロケール。既定は 'en-US' */
|
|
23
|
+
locale?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const DEFAULT_TIMEZONE = 'UTC';
|
|
27
|
+
|
|
28
|
+
const DEFAULT_LOCALE = 'en-US';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* タイムゾーンの指定が不正な場合は、日時の不正とは区別して例外にする
|
|
32
|
+
*/
|
|
33
|
+
const assertZone = (d: LuxonDateTime): void => {
|
|
34
|
+
if (!d.isValid && d.invalidReason === 'unsupported zone') {
|
|
35
|
+
throw new RangeError(d.invalidExplanation ?? d.invalidReason);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* set に渡された要素が min〜max の整数であることを確かめる
|
|
41
|
+
*/
|
|
42
|
+
const assertField = (name: keyof DateTimeFields, value: number | undefined, min: number, max: number): void => {
|
|
43
|
+
if (value === undefined) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!Number.isInteger(value) || value < min || max < value) {
|
|
47
|
+
throw new RangeError(`${name} must be an integer from ${min} to ${max}: ${value}`);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 日時を表す不変オブジェクト
|
|
53
|
+
* 演算メソッドは自身を変更せず、結果を新しいインスタンスで返す
|
|
54
|
+
* インスタンスごとにタイムゾーンを持ち、startOf / endOf / set や各要素の参照はそのタイムゾーンで計算する
|
|
55
|
+
*/
|
|
56
|
+
export class DateTime {
|
|
57
|
+
/**
|
|
58
|
+
* 日時演算インスタンス
|
|
59
|
+
*/
|
|
60
|
+
private dateTime: LuxonDateTime;
|
|
61
|
+
|
|
62
|
+
private constructor(dateTime: LuxonDateTime) {
|
|
63
|
+
this.dateTime = dateTime;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 現在時刻を返す
|
|
68
|
+
* タイムゾーンの指定が不正な場合は RangeError をスローする
|
|
69
|
+
*
|
|
70
|
+
* @param {string} [timezone] IANA名。既定は 'UTC'
|
|
71
|
+
* @returns {DateTime}
|
|
72
|
+
*/
|
|
73
|
+
public static now(timezone: string = DEFAULT_TIMEZONE): DateTime {
|
|
74
|
+
const d = LuxonDateTime.now().setZone(timezone);
|
|
75
|
+
assertZone(d);
|
|
76
|
+
return new DateTime(d);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 値を日時として解釈する
|
|
81
|
+
* 生成したインスタンスは inputTimezone のタイムゾーンを持つ
|
|
82
|
+
* DateTime を渡した場合、inputTimezone を省略すると渡した DateTime のタイムゾーンを引き継ぐ
|
|
83
|
+
* duration / startOf / endOf はそのタイムゾーンで計算する
|
|
84
|
+
* 解釈できない値、null / undefined / 空文字は null を返す。タイムゾーンの指定が不正な場合は RangeError をスローする
|
|
85
|
+
*
|
|
86
|
+
* @param {DateInput} value
|
|
87
|
+
* @param {ParseDateOptions} [options]
|
|
88
|
+
* @returns {DateTime | null}
|
|
89
|
+
*/
|
|
90
|
+
public static parse(value: DateInput, options?: ParseDateOptions): DateTime | null {
|
|
91
|
+
if (value == null || value === '') {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const zone = options?.inputTimezone ?? DEFAULT_TIMEZONE;
|
|
95
|
+
let d: LuxonDateTime | null = null;
|
|
96
|
+
if (value instanceof DateTime) {
|
|
97
|
+
d = options?.inputTimezone ? value.dateTime.setZone(options.inputTimezone) : value.dateTime;
|
|
98
|
+
} else if (value instanceof Date) {
|
|
99
|
+
d = LuxonDateTime.fromJSDate(value, { zone: zone });
|
|
100
|
+
} else if (typeof value === 'number') {
|
|
101
|
+
d = Number.isFinite(value) ? LuxonDateTime.fromMillis(value, { zone: zone }) : null;
|
|
102
|
+
} else {
|
|
103
|
+
const locale = options?.locale ?? DEFAULT_LOCALE;
|
|
104
|
+
const formats = options?.inputFormat ? [options.inputFormat].flat() : [];
|
|
105
|
+
for (const format of formats) {
|
|
106
|
+
const parsed = LuxonDateTime.fromFormat(value, format, {
|
|
107
|
+
zone: zone,
|
|
108
|
+
locale: locale
|
|
109
|
+
});
|
|
110
|
+
assertZone(parsed);
|
|
111
|
+
if (parsed.isValid) {
|
|
112
|
+
d = parsed;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (d == null) {
|
|
117
|
+
d = LuxonDateTime.fromISO(value, { zone: zone });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (d == null) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
assertZone(d);
|
|
124
|
+
if (!d.isValid) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
if (options?.duration) {
|
|
128
|
+
d = d.plus(options.duration);
|
|
129
|
+
}
|
|
130
|
+
if (options?.startOf) {
|
|
131
|
+
d = d.startOf(options.startOf);
|
|
132
|
+
}
|
|
133
|
+
if (options?.endOf) {
|
|
134
|
+
d = d.endOf(options.endOf);
|
|
135
|
+
}
|
|
136
|
+
return new DateTime(d);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** 年 */
|
|
140
|
+
public get year(): number {
|
|
141
|
+
return this.dateTime.year;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** 月(1〜12) */
|
|
145
|
+
public get month(): number {
|
|
146
|
+
return this.dateTime.month;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** 日 */
|
|
150
|
+
public get day(): number {
|
|
151
|
+
return this.dateTime.day;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** 時 */
|
|
155
|
+
public get hour(): number {
|
|
156
|
+
return this.dateTime.hour;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** 分 */
|
|
160
|
+
public get minute(): number {
|
|
161
|
+
return this.dateTime.minute;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** 秒 */
|
|
165
|
+
public get second(): number {
|
|
166
|
+
return this.dateTime.second;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** ミリ秒 */
|
|
170
|
+
public get millisecond(): number {
|
|
171
|
+
return this.dateTime.millisecond;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** 曜日。月曜が1、日曜が7 */
|
|
175
|
+
public get weekday(): number {
|
|
176
|
+
return this.dateTime.weekday;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** その月の日数 */
|
|
180
|
+
public get daysInMonth(): number {
|
|
181
|
+
return this.dateTime.daysInMonth as number;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** 夏時間の期間中か */
|
|
185
|
+
public get isInDST(): boolean {
|
|
186
|
+
return this.dateTime.isInDST;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** タイムゾーン(IANA名) */
|
|
190
|
+
public get timezone(): string {
|
|
191
|
+
return this.dateTime.zoneName as string;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 期間を加算した結果を返す
|
|
196
|
+
* 負の値を指定すると減算になる
|
|
197
|
+
*
|
|
198
|
+
* @param {Duration} duration
|
|
199
|
+
* @returns {DateTime}
|
|
200
|
+
*/
|
|
201
|
+
public plus(duration: Duration): DateTime {
|
|
202
|
+
return new DateTime(this.dateTime.plus(duration));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 期間を減算した結果を返す
|
|
207
|
+
*
|
|
208
|
+
* @param {Duration} duration
|
|
209
|
+
* @returns {DateTime}
|
|
210
|
+
*/
|
|
211
|
+
public minus(duration: Duration): DateTime {
|
|
212
|
+
return new DateTime(this.dateTime.minus(duration));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 指定した単位の最初の時点に切り捨てた結果を返す(month なら月初の00:00:00.000)
|
|
217
|
+
* week は月曜始まりで切り捨てる
|
|
218
|
+
*
|
|
219
|
+
* @param {DateUnit} unit
|
|
220
|
+
* @returns {DateTime}
|
|
221
|
+
*/
|
|
222
|
+
public startOf(unit: DateUnit): DateTime {
|
|
223
|
+
return new DateTime(this.dateTime.startOf(unit));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 指定した単位の最後の時点に切り上げた結果を返す(month なら月末の23:59:59.999)
|
|
228
|
+
* week は日曜終わりで切り上げる
|
|
229
|
+
*
|
|
230
|
+
* @param {DateUnit} unit
|
|
231
|
+
* @returns {DateTime}
|
|
232
|
+
*/
|
|
233
|
+
public endOf(unit: DateUnit): DateTime {
|
|
234
|
+
return new DateTime(this.dateTime.endOf(unit));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* 指定した要素を変更した結果を返す
|
|
239
|
+
* 範囲外の値や整数でない値を指定した場合は RangeError をスローする
|
|
240
|
+
* day を指定せずに year / month を変更し、日がその月に無い場合は月末日にする(1月31日に { month: 2 } なら2月末日)
|
|
241
|
+
*
|
|
242
|
+
* @param {DateTimeFields} fields
|
|
243
|
+
* @returns {DateTime}
|
|
244
|
+
*/
|
|
245
|
+
public set(fields: DateTimeFields): DateTime {
|
|
246
|
+
assertField('month', fields.month, 1, 12);
|
|
247
|
+
assertField('hour', fields.hour, 0, 23);
|
|
248
|
+
assertField('minute', fields.minute, 0, 59);
|
|
249
|
+
assertField('second', fields.second, 0, 59);
|
|
250
|
+
assertField('millisecond', fields.millisecond, 0, 999);
|
|
251
|
+
// day の上限は変更後の年月で決まる
|
|
252
|
+
const daysInMonth = this.dateTime.set({
|
|
253
|
+
year: fields.year ?? this.year,
|
|
254
|
+
month: fields.month ?? this.month,
|
|
255
|
+
day: 1
|
|
256
|
+
}).daysInMonth as number;
|
|
257
|
+
assertField('day', fields.day, 1, daysInMonth);
|
|
258
|
+
return new DateTime(this.dateTime.set(fields));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 同じ時刻を別のタイムゾーンで表した結果を返す
|
|
263
|
+
* タイムゾーンの指定が不正な場合は RangeError をスローする
|
|
264
|
+
*
|
|
265
|
+
* @param {string} timezone IANA名
|
|
266
|
+
* @returns {DateTime}
|
|
267
|
+
*/
|
|
268
|
+
public setZone(timezone: string): DateTime {
|
|
269
|
+
const d = this.dateTime.setZone(timezone);
|
|
270
|
+
assertZone(d);
|
|
271
|
+
return new DateTime(d);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* 自身 − other を unit の単位で返す
|
|
276
|
+
* 両方を自身のタイムゾーンで unit の始まりに切り捨ててから差を取るため、暦の上での差になる(23:00 と翌 01:00 の days は 1)
|
|
277
|
+
*
|
|
278
|
+
* @param {DateTime} other
|
|
279
|
+
* @param {TimeUnit} unit
|
|
280
|
+
* @returns {number}
|
|
281
|
+
*/
|
|
282
|
+
public diff(other: DateTime, unit: TimeUnit): number {
|
|
283
|
+
// 'days' → 'day' のように切り捨ての単位にする
|
|
284
|
+
const dateUnit = unit.slice(0, -1) as DateUnit;
|
|
285
|
+
const target = other.dateTime.setZone(this.dateTime.zone);
|
|
286
|
+
return this.dateTime.startOf(dateUnit).diff(target.startOf(dateUnit), unit).get(unit);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* 時刻を比較する。タイムゾーンが異なっていても同じ時刻なら等しい
|
|
291
|
+
*
|
|
292
|
+
* @param {DateTime} other
|
|
293
|
+
* @returns {-1 | 0 | 1} 自身が前なら-1、等しければ0、後なら1
|
|
294
|
+
*/
|
|
295
|
+
public compareTo(other: DateTime): -1 | 0 | 1 {
|
|
296
|
+
return Math.sign(this.toMillis() - other.toMillis()) as -1 | 0 | 1;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* 同じ時刻か判定する
|
|
301
|
+
*
|
|
302
|
+
* @param {DateTime} other
|
|
303
|
+
* @returns {boolean}
|
|
304
|
+
*/
|
|
305
|
+
public eq(other: DateTime): boolean {
|
|
306
|
+
return this.compareTo(other) === 0;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* 自身が前の時刻か判定する
|
|
311
|
+
*
|
|
312
|
+
* @param {DateTime} other
|
|
313
|
+
* @returns {boolean}
|
|
314
|
+
*/
|
|
315
|
+
public lt(other: DateTime): boolean {
|
|
316
|
+
return this.compareTo(other) < 0;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* 自身が前か同じ時刻か判定する
|
|
321
|
+
*
|
|
322
|
+
* @param {DateTime} other
|
|
323
|
+
* @returns {boolean}
|
|
324
|
+
*/
|
|
325
|
+
public lte(other: DateTime): boolean {
|
|
326
|
+
return this.compareTo(other) <= 0;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* 自身が後の時刻か判定する
|
|
331
|
+
*
|
|
332
|
+
* @param {DateTime} other
|
|
333
|
+
* @returns {boolean}
|
|
334
|
+
*/
|
|
335
|
+
public gt(other: DateTime): boolean {
|
|
336
|
+
return 0 < this.compareTo(other);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* 自身が後か同じ時刻か判定する
|
|
341
|
+
*
|
|
342
|
+
* @param {DateTime} other
|
|
343
|
+
* @returns {boolean}
|
|
344
|
+
*/
|
|
345
|
+
public gte(other: DateTime): boolean {
|
|
346
|
+
return 0 <= this.compareTo(other);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* 自身のタイムゾーンで、指定した書式の文字列にする
|
|
351
|
+
*
|
|
352
|
+
* @param {string} outputFormat
|
|
353
|
+
* @param {DateTimeFormatOptions} [options]
|
|
354
|
+
* @returns {string}
|
|
355
|
+
*/
|
|
356
|
+
public format(outputFormat: string, options?: DateTimeFormatOptions): string {
|
|
357
|
+
return this.dateTime.toFormat(outputFormat, { locale: options?.locale ?? DEFAULT_LOCALE });
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* 自身のタイムゾーンで、ISO形式の文字列にする
|
|
362
|
+
*
|
|
363
|
+
* @returns {string}
|
|
364
|
+
*/
|
|
365
|
+
public toISO(): string {
|
|
366
|
+
return this.dateTime.toISO() as string;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Date に変換する
|
|
371
|
+
*
|
|
372
|
+
* @returns {Date}
|
|
373
|
+
*/
|
|
374
|
+
public toDate(): Date {
|
|
375
|
+
return this.dateTime.toJSDate();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* epoch ミリ秒に変換する
|
|
380
|
+
*
|
|
381
|
+
* @returns {number}
|
|
382
|
+
*/
|
|
383
|
+
public toMillis(): number {
|
|
384
|
+
return this.dateTime.toMillis();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* 自身のタイムゾーンで、ISO形式の文字列にする
|
|
389
|
+
*
|
|
390
|
+
* @returns {string}
|
|
391
|
+
*/
|
|
392
|
+
public toString(): string {
|
|
393
|
+
return this.toISO();
|
|
394
|
+
}
|
|
395
|
+
}
|
package/src/date-util.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { DateTime
|
|
2
|
-
import type { DateTimeUnit } from 'luxon';
|
|
1
|
+
import { DateTime } from './date-time';
|
|
3
2
|
|
|
4
|
-
/**
|
|
5
|
-
|
|
3
|
+
/**
|
|
4
|
+
* 日時として解釈する値。string はISO形式または inputFormat の書式、number は epoch ミリ秒
|
|
5
|
+
* DateTime は inputTimezone を省略した場合、その DateTime のタイムゾーンで扱う
|
|
6
|
+
*/
|
|
7
|
+
export type DateInput = string | Date | number | DateTime | null | undefined;
|
|
6
8
|
|
|
7
9
|
/** 日時の切り捨て(startOf / endOf)に使う単位 */
|
|
8
10
|
export type DateUnit = 'year' | 'quarter' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second' | 'millisecond';
|
|
@@ -37,6 +39,8 @@ export interface ParseDateOptions {
|
|
|
37
39
|
startOf?: DateUnit;
|
|
38
40
|
/** 指定した単位の最後の時点に切り上げる(month なら月末の23:59:59.999) */
|
|
39
41
|
endOf?: DateUnit;
|
|
42
|
+
/** 書式の月名・曜日名を解釈・出力するときのロケール。既定は 'en-US' */
|
|
43
|
+
locale?: string;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
/**
|
|
@@ -119,65 +123,13 @@ const DEFAULT_TIMEZONE_ABBREVIATIONS: TimezoneAbbreviations = {
|
|
|
119
123
|
'America/Sao_Paulo': { standard: 'BRT' }
|
|
120
124
|
};
|
|
121
125
|
|
|
122
|
-
/**
|
|
123
|
-
* タイムゾーンの指定が不正な場合は、日時の不正とは区別して例外にする
|
|
124
|
-
*/
|
|
125
|
-
const assertZone = (d: DateTime): void => {
|
|
126
|
-
if (!d.isValid && d.invalidReason === 'unsupported zone') {
|
|
127
|
-
throw new RangeError(d.invalidExplanation ?? d.invalidReason);
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const toDateTime = (value: DateInput, options?: ParseDateOptions): DateTime | null => {
|
|
132
|
-
if (value == null || value === '') {
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
const zone = options?.inputTimezone ?? DEFAULT_TIMEZONE;
|
|
136
|
-
let d: DateTime | null = null;
|
|
137
|
-
if (value instanceof Date) {
|
|
138
|
-
d = DateTime.fromJSDate(value, { zone: zone });
|
|
139
|
-
} else if (typeof value === 'number') {
|
|
140
|
-
d = Number.isFinite(value) ? DateTime.fromMillis(value, { zone: zone }) : null;
|
|
141
|
-
} else {
|
|
142
|
-
const formats = options?.inputFormat ? [options.inputFormat].flat() : [];
|
|
143
|
-
for (const format of formats) {
|
|
144
|
-
const parsed = DateTime.fromFormat(value, format, { zone: zone });
|
|
145
|
-
assertZone(parsed);
|
|
146
|
-
if (parsed.isValid) {
|
|
147
|
-
d = parsed;
|
|
148
|
-
break;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
if (d == null) {
|
|
152
|
-
d = DateTime.fromISO(value, { zone: zone });
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
if (d == null) {
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
assertZone(d);
|
|
159
|
-
if (!d.isValid) {
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
if (options?.duration) {
|
|
163
|
-
d = d.plus(options.duration);
|
|
164
|
-
}
|
|
165
|
-
if (options?.startOf) {
|
|
166
|
-
d = d.startOf(options.startOf);
|
|
167
|
-
}
|
|
168
|
-
if (options?.endOf) {
|
|
169
|
-
d = d.endOf(options.endOf);
|
|
170
|
-
}
|
|
171
|
-
return d;
|
|
172
|
-
};
|
|
173
|
-
|
|
174
126
|
const compare = (date1: DateInput, date2: DateInput, options: ParseDateOptions | undefined, predicate: (v1: number, v2: number) => boolean): boolean => {
|
|
175
|
-
const d1 =
|
|
176
|
-
const d2 =
|
|
127
|
+
const d1 = DateTime.parse(date1, options);
|
|
128
|
+
const d2 = DateTime.parse(date2, options);
|
|
177
129
|
if (!d1 || !d2) {
|
|
178
130
|
return false;
|
|
179
131
|
}
|
|
180
|
-
return predicate(d1.
|
|
132
|
+
return predicate(d1.toMillis(), d2.toMillis());
|
|
181
133
|
};
|
|
182
134
|
|
|
183
135
|
/**
|
|
@@ -190,7 +142,7 @@ const compare = (date1: DateInput, date2: DateInput, options: ParseDateOptions |
|
|
|
190
142
|
* @returns {Date | null}
|
|
191
143
|
*/
|
|
192
144
|
export function parseDate(value: DateInput, options?: ParseDateOptions): Date | null {
|
|
193
|
-
return
|
|
145
|
+
return DateTime.parse(value, options)?.toDate() ?? null;
|
|
194
146
|
}
|
|
195
147
|
|
|
196
148
|
/**
|
|
@@ -206,19 +158,18 @@ export function parseDate(value: DateInput, options?: ParseDateOptions): Date |
|
|
|
206
158
|
export function formatDate(options: FormatDateOptions): string;
|
|
207
159
|
export function formatDate(value: DateInput, options?: FormatDateOptions): string;
|
|
208
160
|
export function formatDate(value: DateInput | FormatDateOptions, options?: FormatDateOptions): string {
|
|
209
|
-
if (typeof value === 'object' && value !== null && !(value instanceof Date)) {
|
|
161
|
+
if (typeof value === 'object' && value !== null && !(value instanceof Date) && !(value instanceof DateTime)) {
|
|
210
162
|
return formatDate(new Date(), value);
|
|
211
163
|
}
|
|
212
164
|
if (value == null || value === '') {
|
|
213
165
|
return '';
|
|
214
166
|
}
|
|
215
|
-
const d =
|
|
167
|
+
const d = DateTime.parse(value, options);
|
|
216
168
|
if (!d) {
|
|
217
169
|
return options?.invalidText ?? 'Invalid Date';
|
|
218
170
|
}
|
|
219
171
|
const output = d.setZone(options?.outputTimezone ?? DEFAULT_TIMEZONE);
|
|
220
|
-
|
|
221
|
-
return options?.outputFormat ? output.toFormat(options.outputFormat) : output.toISO() as string;
|
|
172
|
+
return options?.outputFormat ? output.format(options.outputFormat, { locale: options.locale }) : output.toISO();
|
|
222
173
|
}
|
|
223
174
|
|
|
224
175
|
/**
|
|
@@ -229,7 +180,7 @@ export function formatDate(value: DateInput | FormatDateOptions, options?: Forma
|
|
|
229
180
|
* @returns {boolean}
|
|
230
181
|
*/
|
|
231
182
|
export function isValid(value: DateInput, options?: ParseDateOptions): boolean {
|
|
232
|
-
return
|
|
183
|
+
return DateTime.parse(value, options) != null;
|
|
233
184
|
}
|
|
234
185
|
|
|
235
186
|
/**
|
|
@@ -300,12 +251,12 @@ export function isSameOrAfter(date1: DateInput, date2: DateInput, options?: Pars
|
|
|
300
251
|
* @returns {boolean}
|
|
301
252
|
*/
|
|
302
253
|
export function isPastDate(value: DateInput, options?: ParseDateOptions): boolean {
|
|
303
|
-
const target =
|
|
254
|
+
const target = DateTime.parse(value, options)?.startOf('day');
|
|
304
255
|
if (!target) {
|
|
305
256
|
return false;
|
|
306
257
|
}
|
|
307
|
-
const today = DateTime.now(
|
|
308
|
-
return target.
|
|
258
|
+
const today = DateTime.now(target.timezone).startOf('day');
|
|
259
|
+
return target.lt(today);
|
|
309
260
|
}
|
|
310
261
|
|
|
311
262
|
/**
|
|
@@ -316,8 +267,8 @@ export function isPastDate(value: DateInput, options?: ParseDateOptions): boolea
|
|
|
316
267
|
* @returns {boolean}
|
|
317
268
|
*/
|
|
318
269
|
export function isPastDateTime(value: DateInput, options?: ParseDateOptions): boolean {
|
|
319
|
-
const target =
|
|
320
|
-
return target != null && target.
|
|
270
|
+
const target = DateTime.parse(value, options);
|
|
271
|
+
return target != null && target.toMillis() < Date.now();
|
|
321
272
|
}
|
|
322
273
|
|
|
323
274
|
/**
|
|
@@ -332,14 +283,12 @@ export function isPastDateTime(value: DateInput, options?: ParseDateOptions): bo
|
|
|
332
283
|
* @returns {number}
|
|
333
284
|
*/
|
|
334
285
|
export function diff(date1: DateInput, date2: DateInput, unit: TimeUnit, options?: ParseDateOptions): number {
|
|
335
|
-
const d1 =
|
|
336
|
-
const d2 =
|
|
286
|
+
const d1 = DateTime.parse(date1, options);
|
|
287
|
+
const d2 = DateTime.parse(date2, options);
|
|
337
288
|
if (!d1 || !d2) {
|
|
338
289
|
return NaN;
|
|
339
290
|
}
|
|
340
|
-
|
|
341
|
-
const dateUnit = unit.slice(0, -1) as DateTimeUnit;
|
|
342
|
-
return d1.startOf(dateUnit).diff(d2.startOf(dateUnit), unit).get(unit);
|
|
291
|
+
return d1.diff(d2, unit);
|
|
343
292
|
}
|
|
344
293
|
|
|
345
294
|
/**
|
|
@@ -349,9 +298,7 @@ export function diff(date1: DateInput, date2: DateInput, unit: TimeUnit, options
|
|
|
349
298
|
* @returns {string}
|
|
350
299
|
*/
|
|
351
300
|
export function currentTimestamp(timezone: string = DEFAULT_TIMEZONE): string {
|
|
352
|
-
|
|
353
|
-
assertZone(d);
|
|
354
|
-
return d.toFormat(TIMESTAMP_FORMAT);
|
|
301
|
+
return DateTime.now(timezone).format(TIMESTAMP_FORMAT);
|
|
355
302
|
}
|
|
356
303
|
|
|
357
304
|
/**
|
|
@@ -378,22 +325,6 @@ export function convertExcelDateSerialToUnixTimeSerial(excelDateSerial: number,
|
|
|
378
325
|
return timeserial;
|
|
379
326
|
}
|
|
380
327
|
|
|
381
|
-
/**
|
|
382
|
-
* 期間の値を別の単位に換算する
|
|
383
|
-
* 月と年は固定長(1か月 = 30日、1年 = 365日)として換算する
|
|
384
|
-
*
|
|
385
|
-
* @example
|
|
386
|
-
* convertTimeUnit(1, 'hours', 'milliseconds'); // 3600000
|
|
387
|
-
*
|
|
388
|
-
* @param {number} value
|
|
389
|
-
* @param {TimeUnit} inputUnit
|
|
390
|
-
* @param {TimeUnit} outputUnit
|
|
391
|
-
* @returns {number}
|
|
392
|
-
*/
|
|
393
|
-
export function convertTimeUnit(value: number, inputUnit: TimeUnit, outputUnit: TimeUnit): number {
|
|
394
|
-
return LuxonDuration.fromObject({ [inputUnit]: value }).as(outputUnit);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
328
|
/**
|
|
398
329
|
* timezone が at の時点で夏時間かどうかを返す
|
|
399
330
|
* at を省略した場合は現在時刻で判定する。at が解釈できない場合は false を返す
|
|
@@ -404,12 +335,8 @@ export function convertTimeUnit(value: number, inputUnit: TimeUnit, outputUnit:
|
|
|
404
335
|
* @returns {boolean}
|
|
405
336
|
*/
|
|
406
337
|
export function isInDST(timezone: string, at?: DateInput): boolean {
|
|
407
|
-
const d = at === undefined ? DateTime.now(
|
|
408
|
-
|
|
409
|
-
return false;
|
|
410
|
-
}
|
|
411
|
-
assertZone(d);
|
|
412
|
-
return d.isInDST;
|
|
338
|
+
const d = at === undefined ? DateTime.now(timezone) : DateTime.parse(at)?.setZone(timezone);
|
|
339
|
+
return d?.isInDST ?? false;
|
|
413
340
|
}
|
|
414
341
|
|
|
415
342
|
/**
|
|
@@ -426,9 +353,8 @@ export function getTimezoneAbbr(timezone: string, options?: TimezoneAbbrOptions)
|
|
|
426
353
|
return null;
|
|
427
354
|
}
|
|
428
355
|
if (definition.daylight) {
|
|
429
|
-
const d = DateTime.
|
|
430
|
-
|
|
431
|
-
if (d.isInDST) {
|
|
356
|
+
const d = DateTime.parse(options?.at ?? new Date())?.setZone(timezone);
|
|
357
|
+
if (d?.isInDST) {
|
|
432
358
|
return definition.daylight;
|
|
433
359
|
}
|
|
434
360
|
}
|