@oneluiz/dual-datepicker 3.4.0 → 3.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -1
- package/core/date-adapter.d.ts +298 -0
- package/core/date-clock.d.ts +82 -0
- package/core/dual-date-range.store.d.ts +113 -0
- package/core/index.d.ts +11 -0
- package/core/native-date-adapter.d.ts +152 -0
- package/core/preset.engine.d.ts +88 -0
- package/core/range.validator.d.ts +37 -0
- package/core/system-clock.d.ts +13 -0
- package/dual-datepicker.component.d.ts +17 -11
- package/esm2022/core/date-adapter.mjs +77 -0
- package/esm2022/core/date-clock.mjs +65 -0
- package/esm2022/core/dual-date-range.store.mjs +329 -0
- package/esm2022/core/index.mjs +12 -0
- package/esm2022/core/native-date-adapter.mjs +286 -0
- package/esm2022/core/preset.engine.mjs +303 -0
- package/esm2022/core/range.validator.mjs +105 -0
- package/esm2022/core/system-clock.mjs +34 -0
- package/esm2022/dual-datepicker.component.mjs +239 -195
- package/esm2022/public-api.mjs +5 -2
- package/fesm2022/oneluiz-dual-datepicker.mjs +1431 -204
- package/fesm2022/oneluiz-dual-datepicker.mjs.map +1 -1
- package/package.json +5 -3
- package/public-api.d.ts +1 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { DateAdapter } from './date-adapter';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
export declare class NativeDateAdapter implements DateAdapter {
|
|
4
|
+
/**
|
|
5
|
+
* Normalize date to start of day (00:00:00.000) in local timezone
|
|
6
|
+
*
|
|
7
|
+
* This is the foundation of timezone-safe operations.
|
|
8
|
+
* All other methods use normalized dates for comparisons.
|
|
9
|
+
*/
|
|
10
|
+
normalize(date: Date): Date;
|
|
11
|
+
/**
|
|
12
|
+
* Check if two dates are the same calendar day
|
|
13
|
+
*
|
|
14
|
+
* Implementation: Compare YYYY-MM-DD components directly
|
|
15
|
+
* Avoids timezone issues from valueOf() comparisons
|
|
16
|
+
*/
|
|
17
|
+
isSameDay(a: Date, b: Date): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Check if date A is before date B (calendar day level)
|
|
20
|
+
*
|
|
21
|
+
* Implementation: Compare normalized dates using valueOf()
|
|
22
|
+
*/
|
|
23
|
+
isBeforeDay(a: Date, b: Date): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Check if date A is after date B (calendar day level)
|
|
26
|
+
*
|
|
27
|
+
* Implementation: Compare normalized dates using valueOf()
|
|
28
|
+
*/
|
|
29
|
+
isAfterDay(a: Date, b: Date): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Add days to a date
|
|
32
|
+
*
|
|
33
|
+
* Implementation: Use setDate() which handles month rollover automatically
|
|
34
|
+
*
|
|
35
|
+
* Example:
|
|
36
|
+
* Jan 31 + 3 days → Feb 3 ✅
|
|
37
|
+
* Feb 28 + 1 day → Mar 1 ✅ (non-leap year)
|
|
38
|
+
*/
|
|
39
|
+
addDays(date: Date, days: number): Date;
|
|
40
|
+
/**
|
|
41
|
+
* Add months to a date
|
|
42
|
+
*
|
|
43
|
+
* CRITICAL: Handles month overflow correctly
|
|
44
|
+
*
|
|
45
|
+
* Algorithm:
|
|
46
|
+
* 1. Add months using setMonth()
|
|
47
|
+
* 2. If day-of-month changed (overflow), set to last day of target month
|
|
48
|
+
*
|
|
49
|
+
* Examples:
|
|
50
|
+
* - Jan 31 + 1 month → Feb 28 (or Feb 29 in leap year) ✅
|
|
51
|
+
* - Jan 31 + 2 months → Mar 31 ✅
|
|
52
|
+
* - Mar 31 + 1 month → Apr 30 ✅
|
|
53
|
+
* - Dec 31 + 1 month → Jan 31 (next year) ✅
|
|
54
|
+
*/
|
|
55
|
+
addMonths(date: Date, months: number): Date;
|
|
56
|
+
/**
|
|
57
|
+
* Get start of day (00:00:00.000)
|
|
58
|
+
*
|
|
59
|
+
* Alias for normalize() with explicit intent
|
|
60
|
+
*/
|
|
61
|
+
startOfDay(date: Date): Date;
|
|
62
|
+
/**
|
|
63
|
+
* Get end of day (23:59:59.999)
|
|
64
|
+
*
|
|
65
|
+
* Useful for inclusive range queries
|
|
66
|
+
*/
|
|
67
|
+
endOfDay(date: Date): Date;
|
|
68
|
+
/**
|
|
69
|
+
* Get first day of month (00:00:00.000)
|
|
70
|
+
*/
|
|
71
|
+
startOfMonth(date: Date): Date;
|
|
72
|
+
/**
|
|
73
|
+
* Get last day of month (23:59:59.999)
|
|
74
|
+
*
|
|
75
|
+
* Algorithm: Go to 1st of next month, subtract 1 day
|
|
76
|
+
*/
|
|
77
|
+
endOfMonth(date: Date): Date;
|
|
78
|
+
/**
|
|
79
|
+
* Get year (4-digit)
|
|
80
|
+
*/
|
|
81
|
+
getYear(date: Date): number;
|
|
82
|
+
/**
|
|
83
|
+
* Get month (0-11)
|
|
84
|
+
*/
|
|
85
|
+
getMonth(date: Date): number;
|
|
86
|
+
/**
|
|
87
|
+
* Get day of month (1-31)
|
|
88
|
+
*/
|
|
89
|
+
getDate(date: Date): number;
|
|
90
|
+
/**
|
|
91
|
+
* Get day of week (0-6, Sunday=0)
|
|
92
|
+
*/
|
|
93
|
+
getDay(date: Date): number;
|
|
94
|
+
/**
|
|
95
|
+
* Convert Date to ISO date string (YYYY-MM-DD)
|
|
96
|
+
*
|
|
97
|
+
* CRITICAL: DO NOT use toISOString() - it converts to UTC!
|
|
98
|
+
*
|
|
99
|
+
* Manual construction ensures local timezone is preserved:
|
|
100
|
+
*
|
|
101
|
+
* Example problem with toISOString():
|
|
102
|
+
* ```
|
|
103
|
+
* // Local timezone: GMT-6 (CST)
|
|
104
|
+
* const date = new Date('2026-02-21T23:00:00'); // 11 PM Feb 21 local
|
|
105
|
+
*
|
|
106
|
+
* // WRONG ❌
|
|
107
|
+
* date.toISOString().split('T')[0]
|
|
108
|
+
* // Returns "2026-02-22" (converted to UTC = Feb 22 05:00 AM)
|
|
109
|
+
*
|
|
110
|
+
* // CORRECT ✅
|
|
111
|
+
* toISODate(date)
|
|
112
|
+
* // Returns "2026-02-21" (local date preserved)
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* Implementation: Build YYYY-MM-DD manually from local date components
|
|
116
|
+
*/
|
|
117
|
+
toISODate(date: Date): string;
|
|
118
|
+
/**
|
|
119
|
+
* Parse ISO date string (YYYY-MM-DD) to Date
|
|
120
|
+
*
|
|
121
|
+
* CRITICAL: DO NOT use new Date(isoString) - may parse as UTC!
|
|
122
|
+
*
|
|
123
|
+
* Example problem with Date constructor:
|
|
124
|
+
* ```
|
|
125
|
+
* // Local timezone: GMT-6 (CST)
|
|
126
|
+
*
|
|
127
|
+
* // WRONG ❌
|
|
128
|
+
* new Date('2026-02-21')
|
|
129
|
+
* // Parsed as UTC: 2026-02-21T00:00:00Z
|
|
130
|
+
* // In local timezone: Feb 20, 2026 6:00 PM (previous day!)
|
|
131
|
+
*
|
|
132
|
+
* // CORRECT ✅
|
|
133
|
+
* parseISODate('2026-02-21')
|
|
134
|
+
* // Returns: 2026-02-21T00:00:00 local time
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* Implementation: Parse components and construct Date in local timezone
|
|
138
|
+
*/
|
|
139
|
+
parseISODate(isoDate: string): Date | null;
|
|
140
|
+
/**
|
|
141
|
+
* Get week start day for locale
|
|
142
|
+
*
|
|
143
|
+
* Default: Sunday (0) for most locales
|
|
144
|
+
* Monday (1) for Europe, ISO 8601
|
|
145
|
+
*
|
|
146
|
+
* Implementation: Simple locale detection
|
|
147
|
+
* For advanced needs, use Intl.Locale or external library
|
|
148
|
+
*/
|
|
149
|
+
getWeekStart(locale?: string): 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
150
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NativeDateAdapter, never>;
|
|
151
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<NativeDateAdapter>;
|
|
152
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
export interface RangePreset {
|
|
3
|
+
/**
|
|
4
|
+
* Resolve preset to actual date range
|
|
5
|
+
* @param now - Current date for deterministic calculation
|
|
6
|
+
*/
|
|
7
|
+
resolve(now: Date): {
|
|
8
|
+
start: Date;
|
|
9
|
+
end: Date;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface PresetRange {
|
|
13
|
+
start: string;
|
|
14
|
+
end: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Registry of built-in presets
|
|
18
|
+
* Can be extended by consumers
|
|
19
|
+
*
|
|
20
|
+
* SSR-Safe Architecture:
|
|
21
|
+
* - Injects DateClock via DI
|
|
22
|
+
* - All presets use clock.now() instead of new Date()
|
|
23
|
+
* - Deterministic: same clock.now() → same preset
|
|
24
|
+
* - Override DATE_CLOCK token in SSR to ensure consistency
|
|
25
|
+
*
|
|
26
|
+
* Timezone-Safe Architecture:
|
|
27
|
+
* - Injects DateAdapter via DI
|
|
28
|
+
* - All date operations use adapter methods
|
|
29
|
+
* - Prevents timezone bugs in cross-timezone scenarios
|
|
30
|
+
* - Override DATE_ADAPTER for Luxon/DayJS/custom implementations
|
|
31
|
+
*/
|
|
32
|
+
export declare class PresetEngine {
|
|
33
|
+
private presets;
|
|
34
|
+
private clock;
|
|
35
|
+
private adapter;
|
|
36
|
+
constructor();
|
|
37
|
+
/**
|
|
38
|
+
* Register a custom preset
|
|
39
|
+
*/
|
|
40
|
+
register(key: string, preset: RangePreset): void;
|
|
41
|
+
/**
|
|
42
|
+
* Resolve a preset to date range
|
|
43
|
+
*
|
|
44
|
+
* SSR Note: Uses injected DateClock for deterministic resolution
|
|
45
|
+
* Timezone Note: Uses injected DateAdapter for consistent date operations
|
|
46
|
+
*
|
|
47
|
+
* Override tokens in SSR scenarios:
|
|
48
|
+
* - DATE_CLOCK: Control current time
|
|
49
|
+
* - DATE_ADAPTER: Control date operations (e.g., Luxon for timezone support)
|
|
50
|
+
*
|
|
51
|
+
* @param key - Preset key (e.g., 'TODAY', 'LAST_7_DAYS')
|
|
52
|
+
* @param now - Optional override for current date (defaults to clock.now())
|
|
53
|
+
*/
|
|
54
|
+
resolve(key: string, now?: Date): PresetRange | null;
|
|
55
|
+
/**
|
|
56
|
+
* Get all available preset keys
|
|
57
|
+
*/
|
|
58
|
+
getPresetKeys(): string[];
|
|
59
|
+
/**
|
|
60
|
+
* Register all built-in presets
|
|
61
|
+
*
|
|
62
|
+
* All presets now use DateAdapter for timezone-safe operations
|
|
63
|
+
*/
|
|
64
|
+
private registerBuiltInPresets;
|
|
65
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<PresetEngine, never>;
|
|
66
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<PresetEngine>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Create a custom preset from a function
|
|
70
|
+
*/
|
|
71
|
+
export declare function createPreset(resolver: (now: Date) => {
|
|
72
|
+
start: Date;
|
|
73
|
+
end: Date;
|
|
74
|
+
}): RangePreset;
|
|
75
|
+
/**
|
|
76
|
+
* @deprecated Use dependency injection instead:
|
|
77
|
+
* ```typescript
|
|
78
|
+
* private engine = inject(PresetEngine);
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* Singleton preset engine instance for backward compatibility
|
|
82
|
+
*
|
|
83
|
+
* WARNING: This singleton uses SystemClock directly and is NOT SSR-safe.
|
|
84
|
+
* For SSR applications, inject PresetEngine and override DATE_CLOCK token.
|
|
85
|
+
*
|
|
86
|
+
* This export will be removed in v4.0.0
|
|
87
|
+
*/
|
|
88
|
+
export declare const presetEngine: PresetEngine;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure validation functions for date ranges
|
|
3
|
+
* No dependencies, no side effects - just logic
|
|
4
|
+
* Perfect for SSR, testing, and reusability
|
|
5
|
+
*/
|
|
6
|
+
export interface ValidationResult {
|
|
7
|
+
valid: boolean;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Validate that end date is not before start date
|
|
12
|
+
*/
|
|
13
|
+
export declare function validateRangeOrder(start: Date | null, end: Date | null): ValidationResult;
|
|
14
|
+
/**
|
|
15
|
+
* Validate that date is within min/max bounds
|
|
16
|
+
*/
|
|
17
|
+
export declare function validateDateBounds(date: Date | null, minDate?: Date, maxDate?: Date): ValidationResult;
|
|
18
|
+
/**
|
|
19
|
+
* Validate that a range is within bounds
|
|
20
|
+
*/
|
|
21
|
+
export declare function validateRangeBounds(start: Date | null, end: Date | null, minDate?: Date, maxDate?: Date): ValidationResult;
|
|
22
|
+
/**
|
|
23
|
+
* Check if a date is disabled
|
|
24
|
+
*/
|
|
25
|
+
export declare function isDateDisabled(date: Date, disabledDates?: Date[] | ((date: Date) => boolean)): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Apply bounds to a date (clamp it)
|
|
28
|
+
*/
|
|
29
|
+
export declare function applyBounds(date: Date, minDate?: Date, maxDate?: Date): Date;
|
|
30
|
+
/**
|
|
31
|
+
* Parse ISO date string to Date object (deterministic)
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseISODate(dateStr: string): Date | null;
|
|
34
|
+
/**
|
|
35
|
+
* Format Date to ISO string (YYYY-MM-DD)
|
|
36
|
+
*/
|
|
37
|
+
export declare function formatISODate(date: Date | null): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DateClock } from './date-clock';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
export declare class SystemClock implements DateClock {
|
|
4
|
+
/**
|
|
5
|
+
* Returns current system time
|
|
6
|
+
*
|
|
7
|
+
* This is the standard behavior for client-side applications.
|
|
8
|
+
* For SSR, override DATE_CLOCK token with a fixed Date.
|
|
9
|
+
*/
|
|
10
|
+
now(): Date;
|
|
11
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SystemClock, never>;
|
|
12
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<SystemClock>;
|
|
13
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EventEmitter, OnInit, OnChanges, SimpleChanges, ElementRef } from '@angular/core';
|
|
2
2
|
import { ControlValueAccessor } from '@angular/forms';
|
|
3
|
+
import { DualDateRangeStore } from './core/dual-date-range.store';
|
|
3
4
|
import * as i0 from "@angular/core";
|
|
4
5
|
export interface DateRange {
|
|
5
6
|
startDate: string;
|
|
@@ -30,8 +31,10 @@ export type ThemeType = 'default' | 'bootstrap' | 'bulma' | 'foundation' | 'tail
|
|
|
30
31
|
export declare class DualDatepickerComponent implements OnInit, OnChanges, ControlValueAccessor {
|
|
31
32
|
private elementRef;
|
|
32
33
|
placeholder: string;
|
|
33
|
-
startDate: string;
|
|
34
|
-
|
|
34
|
+
set startDate(value: string);
|
|
35
|
+
get startDate(): string;
|
|
36
|
+
set endDate(value: string);
|
|
37
|
+
get endDate(): string;
|
|
35
38
|
showPresets: boolean;
|
|
36
39
|
showClearButton: boolean;
|
|
37
40
|
multiRange: boolean;
|
|
@@ -61,24 +64,22 @@ export declare class DualDatepickerComponent implements OnInit, OnChanges, Contr
|
|
|
61
64
|
multiDateRangeChange: EventEmitter<MultiDateRange>;
|
|
62
65
|
multiDateRangeSelected: EventEmitter<MultiDateRange>;
|
|
63
66
|
private dateAdapter;
|
|
67
|
+
protected readonly rangeStore: DualDateRangeStore;
|
|
64
68
|
showDatePicker: import("@angular/core").WritableSignal<boolean>;
|
|
65
|
-
dateRangeText: import("@angular/core").WritableSignal<string>;
|
|
66
|
-
selectingStartDate: import("@angular/core").WritableSignal<boolean>;
|
|
67
69
|
currentMonth: import("@angular/core").WritableSignal<any>;
|
|
68
70
|
previousMonth: import("@angular/core").WritableSignal<any>;
|
|
69
71
|
currentMonthDays: import("@angular/core").WritableSignal<any[]>;
|
|
70
72
|
previousMonthDays: import("@angular/core").WritableSignal<any[]>;
|
|
71
73
|
isDisabled: import("@angular/core").WritableSignal<boolean>;
|
|
72
|
-
pendingStartDate: string;
|
|
73
|
-
pendingEndDate: string;
|
|
74
|
-
hasPendingChanges: import("@angular/core").WritableSignal<boolean>;
|
|
75
|
-
startHour: number;
|
|
76
|
-
startMinute: number;
|
|
77
|
-
endHour: number;
|
|
78
|
-
endMinute: number;
|
|
79
74
|
showStartTimePicker: import("@angular/core").WritableSignal<boolean>;
|
|
80
75
|
showEndTimePicker: import("@angular/core").WritableSignal<boolean>;
|
|
81
76
|
hoverDate: import("@angular/core").WritableSignal<string>;
|
|
77
|
+
get startHour(): number;
|
|
78
|
+
get startMinute(): number;
|
|
79
|
+
get endHour(): number;
|
|
80
|
+
get endMinute(): number;
|
|
81
|
+
private setStartHourMinute;
|
|
82
|
+
private setEndHourMinute;
|
|
82
83
|
selectedRanges: import("@angular/core").WritableSignal<DateRange[]>;
|
|
83
84
|
currentRangeIndex: import("@angular/core").WritableSignal<number>;
|
|
84
85
|
focusedDay: import("@angular/core").WritableSignal<{
|
|
@@ -88,6 +89,11 @@ export declare class DualDatepickerComponent implements OnInit, OnChanges, Contr
|
|
|
88
89
|
currentMonthName: import("@angular/core").Signal<string>;
|
|
89
90
|
previousMonthName: import("@angular/core").Signal<string>;
|
|
90
91
|
weekDayNames: import("@angular/core").Signal<string[]>;
|
|
92
|
+
dateRangeText: import("@angular/core").Signal<string>;
|
|
93
|
+
selectingStartDate: import("@angular/core").Signal<boolean>;
|
|
94
|
+
hasPendingChanges: import("@angular/core").Signal<boolean>;
|
|
95
|
+
get pendingStartDate(): string;
|
|
96
|
+
get pendingEndDate(): string;
|
|
91
97
|
private readonly defaultMonthNames;
|
|
92
98
|
private readonly defaultMonthNamesShort;
|
|
93
99
|
private readonly defaultDayNames;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date Adapter Abstraction for Timezone-Safe Date Operations
|
|
3
|
+
*
|
|
4
|
+
* Problem:
|
|
5
|
+
* Using Date natively in calendar/range logic causes enterprise bugs:
|
|
6
|
+
* - Date ranges shift by 1 day due to timezone/DST
|
|
7
|
+
* - Server (UTC) vs Client (local timezone) discrepancies
|
|
8
|
+
* - Inconsistent reporting in BI/ERP/invoicing/hotel systems
|
|
9
|
+
*
|
|
10
|
+
* Solution:
|
|
11
|
+
* All date calculations go through an adapter layer.
|
|
12
|
+
* This allows:
|
|
13
|
+
* - Timezone-safe operations by default (NativeDateAdapter normalizes to day boundaries)
|
|
14
|
+
* - Optional integration with Luxon/DayJS/date-fns for advanced timezone handling
|
|
15
|
+
* - Consistent behavior across SSR and client
|
|
16
|
+
* - Migration path to timezone-aware libraries without breaking changes
|
|
17
|
+
*
|
|
18
|
+
* Architecture:
|
|
19
|
+
* ```
|
|
20
|
+
* DualDateRangeStore
|
|
21
|
+
* ↓ uses
|
|
22
|
+
* DateAdapter ← Inject via DATE_ADAPTER token
|
|
23
|
+
* ↓ implements
|
|
24
|
+
* NativeDateAdapter (default, zero deps)
|
|
25
|
+
* LuxonDateAdapter (optional, full timezone support)
|
|
26
|
+
* DayJSDateAdapter (optional, lightweight)
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* Usage:
|
|
30
|
+
* ```typescript
|
|
31
|
+
* // Default (NativeDateAdapter)
|
|
32
|
+
* bootstrapApplication(AppComponent);
|
|
33
|
+
*
|
|
34
|
+
* // Advanced (Luxon with timezone)
|
|
35
|
+
* import { LuxonDateAdapter } from '@oneluiz/dual-datepicker/luxon';
|
|
36
|
+
*
|
|
37
|
+
* bootstrapApplication(AppComponent, {
|
|
38
|
+
* providers: [
|
|
39
|
+
* { provide: DATE_ADAPTER, useClass: LuxonDateAdapter }
|
|
40
|
+
* ]
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
import { InjectionToken } from '@angular/core';
|
|
45
|
+
/**
|
|
46
|
+
* Injection token for DateAdapter
|
|
47
|
+
*
|
|
48
|
+
* Default: NativeDateAdapter (zero dependencies)
|
|
49
|
+
*
|
|
50
|
+
* Override for advanced timezone handling:
|
|
51
|
+
*
|
|
52
|
+
* ```typescript
|
|
53
|
+
* // Luxon with timezone
|
|
54
|
+
* import { LuxonDateAdapter } from '@oneluiz/dual-datepicker/luxon';
|
|
55
|
+
*
|
|
56
|
+
* bootstrapApplication(AppComponent, {
|
|
57
|
+
* providers: [
|
|
58
|
+
* {
|
|
59
|
+
* provide: DATE_ADAPTER,
|
|
60
|
+
* useClass: LuxonDateAdapter
|
|
61
|
+
* }
|
|
62
|
+
* ]
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* Custom implementation:
|
|
67
|
+
*
|
|
68
|
+
* ```typescript
|
|
69
|
+
* class CustomDateAdapter implements DateAdapter {
|
|
70
|
+
* // Your implementation for backend-specific date handling
|
|
71
|
+
* }
|
|
72
|
+
*
|
|
73
|
+
* provide(DATE_ADAPTER, { useClass: CustomDateAdapter });
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export const DATE_ADAPTER = new InjectionToken('DATE_ADAPTER');
|
|
77
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGF0ZS1hZGFwdGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2NvcmUvZGF0ZS1hZGFwdGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0EwQ0c7QUFFSCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBaVAvQzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBOEJHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sWUFBWSxHQUFHLElBQUksY0FBYyxDQUFjLGNBQWMsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBEYXRlIEFkYXB0ZXIgQWJzdHJhY3Rpb24gZm9yIFRpbWV6b25lLVNhZmUgRGF0ZSBPcGVyYXRpb25zXG4gKiBcbiAqIFByb2JsZW06XG4gKiBVc2luZyBEYXRlIG5hdGl2ZWx5IGluIGNhbGVuZGFyL3JhbmdlIGxvZ2ljIGNhdXNlcyBlbnRlcnByaXNlIGJ1Z3M6XG4gKiAtIERhdGUgcmFuZ2VzIHNoaWZ0IGJ5IDEgZGF5IGR1ZSB0byB0aW1lem9uZS9EU1RcbiAqIC0gU2VydmVyIChVVEMpIHZzIENsaWVudCAobG9jYWwgdGltZXpvbmUpIGRpc2NyZXBhbmNpZXNcbiAqIC0gSW5jb25zaXN0ZW50IHJlcG9ydGluZyBpbiBCSS9FUlAvaW52b2ljaW5nL2hvdGVsIHN5c3RlbXNcbiAqIFxuICogU29sdXRpb246XG4gKiBBbGwgZGF0ZSBjYWxjdWxhdGlvbnMgZ28gdGhyb3VnaCBhbiBhZGFwdGVyIGxheWVyLlxuICogVGhpcyBhbGxvd3M6XG4gKiAtIFRpbWV6b25lLXNhZmUgb3BlcmF0aW9ucyBieSBkZWZhdWx0IChOYXRpdmVEYXRlQWRhcHRlciBub3JtYWxpemVzIHRvIGRheSBib3VuZGFyaWVzKVxuICogLSBPcHRpb25hbCBpbnRlZ3JhdGlvbiB3aXRoIEx1eG9uL0RheUpTL2RhdGUtZm5zIGZvciBhZHZhbmNlZCB0aW1lem9uZSBoYW5kbGluZ1xuICogLSBDb25zaXN0ZW50IGJlaGF2aW9yIGFjcm9zcyBTU1IgYW5kIGNsaWVudFxuICogLSBNaWdyYXRpb24gcGF0aCB0byB0aW1lem9uZS1hd2FyZSBsaWJyYXJpZXMgd2l0aG91dCBicmVha2luZyBjaGFuZ2VzXG4gKiBcbiAqIEFyY2hpdGVjdHVyZTpcbiAqIGBgYFxuICogRHVhbERhdGVSYW5nZVN0b3JlXG4gKiAgICAg4oaTIHVzZXNcbiAqIERhdGVBZGFwdGVyIOKGkCBJbmplY3QgdmlhIERBVEVfQURBUFRFUiB0b2tlblxuICogICAgIOKGkyBpbXBsZW1lbnRzXG4gKiBOYXRpdmVEYXRlQWRhcHRlciAoZGVmYXVsdCwgemVybyBkZXBzKVxuICogTHV4b25EYXRlQWRhcHRlciAob3B0aW9uYWwsIGZ1bGwgdGltZXpvbmUgc3VwcG9ydClcbiAqIERheUpTRGF0ZUFkYXB0ZXIgKG9wdGlvbmFsLCBsaWdodHdlaWdodClcbiAqIGBgYFxuICogXG4gKiBVc2FnZTpcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIC8vIERlZmF1bHQgKE5hdGl2ZURhdGVBZGFwdGVyKVxuICogYm9vdHN0cmFwQXBwbGljYXRpb24oQXBwQ29tcG9uZW50KTtcbiAqIFxuICogLy8gQWR2YW5jZWQgKEx1eG9uIHdpdGggdGltZXpvbmUpXG4gKiBpbXBvcnQgeyBMdXhvbkRhdGVBZGFwdGVyIH0gZnJvbSAnQG9uZWx1aXovZHVhbC1kYXRlcGlja2VyL2x1eG9uJztcbiAqIFxuICogYm9vdHN0cmFwQXBwbGljYXRpb24oQXBwQ29tcG9uZW50LCB7XG4gKiAgIHByb3ZpZGVyczogW1xuICogICAgIHsgcHJvdmlkZTogREFURV9BREFQVEVSLCB1c2VDbGFzczogTHV4b25EYXRlQWRhcHRlciB9XG4gKiAgIF1cbiAqIH0pO1xuICogYGBgXG4gKi9cblxuaW1wb3J0IHsgSW5qZWN0aW9uVG9rZW4gfSBmcm9tICdAYW5ndWxhci9jb3JlJztcblxuLyoqXG4gKiBEYXRlIGFkYXB0ZXIgaW50ZXJmYWNlIGZvciBhbGwgY2FsZW5kYXIvcmFuZ2Ugb3BlcmF0aW9uc1xuICogXG4gKiBBbGwgbWV0aG9kcyBvcGVyYXRlIG9uIFwiY2FsZW5kYXIgZGF5XCIgbGV2ZWwsIGlnbm9yaW5nIHRpbWUgY29tcG9uZW50cy5cbiAqIEltcGxlbWVudGF0aW9ucyBtdXN0IGVuc3VyZSB0aW1lem9uZS1zYWZlIGJlaGF2aW9yLlxuICogXG4gKiBJbXBsZW1lbnRhdGlvbnM6XG4gKiAtIE5hdGl2ZURhdGVBZGFwdGVyOiBEZWZhdWx0LCB6ZXJvIGRlcGVuZGVuY2llcywgdXNlcyBEYXRlIHdpdGggbm9ybWFsaXphdGlvblxuICogLSBMdXhvbkRhdGVBZGFwdGVyOiBPcHRpb25hbCwgZnVsbCB0aW1lem9uZSBzdXBwb3J0IHdpdGggTHV4b25cbiAqIC0gRGF5SlNEYXRlQWRhcHRlcjogT3B0aW9uYWwsIGxpZ2h0d2VpZ2h0IHRpbWV6b25lIHN1cHBvcnRcbiAqIC0gQ3VzdG9tOiBJbXBsZW1lbnQgZm9yIHlvdXIgc3BlY2lmaWMgYmFja2VuZC90aW1lem9uZSByZXF1aXJlbWVudHNcbiAqL1xuZXhwb3J0IGludGVyZmFjZSBEYXRlQWRhcHRlciB7XG4gIC8qKlxuICAgKiBOb3JtYWxpemUgZGF0ZSB0byBzdGFydCBvZiBkYXkgKDAwOjAwOjAwLjAwMClcbiAgICogXG4gICAqIENyaXRpY2FsIGZvciB0aW1lem9uZS1zYWZlIGNvbXBhcmlzb25zLlxuICAgKiBcbiAgICogRXhhbXBsZTpcbiAgICogYGBgdHlwZXNjcmlwdFxuICAgKiBjb25zdCBkYXRlID0gbmV3IERhdGUoJzIwMjYtMDItMjFUMTU6MzA6MDAnKTtcbiAgICogY29uc3Qgbm9ybWFsaXplZCA9IGFkYXB0ZXIubm9ybWFsaXplKGRhdGUpO1xuICAgKiAvLyDihpIgMjAyNi0wMi0yMVQwMDowMDowMC4wMDAgKGluIGxvY2FsIHRpbWV6b25lKVxuICAgKiBgYGBcbiAgICogXG4gICAqIEBwYXJhbSBkYXRlIC0gRGF0ZSB0byBub3JtYWxpemVcbiAgICogQHJldHVybnMgRGF0ZSB3aXRoIHRpbWUgc2V0IHRvIDAwOjAwOjAwLjAwMFxuICAgKi9cbiAgbm9ybWFsaXplKGRhdGU6IERhdGUpOiBEYXRlO1xuXG4gIC8qKlxuICAgKiBDaGVjayBpZiB0d28gZGF0ZXMgcmVwcmVzZW50IHRoZSBzYW1lIGNhbGVuZGFyIGRheVxuICAgKiBcbiAgICogSWdub3JlcyB0aW1lIGNvbXBvbmVudC4gVGltZXpvbmUtc2FmZS5cbiAgICogXG4gICAqIEV4YW1wbGU6XG4gICAqIGBgYHR5cGVzY3JpcHRcbiAgICogY29uc3QgYSA9IG5ldyBEYXRlKCcyMDI2LTAyLTIxVDIzOjU5OjU5Jyk7XG4gICAqIGNvbnN0IGIgPSBuZXcgRGF0ZSgnMjAyNi0wMi0yMVQwMDowMDowMScpO1xuICAgKiBhZGFwdGVyLmlzU2FtZURheShhLCBiKTsgLy8g4oaSIHRydWVcbiAgICogYGBgXG4gICAqL1xuICBpc1NhbWVEYXkoYTogRGF0ZSwgYjogRGF0ZSk6IGJvb2xlYW47XG5cbiAgLyoqXG4gICAqIENoZWNrIGlmIGRhdGUgQSBpcyBiZWZvcmUgZGF0ZSBCIChjYWxlbmRhciBkYXkgbGV2ZWwpXG4gICAqIFxuICAgKiBFeGFtcGxlOlxuICAgKiBgYGB0eXBlc2NyaXB0XG4gICAqIGFkYXB0ZXIuaXNCZWZvcmVEYXkobmV3IERhdGUoJzIwMjYtMDItMjAnKSwgbmV3IERhdGUoJzIwMjYtMDItMjEnKSk7IC8vIOKGkiB0cnVlXG4gICAqIGFkYXB0ZXIuaXNCZWZvcmVEYXkobmV3IERhdGUoJzIwMjYtMDItMjEnKSwgbmV3IERhdGUoJzIwMjYtMDItMjEnKSk7IC8vIOKGkiBmYWxzZVxuICAgKiBgYGBcbiAgICovXG4gIGlzQmVmb3JlRGF5KGE6IERhdGUsIGI6IERhdGUpOiBib29sZWFuO1xuXG4gIC8qKlxuICAgKiBDaGVjayBpZiBkYXRlIEEgaXMgYWZ0ZXIgZGF0ZSBCIChjYWxlbmRhciBkYXkgbGV2ZWwpXG4gICAqIFxuICAgKiBFeGFtcGxlOlxuICAgKiBgYGB0eXBlc2NyaXB0XG4gICAqIGFkYXB0ZXIuaXNBZnRlckRheShuZXcgRGF0ZSgnMjAyNi0wMi0yMicpLCBuZXcgRGF0ZSgnMjAyNi0wMi0yMScpKTsgLy8g4oaSIHRydWVcbiAgICogYWRhcHRlci5pc0FmdGVyRGF5KG5ldyBEYXRlKCcyMDI2LTAyLTIxJyksIG5ldyBEYXRlKCcyMDI2LTAyLTIxJykpOyAvLyDihpIgZmFsc2VcbiAgICogYGBgXG4gICAqL1xuICBpc0FmdGVyRGF5KGE6IERhdGUsIGI6IERhdGUpOiBib29sZWFuO1xuXG4gIC8qKlxuICAgKiBBZGQgZGF5cyB0byBhIGRhdGVcbiAgICogXG4gICAqIE11c3QgaGFuZGxlIERTVCB0cmFuc2l0aW9ucyBjb3JyZWN0bHkuXG4gICAqIFxuICAgKiBFeGFtcGxlOlxuICAgKiBgYGB0eXBlc2NyaXB0XG4gICAqIGNvbnN0IGRhdGUgPSBuZXcgRGF0ZSgnMjAyNi0wMi0yMScpO1xuICAgKiBjb25zdCBmdXR1cmUgPSBhZGFwdGVyLmFkZERheXMoZGF0ZSwgNyk7XG4gICAqIC8vIOKGkiAyMDI2LTAyLTI4XG4gICAqIGBgYFxuICAgKi9cbiAgYWRkRGF5cyhkYXRlOiBEYXRlLCBkYXlzOiBudW1iZXIpOiBEYXRlO1xuXG4gIC8qKlxuICAgKiBBZGQgbW9udGhzIHRvIGEgZGF0ZVxuICAgKiBcbiAgICogTXVzdCBoYW5kbGUgbW9udGggb3ZlcmZsb3cgKGUuZy4sIEphbiAzMSArIDEgbW9udGgg4oaSIEZlYiAyOCkuXG4gICAqIFxuICAgKiBFeGFtcGxlOlxuICAgKiBgYGB0eXBlc2NyaXB0XG4gICAqIGNvbnN0IGRhdGUgPSBuZXcgRGF0ZSgnMjAyNi0wMS0zMScpO1xuICAgKiBjb25zdCBuZXh0ID0gYWRhcHRlci5hZGRNb250aHMoZGF0ZSwgMSk7XG4gICAqIC8vIOKGkiAyMDI2LTAyLTI4IChub3QgTWFyY2ggM3JkKVxuICAgKiBgYGBcbiAgICovXG4gIGFkZE1vbnRocyhkYXRlOiBEYXRlLCBtb250aHM6IG51bWJlcik6IERhdGU7XG5cbiAgLyoqXG4gICAqIEdldCBzdGFydCBvZiBkYXkgKDAwOjAwOjAwLjAwMClcbiAgICogXG4gICAqIFNpbWlsYXIgdG8gbm9ybWFsaXplKCkgYnV0IGV4cGxpY2l0IGludGVudC5cbiAgICovXG4gIHN0YXJ0T2ZEYXkoZGF0ZTogRGF0ZSk6IERhdGU7XG5cbiAgLyoqXG4gICAqIEdldCBlbmQgb2YgZGF5ICgyMzo1OTo1OS45OTkpXG4gICAqIFxuICAgKiBVc2VmdWwgZm9yIHJhbmdlIHF1ZXJpZXMgdGhhdCBuZWVkIHRvIGluY2x1ZGUgZW50aXJlIGRheS5cbiAgICogXG4gICAqIEV4YW1wbGU6XG4gICAqIGBgYHR5cGVzY3JpcHRcbiAgICogY29uc3QgZGF0ZSA9IG5ldyBEYXRlKCcyMDI2LTAyLTIxJyk7XG4gICAqIGNvbnN0IGVuZCA9IGFkYXB0ZXIuZW5kT2ZEYXkoZGF0ZSk7XG4gICAqIC8vIOKGkiAyMDI2LTAyLTIxVDIzOjU5OjU5Ljk5OVxuICAgKiBgYGBcbiAgICovXG4gIGVuZE9mRGF5KGRhdGU6IERhdGUpOiBEYXRlO1xuXG4gIC8qKlxuICAgKiBHZXQgZmlyc3QgZGF5IG9mIG1vbnRoICgwMDowMDowMC4wMDApXG4gICAqIFxuICAgKiBFeGFtcGxlOlxuICAgKiBgYGB0eXBlc2NyaXB0XG4gICAqIGNvbnN0IGRhdGUgPSBuZXcgRGF0ZSgnMjAyNi0wMi0yMScpO1xuICAgKiBjb25zdCBzdGFydCA9IGFkYXB0ZXIuc3RhcnRPZk1vbnRoKGRhdGUpO1xuICAgKiAvLyDihpIgMjAyNi0wMi0wMVQwMDowMDowMC4wMDBcbiAgICogYGBgXG4gICAqL1xuICBzdGFydE9mTW9udGgoZGF0ZTogRGF0ZSk6IERhdGU7XG5cbiAgLyoqXG4gICAqIEdldCBsYXN0IGRheSBvZiBtb250aCAoMjM6NTk6NTkuOTk5KVxuICAgKiBcbiAgICogRXhhbXBsZTpcbiAgICogYGBgdHlwZXNjcmlwdFxuICAgKiBjb25zdCBkYXRlID0gbmV3IERhdGUoJzIwMjYtMDItMjEnKTtcbiAgICogY29uc3QgZW5kID0gYWRhcHRlci5lbmRPZk1vbnRoKGRhdGUpO1xuICAgKiAvLyDihpIgMjAyNi0wMi0yOFQyMzo1OTo1OS45OTlcbiAgICogYGBgXG4gICAqL1xuICBlbmRPZk1vbnRoKGRhdGU6IERhdGUpOiBEYXRlO1xuXG4gIC8qKlxuICAgKiBHZXQgeWVhciAoNC1kaWdpdClcbiAgICogXG4gICAqIEV4YW1wbGU6XG4gICAqIGBgYHR5cGVzY3JpcHRcbiAgICogYWRhcHRlci5nZXRZZWFyKG5ldyBEYXRlKCcyMDI2LTAyLTIxJykpOyAvLyDihpIgMjAyNlxuICAgKiBgYGBcbiAgICovXG4gIGdldFllYXIoZGF0ZTogRGF0ZSk6IG51bWJlcjtcblxuICAvKipcbiAgICogR2V0IG1vbnRoICgwLTExLCB3aGVyZSAwID0gSmFudWFyeSlcbiAgICogXG4gICAqIEV4YW1wbGU6XG4gICAqIGBgYHR5cGVzY3JpcHRcbiAgICogYWRhcHRlci5nZXRNb250aChuZXcgRGF0ZSgnMjAyNi0wMi0yMScpKTsgLy8g4oaSIDEgKEZlYnJ1YXJ5KVxuICAgKiBgYGBcbiAgICovXG4gIGdldE1vbnRoKGRhdGU6IERhdGUpOiBudW1iZXI7XG5cbiAgLyoqXG4gICAqIEdldCBkYXkgb2YgbW9udGggKDEtMzEpXG4gICAqIFxuICAgKiBFeGFtcGxlOlxuICAgKiBgYGB0eXBlc2NyaXB0XG4gICAqIGFkYXB0ZXIuZ2V0RGF0ZShuZXcgRGF0ZSgnMjAyNi0wMi0yMScpKTsgLy8g4oaSIDIxXG4gICAqIGBgYFxuICAgKi9cbiAgZ2V0RGF0ZShkYXRlOiBEYXRlKTogbnVtYmVyO1xuXG4gIC8qKlxuICAgKiBHZXQgZGF5IG9mIHdlZWsgKDAtNiwgd2hlcmUgMCA9IFN1bmRheSlcbiAgICogXG4gICAqIEV4YW1wbGU6XG4gICAqIGBgYHR5cGVzY3JpcHRcbiAgICogYWRhcHRlci5nZXREYXkobmV3IERhdGUoJzIwMjYtMDItMjEnKSk7IC8vIOKGkiA2IChTYXR1cmRheSlcbiAgICogYGBgXG4gICAqL1xuICBnZXREYXkoZGF0ZTogRGF0ZSk6IG51bWJlcjtcblxuICAvKipcbiAgICogQ29udmVydCBEYXRlIHRvIElTTyBkYXRlIHN0cmluZyAoWVlZWS1NTS1ERClcbiAgICogXG4gICAqIENSSVRJQ0FMOiBNdXN0IGJlIHRpbWV6b25lLXNhZmUhXG4gICAqIERPIE5PVCB1c2UgZGF0ZS50b0lTT1N0cmluZygpIGFzIGl0IGNvbnZlcnRzIHRvIFVUQy5cbiAgICogXG4gICAqIEV4YW1wbGU6XG4gICAqIGBgYHR5cGVzY3JpcHRcbiAgICogLy8gTG9jYWwgdGltZXpvbmUgR01ULTYgKENTVClcbiAgICogY29uc3QgZGF0ZSA9IG5ldyBEYXRlKCcyMDI2LTAyLTIxVDIzOjAwOjAwJyk7IC8vIDExIFBNIENTVFxuICAgKiBcbiAgICogLy8g4p2MIFdST05HOiBkYXRlLnRvSVNPU3RyaW5nKCkuc3BsaXQoJ1QnKVswXVxuICAgKiAvLyBSZXR1cm5zIFwiMjAyNi0wMi0yMlwiIChzaGlmdGVkIHRvIFVUQyEpXG4gICAqIFxuICAgKiAvLyDinIUgQ09SUkVDVDogYWRhcHRlci50b0lTT0RhdGUoZGF0ZSlcbiAgICogLy8gUmV0dXJucyBcIjIwMjYtMDItMjFcIiAobG9jYWwgZGF0ZSBwcmVzZXJ2ZWQpXG4gICAqIGBgYFxuICAgKiBcbiAgICogQHBhcmFtIGRhdGUgLSBEYXRlIHRvIGZvcm1hdCAob3IgbnVsbClcbiAgICogQHJldHVybnMgSVNPIGRhdGUgc3RyaW5nIGluIFlZWVktTU0tREQgZm9ybWF0IChsb2NhbCB0aW1lem9uZSksIGVtcHR5IHN0cmluZyBpZiBudWxsXG4gICAqL1xuICB0b0lTT0RhdGUoZGF0ZTogRGF0ZSB8IG51bGwpOiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIFBhcnNlIElTTyBkYXRlIHN0cmluZyAoWVlZWS1NTS1ERCkgdG8gRGF0ZVxuICAgKiBcbiAgICogQ1JJVElDQUw6IE11c3QgYmUgdGltZXpvbmUtc2FmZSFcbiAgICogRE8gTk9UIHVzZSBuZXcgRGF0ZShpc29TdHJpbmcpIGFzIGl0IG1heSBwYXJzZSBhcyBVVEMuXG4gICAqIFxuICAgKiBFeGFtcGxlOlxuICAgKiBgYGB0eXBlc2NyaXB0XG4gICAqIC8vIOKdjCBXUk9ORzogbmV3IERhdGUoJzIwMjYtMDItMjEnKVxuICAgKiAvLyBNYXkgcGFyc2UgYXMgVVRDIG1pZG5pZ2h0LCB3aGljaCBpcyBwcmV2aW91cyBkYXkgaW4gc29tZSB0aW1lem9uZXNcbiAgICogXG4gICAqIC8vIOKchSBDT1JSRUNUOiBhZGFwdGVyLnBhcnNlSVNPRGF0ZSgnMjAyNi0wMi0yMScpXG4gICAqIC8vIFJldHVybnMgRGF0ZSByZXByZXNlbnRpbmcgMjAyNi0wMi0yMSAwMDowMDowMCBpbiBsb2NhbCB0aW1lem9uZVxuICAgKiBgYGBcbiAgICogXG4gICAqIEBwYXJhbSBpc29EYXRlIC0gSVNPIGRhdGUgc3RyaW5nIChZWVlZLU1NLUREKSBvciBudWxsXG4gICAqIEByZXR1cm5zIERhdGUgb2JqZWN0IG9yIG51bGwgaWYgaW52YWxpZFxuICAgKi9cbiAgcGFyc2VJU09EYXRlKGlzb0RhdGU6IHN0cmluZyB8IG51bGwpOiBEYXRlIHwgbnVsbDtcblxuICAvKipcbiAgICogR2V0IHdlZWsgc3RhcnQgZGF5IGZvciBsb2NhbGVcbiAgICogXG4gICAqIDAgPSBTdW5kYXksIDEgPSBNb25kYXksIGV0Yy5cbiAgICogXG4gICAqIEV4YW1wbGU6XG4gICAqIGBgYHR5cGVzY3JpcHRcbiAgICogYWRhcHRlci5nZXRXZWVrU3RhcnQoJ2VuLVVTJyk7IC8vIOKGkiAwIChTdW5kYXkpXG4gICAqIGFkYXB0ZXIuZ2V0V2Vla1N0YXJ0KCdlbi1HQicpOyAvLyDihpIgMSAoTW9uZGF5KVxuICAgKiBgYGBcbiAgICogXG4gICAqIEBwYXJhbSBsb2NhbGUgLSBMb2NhbGUgc3RyaW5nIChlLmcuLCAnZW4tVVMnLCAnZXMtRVMnKVxuICAgKiBAcmV0dXJucyBEYXkgbnVtYmVyICgwLTYpXG4gICAqL1xuICBnZXRXZWVrU3RhcnQobG9jYWxlPzogc3RyaW5nKTogMCB8IDEgfCAyIHwgMyB8IDQgfCA1IHwgNjtcbn1cblxuLyoqXG4gKiBJbmplY3Rpb24gdG9rZW4gZm9yIERhdGVBZGFwdGVyXG4gKiBcbiAqIERlZmF1bHQ6IE5hdGl2ZURhdGVBZGFwdGVyICh6ZXJvIGRlcGVuZGVuY2llcylcbiAqIFxuICogT3ZlcnJpZGUgZm9yIGFkdmFuY2VkIHRpbWV6b25lIGhhbmRsaW5nOlxuICogXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiAvLyBMdXhvbiB3aXRoIHRpbWV6b25lXG4gKiBpbXBvcnQgeyBMdXhvbkRhdGVBZGFwdGVyIH0gZnJvbSAnQG9uZWx1aXovZHVhbC1kYXRlcGlja2VyL2x1eG9uJztcbiAqIFxuICogYm9vdHN0cmFwQXBwbGljYXRpb24oQXBwQ29tcG9uZW50LCB7XG4gKiAgIHByb3ZpZGVyczogW1xuICogICAgIHtcbiAqICAgICAgIHByb3ZpZGU6IERBVEVfQURBUFRFUixcbiAqICAgICAgIHVzZUNsYXNzOiBMdXhvbkRhdGVBZGFwdGVyXG4gKiAgICAgfVxuICogICBdXG4gKiB9KTtcbiAqIGBgYFxuICogXG4gKiBDdXN0b20gaW1wbGVtZW50YXRpb246XG4gKiBcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIGNsYXNzIEN1c3RvbURhdGVBZGFwdGVyIGltcGxlbWVudHMgRGF0ZUFkYXB0ZXIge1xuICogICAvLyBZb3VyIGltcGxlbWVudGF0aW9uIGZvciBiYWNrZW5kLXNwZWNpZmljIGRhdGUgaGFuZGxpbmdcbiAqIH1cbiAqIFxuICogcHJvdmlkZShEQVRFX0FEQVBURVIsIHsgdXNlQ2xhc3M6IEN1c3RvbURhdGVBZGFwdGVyIH0pO1xuICogYGBgXG4gKi9cbmV4cG9ydCBjb25zdCBEQVRFX0FEQVBURVIgPSBuZXcgSW5qZWN0aW9uVG9rZW48RGF0ZUFkYXB0ZXI+KCdEQVRFX0FEQVBURVInKTtcbiJdfQ==
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date Clock Abstraction for SSR-Safe Date Resolution
|
|
3
|
+
*
|
|
4
|
+
* Problem:
|
|
5
|
+
* Presets like "Last 7 Days" or "This Month" use new Date() which causes:
|
|
6
|
+
* - SSR hydration mismatch
|
|
7
|
+
* - Server renders "2026-02-14", client renders "2026-02-15"
|
|
8
|
+
* - Different filters in dashboards
|
|
9
|
+
* - Different queries in ERP/BI
|
|
10
|
+
* - Cache inconsistency
|
|
11
|
+
*
|
|
12
|
+
* Solution:
|
|
13
|
+
* Inject a DateClock to control time deterministically:
|
|
14
|
+
*
|
|
15
|
+
* Server (SSR):
|
|
16
|
+
* provide(DATE_CLOCK, {
|
|
17
|
+
* useValue: { now: () => new Date('2026-02-21T00:00:00Z') }
|
|
18
|
+
* })
|
|
19
|
+
*
|
|
20
|
+
* Client (Browser):
|
|
21
|
+
* Uses SystemClock by default (new Date())
|
|
22
|
+
*
|
|
23
|
+
* Testing:
|
|
24
|
+
* provide(DATE_CLOCK, {
|
|
25
|
+
* useValue: { now: () => new Date('2026-01-15T10:30:00Z') }
|
|
26
|
+
* })
|
|
27
|
+
*
|
|
28
|
+
* Architecture:
|
|
29
|
+
* - PresetEngine receives DateClock via DI
|
|
30
|
+
* - All preset calculations use clock.now() instead of new Date()
|
|
31
|
+
* - Deterministic: Same clock.now() → Same preset result
|
|
32
|
+
* - SSR-compatible: Server and client resolve identical ranges
|
|
33
|
+
*/
|
|
34
|
+
import { InjectionToken } from '@angular/core';
|
|
35
|
+
/**
|
|
36
|
+
* Injection token for DateClock
|
|
37
|
+
*
|
|
38
|
+
* Usage:
|
|
39
|
+
* ```typescript
|
|
40
|
+
* // Default (uses SystemClock)
|
|
41
|
+
* bootstrapApplication(AppComponent);
|
|
42
|
+
*
|
|
43
|
+
* // SSR Override
|
|
44
|
+
* bootstrapApplication(AppComponent, {
|
|
45
|
+
* providers: [
|
|
46
|
+
* {
|
|
47
|
+
* provide: DATE_CLOCK,
|
|
48
|
+
* useValue: { now: () => new Date('2026-02-21T00:00:00Z') }
|
|
49
|
+
* }
|
|
50
|
+
* ]
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* // Testing Override
|
|
54
|
+
* TestBed.configureTestingModule({
|
|
55
|
+
* providers: [
|
|
56
|
+
* {
|
|
57
|
+
* provide: DATE_CLOCK,
|
|
58
|
+
* useValue: { now: () => new Date('2026-01-15T12:00:00Z') }
|
|
59
|
+
* }
|
|
60
|
+
* ]
|
|
61
|
+
* });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export const DATE_CLOCK = new InjectionToken('DATE_CLOCK');
|
|
65
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGF0ZS1jbG9jay5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9jb3JlL2RhdGUtY2xvY2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBZ0NHO0FBRUgsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQXFCL0M7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E0Qkc7QUFDSCxNQUFNLENBQUMsTUFBTSxVQUFVLEdBQUcsSUFBSSxjQUFjLENBQVksWUFBWSxDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIERhdGUgQ2xvY2sgQWJzdHJhY3Rpb24gZm9yIFNTUi1TYWZlIERhdGUgUmVzb2x1dGlvblxuICogXG4gKiBQcm9ibGVtOlxuICogUHJlc2V0cyBsaWtlIFwiTGFzdCA3IERheXNcIiBvciBcIlRoaXMgTW9udGhcIiB1c2UgbmV3IERhdGUoKSB3aGljaCBjYXVzZXM6XG4gKiAtIFNTUiBoeWRyYXRpb24gbWlzbWF0Y2hcbiAqIC0gU2VydmVyIHJlbmRlcnMgXCIyMDI2LTAyLTE0XCIsIGNsaWVudCByZW5kZXJzIFwiMjAyNi0wMi0xNVwiXG4gKiAtIERpZmZlcmVudCBmaWx0ZXJzIGluIGRhc2hib2FyZHNcbiAqIC0gRGlmZmVyZW50IHF1ZXJpZXMgaW4gRVJQL0JJXG4gKiAtIENhY2hlIGluY29uc2lzdGVuY3lcbiAqIFxuICogU29sdXRpb246XG4gKiBJbmplY3QgYSBEYXRlQ2xvY2sgdG8gY29udHJvbCB0aW1lIGRldGVybWluaXN0aWNhbGx5OlxuICogXG4gKiBTZXJ2ZXIgKFNTUik6XG4gKiBwcm92aWRlKERBVEVfQ0xPQ0ssIHtcbiAqICAgdXNlVmFsdWU6IHsgbm93OiAoKSA9PiBuZXcgRGF0ZSgnMjAyNi0wMi0yMVQwMDowMDowMFonKSB9XG4gKiB9KVxuICogXG4gKiBDbGllbnQgKEJyb3dzZXIpOlxuICogVXNlcyBTeXN0ZW1DbG9jayBieSBkZWZhdWx0IChuZXcgRGF0ZSgpKVxuICogXG4gKiBUZXN0aW5nOlxuICogcHJvdmlkZShEQVRFX0NMT0NLLCB7XG4gKiAgIHVzZVZhbHVlOiB7IG5vdzogKCkgPT4gbmV3IERhdGUoJzIwMjYtMDEtMTVUMTA6MzA6MDBaJykgfVxuICogfSlcbiAqIFxuICogQXJjaGl0ZWN0dXJlOlxuICogLSBQcmVzZXRFbmdpbmUgcmVjZWl2ZXMgRGF0ZUNsb2NrIHZpYSBESVxuICogLSBBbGwgcHJlc2V0IGNhbGN1bGF0aW9ucyB1c2UgY2xvY2subm93KCkgaW5zdGVhZCBvZiBuZXcgRGF0ZSgpXG4gKiAtIERldGVybWluaXN0aWM6IFNhbWUgY2xvY2subm93KCkg4oaSIFNhbWUgcHJlc2V0IHJlc3VsdFxuICogLSBTU1ItY29tcGF0aWJsZTogU2VydmVyIGFuZCBjbGllbnQgcmVzb2x2ZSBpZGVudGljYWwgcmFuZ2VzXG4gKi9cblxuaW1wb3J0IHsgSW5qZWN0aW9uVG9rZW4gfSBmcm9tICdAYW5ndWxhci9jb3JlJztcblxuLyoqXG4gKiBDbG9jayBhYnN0cmFjdGlvbiBmb3IgZGV0ZXJtaW5pc3RpYyBkYXRlIHJlc29sdXRpb25cbiAqIFxuICogVXNlIGNhc2VzOlxuICogLSBTU1I6IEVuc3VyZSBzZXJ2ZXIgYW5kIGNsaWVudCBnZW5lcmF0ZSBpZGVudGljYWwgcHJlc2V0c1xuICogLSBUZXN0aW5nOiBDb250cm9sIHRpbWUgZm9yIHByZWRpY3RhYmxlIHRlc3QgcmVzdWx0c1xuICogLSBSZXBsYXk6IFJlcHJvZHVjZSBleGFjdCB1c2VyIHN0YXRlIGZyb20gcGFzdCBzZXNzaW9uc1xuICogLSBEZW1vOiBGcmVlemUgdGltZSBmb3IgcmVwcm9kdWNpYmxlIGRlbW9zXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgRGF0ZUNsb2NrIHtcbiAgLyoqXG4gICAqIEdldCBjdXJyZW50IGRhdGUvdGltZVxuICAgKiBcbiAgICogRGVmYXVsdCBpbXBsZW1lbnRhdGlvbiByZXR1cm5zIG5ldyBEYXRlKClcbiAgICogT3ZlcnJpZGUgZm9yIFNTUiwgdGVzdGluZywgb3IgdGltZS10cmF2ZWwgc2NlbmFyaW9zXG4gICAqL1xuICBub3coKTogRGF0ZTtcbn1cblxuLyoqXG4gKiBJbmplY3Rpb24gdG9rZW4gZm9yIERhdGVDbG9ja1xuICogXG4gKiBVc2FnZTpcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIC8vIERlZmF1bHQgKHVzZXMgU3lzdGVtQ2xvY2spXG4gKiBib290c3RyYXBBcHBsaWNhdGlvbihBcHBDb21wb25lbnQpO1xuICogXG4gKiAvLyBTU1IgT3ZlcnJpZGVcbiAqIGJvb3RzdHJhcEFwcGxpY2F0aW9uKEFwcENvbXBvbmVudCwge1xuICogICBwcm92aWRlcnM6IFtcbiAqICAgICB7XG4gKiAgICAgICBwcm92aWRlOiBEQVRFX0NMT0NLLFxuICogICAgICAgdXNlVmFsdWU6IHsgbm93OiAoKSA9PiBuZXcgRGF0ZSgnMjAyNi0wMi0yMVQwMDowMDowMFonKSB9XG4gKiAgICAgfVxuICogICBdXG4gKiB9KTtcbiAqIFxuICogLy8gVGVzdGluZyBPdmVycmlkZVxuICogVGVzdEJlZC5jb25maWd1cmVUZXN0aW5nTW9kdWxlKHtcbiAqICAgcHJvdmlkZXJzOiBbXG4gKiAgICAge1xuICogICAgICAgcHJvdmlkZTogREFURV9DTE9DSyxcbiAqICAgICAgIHVzZVZhbHVlOiB7IG5vdzogKCkgPT4gbmV3IERhdGUoJzIwMjYtMDEtMTVUMTI6MDA6MDBaJykgfVxuICogICAgIH1cbiAqICAgXVxuICogfSk7XG4gKiBgYGBcbiAqL1xuZXhwb3J0IGNvbnN0IERBVEVfQ0xPQ0sgPSBuZXcgSW5qZWN0aW9uVG9rZW48RGF0ZUNsb2NrPignREFURV9DTE9DSycpO1xuIl19
|