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

atinc / ngx-tethys / 85805202-86e6-43e8-b885-bc8f4f60d123

13 Feb 2025 10:03AM UTC coverage: 90.223% (-0.05%) from 90.271%
85805202-86e6-43e8-b885-bc8f4f60d123

push

circleci

web-flow
feat(uitl): support timezone #TINFR-1362 (#3287)

5584 of 6849 branches covered (81.53%)

Branch coverage included in aggregate %.

115 of 117 new or added lines in 17 files covered. (98.29%)

44 existing lines in 8 files now uncovered.

13324 of 14108 relevant lines covered (94.44%)

994.11 hits per line

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

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

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

33✔
61
export type WeekDayIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6;
62

2✔
63
export type TinyDateType = TinyDate | Date | null;
2✔
64

65
export function sortRangeValue(rangeValue: TinyDate[]): TinyDate[] {
66
    if (Array.isArray(rangeValue)) {
3✔
67
        const [start, end] = rangeValue;
68
        return start && end && start.isAfterSecond(end) ? [end, start] : [start, end];
69
    }
66✔
70
    return rangeValue;
71
}
72

62✔
73
export const DEFAULT_TIMEZONE = 'Asia/Shanghai';
74

75
export class TinyDate implements Record<string, any> {
62✔
76
    nativeDate: Date;
77

78
    private useTimeZone: string;
1✔
79

80
    protected static defaultTimeZone: string = DEFAULT_TIMEZONE;
81

1✔
82
    constructor(date?: Date | string | number, zone?: string) {
83
        this.useTimeZone = zone || TinyDate.defaultTimeZone;
84
        if (date) {
1✔
85
            if (date instanceof Date) {
86
                this.nativeDate = TinyDate.utcToZonedTime(date, this.useTimeZone);
87
            } else if (typeof date === 'string' || typeof date === 'number') {
88
                this.nativeDate = new TZDate(date as SafeAny, this.useTimeZone);
1✔
89
            } else if (typeof ngDevMode === 'undefined' || ngDevMode) {
1✔
90
                throw new Error(
1✔
91
                    `The input date type is not supported expect Date | string | number | { date: number; with_time: 0 | 1}, actual ${JSON.stringify(
92
                        date
93
                    )}`
102✔
94
                );
102✔
95
            }
102✔
96
        } else {
97
            this.nativeDate = new TZDate(Date.now(), this.useTimeZone);
98
        }
660✔
99
    }
100

101
    static setDefaultTimeZone(zone: string) {
1,256✔
102
        TinyDate.defaultTimeZone = zone ?? DEFAULT_TIMEZONE;
103
    }
104

172✔
105
    static utcToZonedTime(value: Date | number, timeZone?: string): Date {
106
        return TZDate.tz(timeZone || TinyDate.defaultTimeZone, value as any);
107
    }
1✔
108

109
    static createDateInTimeZone(
110
        year: number,
32✔
111
        month: number,
112
        day: number,
113
        hours: number,
32✔
114
        minutes: number,
115
        seconds: number,
116
        timeZone?: string
32✔
117
    ): Date {
118
        return new TZDate(year, month, day, hours, minutes, seconds, timeZone || TinyDate.defaultTimeZone);
119
    }
120

398✔
121
    static fromUnixTime(unixTime: number, timeZone?: string): TinyDate {
122
        return new TinyDate(fromUnixTime(unixTime), timeZone || TinyDate.defaultTimeZone);
123
    }
450✔
124

125
    // get
126
    getTime(): number {
644✔
127
        return this.nativeDate.getTime();
128
    }
129

1✔
130
    getDate(): number {
131
        return this.nativeDate.getDate();
132
    }
28,767✔
133

134
    getYear(): number {
135
        return this.nativeDate.getFullYear();
1✔
136
    }
137

138
    getQuarter(): number {
2✔
139
        return getQuarter(this.nativeDate);
140
    }
141

1✔
142
    getMonth(): number {
143
        return this.nativeDate.getMonth();
144
    }
×
145

146
    getFullYear(): number {
54,809✔
147
        return this.nativeDate.getFullYear();
148
    }
2✔
149

2✔
150
    getWeek(options: { locale?: Locale; weekStartsOn?: WeekDayIndex } = { weekStartsOn: 1 }): number {
151
        return getWeek(this.nativeDate, options);
799✔
152
    }
799✔
153

154
    getDay(): number {
1,421✔
155
        return this.nativeDate.getDay();
1,421✔
156
    }
157

441✔
158
    getHours(): number {
441✔
159
        return this.nativeDate.getHours();
160
    }
52,139✔
161

52,139✔
162
    getMinutes(): number {
163
        return this.nativeDate.getMinutes();
2✔
164
    }
2✔
165

166
    getSeconds(): number {
2✔
167
        return this.nativeDate.getSeconds();
2✔
168
    }
169

2✔
170
    getMilliseconds(): number {
2✔
171
        return this.nativeDate.getMilliseconds();
172
    }
1✔
173

1✔
174
    getDaysInMonth() {
175
        return getDaysInMonth(this.nativeDate);
54,809✔
176
    }
177

178
    getDaysInQuarter() {
787✔
179
        return differenceInCalendarDays(this.endOfQuarter().addSeconds(1).nativeDate, this.startOfQuarter().nativeDate);
180
    }
181

1,363✔
182
    // set
183
    setDate(amount: number): TinyDate {
184
        const date = new Date(this.nativeDate);
441✔
185
        date.setDate(amount);
186
        return new TinyDate(date, this.useTimeZone);
187
    }
52,138✔
188

189
    setHms(hour: number, minute: number, second: number): TinyDate {
190
        const date = new Date(this.nativeDate);
1✔
191
        date.setHours(hour, minute, second);
192
        return new TinyDate(date, this.useTimeZone);
193
    }
1✔
194

195
    setYear(year: number): TinyDate {
196
        return new TinyDate(setYear(this.nativeDate, year), this.useTimeZone);
1✔
197
    }
198

199
    setMonth(month: number): TinyDate {
200
        return new TinyDate(setMonth(this.nativeDate, month), this.useTimeZone);
127✔
201
    }
202

203
    setQuarter(quarter: number): TinyDate {
64✔
204
        return new TinyDate(setQuarter(this.nativeDate, quarter), this.useTimeZone);
205
    }
206

22,111✔
207
    setDay(day: number, options?: { weekStartsOn: WeekDayIndex }): TinyDate {
208
        return new TinyDate(setDay(this.nativeDate, day, options), this.useTimeZone);
209
    }
1✔
210

211
    setHours(hours: number): TinyDate {
212
        return new TinyDate(setHours(this.nativeDate, hours), this.useTimeZone);
7,162✔
213
    }
214

215
    setMinutes(minutes: number): TinyDate {
1✔
216
        return new TinyDate(setMinutes(this.nativeDate, minutes), this.useTimeZone);
217
    }
218

1✔
219
    setSeconds(seconds: number): TinyDate {
220
        return new TinyDate(setSeconds(this.nativeDate, seconds), this.useTimeZone);
221
    }
1✔
222

223
    // add
224
    addYears(amount: number): TinyDate {
2✔
225
        return new TinyDate(addYears(this.nativeDate, amount), this.useTimeZone);
226
    }
UNCOV
227

×
228
    addQuarters(amount: number): TinyDate {
229
        return new TinyDate(addQuarters(this.nativeDate, amount), this.useTimeZone);
230
    }
21,967✔
231

232
    addMonths(amount: number): TinyDate {
233
        return new TinyDate(addMonths(this.nativeDate, amount), this.useTimeZone);
1✔
234
    }
235

236
    addWeeks(amount: number): TinyDate {
2✔
237
        return new TinyDate(addWeeks(this.nativeDate, amount), this.useTimeZone);
238
    }
239

1✔
240
    addDays(amount: number): TinyDate {
241
        return new TinyDate(addDays(this.nativeDate, amount), this.useTimeZone);
242
    }
1✔
243
    addHours(amount: number): TinyDate {
244
        return new TinyDate(addHours(this.nativeDate, amount), this.useTimeZone);
245
    }
15✔
246

247
    addSeconds(amount: number): TinyDate {
248
        return new TinyDate(addSeconds(this.nativeDate, amount), this.useTimeZone);
249
    }
2✔
250

251
    addMinutes(amount: number): TinyDate {
252
        return new TinyDate(addMinutes(this.nativeDate, amount), this.useTimeZone);
43,934✔
253
    }
254

255
    // isSame
2✔
256

257
    isSame(date: TinyDateType, grain: TinyDateCompareGrain = 'day'): boolean {
258
        let fn;
2✔
259
        switch (grain) {
260
            case 'decade':
261
                fn = (pre: Date, next: Date) => Math.abs(pre.getFullYear() - next.getFullYear()) < 11;
262
                break;
379✔
263
            case 'year':
264
                fn = isSameYear;
265
                break;
206✔
266
            case 'month':
267
                fn = isSameMonth;
268
                break;
236✔
269
            case 'quarter':
270
                fn = isSameQuarter;
271
                break;
32✔
272
            case 'day':
273
                fn = isSameDay;
274
                break;
2,376✔
275
            case 'hour':
276
                fn = isSameHour;
277
                break;
113✔
278
            case 'minute':
279
                fn = isSameMinute;
280
                break;
151✔
281
            case 'second':
282
                fn = isSameSecond;
283
                break;
180✔
284
            default:
285
                fn = isSameDay;
286
                break;
33✔
287
        }
288
        return fn(this.nativeDate, this.toNativeDate(date));
289
    }
2,007✔
290

291
    isSameYear(date: TinyDateType): boolean {
292
        return this.isSame(date, 'year');
293
    }
243✔
294

295
    isSameMonth(date: TinyDateType): boolean {
296
        return this.isSame(date, 'month');
1,047✔
297
    }
298

299
    isSameQuarter(date: TinyDateType): boolean {
147✔
300
        return this.isSame(date, 'quarter');
301
    }
302

213✔
303
    isSameDay(date: TinyDateType): boolean {
304
        return this.isSame(date, 'day');
29,468!
305
    }
51,457✔
306

2✔
307
    isSameHour(date: TinyDateType): boolean {
308
        return this.isSame(date, 'hour');
309
    }
51,455!
310

311
    isSameMinute(date: TinyDateType): boolean {
127✔
312
        return this.isSame(date, 'minute');
127✔
313
    }
314

64✔
315
    isSameSecond(date: TinyDateType): boolean {
64✔
316
        return this.isSame(date, 'second');
317
    }
44,078✔
318

44,078✔
319
    // isBefore and isAfter
320
    isBeforeYear(date: TinyDateType): boolean {
7,164✔
321
        return this.compare(date, 'year');
7,164✔
322
    }
323

2✔
324
    isBeforeQuarter(date: TinyDate): boolean {
2✔
325
        return this.compare(date, 'quarter');
326
    }
2✔
327

2✔
328
    isBeforeMonth(date: TinyDateType): boolean {
329
        return this.compare(date, 'month');
2✔
330
    }
2✔
331

332
    isBeforeWeek(date: TinyDateType): boolean {
16✔
333
        return this.compare(date, 'week');
16✔
334
    }
UNCOV
335

×
UNCOV
336
    isBeforeDay(date: TinyDateType): boolean {
×
337
        return this.compare(date, 'day');
338
    }
51,455✔
339

340
    isBeforeHour(date: TinyDateType): boolean {
341
        return this.compare(date, 'hour');
106,264✔
342
    }
343

UNCOV
344
    isBeforeMinute(date: TinyDateType): boolean {
×
345
        return this.compare(date, 'minute');
346
    }
UNCOV
347

×
348
    isBeforeSecond(date: TinyDateType): boolean {
349
        return this.compare(date, 'second');
UNCOV
350
    }
×
351

352
    isAfterYear(date: TinyDateType): boolean {
UNCOV
353
        return this.compare(date, 'year', false);
×
354
    }
355

UNCOV
356
    isAfterQuarter(date: TinyDate): boolean {
×
357
        return this.compare(date, 'quarter', false);
358
    }
359

6✔
360
    isAfterMonth(date: TinyDateType): boolean {
361
        return this.compare(date, 'month', false);
362
    }
363

364
    isAfterWeek(date: TinyDateType): boolean {
365
        return this.compare(date, 'week', false);
366
    }
367

368
    isAfterDay(date: TinyDateType): boolean {
369
        return this.compare(date, 'day', false);
370
    }
371

372
    isAfterHour(date: TinyDateType): boolean {
373
        return this.compare(date, 'hour', false);
374
    }
375

376
    isAfterMinute(date: TinyDateType): boolean {
377
        return this.compare(date, 'minute', false);
378
    }
379

380
    isAfterSecond(date: TinyDateType): boolean {
381
        return this.compare(date, 'second', false);
382
    }
383

384
    // is
385
    isWeekend(): boolean {
386
        return isWeekend(this.nativeDate);
387
    }
388

389
    isToday(): boolean {
390
        return isToday(this.nativeDate);
391
    }
392

393
    isTomorrow(): boolean {
394
        return isTomorrow(this.nativeDate);
395
    }
396

397
    isValid(): boolean {
398
        return isValid(this.nativeDate);
399
    }
400

401
    // startOf and endOf
402
    startOfYear(): TinyDate {
403
        return new TinyDate(startOfYear(this.nativeDate), this.useTimeZone);
404
    }
405

406
    startOfQuarter(): TinyDate {
407
        return new TinyDate(startOfQuarter(this.nativeDate), this.useTimeZone);
408
    }
409

410
    startOfMonth(): TinyDate {
411
        return new TinyDate(startOfMonth(this.nativeDate), this.useTimeZone);
412
    }
413

414
    startOfWeek(options?: { locale?: Locale; weekStartsOn?: WeekDayIndex }): TinyDate {
415
        return new TinyDate(startOfWeek(this.nativeDate, options), this.useTimeZone);
416
    }
417

418
    startOfDay(): TinyDate {
419
        return new TinyDate(startOfDay(this.nativeDate), this.useTimeZone);
420
    }
421

422
    endOfYear(): TinyDate {
423
        return new TinyDate(endOfYear(this.nativeDate), this.useTimeZone);
424
    }
425

426
    endOfQuarter(): TinyDate {
427
        return new TinyDate(endOfQuarter(this.nativeDate), this.useTimeZone);
428
    }
429

430
    endOfMonth(): TinyDate {
431
        return new TinyDate(endOfMonth(this.nativeDate), this.useTimeZone);
432
    }
433

434
    endOfWeek(options?: { locale?: Locale; weekStartsOn?: WeekDayIndex }): TinyDate {
435
        return new TinyDate(endOfWeek(this.nativeDate, options), this.useTimeZone);
436
    }
437

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

442
    // other
443
    format(
444
        mat: string,
445
        options?: {
446
            locale?: Locale;
447
            weekStartsOn?: WeekDayIndex;
448
            firstWeekContainsDate?: FirstWeekContainsDate;
449
            useAdditionalWeekYearTokens?: boolean;
450
            useAdditionalDayOfYearTokens?: boolean;
451
        }
452
    ) {
453
        return format(this.nativeDate, mat, options);
454
    }
455

456
    calendarStart(options?: { weekStartsOn: WeekDayIndex | undefined }): TinyDate {
457
        return new TinyDate(startOfWeek(startOfMonth(this.nativeDate), options), this.useTimeZone);
458
    }
459

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

464
    getUnixTime(): number {
465
        return getUnixTime(this.nativeDate);
466
    }
467

468
    compare(date: TinyDateType, grain: TinyDateCompareGrain = 'day', isBefore: boolean = true): boolean {
469
        if (date === null) {
470
            return false;
471
        }
472
        let fn;
473
        switch (grain) {
474
            case 'year':
475
                fn = differenceInCalendarYears;
476
                break;
477
            case 'quarter':
478
                fn = differenceInCalendarQuarters;
479
                break;
480
            case 'month':
481
                fn = differenceInCalendarMonths;
482
                break;
483
            case 'day':
484
                fn = differenceInCalendarDays;
485
                break;
486
            case 'week':
487
                fn = differenceInWeeks;
488
                break;
489
            case 'hour':
490
                fn = differenceInHours;
491
                break;
492
            case 'minute':
493
                fn = differenceInMinutes;
494
                break;
495
            case 'second':
496
                fn = differenceInSeconds;
497
                break;
498
            default:
499
                fn = differenceInCalendarDays;
500
                break;
501
        }
502
        return isBefore ? fn(this.nativeDate, this.toNativeDate(date)) < 0 : fn(this.nativeDate, this.toNativeDate(date)) > 0;
503
    }
504

505
    private toNativeDate(date: any): Date {
506
        return date instanceof TinyDate ? date.nativeDate : date;
507
    }
508

509
    startOfISOWeek(): TinyDate {
510
        return new TinyDate(startOfISOWeek(this.nativeDate), this.useTimeZone);
511
    }
512

513
    endOfISOWeek(): TinyDate {
514
        return new TinyDate(endOfISOWeek(this.nativeDate), this.useTimeZone);
515
    }
516

517
    differenceInDays(date: Date): number {
518
        return differenceInDays(this.nativeDate, date);
519
    }
520

521
    differenceInHours(date: Date): number {
522
        return differenceInHours(this.nativeDate, date);
523
    }
524

525
    subWeeks(amount: number): TinyDate {
526
        return new TinyDate(subWeeks(this.nativeDate, amount), this.useTimeZone);
527
    }
528

529
    subDays(amount: number): TinyDate {
530
        return new TinyDate(subDays(this.nativeDate, amount), this.useTimeZone);
531
    }
532
}
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