@tactics/toddle-styleguide 5.3.1 → 5.3.3
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/App.tsx +0 -19
- package/app.json +8 -4
- package/index.tsx +0 -4
- package/package.json +55 -27
- package/src/components/atoms/calendar/calendar.component.tsx +10 -6
- package/src/components/atoms/calendar/calendar.preview.tsx +4 -3
- package/src/components/molecules/amount/amount.component.tsx +5 -4
- package/src/components/molecules/calendar-select/calendar-select.component.d.ts +4 -4
- package/src/components/molecules/calendar-select/calendar-select.component.tsx +17 -16
- package/src/components/molecules/calendar-select/calendar-select.preview.tsx +16 -5
- package/src/components/molecules/date-input/date-input.component.d.ts +2 -2
- package/src/components/molecules/date-input/date-input.component.tsx +3 -3
- package/src/components/molecules/date-input/date-input.preview.tsx +5 -5
- package/src/components/molecules/day/day.component.d.ts +3 -3
- package/src/components/molecules/day/day.component.tsx +22 -17
- package/src/components/molecules/selectable-list-item/selectable-list-item.component.tsx +12 -7
- package/src/components/molecules/timestamp/timestamp.component.d.ts +2 -2
- package/src/components/molecules/timestamp/timestamp.component.tsx +12 -5
- package/src/components/organisms/journal-entry/components/journal-entry-type/journal-entry-type.component.d.ts +2 -2
- package/src/components/organisms/journal-entry/components/journal-entry-type/journal-entry-type.component.tsx +5 -5
- package/src/components/organisms/journal-entry/journal-entry.component.d.ts +3 -3
- package/src/components/organisms/journal-entry/journal-entry.component.tsx +7 -3
- package/src/components/organisms/journal-entry/journal-entry.preview.tsx +7 -3
- package/src/components/organisms/text-bubble/text-bubble.component.d.ts +2 -2
- package/src/components/organisms/text-bubble/text-bubble.component.tsx +6 -3
- package/src/components/organisms/text-bubble/text-bubble.preview.tsx +19 -7
- package/src/components/templates/popover-action/popover-action.component.tsx +9 -8
- package/src/icons/solid/cloud-download/cloud-download.icon.tsx +1 -1
- package/src/icons/solid/refresh/refresh-solid.icon.tsx +2 -2
- package/src/utilities/datetime/clock.class.tsx +14 -0
- package/src/utilities/datetime/dateonly.class.tsx +287 -0
- package/src/utilities/datetime/datetime.class.tsx +288 -0
- package/src/utilities/datetime/day.class.tsx +48 -0
- package/src/utilities/datetime/dayjs-config.ts +96 -0
- package/src/utilities/datetime/dayoftheweek.class.tsx +242 -0
- package/src/utilities/datetime/hour.class.tsx +60 -0
- package/src/utilities/datetime/locale.tsx +6 -0
- package/src/utilities/datetime/millisecond.class.tsx +48 -0
- package/src/utilities/datetime/minute.class.tsx +55 -0
- package/src/utilities/datetime/month.class.tsx +74 -0
- package/src/utilities/datetime/second.class.tsx +52 -0
- package/src/utilities/datetime/time.class.tsx +190 -0
- package/src/utilities/datetime/timezone.class.tsx +36 -0
- package/src/utilities/datetime/year.class.tsx +78 -0
- package/src/utilities/datetime/yearandmonth.class.tsx +80 -0
- package/src/components/atoms/background-gradient/__snapshots__/background-gradient.test.js.snap +0 -40
- package/src/components/atoms/background-gradient/background-gradient.test.js +0 -10
- package/src/components/atoms/calendar/__snapshots__/calendar.test.js.snap +0 -6817
- package/src/components/atoms/calendar/calendar.test.js +0 -35
- package/src/components/atoms/check-switch/__snapshots__/check-switch.test.js.snap +0 -84
- package/src/components/atoms/check-switch/check-switch.test.js +0 -13
- package/src/components/atoms/heading-components/all-caps-heading/__snapshots__/all-caps-heading.test.js.snap +0 -113
- package/src/components/atoms/heading-components/all-caps-heading/all-caps-heading.test.js +0 -44
- package/src/components/atoms/heading-components/heading1/__snapshots__/heading1.test.js.snap +0 -121
- package/src/components/atoms/heading-components/heading1/heading1.test.js +0 -51
- package/src/components/atoms/heading-components/heading2/__snapshots__/heading2.test.js.snap +0 -121
- package/src/components/atoms/heading-components/heading2/heading2.test.js +0 -51
- package/src/components/atoms/heading-components/heading3/__snapshots__/heading3.test.js.snap +0 -121
- package/src/components/atoms/heading-components/heading3/heading3.test.js +0 -51
- package/src/components/atoms/heading-components/heading4/__snapshots__/heading4.test.js.snap +0 -121
- package/src/components/atoms/heading-components/heading4/heading4.test.js +0 -51
- package/src/components/atoms/image-bubble/__snapshots__/image-bubble.test.js.snap +0 -67
- package/src/components/atoms/image-bubble/image-bubble.test.js +0 -20
- package/src/components/atoms/increment-input/__snapshots__/increment-input.test.js.snap +0 -269
- package/src/components/atoms/increment-input/increment-input.test.js +0 -14
- package/src/components/atoms/logo/__snapshots__/logo.test.js.snap +0 -113
- package/src/components/atoms/logo/logo.test.js +0 -16
- package/src/components/atoms/paragraph-components/paragraph/__snapshots__/paragraph.test.js.snap +0 -121
- package/src/components/atoms/paragraph-components/paragraph/paragraph.test.js +0 -76
- package/src/components/atoms/paragraph-components/small-text/__snapshots__/small-text.test.js.snap +0 -121
- package/src/components/atoms/paragraph-components/small-text/small-text.test.js +0 -76
- package/src/components/atoms/paragraph-components/tiny-text/__snapshots__/tiny-text.test.js.snap +0 -121
- package/src/components/atoms/paragraph-components/tiny-text/tiny-text.test.js +0 -76
- package/src/components/atoms/quick-message/__snapshots__/quick-message.test.js.snap +0 -143
- package/src/components/atoms/quick-message/quick-message.test.js +0 -58
- package/src/components/atoms/split-container/__snapshots__/split-container.test.js.snap +0 -333
- package/src/components/atoms/split-container/split-container.test.js +0 -45
- package/src/components/atoms/text-input/__snapshots__/text-input.test.js.snap +0 -123
- package/src/components/atoms/text-input/text-input.test.js +0 -59
- package/src/components/molecules/avatar/__snapshots__/avatar.test.js.snap +0 -97
- package/src/components/molecules/avatar/avatar.test.js +0 -22
- package/src/components/molecules/blocked-message/__snapshots__/blocked-message.test.js.snap +0 -107
- package/src/components/molecules/blocked-message/blocked-message.test.js +0 -12
- package/src/components/molecules/button/__snapshots__/button.test.js.snap +0 -652
- package/src/components/molecules/button/button.test.js +0 -56
- package/src/components/molecules/calendar-select/__snapshots__/calendar-select.test.js.snap +0 -343
- package/src/components/molecules/calendar-select/calendar-select.test.js +0 -20
- package/src/components/molecules/cancel-link/__snapshots__/cancel-link.test.js.snap +0 -139
- package/src/components/molecules/cancel-link/cancel-link.test.js +0 -28
- package/src/components/molecules/checkbox/__snapshots__/checkbox.test.js.snap +0 -176
- package/src/components/molecules/checkbox/checkbox.test.js +0 -30
- package/src/components/molecules/contact-address/__snapshots__/contact-address.test.js.snap +0 -113
- package/src/components/molecules/contact-address/contact-address.test.js +0 -18
- package/src/components/molecules/contact-role/__snapshots__/contact-role.test.js.snap +0 -113
- package/src/components/molecules/contact-role/contact-role.test.js +0 -18
- package/src/components/molecules/date-input/__snapshots__/date-input.test.js.snap +0 -140
- package/src/components/molecules/date-input/date-input.test.js +0 -23
- package/src/components/molecules/day/__snapshots__/day.test.js.snap +0 -263
- package/src/components/molecules/day/day.test.js +0 -37
- package/src/components/molecules/default-select/__snapshots__/default-select.test.js.snap +0 -140
- package/src/components/molecules/default-select/default-select.test.js +0 -17
- package/src/components/molecules/department_logo/__snapshots__/department-logo.test.js.snap +0 -27
- package/src/components/molecules/department_logo/department-logo.test.js +0 -12
- package/src/components/molecules/failed-to-send/__snapshots__/failed-bubble.test.js.snap +0 -386
- package/src/components/molecules/failed-to-send/failed-bubble.test.js +0 -75
- package/src/components/molecules/filter-range/__snapshots__/filter-range.test.js.snap +0 -208
- package/src/components/molecules/filter-range/filter-range.test.js +0 -20
- package/src/components/molecules/filter-tab/__snapshots__/filter-tab.test.js.snap +0 -536
- package/src/components/molecules/filter-tab/filter-tab.test.js +0 -42
- package/src/components/molecules/info/__snapshots__/info.test.js.snap +0 -64
- package/src/components/molecules/info/info.test.js +0 -18
- package/src/components/molecules/language-button/__snapshots__/language-button.test.js.snap +0 -129
- package/src/components/molecules/language-button/language-button.test.js +0 -29
- package/src/components/molecules/message-input/__snapshots__/message-input.test.js.snap +0 -611
- package/src/components/molecules/message-input/message-input.test.js +0 -63
- package/src/components/molecules/more-info-button/__snapshots__/more-info-button.test.js.snap +0 -133
- package/src/components/molecules/more-info-button/more-info-button.test.js +0 -29
- package/src/components/molecules/password-input/__snapshots__/password-input.test.js.snap +0 -504
- package/src/components/molecules/password-input/password-input.test.js +0 -46
- package/src/components/molecules/pill/__snapshots__/pill.test.js.snap +0 -226
- package/src/components/molecules/pill/pill.test.js +0 -42
- package/src/components/molecules/pressable-icon/__snapshots__/pressable-icon.test.js.snap +0 -460
- package/src/components/molecules/pressable-icon/pressable-icon.test.js +0 -51
- package/src/components/molecules/quick-filter/__snapshots__/quick-filter.test.js.snap +0 -557
- package/src/components/molecules/quick-filter/quick-filter.test.js +0 -134
- package/src/components/molecules/search-input/__snapshots__/search.test.js.snap +0 -145
- package/src/components/molecules/search-input/search.test.js +0 -22
- package/src/components/molecules/select-link/__snapshots__/select-link.test.js.snap +0 -70
- package/src/components/molecules/select-link/select-link.test.js +0 -17
- package/src/components/molecules/select-list-item/__snapshots__/select-list-item.test.js.snap +0 -762
- package/src/components/molecules/select-list-item/select-list-item.test.js +0 -38
- package/src/components/molecules/select-picker/__snapshots__/select-picker.test.js.snap +0 -407
- package/src/components/molecules/select-picker/select-picker.test.js +0 -31
- package/src/components/molecules/send-bubble/__snapshots__/send-text-bubble.test.js.snap +0 -1979
- package/src/components/molecules/send-bubble/send-text-bubble.test.js +0 -156
- package/src/components/molecules/snackbar/__snapshots__/snackbar.test.js.snap +0 -557
- package/src/components/molecules/snackbar/snackbar.test.js +0 -35
- package/src/components/molecules/swipe/__snapshots__/swipe.test.js.snap +0 -340
- package/src/components/molecules/swipe/swipe.test.js +0 -46
- package/src/components/molecules/tag/__snapshots__/tag.test.js.snap +0 -139
- package/src/components/molecules/tag/tag.test.js +0 -34
- package/src/components/molecules/time-picker/__snapshots__/time-picker.test.js.snap +0 -2221
- package/src/components/molecules/time-picker/time-picker.test.js +0 -18
- package/src/components/molecules/time-tracker/__snapshots__/time-tracker.test.js.snap +0 -266
- package/src/components/molecules/time-tracker/time-tracker.test.js +0 -36
- package/src/components/molecules/timeline/__snapshots__/timeline.test.js.snap +0 -257
- package/src/components/molecules/timeline/timeline.test.js +0 -18
- package/src/components/molecules/timestamp/__snapshots__/timestamp.test.js.snap +0 -28
- package/src/components/molecules/timestamp/timestamp.test.js +0 -16
- package/src/components/molecules/wave-background/__snapshots__/wave.test.js.snap +0 -173
- package/src/components/molecules/wave-background/wave.test.js +0 -25
- package/src/components/molecules/wide-button/__snapshots__/wide-button.test.js.snap +0 -269
- package/src/components/molecules/wide-button/wide-button.test.js +0 -30
- package/src/components/organisms/child-list-item/__snapshots__/child-list-item.test.js.snap +0 -1040
- package/src/components/organisms/child-list-item/child-list-item.test.js +0 -75
- package/src/components/organisms/contact-item/__snapshots__/contact-item.test.js.snap +0 -404
- package/src/components/organisms/contact-item/contact-item.test.js +0 -22
- package/src/components/organisms/loading-indicator/__snapshots__/loading-indicator.test.js.snap +0 -474
- package/src/components/organisms/loading-indicator/loading-indicator.test.js +0 -41
- package/src/components/organisms/my-child-list-item/__snapshots__/my-child-list-item.test.js.snap +0 -293
- package/src/components/organisms/my-child-list-item/my-child-list-item.test.js +0 -23
- package/src/components/organisms/person-info-card/__snapshots__/person-info-card.test.js.snap +0 -709
- package/src/components/organisms/person-info-card/person-info-card.test.js +0 -85
- package/src/components/organisms/text-bubble/__snapshots__/text-bubble.test.js.snap +0 -3046
- package/src/components/organisms/text-bubble/text-bubble.test.js +0 -144
- package/src/utilities/toddle-datetime/interfaces/duration.interface.d.ts +0 -22
- package/src/utilities/toddle-datetime/interfaces/duration.interface.tsx +0 -23
- package/src/utilities/toddle-datetime/interfaces/toddle-datetime.interface.d.ts +0 -22
- package/src/utilities/toddle-datetime/interfaces/toddle-datetime.interface.tsx +0 -25
- package/src/utilities/toddle-datetime/toddle-datetime.class.d.ts +0 -50
- package/src/utilities/toddle-datetime/toddle-datetime.class.tsx +0 -206
- package/src/utilities/toddle-datetime/toddle-datetime.preview.d.ts +0 -2
- package/src/utilities/toddle-datetime/toddle-datetime.preview.tsx +0 -160
- package/src/utilities/toddle-datetime/toddle-datetime.test.js +0 -127
- package/src/utilities/toddle-datetime/types/duration.type.d.ts +0 -4
- package/src/utilities/toddle-datetime/types/duration.type.tsx +0 -6
- package/src/utilities/toddle-datetime/types/toddle-datetime.type.d.ts +0 -5
- package/src/utilities/toddle-datetime/types/toddle-datetime.type.tsx +0 -23
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import {Day, DayOutputFormat} from './day.class';
|
|
2
|
+
import {DayOfWeek, ZeroBasedWeekdayNumbers} from './dayoftheweek.class';
|
|
3
|
+
import {Month, MonthIndex, MonthLocalAwareOutputFormat, MonthOutputFormat} from './month.class';
|
|
4
|
+
import {Year, YearOutputFormat} from './year.class';
|
|
5
|
+
import dayjs, {Dayjs} from 'dayjs';
|
|
6
|
+
import {YearAndMonth, YearAndMonthLocaleAwareOutputFormat, YearAndMonthOutputFormat} from './yearandmonth.class';
|
|
7
|
+
import {Locale} from './locale';
|
|
8
|
+
|
|
9
|
+
export class InvalidDate extends Error {
|
|
10
|
+
static invalidDate() {
|
|
11
|
+
return new InvalidDate("The provided date is invalid.");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static invalidDateFormat() {
|
|
15
|
+
return new InvalidDate("The provided date format is invalid.");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export enum DateOnlyOutputFormat {
|
|
20
|
+
/** ISO 8601 standard format (e.g., 2024-10-18) */
|
|
21
|
+
DATEONLY_ISO8601 = "yyyy-MM-dd",
|
|
22
|
+
|
|
23
|
+
/** Format in most european countries (e.g., 18-10-2024) */
|
|
24
|
+
DATEONLY_DAY_MONTH_YEAR_DASH = "dd-MM-yyyy",
|
|
25
|
+
|
|
26
|
+
/** Format in uk, france, ... (e.g., 18/10/2024) */
|
|
27
|
+
DATEONLY_DAY_MONTH_YEAR_SLASH = "dd/MM/yyyy",
|
|
28
|
+
|
|
29
|
+
/** Format in usa, with month first (e.g., 10/18/2024) */
|
|
30
|
+
DATEONLY_MONTH_DAY_YEAR_SLASH = "MM/dd/yyyy",
|
|
31
|
+
|
|
32
|
+
/** Format in germany (e.g., 18.10.2024) */
|
|
33
|
+
DATEONLY_DAY_MONTH_YEAR_PERIOD = "dd.MM.yyyy",
|
|
34
|
+
|
|
35
|
+
/** Custom format often used in logging (e.g., 20241018) */
|
|
36
|
+
DATEONLY_COMPACT = "yyyyMMdd",
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export enum DateOnlyLocaleAwareOutputFormat {
|
|
41
|
+
|
|
42
|
+
/** Full month name with day and year (e.g., October 18, 2024) */
|
|
43
|
+
DATEONLY_FULL = "ddd, D MMM YYYY",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
export interface DateOnlyCalculation {
|
|
48
|
+
years?: number;
|
|
49
|
+
months?: number;
|
|
50
|
+
days?: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface DateOnlyInterface {
|
|
54
|
+
year: Year;
|
|
55
|
+
month: Month;
|
|
56
|
+
day: Day;
|
|
57
|
+
dayOfWeek: DayOfWeek;
|
|
58
|
+
|
|
59
|
+
isSame(zone: DateOnlyInterface): boolean;
|
|
60
|
+
isBefore(dateOnly: DateOnlyInterface): boolean;
|
|
61
|
+
isAfter(dateOnly: DateOnlyInterface): boolean;
|
|
62
|
+
|
|
63
|
+
format(format: DateOnlyOutputFormat): string;
|
|
64
|
+
formatLocale(format: DateOnlyLocaleAwareOutputFormat, locale: Locale): string;
|
|
65
|
+
|
|
66
|
+
toIso8601(): string;
|
|
67
|
+
toYearAndMonth(): YearAndMonth
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class DateOnly implements DateOnlyInterface {
|
|
71
|
+
public readonly year: Year;
|
|
72
|
+
public readonly month: Month;
|
|
73
|
+
public readonly day: Day;
|
|
74
|
+
public readonly dayOfWeek: DayOfWeek;
|
|
75
|
+
|
|
76
|
+
private constructor(year: Year, month: Month, day: Day) {
|
|
77
|
+
// We just convert it to a datetime to check if the date is valid with dayjs.
|
|
78
|
+
// f.e 31 februari 2024 is a date you could form, but is not valid.
|
|
79
|
+
const asDate = dayjs(`${year.format(YearOutputFormat.YEAR_NUMERIC)}-${month.format(MonthOutputFormat.MONTH_TWO_DIGIT)}-${day.format(DayOutputFormat.DAY_TWO_DIGIT)}`, 'YYYY-MM-DD', true); // strict parsing
|
|
80
|
+
if (!asDate.isValid) {
|
|
81
|
+
throw InvalidDate.invalidDate();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.year = year;
|
|
85
|
+
this.month = month;
|
|
86
|
+
this.day = day;
|
|
87
|
+
|
|
88
|
+
this.dayOfWeek = DayOfWeek.fromZeroBased(
|
|
89
|
+
asDate.day() as ZeroBasedWeekdayNumbers,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static from(year: Year, month: Month, day: Day) {
|
|
94
|
+
return new DateOnly(year, month, day);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// https://nl.wikipedia.org/wiki/ISO_8601
|
|
98
|
+
static fromIso8601(dateString: string): DateOnlyInterface {
|
|
99
|
+
const regex = /^\d{4}-\d{2}-\d{2}$/;
|
|
100
|
+
|
|
101
|
+
// Check needed to prevent Dayjs from parsing other formats we don't want.
|
|
102
|
+
if (!regex.test(dateString)) {
|
|
103
|
+
throw InvalidDate.invalidDateFormat();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const parsed = dayjs(dateString);
|
|
107
|
+
if (!parsed.isValid()) {
|
|
108
|
+
throw InvalidDate.invalidDateFormat();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return DateOnly.from(Year.from(parsed.year()), Month.from(parsed.month(), MonthIndex.ZERO_BASED), Day.from(parsed.date()));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static next(
|
|
115
|
+
now: DateOnlyInterface,
|
|
116
|
+
month: Month,
|
|
117
|
+
day: Day,
|
|
118
|
+
): DateOnlyInterface {
|
|
119
|
+
if (now.month.asInt() > month.asInt()) {
|
|
120
|
+
return DateOnly.from(now.year.next(), month, day);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (now.month.asInt() === month.asInt() && now.day.asInt() >= day.asInt()) {
|
|
124
|
+
return DateOnly.from(now.year.next(), month, day);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return DateOnly.from(now.year, month, day);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public toIso8601(): string {
|
|
131
|
+
return this.year.format(YearOutputFormat.YEAR_NUMERIC) + '-' + this.month.format(MonthOutputFormat.MONTH_TWO_DIGIT) + '-' + this.day.format(DayOutputFormat.DAY_TWO_DIGIT);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public isSame(dateOnly: DateOnlyInterface): boolean {
|
|
135
|
+
return (
|
|
136
|
+
this.year.isSame(dateOnly.year) &&
|
|
137
|
+
this.month.isSame(dateOnly.month) &&
|
|
138
|
+
this.day.isSame(dateOnly.day)
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
public isBefore(dateOnly: DateOnlyInterface): boolean {
|
|
143
|
+
if (this.year.asInt() < dateOnly.year.asInt()) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (
|
|
148
|
+
this.year.asInt() === dateOnly.year.asInt() &&
|
|
149
|
+
this.month.asInt() < dateOnly.month.asInt()
|
|
150
|
+
) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
this.year.asInt() === dateOnly.year.asInt() &&
|
|
156
|
+
this.month.asInt() === dateOnly.month.asInt() &&
|
|
157
|
+
this.day.asInt() < dateOnly.day.asInt()
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public isAfter(dateOnly: DateOnlyInterface): boolean {
|
|
162
|
+
if (this.year.asInt() > dateOnly.year.asInt()) {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (
|
|
167
|
+
this.year.asInt() === dateOnly.year.asInt() &&
|
|
168
|
+
this.month.asInt() > dateOnly.month.asInt()
|
|
169
|
+
) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
this.year.asInt() === dateOnly.year.asInt() &&
|
|
175
|
+
this.month.asInt() === dateOnly.month.asInt() &&
|
|
176
|
+
this.day.asInt() > dateOnly.day.asInt()
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public toYearAndMonth(): YearAndMonth {
|
|
181
|
+
return YearAndMonth.from(this.year, this.month);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private toUtcDayJs(year: Year, month: Month, day: Day): Dayjs {
|
|
185
|
+
const parsed = dayjs.utc(
|
|
186
|
+
year.format(YearOutputFormat.YEAR_NUMERIC) + '-' + month.format(MonthOutputFormat.MONTH_TWO_DIGIT) + '-' + day.format(DayOutputFormat.DAY_TWO_DIGIT) + 'T' + '12:00:00'
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
if (!parsed.isValid()) {
|
|
190
|
+
throw InvalidDate.invalidDateFormat();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return parsed.utc(false);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public format(
|
|
197
|
+
format: DateOnlyOutputFormat | YearAndMonthOutputFormat | YearOutputFormat | MonthOutputFormat | DayOutputFormat,
|
|
198
|
+
): string {
|
|
199
|
+
|
|
200
|
+
const isYearAndMonthFormat = Object.values(
|
|
201
|
+
YearAndMonthOutputFormat,
|
|
202
|
+
).includes(format as YearAndMonthOutputFormat);
|
|
203
|
+
|
|
204
|
+
if (isYearAndMonthFormat) {
|
|
205
|
+
return this.toYearAndMonth().format(
|
|
206
|
+
format as YearAndMonthOutputFormat,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const isYearFormat = Object.values(
|
|
211
|
+
YearOutputFormat,
|
|
212
|
+
).includes(format as YearOutputFormat);
|
|
213
|
+
|
|
214
|
+
if (isYearFormat) {
|
|
215
|
+
return this.year.format(
|
|
216
|
+
format as YearOutputFormat,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const isMonthFormat = Object.values(
|
|
221
|
+
MonthOutputFormat,
|
|
222
|
+
).includes(format as MonthOutputFormat);
|
|
223
|
+
|
|
224
|
+
if (isMonthFormat) {
|
|
225
|
+
return this.month.format(
|
|
226
|
+
format as MonthOutputFormat,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
switch (format) {
|
|
231
|
+
case DateOnlyOutputFormat.DATEONLY_ISO8601:
|
|
232
|
+
return `${this.year.asInt()}-${this.month.format(MonthOutputFormat.MONTH_TWO_DIGIT)}-${this.day.format(DayOutputFormat.DAY_TWO_DIGIT)}`;
|
|
233
|
+
|
|
234
|
+
case DateOnlyOutputFormat.DATEONLY_DAY_MONTH_YEAR_DASH:
|
|
235
|
+
return `${this.day.format(DayOutputFormat.DAY_TWO_DIGIT)}-${this.month.format(MonthOutputFormat.MONTH_TWO_DIGIT)}-${this.year.asInt()}`;
|
|
236
|
+
|
|
237
|
+
case DateOnlyOutputFormat.DATEONLY_DAY_MONTH_YEAR_SLASH:
|
|
238
|
+
return `${this.day.format(DayOutputFormat.DAY_TWO_DIGIT)}/${this.month.format(MonthOutputFormat.MONTH_TWO_DIGIT)}/${this.year.asInt()}`;
|
|
239
|
+
|
|
240
|
+
case DateOnlyOutputFormat.DATEONLY_DAY_MONTH_YEAR_PERIOD:
|
|
241
|
+
return `${this.day.format(DayOutputFormat.DAY_TWO_DIGIT)}.${this.month.format(MonthOutputFormat.MONTH_TWO_DIGIT)}.${this.year.asInt()}`;
|
|
242
|
+
|
|
243
|
+
case DateOnlyOutputFormat.DATEONLY_MONTH_DAY_YEAR_SLASH:
|
|
244
|
+
return `${this.month.format(MonthOutputFormat.MONTH_TWO_DIGIT)}/${this.day.format(DayOutputFormat.DAY_TWO_DIGIT)}/${this.year.asInt()}`;
|
|
245
|
+
|
|
246
|
+
case DateOnlyOutputFormat.DATEONLY_COMPACT:
|
|
247
|
+
return `${this.year.asInt()}${this.month.format(MonthOutputFormat.MONTH_TWO_DIGIT)}${this.day.format(DayOutputFormat.DAY_TWO_DIGIT)}`;
|
|
248
|
+
|
|
249
|
+
default:
|
|
250
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
public formatLocale(format: DateOnlyLocaleAwareOutputFormat | YearAndMonthLocaleAwareOutputFormat | MonthLocalAwareOutputFormat, locale: Locale): string {
|
|
255
|
+
|
|
256
|
+
const isYearAndMonthFormat = Object.values(
|
|
257
|
+
YearAndMonthLocaleAwareOutputFormat,
|
|
258
|
+
).includes(format as YearAndMonthLocaleAwareOutputFormat);
|
|
259
|
+
|
|
260
|
+
if (isYearAndMonthFormat) {
|
|
261
|
+
return this.toYearAndMonth().formatLocale(
|
|
262
|
+
format as YearAndMonthLocaleAwareOutputFormat,
|
|
263
|
+
locale
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const isMonthFormat = Object.values(
|
|
268
|
+
MonthLocalAwareOutputFormat,
|
|
269
|
+
).includes(format as MonthLocalAwareOutputFormat);
|
|
270
|
+
|
|
271
|
+
if (isMonthFormat) {
|
|
272
|
+
return this.month.formatLocale(
|
|
273
|
+
format as MonthLocalAwareOutputFormat,
|
|
274
|
+
locale
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
switch (format) {
|
|
279
|
+
case DateOnlyLocaleAwareOutputFormat.DATEONLY_FULL:
|
|
280
|
+
const day = this.toUtcDayJs(this.year, this.month, this.day);
|
|
281
|
+
return day.locale(locale).format(DateOnlyLocaleAwareOutputFormat.DATEONLY_FULL);
|
|
282
|
+
default:
|
|
283
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import dayjs, {Dayjs} from 'dayjs';
|
|
2
|
+
import {Hour} from './hour.class';
|
|
3
|
+
import {Minute} from './minute.class';
|
|
4
|
+
import {Second} from './second.class';
|
|
5
|
+
import {Millisecond} from './millisecond.class';
|
|
6
|
+
import {Day, DayOutputFormat} from './day.class';
|
|
7
|
+
import {Month, MonthIndex, MonthLocalAwareOutputFormat, MonthOutputFormat} from './month.class';
|
|
8
|
+
import {Year, YearOutputFormat} from './year.class';
|
|
9
|
+
import {DayOfWeek, IDayOfWeek, ZeroBasedWeekdayNumbers} from './dayoftheweek.class';
|
|
10
|
+
import {YearAndMonth, YearAndMonthLocaleAwareOutputFormat} from './yearandmonth.class';
|
|
11
|
+
import {TimeZone, TimeZoneInterface, UTCTimezone} from './timezone.class';
|
|
12
|
+
import {Time, TimeInterface} from './time.class';
|
|
13
|
+
import {Locale} from "./locale";
|
|
14
|
+
import {DateOnlyLocaleAwareOutputFormat, InvalidDate} from "./dateonly.class";
|
|
15
|
+
|
|
16
|
+
// Extends for dayjs.
|
|
17
|
+
|
|
18
|
+
export class InvalidDateTime extends Error {
|
|
19
|
+
static invalidDateTimeFormat() {
|
|
20
|
+
return new InvalidDateTime("The provided datetime format is invalid.");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export enum DateTimeOutputFormat {
|
|
25
|
+
/** ISO 8601 standard format (e.g., 2024-10-18) */
|
|
26
|
+
DATETIME_ISO8601 = "yyyy-MM-dd'T'HH:mm:ss.SSS",
|
|
27
|
+
|
|
28
|
+
/** ISO 8601 date and time with offset but without milliseconds (e.g., 2024-10-19T15:30:45) */
|
|
29
|
+
DATETIME_ISO8601_NO_MILLIS = "yyyy-MM-dd'T'HH:mm:ss",
|
|
30
|
+
|
|
31
|
+
/** ISO 8601 date and time with timezone but without seconds (e.g., 2024-10-19T15:30) */
|
|
32
|
+
DATETIME_ISO8601_NO_SECONDS = "yyyy-MM-dd'T'HH:mm",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface DateTimeCalculation {
|
|
36
|
+
years?: number;
|
|
37
|
+
months?: number;
|
|
38
|
+
days?: number;
|
|
39
|
+
hours?: number;
|
|
40
|
+
minutes?: number;
|
|
41
|
+
seconds?: number;
|
|
42
|
+
milliseconds?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface DateTimeInterface {
|
|
46
|
+
year: Year;
|
|
47
|
+
month : Month;
|
|
48
|
+
day: Day;
|
|
49
|
+
hour: Hour;
|
|
50
|
+
minute: Minute;
|
|
51
|
+
second: Second;
|
|
52
|
+
millisecond: Millisecond;
|
|
53
|
+
dayOfWeek: IDayOfWeek;
|
|
54
|
+
add(calculation : DateTimeCalculation): DateTimeInterface;
|
|
55
|
+
subtract(calculation : DateTimeCalculation): DateTimeInterface;
|
|
56
|
+
|
|
57
|
+
isSameDay($other : DateTimeInterface) : boolean;
|
|
58
|
+
startOfDay(): DateTimeInterface;
|
|
59
|
+
endOfDay(): DateTimeInterface;
|
|
60
|
+
|
|
61
|
+
toYearAndMonth(): YearAndMonth;
|
|
62
|
+
toTime(): TimeInterface;
|
|
63
|
+
toTimestamp(): number;
|
|
64
|
+
toNativeJsDate(): Date;
|
|
65
|
+
toDateOnlyIso8601(): string;
|
|
66
|
+
|
|
67
|
+
format(format: DateTimeOutputFormat): string;
|
|
68
|
+
formatLocale(format: DateOnlyLocaleAwareOutputFormat, locale: Locale): string;
|
|
69
|
+
|
|
70
|
+
inTimezone(timezone: TimeZoneInterface): DateTimeInterface;
|
|
71
|
+
inUtc(): DateTimeInterface;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export class DateTime implements DateTimeInterface {
|
|
75
|
+
private dateTime: Dayjs;
|
|
76
|
+
year: Year;
|
|
77
|
+
month: Month;
|
|
78
|
+
day: Day;
|
|
79
|
+
dayOfWeek: IDayOfWeek;
|
|
80
|
+
hour: Hour;
|
|
81
|
+
minute: Minute;
|
|
82
|
+
second: Second;
|
|
83
|
+
millisecond: Millisecond;
|
|
84
|
+
zone: TimeZoneInterface;
|
|
85
|
+
|
|
86
|
+
private constructor(dateTime: Dayjs, timezone: TimeZoneInterface) {
|
|
87
|
+
this.dateTime = dateTime;
|
|
88
|
+
this.zone = timezone;
|
|
89
|
+
|
|
90
|
+
this.year = Year.from(this.dateTime.year());
|
|
91
|
+
this.month = Month.from(this.dateTime.month(), MonthIndex.ZERO_BASED);
|
|
92
|
+
this.day = Day.from(this.dateTime.date());
|
|
93
|
+
this.hour = Hour.from(this.dateTime.hour());
|
|
94
|
+
this.minute = Minute.from(this.dateTime.minute());
|
|
95
|
+
this.second = Second.from(this.dateTime.second());
|
|
96
|
+
this.millisecond = Millisecond.from(this.dateTime.millisecond());
|
|
97
|
+
this.dayOfWeek = DayOfWeek.fromZeroBased(
|
|
98
|
+
this.dateTime.day() as ZeroBasedWeekdayNumbers,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static fromTimestamp(timestamp: number): DateTime {
|
|
103
|
+
const utcDate = dayjs.unix(timestamp);
|
|
104
|
+
return new DateTime(utcDate, TimeZone.UTC());
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
static fromIso8601(
|
|
108
|
+
dateString: string,
|
|
109
|
+
timezone: TimeZoneInterface,
|
|
110
|
+
): DateTimeInterface {
|
|
111
|
+
|
|
112
|
+
const dateStringRegex =
|
|
113
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3,6})?(Z)?$/;
|
|
114
|
+
const timezoneRegex = /Z$/;
|
|
115
|
+
|
|
116
|
+
const containsZ = timezoneRegex.test(dateString);
|
|
117
|
+
const isSupported = dateStringRegex.test(dateString);
|
|
118
|
+
|
|
119
|
+
if (containsZ && timezone.name() !== "UTC") {
|
|
120
|
+
throw new Error('Timezones are not matching!')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check needed to prevent dayjs from parsing other formats we don't want.
|
|
124
|
+
if (!isSupported) {
|
|
125
|
+
throw new Error('The provided datetime format is invalid!')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const asDate = dayjs.tz(dateString, timezone.name());
|
|
129
|
+
return new DateTime(asDate, timezone);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public add(calculation : DateTimeCalculation): DateTimeInterface {
|
|
133
|
+
const {
|
|
134
|
+
years = 0,
|
|
135
|
+
months = 0,
|
|
136
|
+
days = 0,
|
|
137
|
+
hours = 0,
|
|
138
|
+
minutes = 0,
|
|
139
|
+
seconds = 0,
|
|
140
|
+
milliseconds = 0,
|
|
141
|
+
} = calculation;
|
|
142
|
+
|
|
143
|
+
let _current = this.dateTime;
|
|
144
|
+
|
|
145
|
+
_current = _current.add(years, 'years');
|
|
146
|
+
_current = _current.add(months, 'months');
|
|
147
|
+
_current = _current.add(days, 'days');
|
|
148
|
+
_current = _current.add(hours, 'hours');
|
|
149
|
+
_current = _current.add(minutes, 'minutes');
|
|
150
|
+
_current = _current.add(seconds, 'seconds');
|
|
151
|
+
_current = _current.add(milliseconds, 'milliseconds');
|
|
152
|
+
|
|
153
|
+
return DateTime.fromTimestamp(_current.unix());
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public subtract(calculation : DateTimeCalculation): DateTimeInterface {
|
|
157
|
+
const {
|
|
158
|
+
years = 0,
|
|
159
|
+
months = 0,
|
|
160
|
+
days = 0,
|
|
161
|
+
hours = 0,
|
|
162
|
+
minutes = 0,
|
|
163
|
+
seconds = 0,
|
|
164
|
+
milliseconds = 0,
|
|
165
|
+
} = calculation;
|
|
166
|
+
|
|
167
|
+
let _current = this.dateTime;
|
|
168
|
+
|
|
169
|
+
_current = _current.subtract(years, 'years');
|
|
170
|
+
_current = _current.subtract(months, 'months');
|
|
171
|
+
_current = _current.subtract(days, 'days');
|
|
172
|
+
_current = _current.subtract(hours, 'hours');
|
|
173
|
+
_current = _current.subtract(minutes, 'minutes');
|
|
174
|
+
_current =_current.subtract(seconds, 'seconds');
|
|
175
|
+
_current =_current.subtract(milliseconds, 'milliseconds');
|
|
176
|
+
|
|
177
|
+
return DateTime.fromTimestamp(_current.unix());
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public startOfDay(): DateTimeInterface {
|
|
181
|
+
this.dateTime = this.dateTime.startOf('day');
|
|
182
|
+
return this;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
public endOfDay(): DateTimeInterface {
|
|
186
|
+
this.dateTime = this.dateTime.endOf('day');
|
|
187
|
+
return this;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
public toTime(): TimeInterface {
|
|
191
|
+
return Time.from(
|
|
192
|
+
this.hour.asInt(),
|
|
193
|
+
this.minute.asInt(),
|
|
194
|
+
this.second.asInt(),
|
|
195
|
+
this.millisecond.asInt(),
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public toTimestamp(): number {
|
|
200
|
+
return this.dateTime.unix();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
public inTimezone(timezone: TimeZoneInterface): DateTimeInterface {
|
|
204
|
+
const dayjs = this.dateTime.tz(timezone.name(), false);
|
|
205
|
+
return new DateTime(dayjs, timezone);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
public inUtc(): DateTimeInterface {
|
|
209
|
+
const dayjs = this.dateTime.tz(UTCTimezone.name(), false);
|
|
210
|
+
return new DateTime(dayjs, UTCTimezone);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
public toYearAndMonth(): YearAndMonth {
|
|
214
|
+
return YearAndMonth.from(this.year, this.month);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
public toNativeJsDate(): Date {
|
|
218
|
+
return this.dateTime.toDate();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
public format(format: DateTimeOutputFormat): string {
|
|
222
|
+
switch (format) {
|
|
223
|
+
case DateTimeOutputFormat.DATETIME_ISO8601:
|
|
224
|
+
return this.dateTime.format('YYYY-MM-DDTHH:mm:ss.SSS');
|
|
225
|
+
case DateTimeOutputFormat.DATETIME_ISO8601_NO_MILLIS:
|
|
226
|
+
return this.dateTime.format('YYYY-MM-DDTHH:mm:ss');
|
|
227
|
+
case DateTimeOutputFormat.DATETIME_ISO8601_NO_SECONDS:
|
|
228
|
+
return this.dateTime.format('YYYY-MM-DDTHH:mm');
|
|
229
|
+
default:
|
|
230
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
public formatLocale(format: DateOnlyLocaleAwareOutputFormat | YearAndMonthLocaleAwareOutputFormat | MonthLocalAwareOutputFormat, locale: Locale): string {
|
|
235
|
+
|
|
236
|
+
const isYearAndMonthFormat = Object.values(
|
|
237
|
+
YearAndMonthLocaleAwareOutputFormat,
|
|
238
|
+
).includes(format as YearAndMonthLocaleAwareOutputFormat);
|
|
239
|
+
|
|
240
|
+
if (isYearAndMonthFormat) {
|
|
241
|
+
return this.toYearAndMonth().formatLocale(
|
|
242
|
+
format as YearAndMonthLocaleAwareOutputFormat,
|
|
243
|
+
locale
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const isMonthFormat = Object.values(
|
|
248
|
+
MonthLocalAwareOutputFormat,
|
|
249
|
+
).includes(format as MonthLocalAwareOutputFormat);
|
|
250
|
+
|
|
251
|
+
if (isMonthFormat) {
|
|
252
|
+
return this.month.formatLocale(
|
|
253
|
+
format as MonthLocalAwareOutputFormat,
|
|
254
|
+
locale
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
switch (format) {
|
|
259
|
+
case DateOnlyLocaleAwareOutputFormat.DATEONLY_FULL:
|
|
260
|
+
const day = this.toUtcDayJs(this.year, this.month, this.day);
|
|
261
|
+
return day.locale(locale).format(DateOnlyLocaleAwareOutputFormat.DATEONLY_FULL);
|
|
262
|
+
default:
|
|
263
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
public isSameDay($other : DateTimeInterface) : boolean {
|
|
269
|
+
return this.day.isSame($other.day) && this.month.isSame($other.month) && this.year.isSame($other.year);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private toUtcDayJs(year: Year, month: Month, day: Day): Dayjs {
|
|
273
|
+
const parsed = dayjs.utc(
|
|
274
|
+
year.format(YearOutputFormat.YEAR_NUMERIC) + '-' + month.format(MonthOutputFormat.MONTH_TWO_DIGIT) + '-' + day.format(DayOutputFormat.DAY_TWO_DIGIT) + 'T' + '12:00:00'
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
if (!parsed.isValid()) {
|
|
278
|
+
throw InvalidDate.invalidDateFormat();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return parsed.utc(false);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
public toDateOnlyIso8601(): string {
|
|
285
|
+
return this.year.format(YearOutputFormat.YEAR_NUMERIC) + '-' + this.month.format(MonthOutputFormat.MONTH_TWO_DIGIT) + '-' + this.day.format(DayOutputFormat.DAY_TWO_DIGIT);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export class InvalidDay extends Error {
|
|
2
|
+
static notInScope() {
|
|
3
|
+
return new InvalidDay("A day must be between 1 and 31.");
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export enum DayOutputFormat {
|
|
8
|
+
/** Month and day numeric (e.g., 1) */
|
|
9
|
+
DAY_NUMERIC = "day-numeric",
|
|
10
|
+
/** Month and day numeric (e.g., 01) */
|
|
11
|
+
DAY_TWO_DIGIT = "day-2-digit",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface DayInterface {
|
|
15
|
+
asInt(): number;
|
|
16
|
+
isSame(day: Day): boolean;
|
|
17
|
+
format(format: DayOutputFormat): string;
|
|
18
|
+
// formatLocale is not included since days don't get formatted local.
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class Day implements DayInterface {
|
|
22
|
+
private constructor(private readonly day: number) {
|
|
23
|
+
if (this.day < 1 || this.day > 31) {
|
|
24
|
+
throw InvalidDay.notInScope();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static from(day: number): Day {
|
|
29
|
+
return new Day(day);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
asInt(): number {
|
|
33
|
+
return this.day;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
format(format: DayOutputFormat): string {
|
|
37
|
+
switch (format) {
|
|
38
|
+
case DayOutputFormat.DAY_NUMERIC:
|
|
39
|
+
return String(this.asInt());
|
|
40
|
+
case DayOutputFormat.DAY_TWO_DIGIT:
|
|
41
|
+
return this.day.toString().padStart(2, "0");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
isSame(day: Day): boolean {
|
|
46
|
+
return this.asInt() === day.asInt();
|
|
47
|
+
}
|
|
48
|
+
}
|