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

atinc / ngx-tethys / 2e530451-f80d-4fee-9e1e-bdd059b54859

10 Apr 2025 08:03AM UTC coverage: 90.216% (-0.02%) from 90.236%
2e530451-f80d-4fee-9e1e-bdd059b54859

Pull #3331

circleci

xinglu01
fix(util): fix test
Pull Request #3331: fix(util): handle i18n and tinyDate set default locale

5607 of 6879 branches covered (81.51%)

Branch coverage included in aggregate %.

13 of 14 new or added lines in 3 files covered. (92.86%)

3 existing lines in 2 files now uncovered.

13369 of 14155 relevant lines covered (94.45%)

995.33 hits per line

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

91.78
/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 { isIncludeLocale, ThyLocaleType } from 'ngx-tethys/i18n';
4
import { SafeAny } from 'ngx-tethys/types';
5
import {
6
    addDays,
15✔
7
    addHours,
14✔
8
    addMinutes,
14✔
9
    addMonths,
10
    addQuarters,
1✔
11
    addSeconds,
12
    addWeeks,
1✔
13
    addYears,
14
    differenceInCalendarDays,
1✔
15
    differenceInCalendarMonths,
1✔
16
    differenceInCalendarQuarters,
1✔
17
    differenceInCalendarYears,
18
    differenceInDays,
44,209✔
19
    differenceInHours,
44,209✔
20
    differenceInMinutes,
44,209✔
21
    differenceInSeconds,
41,532✔
22
    differenceInWeeks,
41,122✔
23
    endOfDay,
24
    endOfISOWeek,
410✔
25
    endOfMonth,
409✔
26
    endOfQuarter,
27
    endOfWeek,
1!
28
    endOfYear,
1✔
29
    format,
30
    fromUnixTime,
31
    getDateFnsLocale,
32
    getDaysInMonth,
2,677✔
33
    getQuarter,
34
    getUnixTime,
35
    getWeek,
36
    isSameDay,
1✔
37
    isSameHour,
1!
38
    isSameMinute,
1✔
39
    isSameMonth,
40
    isSameQuarter,
1!
41
    isSameSecond,
42
    isSameYear,
43
    isToday,
67✔
44
    isTomorrow,
67✔
45
    isValid,
67✔
46
    isWeekend,
47
    setDay,
UNCOV
48
    setDefaultOptions,
×
49
    setMonth,
50
    setQuarter,
51
    setYear,
1!
52
    startOfDay,
53
    startOfISOWeek,
UNCOV
54
    startOfMonth,
×
55
    startOfQuarter,
56
    startOfWeek,
57
    startOfYear,
41,122!
58
    subDays,
59
    subWeeks
60
} from './functions';
41✔
61

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

172✔
64
export type WeekDayIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6;
65

66
export type TinyDateType = TinyDate | Date | null;
67

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

76
export const DEFAULT_TIMEZONE = 'Asia/Shanghai';
174✔
77

78
export class TinyDate implements Record<string, any> {
79
    nativeDate: Date;
755✔
80

81
    private useTimeZone: string;
82

33✔
83
    private static locale: string = TinyDate.getDefaultLocaleId();
84

2✔
85
    protected static dateFnsLocale: Locale = getDateFnsLocale(TinyDate.locale);
2✔
86

87
    protected static defaultTimeZone: string = DEFAULT_TIMEZONE;
88

3✔
89
    constructor(date?: Date | string | number, zone?: string) {
90
        setDefaultOptions({ locale: TinyDate.dateFnsLocale });
91
        this.useTimeZone = zone || TinyDate.defaultTimeZone;
66✔
92
        if (date) {
93
            if (date instanceof Date) {
94
                this.nativeDate = TinyDate.utcToZonedTime(date, this.useTimeZone);
62✔
95
            } else if (typeof date === 'string' || typeof date === 'number') {
96
                this.nativeDate = new TZDate(date as SafeAny, this.useTimeZone);
97
            } else if (typeof ngDevMode === 'undefined' || ngDevMode) {
62✔
98
                throw new Error(
99
                    `The input date type is not supported expect Date | string | number | { date: number; with_time: 0 | 1}, actual ${JSON.stringify(
100
                        date
1✔
101
                    )}`
102
                );
103
            }
1✔
104
        } else {
105
            this.nativeDate = new TZDate(Date.now(), this.useTimeZone);
106
        }
1✔
107
    }
108

109
    static getDefaultLocaleId(): ThyLocaleType {
110
        let defaultLocaleId = ThyLocaleType.zhHans;
1✔
111
        if (typeof window !== 'undefined' && window?.navigator?.language) {
1✔
112
            defaultLocaleId = window.navigator?.language?.toLowerCase() as ThyLocaleType;
1✔
113
        }
114
        return isIncludeLocale(defaultLocaleId) ? defaultLocaleId : ThyLocaleType.zhHans;
115
    }
102✔
116

102✔
117
    static setDefaultLocale(locale: string) {
102✔
118
        TinyDate.locale = locale;
119
        TinyDate.dateFnsLocale = getDateFnsLocale(locale);
120
        return setDefaultOptions({ locale: TinyDate.dateFnsLocale });
660✔
121
    }
122

123
    static getDefaultLocale(): { locale: string; dateFnsLocale: Locale } {
1,256✔
124
        return { locale: TinyDate.locale, dateFnsLocale: TinyDate.dateFnsLocale };
125
    }
126

172✔
127
    static setDefaultTimeZone(zone: string) {
128
        TinyDate.defaultTimeZone = zone ?? DEFAULT_TIMEZONE;
129
    }
1✔
130

131
    static getDefaultTimeZone(): string {
132
        return TinyDate.defaultTimeZone;
32✔
133
    }
134

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

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

151
    static fromUnixTime(unixTime: number, timeZone?: string): TinyDate {
1✔
152
        return new TinyDate(fromUnixTime(unixTime), timeZone || TinyDate.defaultTimeZone);
153
    }
154

28,987✔
155
    // get
156
    getTime(): number {
157
        return this.nativeDate.getTime();
1✔
158
    }
159

160
    getDate(): number {
2✔
161
        return this.nativeDate.getDate();
162
    }
163

1✔
164
    getYear(): number {
165
        return this.nativeDate.getFullYear();
166
    }
×
167

168
    getQuarter(): number {
55,148✔
169
        return getQuarter(this.nativeDate);
170
    }
2✔
171

2✔
172
    getMonth(): number {
173
        return this.nativeDate.getMonth();
799✔
174
    }
799✔
175

176
    getFullYear(): number {
1,425✔
177
        return this.nativeDate.getFullYear();
1,425✔
178
    }
179

441✔
180
    getWeek(options: { locale?: Locale; weekStartsOn?: WeekDayIndex } = { weekStartsOn: 1 }): number {
441✔
181
        return getWeek(this.nativeDate, options);
182
    }
52,474✔
183

52,474✔
184
    getDay(): number {
185
        return this.nativeDate.getDay();
2✔
186
    }
2✔
187

188
    getHours(): number {
2✔
189
        return this.nativeDate.getHours();
2✔
190
    }
191

2✔
192
    getMinutes(): number {
2✔
193
        return this.nativeDate.getMinutes();
194
    }
1✔
195

1✔
196
    getSeconds(): number {
197
        return this.nativeDate.getSeconds();
55,148✔
198
    }
199

200
    getMilliseconds(): number {
787✔
201
        return this.nativeDate.getMilliseconds();
202
    }
203

1,363✔
204
    getDaysInMonth() {
205
        return getDaysInMonth(this.nativeDate);
206
    }
441✔
207

208
    getDaysInQuarter() {
209
        return differenceInCalendarDays(this.endOfQuarter().addSeconds(1).nativeDate, this.startOfQuarter().nativeDate);
52,473✔
210
    }
211

212
    // set
1✔
213
    setDate(amount: number): TinyDate {
214
        const date = new Date(this.nativeDate);
215
        date.setDate(amount);
1✔
216
        return new TinyDate(date, this.useTimeZone);
217
    }
218

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

225
    setYear(year: number): TinyDate {
64✔
226
        return new TinyDate(setYear(this.nativeDate, year), this.useTimeZone);
227
    }
228

22,279✔
229
    setMonth(month: number): TinyDate {
230
        return new TinyDate(setMonth(this.nativeDate, month), this.useTimeZone);
231
    }
1✔
232

233
    setQuarter(quarter: number): TinyDate {
234
        return new TinyDate(setQuarter(this.nativeDate, quarter), this.useTimeZone);
7,151✔
235
    }
236

237
    setDay(day: number, options?: { weekStartsOn: WeekDayIndex }): TinyDate {
1✔
238
        return new TinyDate(setDay(this.nativeDate, day, options), this.useTimeZone);
239
    }
240

1✔
241
    setHours(hours: number): TinyDate {
242
        return new TinyDate(setHours(this.nativeDate, hours), this.useTimeZone);
243
    }
1✔
244

245
    setMinutes(minutes: number): TinyDate {
246
        return new TinyDate(setMinutes(this.nativeDate, minutes), this.useTimeZone);
2✔
247
    }
248

249
    setSeconds(seconds: number): TinyDate {
×
250
        return new TinyDate(setSeconds(this.nativeDate, seconds), this.useTimeZone);
251
    }
252

22,135✔
253
    // add
254
    addYears(amount: number): TinyDate {
255
        return new TinyDate(addYears(this.nativeDate, amount), this.useTimeZone);
1✔
256
    }
257

258
    addQuarters(amount: number): TinyDate {
2✔
259
        return new TinyDate(addQuarters(this.nativeDate, amount), this.useTimeZone);
260
    }
261

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

266
    addWeeks(amount: number): TinyDate {
267
        return new TinyDate(addWeeks(this.nativeDate, amount), this.useTimeZone);
15✔
268
    }
269

270
    addDays(amount: number): TinyDate {
271
        return new TinyDate(addDays(this.nativeDate, amount), this.useTimeZone);
2✔
272
    }
273
    addHours(amount: number): TinyDate {
274
        return new TinyDate(addHours(this.nativeDate, amount), this.useTimeZone);
44,270✔
275
    }
276

277
    addSeconds(amount: number): TinyDate {
2✔
278
        return new TinyDate(addSeconds(this.nativeDate, amount), this.useTimeZone);
279
    }
280

2✔
281
    addMinutes(amount: number): TinyDate {
282
        return new TinyDate(addMinutes(this.nativeDate, amount), this.useTimeZone);
283
    }
284

379✔
285
    // isSame
286

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

321
    isSameYear(date: TinyDateType): boolean {
147✔
322
        return this.isSame(date, 'year');
323
    }
324

213✔
325
    isSameMonth(date: TinyDateType): boolean {
326
        return this.isSame(date, 'month');
29,625!
327
    }
51,782✔
328

2✔
329
    isSameQuarter(date: TinyDateType): boolean {
330
        return this.isSame(date, 'quarter');
331
    }
51,780!
332

333
    isSameDay(date: TinyDateType): boolean {
127✔
334
        return this.isSame(date, 'day');
127✔
335
    }
336

64✔
337
    isSameHour(date: TinyDateType): boolean {
64✔
338
        return this.isSame(date, 'hour');
339
    }
44,414✔
340

44,414✔
341
    isSameMinute(date: TinyDateType): boolean {
342
        return this.isSame(date, 'minute');
7,153✔
343
    }
7,153✔
344

345
    isSameSecond(date: TinyDateType): boolean {
2✔
346
        return this.isSame(date, 'second');
2✔
347
    }
348

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

354
    isBeforeQuarter(date: TinyDate): boolean {
16✔
355
        return this.compare(date, 'quarter');
16✔
356
    }
357

×
358
    isBeforeMonth(date: TinyDateType): boolean {
×
359
        return this.compare(date, 'month');
360
    }
51,780✔
361

362
    isBeforeWeek(date: TinyDateType): boolean {
363
        return this.compare(date, 'week');
106,928✔
364
    }
365

366
    isBeforeDay(date: TinyDateType): boolean {
×
367
        return this.compare(date, 'day');
368
    }
369

×
370
    isBeforeHour(date: TinyDateType): boolean {
371
        return this.compare(date, 'hour');
372
    }
×
373

374
    isBeforeMinute(date: TinyDateType): boolean {
375
        return this.compare(date, 'minute');
×
376
    }
377

378
    isBeforeSecond(date: TinyDateType): boolean {
×
379
        return this.compare(date, 'second');
380
    }
381

6✔
382
    isAfterYear(date: TinyDateType): boolean {
383
        return this.compare(date, 'year', false);
384
    }
385

386
    isAfterQuarter(date: TinyDate): boolean {
387
        return this.compare(date, 'quarter', false);
388
    }
389

390
    isAfterMonth(date: TinyDateType): boolean {
391
        return this.compare(date, 'month', false);
392
    }
393

394
    isAfterWeek(date: TinyDateType): boolean {
395
        return this.compare(date, 'week', false);
396
    }
397

398
    isAfterDay(date: TinyDateType): boolean {
399
        return this.compare(date, 'day', false);
400
    }
401

402
    isAfterHour(date: TinyDateType): boolean {
403
        return this.compare(date, 'hour', false);
404
    }
405

406
    isAfterMinute(date: TinyDateType): boolean {
407
        return this.compare(date, 'minute', false);
408
    }
409

410
    isAfterSecond(date: TinyDateType): boolean {
411
        return this.compare(date, 'second', false);
412
    }
413

414
    // is
415
    isWeekend(): boolean {
416
        return isWeekend(this.nativeDate);
417
    }
418

419
    isToday(): boolean {
420
        return isToday(this.nativeDate);
421
    }
422

423
    isTomorrow(): boolean {
424
        return isTomorrow(this.nativeDate);
425
    }
426

427
    isValid(): boolean {
428
        return isValid(this.nativeDate);
429
    }
430

431
    // startOf and endOf
432
    startOfYear(): TinyDate {
433
        return new TinyDate(startOfYear(this.nativeDate), this.useTimeZone);
434
    }
435

436
    startOfQuarter(): TinyDate {
437
        return new TinyDate(startOfQuarter(this.nativeDate), this.useTimeZone);
438
    }
439

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

444
    startOfWeek(options?: { locale?: Locale; weekStartsOn?: WeekDayIndex }): TinyDate {
445
        return new TinyDate(startOfWeek(this.nativeDate, options), this.useTimeZone);
446
    }
447

448
    startOfDay(): TinyDate {
449
        return new TinyDate(startOfDay(this.nativeDate), this.useTimeZone);
450
    }
451

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

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

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

464
    endOfWeek(options?: { locale?: Locale; weekStartsOn?: WeekDayIndex }): TinyDate {
465
        return new TinyDate(endOfWeek(this.nativeDate, options), this.useTimeZone);
466
    }
467

468
    endOfDay(): TinyDate {
469
        return new TinyDate(endOfDay(this.nativeDate), this.useTimeZone);
470
    }
471

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

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

490
    clone(): TinyDate {
491
        return new TinyDate(new Date(this.nativeDate), this.useTimeZone);
492
    }
493

494
    getUnixTime(): number {
495
        return getUnixTime(this.nativeDate);
496
    }
497

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

535
    private toNativeDate(date: any): Date {
536
        return date instanceof TinyDate ? date.nativeDate : date;
537
    }
538

539
    startOfISOWeek(): TinyDate {
540
        return new TinyDate(startOfISOWeek(this.nativeDate), this.useTimeZone);
541
    }
542

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

547
    differenceInDays(date: Date): number {
548
        return differenceInDays(this.nativeDate, date);
549
    }
550

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

555
    subWeeks(amount: number): TinyDate {
556
        return new TinyDate(subWeeks(this.nativeDate, amount), this.useTimeZone);
557
    }
558

559
    subDays(amount: number): TinyDate {
560
        return new TinyDate(subDays(this.nativeDate, amount), this.useTimeZone);
561
    }
562
}
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

© 2026 Coveralls, Inc