• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

atinc / ngx-tethys / 0da75401-415d-4187-8c75-efbd749fd7ee

16 Apr 2025 09:09AM UTC coverage: 90.197% (+0.02%) from 90.176%
0da75401-415d-4187-8c75-efbd749fd7ee

push

circleci

xinglu01
feat(date-picker): add timezone support to date picker components and utilities #TINFR-1734 (#3335)

5611 of 6886 branches covered (81.48%)

Branch coverage included in aggregate %.

50 of 53 new or added lines in 11 files covered. (94.34%)

46 existing lines in 9 files now uncovered.

13379 of 14168 relevant lines covered (94.43%)

993.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.66
/src/util/date/tiny-date.ts
1
import { TZDate } from '@date-fns/tz';
2
import { FirstWeekContainsDate, Locale, setHours, setMinutes, setSeconds } from 'date-fns';
3
import { fromZonedTime } from 'date-fns-tz';
4
import { getDefaultLocaleId } from 'ngx-tethys/i18n';
5
import { hasTimeInStringDate } from '../helpers';
6
import {
7
    addDays,
8
    addHours,
15✔
9
    addMinutes,
14✔
10
    addMonths,
14✔
11
    addQuarters,
12
    addSeconds,
1✔
13
    addWeeks,
14
    addYears,
1✔
15
    differenceInCalendarDays,
16
    differenceInCalendarMonths,
1✔
17
    differenceInCalendarQuarters,
1✔
18
    differenceInCalendarYears,
1✔
19
    differenceInDays,
20
    differenceInHours,
43,872✔
21
    differenceInMinutes,
43,872✔
22
    differenceInSeconds,
43,872✔
23
    differenceInWeeks,
41,199✔
24
    endOfDay,
40,789✔
25
    endOfISOWeek,
26
    endOfMonth,
410✔
27
    endOfQuarter,
140✔
28
    endOfWeek,
29
    endOfYear,
83✔
30
    format,
83✔
31
    fromUnixTime,
83✔
32
    getDateFnsLocale,
83✔
33
    getDaysInMonth,
34
    getQuarter,
35
    getUnixTime,
57✔
36
    getWeek,
37
    isSameDay,
38
    isSameHour,
270✔
39
    isSameMinute,
269✔
40
    isSameMonth,
41
    isSameQuarter,
1!
42
    isSameSecond,
1✔
43
    isSameYear,
44
    isToday,
45
    isTomorrow,
46
    isValid,
2,673✔
47
    isWeekend,
48
    setDay,
49
    setDefaultOptions,
50
    setMonth,
67✔
51
    setQuarter,
67✔
52
    setYear,
67✔
53
    startOfDay,
54
    startOfISOWeek,
UNCOV
55
    startOfMonth,
×
56
    startOfQuarter,
57
    startOfWeek,
58
    startOfYear,
1!
59
    subDays,
60
    subWeeks
UNCOV
61
} from './functions';
×
62

63
export type TinyDateCompareGrain = 'decade' | 'year' | 'quarter' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second';
64

40,789!
65
export type WeekDayIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6;
66

67
export type TinyDateType = TinyDate | Date | null;
41✔
68

69
export function sortRangeValue(rangeValue: TinyDate[]): TinyDate[] {
70
    if (Array.isArray(rangeValue)) {
172✔
71
        const [start, end] = rangeValue;
72
        return start && end && start.isAfterSecond(end) ? [end, start] : [start, end];
73
    }
74
    return rangeValue;
1,222✔
75
}
76

77
export const DEFAULT_TIMEZONE = 'Asia/Shanghai';
22,191✔
78

79
export class TinyDate implements Record<string, any> {
80
    nativeDate: Date;
7,066✔
81

82
    private useTimeZone: string;
83

174✔
84
    private static locale: string = getDefaultLocaleId();
85

86
    protected static dateFnsLocale: Locale = getDateFnsLocale(TinyDate.locale);
751✔
87

88
    protected static defaultTimeZone: string = DEFAULT_TIMEZONE;
89

33✔
90
    constructor(date?: Date | string | number, zone?: string) {
91
        setDefaultOptions({ locale: TinyDate.dateFnsLocale });
2✔
92
        this.useTimeZone = zone || TinyDate.defaultTimeZone;
2✔
93
        if (date) {
94
            if (date instanceof Date) {
95
                this.nativeDate = TinyDate.utcToZonedTime(date, this.useTimeZone);
3✔
96
            } else if (typeof date === 'string') {
97
                if (hasTimeInStringDate(date)) {
98
                    // 如果字符串中包含时间,则需要将时间转换为UTC时间再传递给TZDate
66✔
99
                    const originTime = new Date(date);
100
                    const zoneTime = TZDate.tz(this.useTimeZone, originTime);
101
                    const utcDate = fromZonedTime(zoneTime, this.useTimeZone).toISOString();
62✔
102
                    this.nativeDate = new TZDate(utcDate, this.useTimeZone);
103
                } else {
104
                    this.nativeDate = new TZDate(date, this.useTimeZone);
62✔
105
                }
106
            } else if (typeof date === 'number') {
107
                this.nativeDate = new TZDate(date, this.useTimeZone);
1✔
108
            } else if (typeof ngDevMode === 'undefined' || ngDevMode) {
109
                throw new Error(
110
                    `The input date type is not supported expect Date | string | number | { date: number; with_time: 0 | 1}, actual ${JSON.stringify(
1✔
111
                        date
112
                    )}`
113
                );
1✔
114
            }
115
        } else {
116
            this.nativeDate = new TZDate(Date.now(), this.useTimeZone);
117
        }
1✔
118
    }
1✔
119

1✔
120
    static setDefaultLocale(locale: string) {
121
        TinyDate.locale = locale;
122
        TinyDate.dateFnsLocale = getDateFnsLocale(locale);
102✔
123
        return setDefaultOptions({ locale: TinyDate.dateFnsLocale });
102✔
124
    }
102✔
125

126
    static getDefaultLocale(): { locale: string; dateFnsLocale: Locale } {
127
        return { locale: TinyDate.locale, dateFnsLocale: TinyDate.dateFnsLocale };
660✔
128
    }
129

130
    static setDefaultTimeZone(zone: string) {
1,256✔
131
        TinyDate.defaultTimeZone = zone ?? DEFAULT_TIMEZONE;
132
    }
133

172✔
134
    static getDefaultTimeZone(): string {
135
        return TinyDate.defaultTimeZone;
136
    }
1✔
137

138
    static utcToZonedTime(value: Date | number, timeZone?: string): Date {
139
        return TZDate.tz(timeZone || TinyDate.defaultTimeZone, value as any);
32✔
140
    }
141

142
    static createDateInTimeZone(
32✔
143
        year: number,
144
        month: number,
145
        day: number,
32✔
146
        hours: number,
147
        minutes: number,
148
        seconds: number,
149
        timeZone?: string
398✔
150
    ): Date {
151
        return new TZDate(year, month, day, hours, minutes, seconds, timeZone || TinyDate.defaultTimeZone);
152
    }
450✔
153

154
    static fromUnixTime(unixTime: number, timeZone?: string): TinyDate {
155
        return new TinyDate(fromUnixTime(unixTime), timeZone || TinyDate.defaultTimeZone);
644✔
156
    }
157

158
    // get
1✔
159
    getTime(): number {
160
        return this.nativeDate.getTime();
161
    }
28,767✔
162

163
    getDate(): number {
164
        return this.nativeDate.getDate();
1✔
165
    }
166

167
    getYear(): number {
2✔
168
        return this.nativeDate.getFullYear();
169
    }
170

1✔
171
    getQuarter(): number {
172
        return getQuarter(this.nativeDate);
173
    }
×
174

175
    getMonth(): number {
54,807✔
176
        return this.nativeDate.getMonth();
177
    }
2✔
178

2✔
179
    getFullYear(): number {
180
        return this.nativeDate.getFullYear();
799✔
181
    }
799✔
182

183
    getWeek(options: { locale?: Locale; weekStartsOn?: WeekDayIndex } = { weekStartsOn: 1 }): number {
1,421✔
184
        return getWeek(this.nativeDate, options);
1,421✔
185
    }
186

441✔
187
    getDay(): number {
441✔
188
        return this.nativeDate.getDay();
189
    }
52,137✔
190

52,137✔
191
    getHours(): number {
192
        return this.nativeDate.getHours();
2✔
193
    }
2✔
194

195
    getMinutes(): number {
2✔
196
        return this.nativeDate.getMinutes();
2✔
197
    }
198

2✔
199
    getSeconds(): number {
2✔
200
        return this.nativeDate.getSeconds();
201
    }
1✔
202

1✔
203
    getMilliseconds(): number {
204
        return this.nativeDate.getMilliseconds();
54,807✔
205
    }
206

207
    getDaysInMonth() {
787✔
208
        return getDaysInMonth(this.nativeDate);
209
    }
210

1,363✔
211
    getDaysInQuarter() {
212
        return differenceInCalendarDays(this.endOfQuarter().addSeconds(1).nativeDate, this.startOfQuarter().nativeDate);
213
    }
441✔
214

215
    // set
216
    setDate(amount: number): TinyDate {
52,136✔
217
        const date = new Date(this.nativeDate);
218
        date.setDate(amount);
219
        return new TinyDate(date, this.useTimeZone);
1✔
220
    }
221

222
    setHms(hour: number, minute: number, second: number): TinyDate {
1✔
223
        const date = new Date(this.nativeDate);
224
        date.setHours(hour, minute, second);
225
        return new TinyDate(date, this.useTimeZone);
1✔
226
    }
227

228
    setYear(year: number): TinyDate {
229
        return new TinyDate(setYear(this.nativeDate, year), this.useTimeZone);
127✔
230
    }
231

232
    setMonth(month: number): TinyDate {
64✔
233
        return new TinyDate(setMonth(this.nativeDate, month), this.useTimeZone);
234
    }
235

22,111✔
236
    setQuarter(quarter: number): TinyDate {
237
        return new TinyDate(setQuarter(this.nativeDate, quarter), this.useTimeZone);
238
    }
1✔
239

240
    setDay(day: number, options?: { weekStartsOn: WeekDayIndex }): TinyDate {
241
        return new TinyDate(setDay(this.nativeDate, day, options), this.useTimeZone);
7,102✔
242
    }
243

244
    setHours(hours: number): TinyDate {
1✔
245
        return new TinyDate(setHours(this.nativeDate, hours), this.useTimeZone);
246
    }
247

1✔
248
    setMinutes(minutes: number): TinyDate {
249
        return new TinyDate(setMinutes(this.nativeDate, minutes), this.useTimeZone);
250
    }
1✔
251

252
    setSeconds(seconds: number): TinyDate {
253
        return new TinyDate(setSeconds(this.nativeDate, seconds), this.useTimeZone);
2✔
254
    }
255

UNCOV
256
    // add
×
257
    addYears(amount: number): TinyDate {
258
        return new TinyDate(addYears(this.nativeDate, amount), this.useTimeZone);
259
    }
21,967✔
260

261
    addQuarters(amount: number): TinyDate {
262
        return new TinyDate(addQuarters(this.nativeDate, amount), this.useTimeZone);
1✔
263
    }
264

265
    addMonths(amount: number): TinyDate {
2✔
266
        return new TinyDate(addMonths(this.nativeDate, amount), this.useTimeZone);
267
    }
268

1✔
269
    addWeeks(amount: number): TinyDate {
270
        return new TinyDate(addWeeks(this.nativeDate, amount), this.useTimeZone);
271
    }
1✔
272

273
    addDays(amount: number): TinyDate {
274
        return new TinyDate(addDays(this.nativeDate, amount), this.useTimeZone);
15✔
275
    }
276
    addHours(amount: number): TinyDate {
277
        return new TinyDate(addHours(this.nativeDate, amount), this.useTimeZone);
278
    }
2✔
279

280
    addSeconds(amount: number): TinyDate {
281
        return new TinyDate(addSeconds(this.nativeDate, amount), this.useTimeZone);
43,934✔
282
    }
283

284
    addMinutes(amount: number): TinyDate {
2✔
285
        return new TinyDate(addMinutes(this.nativeDate, amount), this.useTimeZone);
286
    }
287

2✔
288
    // isSame
289

290
    isSame(date: TinyDateType, grain: TinyDateCompareGrain = 'day'): boolean {
291
        let fn;
379✔
292
        switch (grain) {
293
            case 'decade':
294
                fn = (pre: Date, next: Date) => Math.abs(pre.getFullYear() - next.getFullYear()) < 11;
206✔
295
                break;
296
            case 'year':
297
                fn = isSameYear;
236✔
298
                break;
299
            case 'month':
300
                fn = isSameMonth;
32✔
301
                break;
302
            case 'quarter':
303
                fn = isSameQuarter;
2,376✔
304
                break;
305
            case 'day':
306
                fn = isSameDay;
113✔
307
                break;
308
            case 'hour':
309
                fn = isSameHour;
151✔
310
                break;
311
            case 'minute':
312
                fn = isSameMinute;
180✔
313
                break;
314
            case 'second':
315
                fn = isSameSecond;
33✔
316
                break;
317
            default:
318
                fn = isSameDay;
2,024✔
319
                break;
320
        }
321
        return fn(this.nativeDate, this.toNativeDate(date));
322
    }
402✔
323

324
    isSameYear(date: TinyDateType): boolean {
325
        return this.isSame(date, 'year');
1,047✔
326
    }
327

328
    isSameMonth(date: TinyDateType): boolean {
147✔
329
        return this.isSame(date, 'month');
330
    }
331

213✔
332
    isSameQuarter(date: TinyDateType): boolean {
333
        return this.isSame(date, 'quarter');
29,408!
334
    }
51,397✔
335

2✔
336
    isSameDay(date: TinyDateType): boolean {
337
        return this.isSame(date, 'day');
338
    }
51,395!
339

340
    isSameHour(date: TinyDateType): boolean {
127✔
341
        return this.isSame(date, 'hour');
127✔
342
    }
343

64✔
344
    isSameMinute(date: TinyDateType): boolean {
64✔
345
        return this.isSame(date, 'minute');
346
    }
44,078✔
347

44,078✔
348
    isSameSecond(date: TinyDateType): boolean {
349
        return this.isSame(date, 'second');
7,104✔
350
    }
7,104✔
351

352
    // isBefore and isAfter
2✔
353
    isBeforeYear(date: TinyDateType): boolean {
2✔
354
        return this.compare(date, 'year');
355
    }
2✔
356

2✔
357
    isBeforeQuarter(date: TinyDate): boolean {
358
        return this.compare(date, 'quarter');
2✔
359
    }
2✔
360

361
    isBeforeMonth(date: TinyDateType): boolean {
16✔
362
        return this.compare(date, 'month');
16✔
363
    }
UNCOV
364

×
UNCOV
365
    isBeforeWeek(date: TinyDateType): boolean {
×
366
        return this.compare(date, 'week');
367
    }
51,395✔
368

369
    isBeforeDay(date: TinyDateType): boolean {
370
        return this.compare(date, 'day');
106,202✔
371
    }
372

373
    isBeforeHour(date: TinyDateType): boolean {
×
374
        return this.compare(date, 'hour');
375
    }
376

×
377
    isBeforeMinute(date: TinyDateType): boolean {
378
        return this.compare(date, 'minute');
379
    }
×
380

381
    isBeforeSecond(date: TinyDateType): boolean {
382
        return this.compare(date, 'second');
×
383
    }
384

UNCOV
385
    isAfterYear(date: TinyDateType): boolean {
×
386
        return this.compare(date, 'year', false);
387
    }
388

6✔
389
    isAfterQuarter(date: TinyDate): boolean {
390
        return this.compare(date, 'quarter', false);
391
    }
392

393
    isAfterMonth(date: TinyDateType): boolean {
394
        return this.compare(date, 'month', false);
395
    }
396

397
    isAfterWeek(date: TinyDateType): boolean {
398
        return this.compare(date, 'week', false);
399
    }
400

401
    isAfterDay(date: TinyDateType): boolean {
402
        return this.compare(date, 'day', false);
403
    }
404

405
    isAfterHour(date: TinyDateType): boolean {
406
        return this.compare(date, 'hour', false);
407
    }
408

409
    isAfterMinute(date: TinyDateType): boolean {
410
        return this.compare(date, 'minute', false);
411
    }
412

413
    isAfterSecond(date: TinyDateType): boolean {
414
        return this.compare(date, 'second', false);
415
    }
416

417
    // is
418
    isWeekend(): boolean {
419
        return isWeekend(this.nativeDate);
420
    }
421

422
    isToday(): boolean {
423
        return isToday(this.nativeDate);
424
    }
425

426
    isTomorrow(): boolean {
427
        return isTomorrow(this.nativeDate);
428
    }
429

430
    isValid(): boolean {
431
        return isValid(this.nativeDate);
432
    }
433

434
    // startOf and endOf
435
    startOfYear(): TinyDate {
436
        return new TinyDate(startOfYear(this.nativeDate), this.useTimeZone);
437
    }
438

439
    startOfQuarter(): TinyDate {
440
        return new TinyDate(startOfQuarter(this.nativeDate), this.useTimeZone);
441
    }
442

443
    startOfMonth(): TinyDate {
444
        return new TinyDate(startOfMonth(this.nativeDate), this.useTimeZone);
445
    }
446

447
    startOfWeek(options?: { locale?: Locale; weekStartsOn?: WeekDayIndex }): TinyDate {
448
        return new TinyDate(startOfWeek(this.nativeDate, options), this.useTimeZone);
449
    }
450

451
    startOfDay(): TinyDate {
452
        return new TinyDate(startOfDay(this.nativeDate), this.useTimeZone);
453
    }
454

455
    endOfYear(): TinyDate {
456
        return new TinyDate(endOfYear(this.nativeDate), this.useTimeZone);
457
    }
458

459
    endOfQuarter(): TinyDate {
460
        return new TinyDate(endOfQuarter(this.nativeDate), this.useTimeZone);
461
    }
462

463
    endOfMonth(): TinyDate {
464
        return new TinyDate(endOfMonth(this.nativeDate), this.useTimeZone);
465
    }
466

467
    endOfWeek(options?: { locale?: Locale; weekStartsOn?: WeekDayIndex }): TinyDate {
468
        return new TinyDate(endOfWeek(this.nativeDate, options), this.useTimeZone);
469
    }
470

471
    endOfDay(): TinyDate {
472
        return new TinyDate(endOfDay(this.nativeDate), this.useTimeZone);
473
    }
474

475
    // other
476
    format(
477
        mat: string,
478
        options?: {
479
            locale?: Locale;
480
            weekStartsOn?: WeekDayIndex;
481
            firstWeekContainsDate?: FirstWeekContainsDate;
482
            useAdditionalWeekYearTokens?: boolean;
483
            useAdditionalDayOfYearTokens?: boolean;
484
        }
485
    ) {
486
        return format(this.nativeDate, mat, options);
487
    }
488

489
    calendarStart(options?: { weekStartsOn: WeekDayIndex | undefined }): TinyDate {
490
        return new TinyDate(startOfWeek(startOfMonth(this.nativeDate), options), this.useTimeZone);
491
    }
492

493
    clone(): TinyDate {
494
        return new TinyDate(new Date(this.nativeDate), this.useTimeZone);
495
    }
496

497
    getUnixTime(): number {
498
        return getUnixTime(this.nativeDate);
499
    }
500

501
    compare(date: TinyDateType, grain: TinyDateCompareGrain = 'day', isBefore: boolean = true): boolean {
502
        if (date === null) {
503
            return false;
504
        }
505
        let fn;
506
        switch (grain) {
507
            case 'year':
508
                fn = differenceInCalendarYears;
509
                break;
510
            case 'quarter':
511
                fn = differenceInCalendarQuarters;
512
                break;
513
            case 'month':
514
                fn = differenceInCalendarMonths;
515
                break;
516
            case 'day':
517
                fn = differenceInCalendarDays;
518
                break;
519
            case 'week':
520
                fn = differenceInWeeks;
521
                break;
522
            case 'hour':
523
                fn = differenceInHours;
524
                break;
525
            case 'minute':
526
                fn = differenceInMinutes;
527
                break;
528
            case 'second':
529
                fn = differenceInSeconds;
530
                break;
531
            default:
532
                fn = differenceInCalendarDays;
533
                break;
534
        }
535
        return isBefore ? fn(this.nativeDate, this.toNativeDate(date)) < 0 : fn(this.nativeDate, this.toNativeDate(date)) > 0;
536
    }
537

538
    private toNativeDate(date: any): Date {
539
        return date instanceof TinyDate ? date.nativeDate : date;
540
    }
541

542
    startOfISOWeek(): TinyDate {
543
        return new TinyDate(startOfISOWeek(this.nativeDate), this.useTimeZone);
544
    }
545

546
    endOfISOWeek(): TinyDate {
547
        return new TinyDate(endOfISOWeek(this.nativeDate), this.useTimeZone);
548
    }
549

550
    differenceInDays(date: Date): number {
551
        return differenceInDays(this.nativeDate, date);
552
    }
553

554
    differenceInHours(date: Date): number {
555
        return differenceInHours(this.nativeDate, date);
556
    }
557

558
    subWeeks(amount: number): TinyDate {
559
        return new TinyDate(subWeeks(this.nativeDate, amount), this.useTimeZone);
560
    }
561

562
    subDays(amount: number): TinyDate {
563
        return new TinyDate(subDays(this.nativeDate, amount), this.useTimeZone);
564
    }
565
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc