@energycap/components 0.46.13 → 0.46.14-esm-luxon.20260713-1107

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.
@@ -16,8 +16,8 @@ import { TranslateModule, TranslateService } from '@ngx-translate/core';
16
16
  import { ClipboardService } from 'ngx-clipboard';
17
17
  import { Subject, of, lastValueFrom, forkJoin, merge, fromEvent, EMPTY, timer, interval, race, ReplaySubject } from 'rxjs';
18
18
  import { takeUntil, switchMap, delay, distinctUntilChanged, filter, take, debounceTime, skipUntil, tap, pluck, map, catchError, skipWhile } from 'rxjs/operators';
19
- import moment from 'moment';
20
- import { range, cloneDeep, isEqual, uniqueId, sortBy, orderBy, get, upperFirst } from 'lodash';
19
+ import { DateTime, Info } from 'luxon';
20
+ import { range, cloneDeep, isEqual, uniqueId, sortBy, orderBy, get, upperFirst } from 'lodash-es';
21
21
  import { ComponentPortal, TemplatePortal } from '@angular/cdk/portal';
22
22
  import Popper from 'popper.js';
23
23
 
@@ -539,7 +539,7 @@ class DateTimeHelper {
539
539
  /** 3000-01-01 */
540
540
  static { this.endOfTime = new Date(3000, 0, 1); }
541
541
  /** 3000-01-01 UTC */
542
- static { this.endOfTimeUTC = moment.utc('3000-01-01').toDate(); }
542
+ static { this.endOfTimeUTC = DateTime.fromISO('3000-01-01', { zone: 'utc' }).toJSDate(); }
543
543
  /** Default effective date for new AccountMeter relationships: 2010-01-01 */
544
544
  static { this.defaultAccountMeterEffectiveDate = new Date(2010, 0, 1); }
545
545
  /** The default date that a newly created account becomes effective: 2010-01-01 */
@@ -581,20 +581,19 @@ class DateTimeHelper {
581
581
  "yyyyMMdd"
582
582
  ]; }
583
583
  /**
584
- * User input formats that we will parse into a date
585
- * when the users date format preference has month before day.
586
- * To be used with moment.js. Moment ignores non-alphanumeric characters
587
- * so dates like 2020/09/10 will be parsed the same as 2020-09-10
588
- */
589
- static { this.momentMonthFirstParseFormats = [
590
- 'M-D',
591
- 'MM-DD',
592
- 'M-D-YY',
593
- 'MM-DD-YY',
594
- 'MMDDYYYY',
595
- 'YYYYMMDD',
596
- 'YYYY-MM-DD',
597
- 'MM-DD-YYYY'
584
+ * Luxon parse formats for the month-first preference. Separator characters
585
+ * in the input are normalized to a hyphen before matching (see {@link parseWithFormats}),
586
+ * so the formats below intentionally use a single separator.
587
+ */
588
+ static { this.luxonMonthFirstParseFormats = [
589
+ 'M-d',
590
+ 'MM-dd',
591
+ 'M-d-yy',
592
+ 'MM-dd-yy',
593
+ 'MMddyyyy',
594
+ 'yyyyMMdd',
595
+ 'yyyy-MM-dd',
596
+ 'MM-dd-yyyy'
598
597
  ]; }
599
598
  /**
600
599
  * User input formats that we will parse into a date
@@ -618,44 +617,48 @@ class DateTimeHelper {
618
617
  "yyyyDDmm"
619
618
  ]; }
620
619
  /**
621
- * User input formats that we will parse into a date
622
- * when the users date format preference has day before month.
623
- * To be used with moment.js. Moment ignores non-alphanumeric characters
624
- * so dates like 2020/09/10 will be parsed the same as 2020-09-10
625
- */
626
- static { this.momentDateFirstParseFormats = [
627
- 'D-M',
628
- 'DD-MM',
629
- 'D-M-YY',
630
- 'DD-MM-YY',
631
- 'DDMMYYYY',
632
- 'YYYYMMDD',
633
- 'YYYY-MM-DD',
634
- 'DD-MM-YYYY'
620
+ * Luxon parse formats for the day-first preference. Separator characters
621
+ * in the input are normalized to a hyphen before matching (see {@link parseWithFormats}).
622
+ */
623
+ static { this.luxonDateFirstParseFormats = [
624
+ 'd-M',
625
+ 'dd-MM',
626
+ 'd-M-yy',
627
+ 'dd-MM-yy',
628
+ 'ddMMyyyy',
629
+ 'yyyyMMdd',
630
+ 'yyyy-MM-dd',
631
+ 'dd-MM-yyyy'
635
632
  ]; }
636
633
  /**
637
- * Formats used to parse dates that are in the medium or long format by moment js.
638
- * These are shared by date-first and month-first date formats. The date can't be
639
- * parsed differently based on the date preference since the month is not numeric.
640
- * This is not an exhaustive list of formats, but it should cover most common formats.
641
- */
642
- static { this.momentMedAndLongDateFormats = [
643
- 'DD MMM',
644
- 'MMM DD',
645
- 'DD MMM YY',
646
- 'DD MMM YYYY',
647
- 'DD MMMM YYYY',
648
- 'MMM D, YY',
649
- 'MMM D, YYYY',
650
- 'MMM DD, YYYY',
651
- 'MMMM D, YYYY',
652
- 'MMMM DD, YYYY',
653
- 'MMM YY',
654
- 'MMM YYYY',
655
- 'MMMM YYYY',
656
- 'MMM, YY',
657
- 'MMM, YYYY',
658
- 'MMMM, YYYY',
634
+ * Luxon parse formats for medium/long date strings (e.g. "Jan 5, 2024").
635
+ * Shared by date-first and month-first preferences because the month is
636
+ * not numeric, so the order is unambiguous.
637
+ */
638
+ static { this.luxonMedAndLongDateFormats = [
639
+ // Moment was forgiving with leading zeros (`DD` matched both `9` and `09`);
640
+ // Luxon's `dd` is strict, so single-digit `d` variants are listed explicitly.
641
+ 'd MMM',
642
+ 'dd MMM',
643
+ 'MMM d',
644
+ 'MMM dd',
645
+ 'd MMM yy',
646
+ 'dd MMM yy',
647
+ 'd MMM yyyy',
648
+ 'dd MMM yyyy',
649
+ 'd MMMM yyyy',
650
+ 'dd MMMM yyyy',
651
+ 'MMM d, yy',
652
+ 'MMM d, yyyy',
653
+ 'MMM dd, yyyy',
654
+ 'MMMM d, yyyy',
655
+ 'MMMM dd, yyyy',
656
+ 'MMM yy',
657
+ 'MMM yyyy',
658
+ 'MMMM yyyy',
659
+ 'MMM, yy',
660
+ 'MMM, yyyy',
661
+ 'MMMM, yyyy',
659
662
  ]; }
660
663
  /**
661
664
  * Format to display the time portion of a datetime
@@ -664,6 +667,99 @@ class DateTimeHelper {
664
667
  ///////////////////////////////////////////////////////
665
668
  // METHODS
666
669
  ///////////////////////////////////////////////////////
670
+ /**
671
+ * Convert any date-like value into a Luxon {@link DateTime}. Strings are parsed
672
+ * as ISO 8601; numbers are treated as epoch milliseconds. Returns an invalid
673
+ * DateTime when the input is null/undefined.
674
+ */
675
+ static toDateTime(value) {
676
+ if (value === null || value === undefined) {
677
+ return DateTime.invalid('null or undefined');
678
+ }
679
+ if (DateTime.isDateTime(value)) {
680
+ return value;
681
+ }
682
+ if (value instanceof Date) {
683
+ return DateTime.fromJSDate(value);
684
+ }
685
+ if (typeof value === 'number') {
686
+ return DateTime.fromMillis(value);
687
+ }
688
+ return DateTime.fromISO(value);
689
+ }
690
+ /**
691
+ * Translate a moment-style format string (e.g. `MM/DD/YYYY HH:mm:ss A`) into
692
+ * the Luxon equivalent (`MM/dd/yyyy HH:mm:ss a`). Use this when format strings
693
+ * originate from external sources (such as user preferences) that still use
694
+ * moment tokens.
695
+ *
696
+ * Only the tokens currently in use across this library are translated:
697
+ * `Y`→`y`, `D`→`d`, `A`→`a`. `M`, `H`, `h`, `m`, `s` are identical between
698
+ * the two libraries.
699
+ */
700
+ static momentToLuxonFormat(format) {
701
+ return format
702
+ .replace(/Y/g, 'y')
703
+ .replace(/D/g, 'd')
704
+ .replace(/A/g, 'a');
705
+ }
706
+ /**
707
+ * Format a date-like value using a moment-style format string. The format
708
+ * string is automatically translated to Luxon tokens, so callers can keep
709
+ * passing the same preference strings (e.g. `MM/DD/YYYY HH:mm:ss`).
710
+ *
711
+ * When `displayInUTC` is true, string inputs without zone info are parsed
712
+ * as UTC (matching `moment.utc(str)`), and all other inputs are shifted to
713
+ * the UTC zone for display. Returns an empty string for invalid input.
714
+ *
715
+ * Moment's lowercase `a` token produces lowercase `am`/`pm`, but Luxon's
716
+ * `a` is locale-aware and emits uppercase `AM`/`PM` in en-US. When the
717
+ * incoming format uses lowercase `a` (and not uppercase `A`), the meridiem
718
+ * in the output is lowercased to preserve the moment contract for user
719
+ * preferences like `h:mm:ss a` that are stored externally.
720
+ */
721
+ static formatDate(date, momentFormat, displayInUTC = false) {
722
+ let dt;
723
+ if (displayInUTC && typeof date === 'string') {
724
+ dt = DateTime.fromISO(date, { zone: 'utc' });
725
+ }
726
+ else {
727
+ dt = this.toDateTime(date);
728
+ if (displayInUTC) {
729
+ dt = dt.setZone('utc');
730
+ }
731
+ }
732
+ if (!dt.isValid)
733
+ return '';
734
+ let result = dt.toFormat(this.momentToLuxonFormat(momentFormat));
735
+ if (momentFormat.includes('a') && !momentFormat.includes('A')) {
736
+ result = result.replace(/AM|PM/g, m => m.toLowerCase());
737
+ }
738
+ return result;
739
+ }
740
+ /**
741
+ * Parse a string against a list of format patterns. Returns the first
742
+ * successful parse as a {@link DateTime} (which may be invalid if none match).
743
+ *
744
+ * Format strings may use either Luxon or moment tokens — each pattern is
745
+ * run through {@link momentToLuxonFormat} before matching, so callers can
746
+ * keep passing arrays like `['M/D/YYYY']` from previously moment-based code.
747
+ *
748
+ * Mirrors moment's behavior of ignoring separator characters by normalizing
749
+ * `/`, `.` and `-` in both input and patterns before matching.
750
+ */
751
+ static parseWithFormats(value, formats) {
752
+ const normalize = (s) => s.replace(/[\/.\-]/g, '-');
753
+ const normalizedValue = normalize(value);
754
+ for (const format of formats) {
755
+ const luxonFormat = this.momentToLuxonFormat(format);
756
+ const dt = DateTime.fromFormat(normalizedValue, normalize(luxonFormat));
757
+ if (dt.isValid) {
758
+ return dt;
759
+ }
760
+ }
761
+ return DateTime.invalid('no matching format');
762
+ }
667
763
  /**
668
764
  * Return true if @see date is equal to the @see endOfTime
669
765
  */
@@ -675,7 +771,7 @@ class DateTimeHelper {
675
771
  * Compare two dates and return true if they represent the same year, month, and day (time is ignored).
676
772
  */
677
773
  static isSameDay(date1, date2) {
678
- return moment(date1).isSame(moment(date2), 'day');
774
+ return this.toDateTime(date1).hasSame(this.toDateTime(date2), 'day');
679
775
  }
680
776
  /**
681
777
  * Get the parse formats based on the given date format. If no date format is
@@ -694,19 +790,19 @@ class DateTimeHelper {
694
790
  return dateFormat ? map[dateFormat] : this.monthFirstParseFormats;
695
791
  }
696
792
  /**
697
- * Get the moment parse formats based on the given date format. If no date format is
793
+ * Get the Luxon parse formats based on the given date format. If no date format is
698
794
  * given, or we don't recognize the format, return the month first parse formats.
699
795
  */
700
- static getMomentParseFormats(dateFormat) {
796
+ static getLuxonParseFormats(dateFormat) {
701
797
  let map = {
702
- 'MM/DD/YYYY': [...this.momentMonthFirstParseFormats, ...this.momentMedAndLongDateFormats],
703
- 'MM-DD-YYYY': [...this.momentMonthFirstParseFormats, ...this.momentMedAndLongDateFormats],
704
- 'DD/MM/YYYY': [...this.momentDateFirstParseFormats, ...this.momentMedAndLongDateFormats],
705
- 'DD-MM-YYYY': [...this.momentDateFirstParseFormats, ...this.momentMedAndLongDateFormats],
706
- 'YYYY/MM/DD': [...this.momentMonthFirstParseFormats, ...this.momentMedAndLongDateFormats],
707
- 'YYYY-MM-DD': [...this.momentMonthFirstParseFormats, ...this.momentMedAndLongDateFormats]
798
+ 'MM/DD/YYYY': [...this.luxonMonthFirstParseFormats, ...this.luxonMedAndLongDateFormats],
799
+ 'MM-DD-YYYY': [...this.luxonMonthFirstParseFormats, ...this.luxonMedAndLongDateFormats],
800
+ 'DD/MM/YYYY': [...this.luxonDateFirstParseFormats, ...this.luxonMedAndLongDateFormats],
801
+ 'DD-MM-YYYY': [...this.luxonDateFirstParseFormats, ...this.luxonMedAndLongDateFormats],
802
+ 'YYYY/MM/DD': [...this.luxonMonthFirstParseFormats, ...this.luxonMedAndLongDateFormats],
803
+ 'YYYY-MM-DD': [...this.luxonMonthFirstParseFormats, ...this.luxonMedAndLongDateFormats]
708
804
  };
709
- return dateFormat ? map[dateFormat] : this.momentMonthFirstParseFormats;
805
+ return dateFormat ? map[dateFormat] : this.luxonMonthFirstParseFormats;
710
806
  }
711
807
  /**
712
808
  * Get relationship start date (favoring date in partialFormState if exists)
@@ -729,28 +825,23 @@ class DateTimeHelper {
729
825
  return null;
730
826
  }
731
827
  static isDateBetween(date, startDate, endDate, includeStart, includeEnd) {
732
- let inclusivity = '()';
733
- if (includeStart && includeEnd) {
734
- inclusivity = '[]';
735
- }
736
- else if (includeStart) {
737
- inclusivity = '[)';
738
- }
739
- else if (includeEnd) {
740
- inclusivity = '(]';
741
- }
742
828
  // If a start date is not provide, we'll use our beginning of time instead
743
829
  startDate = startDate || this.beginningOfTime;
744
830
  // If an end date is not provided, we'll use our end of time instead
745
831
  endDate = endDate || this.endOfTime;
746
- return moment(date).isBetween(startDate, endDate, 'day', inclusivity);
832
+ const d = this.toDateTime(date).startOf('day');
833
+ const s = this.toDateTime(startDate).startOf('day');
834
+ const e = this.toDateTime(endDate).startOf('day');
835
+ const afterStart = includeStart ? d >= s : d > s;
836
+ const beforeEnd = includeEnd ? d <= e : d < e;
837
+ return afterStart && beforeEnd;
747
838
  }
748
839
  static stringToDate(dateString) {
749
840
  if (dateString === null || dateString === undefined) {
750
841
  return dateString;
751
842
  }
752
843
  else {
753
- return moment(dateString).toDate();
844
+ return DateTime.fromISO(dateString).toJSDate();
754
845
  }
755
846
  }
756
847
  }
@@ -764,15 +855,15 @@ class DateInputSelectionStrategyBase {
764
855
  if (!value) {
765
856
  return null;
766
857
  }
767
- const date = moment(value, parseFormats);
768
- if (date.isValid()) {
858
+ let date = DateTimeHelper.parseWithFormats(value, parseFormats);
859
+ if (date.isValid) {
769
860
  // If we're in a temporary state (when the user is typing in a textbox), we need to set the year to the current year if it's before the min year
770
861
  // This prevents the calendars from showing unhelpful dates that are in the distant past before the user finishes typing in the date.
771
862
  // We don't do this modification when the real selection happens on textbox blur.
772
- if (opts?.shiftToCurrentYearIfBelow && date.isBefore(opts.shiftToCurrentYearIfBelow)) {
773
- date.set('year', moment().year());
863
+ if (opts?.shiftToCurrentYearIfBelow && date < DateTime.fromJSDate(opts.shiftToCurrentYearIfBelow)) {
864
+ date = date.set({ year: DateTime.now().year });
774
865
  }
775
- return date.toDate();
866
+ return date.toJSDate();
776
867
  }
777
868
  return null;
778
869
  }
@@ -854,7 +945,7 @@ class DaySelectionStrategy extends DateInputSelectionStrategyBase {
854
945
  getNextSelection(selection) {
855
946
  // Add a single day to the selection
856
947
  if (DateInput.isSelectionSingleDate(selection)) {
857
- return moment(selection).add(1, 'day').toDate();
948
+ return DateTime.fromJSDate(selection).plus({ days: 1 }).toJSDate();
858
949
  }
859
950
  // This shouldn't happen based on current date input flows, but if it does just return the selection as-is.
860
951
  return selection;
@@ -862,7 +953,7 @@ class DaySelectionStrategy extends DateInputSelectionStrategyBase {
862
953
  getPreviousSelection(selection) {
863
954
  // Subtract a single day from the selection
864
955
  if (DateInput.isSelectionSingleDate(selection)) {
865
- return moment(selection).subtract(1, 'day').toDate();
956
+ return DateTime.fromJSDate(selection).minus({ days: 1 }).toJSDate();
866
957
  }
867
958
  // This shouldn't happen based on current date input flows, but if it does just return the selection as-is.
868
959
  return selection;
@@ -893,19 +984,19 @@ class Last28DaysSelectionStrategy extends DateInputSelectionStrategyBase {
893
984
  // Update the start and end dates to match a 28-day range, keying off the end date if parseFromEnd is true
894
985
  // parseFromEnd is true when a date is being entered from the end date textbox
895
986
  if (opts?.parseFromEnd) {
896
- start = moment(end).subtract(27, 'days').toDate();
987
+ start = DateTime.fromJSDate(end).minus({ days: 27 }).toJSDate();
897
988
  }
898
989
  else {
899
- end = moment(start).add(27, 'days').toDate();
990
+ end = DateTime.fromJSDate(start).plus({ days: 27 }).toJSDate();
900
991
  }
901
992
  }
902
993
  // If the start date is not provided, we'll use the end date to calculate the start date
903
994
  if (!start && end && !opts?.preventAutoComplete) {
904
- start = moment(end).subtract(27, 'days').toDate();
995
+ start = DateTime.fromJSDate(end).minus({ days: 27 }).toJSDate();
905
996
  }
906
997
  // If the end date is not provided, we'll use the start date to calculate the end date
907
998
  if (!end && start && !opts?.preventAutoComplete) {
908
- end = moment(start).add(27, 'days').toDate();
999
+ end = DateTime.fromJSDate(start).plus({ days: 27 }).toJSDate();
909
1000
  }
910
1001
  if (!start && !end) {
911
1002
  return null;
@@ -916,7 +1007,7 @@ class Last28DaysSelectionStrategy extends DateInputSelectionStrategyBase {
916
1007
  }
917
1008
  getSelectionFromDate(date) {
918
1009
  return {
919
- start: moment(date).subtract(27, 'days').toDate(),
1010
+ start: DateTime.fromJSDate(date).minus({ days: 27 }).toJSDate(),
920
1011
  end: date
921
1012
  };
922
1013
  }
@@ -926,7 +1017,7 @@ class Last28DaysSelectionStrategy extends DateInputSelectionStrategyBase {
926
1017
  if (!previousSelection.end && previousSelection.start) {
927
1018
  return {
928
1019
  start: previousSelection.start,
929
- end: moment(previousSelection.start).add(27, 'days').toDate()
1020
+ end: DateTime.fromJSDate(previousSelection.start).plus({ days: 27 }).toJSDate()
930
1021
  };
931
1022
  // If the range only has an end date, make the 28-day range end on that date
932
1023
  }
@@ -943,9 +1034,10 @@ class Last28DaysSelectionStrategy extends DateInputSelectionStrategyBase {
943
1034
  getNextSelection(selection) {
944
1035
  // Move the selection to the next 28 days
945
1036
  if (DateInput.isSelectionRange(selection) && selection.end) {
1037
+ const end = DateTime.fromJSDate(selection.end);
946
1038
  return {
947
- start: moment(selection.end).add(1, 'day').toDate(),
948
- end: moment(selection.end).add(28, 'days').toDate()
1039
+ start: end.plus({ days: 1 }).toJSDate(),
1040
+ end: end.plus({ days: 28 }).toJSDate()
949
1041
  };
950
1042
  }
951
1043
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
@@ -955,9 +1047,10 @@ class Last28DaysSelectionStrategy extends DateInputSelectionStrategyBase {
955
1047
  getPreviousSelection(selection) {
956
1048
  // Move the selection to the previous 28 days
957
1049
  if (DateInput.isSelectionRange(selection) && selection.start) {
1050
+ const start = DateTime.fromJSDate(selection.start);
958
1051
  return {
959
- start: moment(selection.start).subtract(28, 'days').toDate(),
960
- end: moment(selection.start).subtract(1, 'day').toDate()
1052
+ start: start.minus({ days: 28 }).toJSDate(),
1053
+ end: start.minus({ days: 1 }).toJSDate()
961
1054
  };
962
1055
  }
963
1056
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
@@ -990,19 +1083,19 @@ class Last7DaysSelectionStrategy extends DateInputSelectionStrategyBase {
990
1083
  // Update the start and end dates to match a 7-day range, keying off the end date if parseFromEnd is true
991
1084
  // parseFromEnd is true when a date is being entered from the end date textbox
992
1085
  if (opts?.parseFromEnd) {
993
- start = moment(end).subtract(6, 'days').toDate();
1086
+ start = DateTime.fromJSDate(end).minus({ days: 6 }).toJSDate();
994
1087
  }
995
1088
  else {
996
- end = moment(start).add(6, 'days').toDate();
1089
+ end = DateTime.fromJSDate(start).plus({ days: 6 }).toJSDate();
997
1090
  }
998
1091
  }
999
1092
  // If the start date is not provided, we'll use the end date to calculate the start date
1000
1093
  if (!start && end && !opts?.preventAutoComplete) {
1001
- start = moment(end).subtract(6, 'days').toDate();
1094
+ start = DateTime.fromJSDate(end).minus({ days: 6 }).toJSDate();
1002
1095
  }
1003
1096
  // If the end date is not provided, we'll use the start date to calculate the end date
1004
1097
  if (!end && start && !opts?.preventAutoComplete) {
1005
- end = moment(start).add(6, 'days').toDate();
1098
+ end = DateTime.fromJSDate(start).plus({ days: 6 }).toJSDate();
1006
1099
  }
1007
1100
  if (!start && !end) {
1008
1101
  return null;
@@ -1014,7 +1107,7 @@ class Last7DaysSelectionStrategy extends DateInputSelectionStrategyBase {
1014
1107
  getSelectionFromDate(date) {
1015
1108
  // Base the selection on the given date, moving back 6 days
1016
1109
  return {
1017
- start: moment(date).subtract(6, 'days').toDate(),
1110
+ start: DateTime.fromJSDate(date).minus({ days: 6 }).toJSDate(),
1018
1111
  end: date
1019
1112
  };
1020
1113
  }
@@ -1024,7 +1117,7 @@ class Last7DaysSelectionStrategy extends DateInputSelectionStrategyBase {
1024
1117
  if (!previousSelection.end && previousSelection.start) {
1025
1118
  return {
1026
1119
  start: previousSelection.start,
1027
- end: moment(previousSelection.start).add(6, 'days').toDate()
1120
+ end: DateTime.fromJSDate(previousSelection.start).plus({ days: 6 }).toJSDate()
1028
1121
  };
1029
1122
  // Otherwise, create the new selection based on the end date backwards
1030
1123
  }
@@ -1041,9 +1134,10 @@ class Last7DaysSelectionStrategy extends DateInputSelectionStrategyBase {
1041
1134
  getNextSelection(selection) {
1042
1135
  // Shift the range forward by 7 days
1043
1136
  if (DateInput.isSelectionRange(selection) && selection.end) {
1137
+ const end = DateTime.fromJSDate(selection.end);
1044
1138
  return {
1045
- start: moment(selection.end).add(1, 'day').toDate(),
1046
- end: moment(selection.end).add(7, 'days').toDate()
1139
+ start: end.plus({ days: 1 }).toJSDate(),
1140
+ end: end.plus({ days: 7 }).toJSDate()
1047
1141
  };
1048
1142
  }
1049
1143
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
@@ -1053,9 +1147,10 @@ class Last7DaysSelectionStrategy extends DateInputSelectionStrategyBase {
1053
1147
  getPreviousSelection(selection) {
1054
1148
  // Shift the range back 7 days
1055
1149
  if (DateInput.isSelectionRange(selection) && selection.start) {
1150
+ const start = DateTime.fromJSDate(selection.start);
1056
1151
  return {
1057
- start: moment(selection.start).subtract(7, 'days').toDate(),
1058
- end: moment(selection.start).subtract(1, 'day').toDate()
1152
+ start: start.minus({ days: 7 }).toJSDate(),
1153
+ end: start.minus({ days: 1 }).toJSDate()
1059
1154
  };
1060
1155
  }
1061
1156
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
@@ -1083,7 +1178,7 @@ class MonthSelectionStrategy extends DateInputSelectionStrategyBase {
1083
1178
  // A single date selection shouldn't happen in this mode but it's simple enough to handle
1084
1179
  const date = DateInput.isSelectionRange(selection) ? selection.start : selection;
1085
1180
  return {
1086
- textbox: moment(date).format('MMM, YYYY'),
1181
+ textbox: date ? DateTime.fromJSDate(date).toFormat('MMM, yyyy') : null,
1087
1182
  textbox2: null
1088
1183
  };
1089
1184
  }
@@ -1096,9 +1191,10 @@ class MonthSelectionStrategy extends DateInputSelectionStrategyBase {
1096
1191
  return this.getSelectionFromDate(date);
1097
1192
  }
1098
1193
  getSelectionFromDate(date) {
1194
+ const dt = DateTime.fromJSDate(date);
1099
1195
  return {
1100
- start: moment(date).startOf('month').toDate(),
1101
- end: moment(date).endOf('month').toDate()
1196
+ start: dt.startOf('month').toJSDate(),
1197
+ end: dt.endOf('month').toJSDate()
1102
1198
  };
1103
1199
  }
1104
1200
  getNewSelectionFromExisting(previousSelection) {
@@ -1118,8 +1214,8 @@ class MonthSelectionStrategy extends DateInputSelectionStrategyBase {
1118
1214
  getNextSelection(selection) {
1119
1215
  // Move the selection to the next month
1120
1216
  if (DateInput.isSelectionRange(selection) && selection.start) {
1121
- const date = moment(selection.start).add(1, 'month');
1122
- return this.getSelectionFromDate(date.toDate());
1217
+ const date = DateTime.fromJSDate(selection.start).plus({ months: 1 });
1218
+ return this.getSelectionFromDate(date.toJSDate());
1123
1219
  }
1124
1220
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
1125
1221
  // If it does, just return the selection as-is.
@@ -1128,8 +1224,8 @@ class MonthSelectionStrategy extends DateInputSelectionStrategyBase {
1128
1224
  getPreviousSelection(selection) {
1129
1225
  // Move the selection to the previous month
1130
1226
  if (DateInput.isSelectionRange(selection) && selection.start) {
1131
- const date = moment(selection.start).subtract(1, 'month');
1132
- return this.getSelectionFromDate(date.toDate());
1227
+ const date = DateTime.fromJSDate(selection.start).minus({ months: 1 });
1228
+ return this.getSelectionFromDate(date.toJSDate());
1133
1229
  }
1134
1230
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
1135
1231
  // If it does, just return the selection as-is.
@@ -1152,9 +1248,9 @@ class QuarterSelectionStrategy extends DateInputSelectionStrategyBase {
1152
1248
  // Format the the quarter range as "MMM–MMM, YYYY"
1153
1249
  // We only have one textbox in this mode
1154
1250
  if (DateInput.isSelectionRange(selection)) {
1155
- const start = moment(selection.start).format('MMM');
1156
- const end = moment(selection.end).format('MMM');
1157
- const year = moment(selection.end).format('YYYY');
1251
+ const start = selection.start ? DateTime.fromJSDate(selection.start).toFormat('MMM') : '';
1252
+ const end = selection.end ? DateTime.fromJSDate(selection.end).toFormat('MMM') : '';
1253
+ const year = selection.end ? DateTime.fromJSDate(selection.end).toFormat('yyyy') : '';
1158
1254
  return {
1159
1255
  textbox: `${start}–${end}, ${year}`,
1160
1256
  textbox2: null
@@ -1172,9 +1268,10 @@ class QuarterSelectionStrategy extends DateInputSelectionStrategyBase {
1172
1268
  return this.getSelectionFromDate(date);
1173
1269
  }
1174
1270
  getSelectionFromDate(date) {
1271
+ const dt = DateTime.fromJSDate(date);
1175
1272
  return {
1176
- start: moment(date).startOf('quarter').toDate(),
1177
- end: moment(date).endOf('quarter').toDate()
1273
+ start: dt.startOf('quarter').toJSDate(),
1274
+ end: dt.endOf('quarter').toJSDate()
1178
1275
  };
1179
1276
  }
1180
1277
  getNewSelectionFromExisting(previousSelection) {
@@ -1194,8 +1291,8 @@ class QuarterSelectionStrategy extends DateInputSelectionStrategyBase {
1194
1291
  getNextSelection(selection) {
1195
1292
  // Move the selection to the next quarter
1196
1293
  if (DateInput.isSelectionRange(selection) && selection.start) {
1197
- const date = moment(selection.start).add(1, 'quarter');
1198
- return this.getSelectionFromDate(date.toDate());
1294
+ const date = DateTime.fromJSDate(selection.start).plus({ quarters: 1 });
1295
+ return this.getSelectionFromDate(date.toJSDate());
1199
1296
  }
1200
1297
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
1201
1298
  // If it does, just return the selection as-is.
@@ -1204,8 +1301,8 @@ class QuarterSelectionStrategy extends DateInputSelectionStrategyBase {
1204
1301
  getPreviousSelection(selection) {
1205
1302
  // Move the selection to the previous quarter
1206
1303
  if (DateInput.isSelectionRange(selection) && selection.start) {
1207
- const date = moment(selection.start).subtract(1, 'quarter');
1208
- return this.getSelectionFromDate(date.toDate());
1304
+ const date = DateTime.fromJSDate(selection.start).minus({ quarters: 1 });
1305
+ return this.getSelectionFromDate(date.toJSDate());
1209
1306
  }
1210
1307
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
1211
1308
  // If it does, just return the selection as-is.
@@ -1228,13 +1325,13 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1228
1325
  if (!selection) {
1229
1326
  return { textbox: null, textbox2: null };
1230
1327
  }
1231
- // We use the 'll' moment medium format here to make sure the formatted date is formatted according to the user's locale
1328
+ // We use Luxon's DATE_MED preset here (equivalent to moment's 'll') so the formatted date follows the user's locale
1232
1329
  if (DateInput.isSelectionSingleDate(selection)) {
1233
- return { textbox: moment(selection).format('ll'), textbox2: null };
1330
+ return { textbox: DateTime.fromJSDate(selection).toLocaleString(DateTime.DATE_MED), textbox2: null };
1234
1331
  }
1235
1332
  else {
1236
- const start = selection.start ? moment(selection.start).format('ll') : null;
1237
- const end = selection.end ? moment(selection.end).format('ll') : null;
1333
+ const start = selection.start ? DateTime.fromJSDate(selection.start).toLocaleString(DateTime.DATE_MED) : null;
1334
+ const end = selection.end ? DateTime.fromJSDate(selection.end).toLocaleString(DateTime.DATE_MED) : null;
1238
1335
  return { textbox: start, textbox2: end };
1239
1336
  }
1240
1337
  }
@@ -1245,7 +1342,7 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1245
1342
  // because we don't have a fixed range to automatically adjust the dates to. If the user is changing the begin date or end date and ends
1246
1343
  // up crossing the other date, we should clear out the date that's not being changed so that the user can enter a valid range using the
1247
1344
  // begin/end date they just entered.
1248
- if (start && end && moment(start).isAfter(end)) {
1345
+ if (start && end && DateTime.fromJSDate(start) > DateTime.fromJSDate(end)) {
1249
1346
  if (opts?.parseFromEnd) {
1250
1347
  start = null;
1251
1348
  }
@@ -1263,18 +1360,19 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1263
1360
  getSelectionFromDate(date, existingSelection) {
1264
1361
  if (DateInput.isSelectionRange(existingSelection)) {
1265
1362
  const { start, end } = existingSelection;
1363
+ const dDt = DateTime.fromJSDate(date);
1266
1364
  if (start && !end) {
1267
1365
  // If the selected date is before the existing start date, set it as the new start date and use the existing start date as the end date
1268
- return moment(date).isBefore(start) ? { start: date, end: start } : { start, end: date };
1366
+ return dDt < DateTime.fromJSDate(start) ? { start: date, end: start } : { start, end: date };
1269
1367
  }
1270
1368
  if (end && !start) {
1271
1369
  // If the selected date is after the existing end date, set it as the new end date and use the existing end date as the start date
1272
- return moment(date).isBefore(end) ? { start: date, end } : { start: end, end: date };
1370
+ return dDt < DateTime.fromJSDate(end) ? { start: date, end } : { start: end, end: date };
1273
1371
  }
1274
1372
  }
1275
1373
  else if (DateInput.isSelectionSingleDate(existingSelection)) {
1276
1374
  // If the selected date is before the existing date, set it as the new start date and use the existing date as the end date
1277
- return moment(date).isBefore(existingSelection) ? { start: date, end: existingSelection } : { start: existingSelection, end: date };
1375
+ return DateTime.fromJSDate(date) < DateTime.fromJSDate(existingSelection) ? { start: date, end: existingSelection } : { start: existingSelection, end: date };
1278
1376
  }
1279
1377
  // If no selection exists or we already have a complete selection, start a new one with the selected date as the start date.
1280
1378
  return { start: date, end: null };
@@ -1295,9 +1393,9 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1295
1393
  // If we have a valid range, we'll shift the range to the date provided, using it as the end date
1296
1394
  // The new selection will have the same length as the previous.
1297
1395
  if (DateInput.isSelectionRange(existingSelection) && existingSelection.start && existingSelection.end) {
1298
- const length = moment(existingSelection.end).diff(existingSelection.start, 'days');
1396
+ const length = Math.floor(DateTime.fromJSDate(existingSelection.end).diff(DateTime.fromJSDate(existingSelection.start), 'days').days);
1299
1397
  return {
1300
- start: moment(date).subtract(length, 'days').toDate(),
1398
+ start: DateTime.fromJSDate(date).minus({ days: length }).toJSDate(),
1301
1399
  end: date
1302
1400
  };
1303
1401
  }
@@ -1309,10 +1407,11 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1309
1407
  // We can only determine the next selection if we have a valid range
1310
1408
  if (DateInput.isSelectionRange(selection) && selection.start && selection.end) {
1311
1409
  // Shift the range forward by the number of days between the start and end dates
1312
- const length = moment(selection.end).diff(selection.start, 'days');
1410
+ const end = DateTime.fromJSDate(selection.end);
1411
+ const length = Math.floor(end.diff(DateTime.fromJSDate(selection.start), 'days').days);
1313
1412
  return {
1314
- start: moment(selection.end).add(1, 'days').toDate(),
1315
- end: moment(selection.end).add(1 + length, 'days').toDate()
1413
+ start: end.plus({ days: 1 }).toJSDate(),
1414
+ end: end.plus({ days: 1 + length }).toJSDate()
1316
1415
  };
1317
1416
  }
1318
1417
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
@@ -1323,10 +1422,11 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1323
1422
  // We can only determine the previous selection if we have a valid range
1324
1423
  if (DateInput.isSelectionRange(selection) && selection.start && selection.end) {
1325
1424
  // Shift the range back by the number of days between the start and end dates
1326
- const length = moment(selection.end).diff(selection.start, 'days');
1425
+ const start = DateTime.fromJSDate(selection.start);
1426
+ const length = Math.floor(DateTime.fromJSDate(selection.end).diff(start, 'days').days);
1327
1427
  return {
1328
- start: moment(selection.start).subtract(1 + length, 'days').toDate(),
1329
- end: moment(selection.start).subtract(1, 'days').toDate()
1428
+ start: start.minus({ days: 1 + length }).toJSDate(),
1429
+ end: start.minus({ days: 1 }).toJSDate()
1330
1430
  };
1331
1431
  }
1332
1432
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
@@ -1336,31 +1436,33 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1336
1436
  getPrimaryCalendarView(selection, minDate, maxDate, currentView) {
1337
1437
  let viewDate;
1338
1438
  if (DateInput.isSelectionRange(selection)) {
1339
- let start = selection.start ? moment(selection.start) : null;
1340
- let end = selection.end ? moment(selection.end) : null;
1439
+ const start = selection.start ? DateTime.fromJSDate(selection.start) : null;
1440
+ const end = selection.end ? DateTime.fromJSDate(selection.end) : null;
1441
+ const minDt = DateTime.fromJSDate(minDate);
1442
+ const maxDt = DateTime.fromJSDate(maxDate);
1341
1443
  // Since we're in range mode, the primary calendar is mostly concerned with the start date
1342
1444
  if (start) {
1343
1445
  // If we have a start date in the selection and it's before the min date, shift the view to the min date.
1344
- if (start.isBefore(minDate, 'month')) {
1446
+ if (start.startOf('month') < minDt.startOf('month')) {
1345
1447
  viewDate = minDate;
1346
1448
  // If the start is after the max date, shift the view to the max date.
1347
1449
  }
1348
- else if (start.isSameOrAfter(maxDate, 'month')) {
1450
+ else if (start.startOf('month') >= maxDt.startOf('month')) {
1349
1451
  // the secondary calendar will show the max date month.
1350
1452
  // The primary calendar will show the month before the max date.
1351
- viewDate = moment(maxDate).subtract(1, 'month').toDate();
1453
+ viewDate = maxDt.minus({ months: 1 }).toJSDate();
1352
1454
  }
1353
1455
  else {
1354
- viewDate = start.toDate();
1456
+ viewDate = start.toJSDate();
1355
1457
  }
1356
1458
  }
1357
- else if (end && end.isSameOrBefore(minDate, 'month')) {
1459
+ else if (end && end.startOf('month') <= minDt.startOf('month')) {
1358
1460
  // If we don't have a start date, and the end date is the same month or before the min date, shift the view to the min date.
1359
1461
  viewDate = minDate;
1360
1462
  }
1361
- else if (end && end.isSameOrBefore(new Date(), 'month')) {
1463
+ else if (end && end.startOf('month') <= DateTime.now().startOf('month')) {
1362
1464
  // If we don't have a start date, and the end date is the same month or before the current month, shift the view to the month before the end
1363
- viewDate = moment(end).subtract(1, 'month').toDate();
1465
+ viewDate = end.minus({ months: 1 }).toJSDate();
1364
1466
  }
1365
1467
  else {
1366
1468
  // Otherwise, show the current month.
@@ -1378,32 +1480,34 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1378
1480
  getSecondaryCalendarView(selection, minDate, maxDate, currentView) {
1379
1481
  let viewDate;
1380
1482
  if (DateInput.isSelectionRange(selection)) {
1381
- let start = selection.start ? moment(selection.start) : null;
1382
- let end = selection.end ? moment(selection.end) : null;
1483
+ const start = selection.start ? DateTime.fromJSDate(selection.start) : null;
1484
+ const end = selection.end ? DateTime.fromJSDate(selection.end) : null;
1485
+ const minDt = DateTime.fromJSDate(minDate);
1486
+ const maxDt = DateTime.fromJSDate(maxDate);
1383
1487
  // The secondary calendar is primarily concerned with the end date in the selection.
1384
1488
  if (end) {
1385
- if (end.isAfter(maxDate, 'month')) {
1489
+ if (end.startOf('month') > maxDt.startOf('month')) {
1386
1490
  // If the end date is after the max date, shift the view to the max date.
1387
1491
  viewDate = maxDate;
1388
1492
  }
1389
- else if (end.isSameOrBefore(minDate, 'month')) {
1493
+ else if (end.startOf('month') <= minDt.startOf('month')) {
1390
1494
  // If the end date is the same month or before the min date, shift the view to show
1391
1495
  // the month after the min date. The primary calendar will show the min date month.
1392
- viewDate = moment(minDate).add(1, 'month').toDate();
1496
+ viewDate = minDt.plus({ months: 1 }).toJSDate();
1393
1497
  }
1394
1498
  else {
1395
1499
  // Otherwise, show the end date month.
1396
- viewDate = end.toDate();
1500
+ viewDate = end.toJSDate();
1397
1501
  }
1398
1502
  }
1399
- else if (start && start.isSameOrAfter(maxDate, 'month')) {
1503
+ else if (start && start.startOf('month') >= maxDt.startOf('month')) {
1400
1504
  // If we don't have an end date, and the start date is after the max date, show the max date month
1401
1505
  viewDate = maxDate;
1402
1506
  }
1403
- else if (start && start.isSameOrAfter(new Date(), 'month')) {
1507
+ else if (start && start.startOf('month') >= DateTime.now().startOf('month')) {
1404
1508
  // If we don't have an end date, and the start date is on or after the current month,
1405
1509
  // show the month after the start date.
1406
- viewDate = moment(start).add(1, 'month').toDate();
1510
+ viewDate = start.plus({ months: 1 }).toJSDate();
1407
1511
  }
1408
1512
  else {
1409
1513
  // Otherwise, show the current month.
@@ -1438,12 +1542,12 @@ class YearSelectionStrategy extends DateInputSelectionStrategyBase {
1438
1542
  // Only one date is needed for formatting since the ranges span a single year.
1439
1543
  // There shouldn't be any case where there's only a single date but it's covered anyway.
1440
1544
  const date = DateInput.isSelectionRange(selection) ? selection.start : selection;
1441
- return { textbox: moment(date).format('YYYY'), textbox2: null };
1545
+ return { textbox: date ? DateTime.fromJSDate(date).toFormat('yyyy') : null, textbox2: null };
1442
1546
  }
1443
1547
  parseString(value, parseFormats, opts) {
1444
- // Including 'YY' and 'YYYY' as valid formats for year input. These aren't included in the default parse
1548
+ // Including 'yy' and 'yyyy' as valid formats for year input. These aren't included in the default parse
1445
1549
  // formats because they cause issues with the default date parsing.
1446
- return super.parseString(value, ['YY', 'YYYY', ...parseFormats], opts);
1550
+ return super.parseString(value, ['yy', 'yyyy', ...parseFormats], opts);
1447
1551
  }
1448
1552
  parseTextboxValues(value, parseFormats, opts) {
1449
1553
  // Only a single textbox is used in year mode
@@ -1454,9 +1558,10 @@ class YearSelectionStrategy extends DateInputSelectionStrategyBase {
1454
1558
  return this.getSelectionFromDate(date);
1455
1559
  }
1456
1560
  getSelectionFromDate(date) {
1561
+ const dt = DateTime.fromJSDate(date);
1457
1562
  return {
1458
- start: moment(date).startOf('year').toDate(),
1459
- end: moment(date).endOf('year').toDate()
1563
+ start: dt.startOf('year').toJSDate(),
1564
+ end: dt.endOf('year').toJSDate()
1460
1565
  };
1461
1566
  }
1462
1567
  getNewSelectionFromExisting(previousSelection) {
@@ -1479,8 +1584,8 @@ class YearSelectionStrategy extends DateInputSelectionStrategyBase {
1479
1584
  getNextSelection(selection) {
1480
1585
  // Move the selection to the next year
1481
1586
  if (DateInput.isSelectionRange(selection) && selection.start) {
1482
- const date = moment(selection.start).add(1, 'year');
1483
- return this.getSelectionFromDate(date.toDate());
1587
+ const date = DateTime.fromJSDate(selection.start).plus({ years: 1 });
1588
+ return this.getSelectionFromDate(date.toJSDate());
1484
1589
  }
1485
1590
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
1486
1591
  // If it does, just return the selection as-is.
@@ -1489,8 +1594,8 @@ class YearSelectionStrategy extends DateInputSelectionStrategyBase {
1489
1594
  getPreviousSelection(selection) {
1490
1595
  // Move the selection to the previous year
1491
1596
  if (DateInput.isSelectionRange(selection) && selection.start) {
1492
- const date = moment(selection.start).subtract(1, 'year');
1493
- return this.getSelectionFromDate(date.toDate());
1597
+ const date = DateTime.fromJSDate(selection.start).minus({ years: 1 });
1598
+ return this.getSelectionFromDate(date.toJSDate());
1494
1599
  }
1495
1600
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
1496
1601
  // If it does, just return the selection as-is.
@@ -1529,10 +1634,10 @@ class DateDisplayPipe {
1529
1634
  transform(date, showEndOfTime, showTime, showSeconds) {
1530
1635
  let display = '';
1531
1636
  let formatString = '';
1532
- if (date && moment(date).isValid() && (showEndOfTime || !DateTimeHelper.isEndOfTime(date))) {
1637
+ if (date != null && DateTimeHelper.toDateTime(date).isValid && (showEndOfTime || !DateTimeHelper.isEndOfTime(date))) {
1533
1638
  // use user-preferred formats
1534
1639
  this.userPreferenceService.getPreferences().subscribe(result => {
1535
- //if preferences exist then format date to users preference
1640
+ //if preferences exist then format date to users preference
1536
1641
  //otherwise use the last user preference
1537
1642
  if (result.preference) {
1538
1643
  formatString = result.preference.dateFormat;
@@ -1546,7 +1651,7 @@ class DateDisplayPipe {
1546
1651
  this.lastFormatString = formatString;
1547
1652
  }
1548
1653
  });
1549
- display = moment(date).format(this.lastFormatString);
1654
+ display = DateTimeHelper.formatDate(date, this.lastFormatString);
1550
1655
  }
1551
1656
  return display;
1552
1657
  }
@@ -1571,6 +1676,32 @@ var DateInput;
1571
1676
  return !!selection && !isSelectionSingleDate(selection);
1572
1677
  }
1573
1678
  DateInput.isSelectionRange = isSelectionRange;
1679
+ /**
1680
+ * Coerces a selection into one backed by real {@link Date} objects.
1681
+ *
1682
+ * The form model is typed as `Date | Range`, but hosts commonly bind ISO date strings
1683
+ * (e.g. values straight off an API response). The display path tolerates strings via
1684
+ * Luxon parsing, but the calendar view builders pass the value to `DateTime.fromJSDate`,
1685
+ * which only accepts a `Date` — a string yields an Invalid Date and crashes the calendar
1686
+ * (`RangeError: Invalid time value`). Normalizing at the boundary lets all downstream
1687
+ * calendar/stepper logic rely on actual Dates. Invalid/unparseable values become null.
1688
+ */
1689
+ function normalizeSelection(selection) {
1690
+ if (selection === null || selection === undefined) {
1691
+ return null;
1692
+ }
1693
+ if (isSelectionSingleDate(selection)) {
1694
+ const dt = DateTimeHelper.toDateTime(selection);
1695
+ return dt.isValid ? dt.toJSDate() : null;
1696
+ }
1697
+ const start = selection.start ? DateTimeHelper.toDateTime(selection.start) : null;
1698
+ const end = selection.end ? DateTimeHelper.toDateTime(selection.end) : null;
1699
+ return {
1700
+ start: start?.isValid ? start.toJSDate() : null,
1701
+ end: end?.isValid ? end.toJSDate() : null
1702
+ };
1703
+ }
1704
+ DateInput.normalizeSelection = normalizeSelection;
1574
1705
  class SelectionStrategies {
1575
1706
  constructor(dateDisplayPipe) {
1576
1707
  this.dateDisplayPipe = dateDisplayPipe;
@@ -1613,9 +1744,14 @@ class CalendarItemComponent {
1613
1744
  this.today = false;
1614
1745
  this.outsideActiveMonth = false;
1615
1746
  }
1747
+ else if (this.item?.date) {
1748
+ const itemDt = DateTime.fromJSDate(this.item.date);
1749
+ this.today = itemDt.hasSame(DateTime.now(), 'day');
1750
+ this.outsideActiveMonth = !itemDt.hasSame(DateTime.fromJSDate(this.view.date), 'month');
1751
+ }
1616
1752
  else {
1617
- this.today = moment(this.item?.date).isSame(moment(), 'day');
1618
- this.outsideActiveMonth = !moment(this.item?.date).isSame(this.view.date, 'month');
1753
+ this.today = false;
1754
+ this.outsideActiveMonth = false;
1619
1755
  }
1620
1756
  if (this.item?.date) {
1621
1757
  this.hidden = this.isHidden(this.item.date, this.minDate, this.maxDate, this.view.mode);
@@ -1629,24 +1765,30 @@ class CalendarItemComponent {
1629
1765
  if (!date || !selection) {
1630
1766
  return false;
1631
1767
  }
1768
+ const unit = view ?? 'day';
1769
+ const dt = DateTime.fromJSDate(date);
1632
1770
  if (DateInput.isSelectionSingleDate(selection)) {
1633
- return moment(date).isSame(selection, view);
1771
+ return dt.hasSame(DateTime.fromJSDate(selection), unit);
1634
1772
  }
1635
1773
  else {
1636
1774
  if (selection.start && !selection.end) {
1637
- return moment(date).isSame(selection.start, view);
1775
+ return dt.hasSame(DateTime.fromJSDate(selection.start), unit);
1638
1776
  }
1639
1777
  else if (!selection.start && selection.end) {
1640
- return moment(date).isSame(selection.end, view);
1778
+ return dt.hasSame(DateTime.fromJSDate(selection.end), unit);
1641
1779
  }
1642
1780
  else if (selection.start && selection.end) {
1643
- return moment(date).isBetween(selection.start, selection.end, view, '[]');
1781
+ const d = dt.startOf(unit);
1782
+ const s = DateTime.fromJSDate(selection.start).startOf(unit);
1783
+ const e = DateTime.fromJSDate(selection.end).startOf(unit);
1784
+ return d >= s && d <= e;
1644
1785
  }
1645
1786
  }
1646
1787
  return false;
1647
1788
  }
1648
1789
  isHidden(date, minDate, maxDate, view) {
1649
- return moment(date).isBefore(minDate, view) || moment(date).isAfter(maxDate, view);
1790
+ const d = DateTime.fromJSDate(date).startOf(view);
1791
+ return d < DateTime.fromJSDate(minDate).startOf(view) || d > DateTime.fromJSDate(maxDate).startOf(view);
1650
1792
  }
1651
1793
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CalendarItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1652
1794
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.4", type: CalendarItemComponent, isStandalone: false, selector: "button[ec-calendar-item]", inputs: { item: "item", selection: "selection", view: "view", minDate: "minDate", maxDate: "maxDate" }, host: { properties: { "class.is-today": "this.today", "class.is-selected": "this.selected", "class.is-outside-active-month": "this.outsideActiveMonth", "class.is-hidden": "this.hidden", "attr.disabled": "this.disabled" } }, usesOnChanges: true, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: [":host{font-size:var(--ec-font-size-action);height:2rem;line-height:1.25rem;padding:.3125rem .5rem;border:0;border-radius:var(--ec-border-radius);display:flex;align-items:center;justify-content:center;cursor:pointer}:host .label{display:flex;align-items:center;justify-content:center;white-space:nowrap;flex:auto}:host .ec-icon{flex:none}:host .ec-icon+.label{flex:none;margin-left:.25rem}:host.has-badge{padding-right:.0625rem}:host:focus{outline:none;position:relative;z-index:1}:host:disabled{background-color:var(--ec-background-color-disabled);border:1px solid var(--ec-form-control-border-color-disabled);color:var(--ec-color-disabled-dark);opacity:var(--ec-form-control-opacity-disabled);cursor:default}:host:disabled .ec-icon{color:var(--ec-color-disabled-dark)}:host{background-color:transparent}:host:disabled{background-color:transparent;border-color:transparent;color:var(--ec-color-hint-dark);--ec-color-icon: var(--ec-color-hint-dark)}:host:hover:not(:disabled){background-color:var(--ec-background-color-hover)}:host:active:not(:disabled){background-color:var(--ec-background-color-selected);font-weight:700}:host:focus:not(:disabled){box-shadow:var(--ec-button-box-shadow-active, 0 0 0 2px var(--ec-border-color-focus))}:host.is-selected{background-color:var(--ec-background-color-selected);font-weight:700}:host{line-height:1.125rem}:host.is-selected,:host.is-selected:disabled{background-color:var(--ec-background-color-selected);font-weight:700}:host.is-today{position:relative}:host.is-today:after{content:\"\";position:absolute;top:50%;left:50%;width:1.75rem;height:1.75rem;transform:translate(-50%,-50%);border:1px solid;border-radius:50%;pointer-events:none}:host.is-outside-active-month{color:var(--ec-color-hint-dark)}:host.is-hidden{opacity:0!important}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
@@ -1695,8 +1837,12 @@ class CalendarComponent {
1695
1837
  this.viewChange = new EventEmitter();
1696
1838
  /** Array of calendar item rows to display in the calendar. */
1697
1839
  this.calendarItems = [];
1698
- /** Weekday labels to show at the top of the calendar in day view. */
1699
- this.weekDays = moment.weekdaysShort().map(d => d.slice(0, 1));
1840
+ /** Weekday labels to show at the top of the calendar in day view. Starts on Sunday to match the calendar grid layout. */
1841
+ this.weekDays = (() => {
1842
+ // Info.weekdays returns Monday-first; reorder so Sunday is first to match the calendar grid.
1843
+ const mondayFirst = Info.weekdays('short');
1844
+ return [mondayFirst[6], ...mondayFirst.slice(0, 6)].map(d => d.slice(0, 1));
1845
+ })();
1700
1846
  this.disablePreviousButton = false;
1701
1847
  this.disableNextButton = false;
1702
1848
  // Trackby functions for ngFor loops. These reduce the number of DOM elements that need to be updated when the calendar is re-drawn.
@@ -1720,19 +1866,19 @@ class CalendarComponent {
1720
1866
  onNextClick() {
1721
1867
  // If we're in day view, we're incrementing by month
1722
1868
  // In month, quarter, and year views we're incrementing by year\
1723
- const unit = this.view.mode === 'day' ? 'month' : 'year';
1869
+ const unit = this.view.mode === 'day' ? 'months' : 'years';
1724
1870
  // In year view, we're moving by 16 years at a time. Otherwise we're just moving by 1 month/year.
1725
1871
  const count = this.view.mode === 'year' ? 16 : 1;
1726
- const goToDate = moment(this.view.date).add(count, unit).toDate();
1872
+ const goToDate = DateTime.fromJSDate(this.view.date).plus({ [unit]: count }).toJSDate();
1727
1873
  this.drawCalendar({ mode: this.view.mode, date: goToDate });
1728
1874
  }
1729
1875
  onPreviousClick() {
1730
1876
  // If we're in day view, we're incrementing by month
1731
1877
  // In month, quarter, and year views we're incrementing by year
1732
- const unit = this.view.mode === 'day' ? 'month' : 'year';
1878
+ const unit = this.view.mode === 'day' ? 'months' : 'years';
1733
1879
  // In year view, we're moving by 16 years at a time. Otherwise we're just moving by 1 month/year.
1734
1880
  const count = this.view.mode === 'year' ? 16 : 1;
1735
- const goToDate = moment(this.view.date).subtract(count, unit).toDate();
1881
+ const goToDate = DateTime.fromJSDate(this.view.date).minus({ [unit]: count }).toJSDate();
1736
1882
  this.drawCalendar({ mode: this.view.mode, date: goToDate });
1737
1883
  }
1738
1884
  /** Switches the calendar to month view. */
@@ -1799,13 +1945,14 @@ class CalendarComponent {
1799
1945
  this.view = view;
1800
1946
  this.viewChange.emit(view);
1801
1947
  }
1802
- this.month = moment(this.view.date).format('MMM');
1803
- this.year = moment(this.view.date).format('YYYY');
1948
+ const viewDt = DateTime.fromJSDate(this.view.date);
1949
+ this.month = viewDt.toFormat('MMM');
1950
+ this.year = viewDt.toFormat('yyyy');
1804
1951
  this.calendarItems = this.getCalendarItems(this.view);
1805
1952
  const startYear = this.calendarItems[0][0].date < this.minDate ? this.minDate : this.calendarItems[0][0].date;
1806
1953
  const lastItem = this.getLastItem();
1807
1954
  const endYear = lastItem.date > this.maxDate ? this.maxDate : lastItem.date;
1808
- this.yearRange = `${moment(startYear).format('YYYY')}&ndash;${moment(endYear).format('YYYY')}`;
1955
+ this.yearRange = `${DateTime.fromJSDate(startYear).toFormat('yyyy')}&ndash;${DateTime.fromJSDate(endYear).toFormat('yyyy')}`;
1809
1956
  this.updateNextPreviousStates();
1810
1957
  }
1811
1958
  getCalendarItems(view) {
@@ -1817,53 +1964,58 @@ class CalendarComponent {
1817
1964
  }
1818
1965
  }
1819
1966
  getDayViewItems(activeDate) {
1820
- // 6 rows of 7 days = 42 days
1821
- return range(0, 6).map(r => {
1822
- return range(0, 7).map(d => {
1823
- // Use the start of the week of the first day of the month.
1824
- // This pads out the first week with any days from the previous month.
1825
- const date = moment(activeDate).startOf('month').startOf('week').add(r * 7 + d, 'day');
1967
+ // 6 rows of 7 days = 42 days.
1968
+ // Use the start of the week of the first day of the month, anchored to Sunday to match the calendar grid.
1969
+ // Luxon's startOf('week') is Monday-based per ISO, so shift back to Sunday explicitly.
1970
+ const monthStart = DateTime.fromJSDate(activeDate).startOf('month');
1971
+ const sundayStart = monthStart.weekday === 7 ? monthStart : monthStart.startOf('week').minus({ days: 1 });
1972
+ return range(0, 6).map((r) => {
1973
+ return range(0, 7).map((d) => {
1974
+ const date = sundayStart.plus({ days: r * 7 + d });
1826
1975
  return {
1827
- date: date.toDate(),
1828
- label: date.format('D')
1976
+ date: date.toJSDate(),
1977
+ label: date.toFormat('d')
1829
1978
  };
1830
1979
  });
1831
1980
  });
1832
1981
  }
1833
1982
  getMonthViewItems(activeDate) {
1834
1983
  // 4 rows of 3 months = 12 months
1835
- return range(0, 4).map(r => {
1836
- return range(0, 3).map(m => {
1837
- const date = moment(activeDate).startOf('year').add(r * 3 + m, 'month');
1984
+ const yearStart = DateTime.fromJSDate(activeDate).startOf('year');
1985
+ return range(0, 4).map((r) => {
1986
+ return range(0, 3).map((m) => {
1987
+ const date = yearStart.plus({ months: r * 3 + m });
1838
1988
  return {
1839
- date: date.toDate(),
1840
- label: date.format('MMM')
1989
+ date: date.toJSDate(),
1990
+ label: date.toFormat('MMM')
1841
1991
  };
1842
1992
  });
1843
1993
  });
1844
1994
  }
1845
1995
  getQuarterViewItems(activeDate) {
1846
1996
  // 2 rows of 2 quarters = 4 quarters
1847
- return range(0, 2).map(r => {
1848
- return range(0, 2).map(q => {
1849
- const date = moment(activeDate).startOf('year').add(r * 2 + q, 'quarter');
1850
- const endDate = moment(date).endOf('quarter');
1997
+ const yearStart = DateTime.fromJSDate(activeDate).startOf('year');
1998
+ return range(0, 2).map((r) => {
1999
+ return range(0, 2).map((q) => {
2000
+ const date = yearStart.plus({ quarters: r * 2 + q });
2001
+ const endDate = date.endOf('quarter');
1851
2002
  return {
1852
- date: date.toDate(),
1853
- label: `${date.format('MMM')}&ndash;${endDate.format('MMM')}`
2003
+ date: date.toJSDate(),
2004
+ label: `${date.toFormat('MMM')}&ndash;${endDate.toFormat('MMM')}`
1854
2005
  };
1855
2006
  });
1856
2007
  });
1857
2008
  }
1858
2009
  getYearViewItems(activeDate) {
1859
2010
  // 4 rows of 4 years = 16 years
1860
- return range(0, 4).map(r => {
1861
- return range(0, 4).map(y => {
1862
- // Subtracting 8 years to put the current active year at the beginning of the 3rd row.
1863
- const date = moment(activeDate).startOf('year').subtract(8, 'years').add(r * 4 + y, 'year');
2011
+ // Subtracting 8 years to put the current active year at the beginning of the 3rd row.
2012
+ const yearStart = DateTime.fromJSDate(activeDate).startOf('year').minus({ years: 8 });
2013
+ return range(0, 4).map((r) => {
2014
+ return range(0, 4).map((y) => {
2015
+ const date = yearStart.plus({ years: r * 4 + y });
1864
2016
  return {
1865
- date: date.toDate(),
1866
- label: date.format('YYYY')
2017
+ date: date.toJSDate(),
2018
+ label: date.toFormat('yyyy')
1867
2019
  };
1868
2020
  });
1869
2021
  });
@@ -1873,24 +2025,20 @@ class CalendarComponent {
1873
2025
  // We don't want to disable the next/previous buttons if the min or max date is within the
1874
2026
  // next or previous view
1875
2027
  const unit = this.view.mode === 'day' ? 'month' : 'year';
2028
+ const pluralUnit = this.view.mode === 'day' ? 'months' : 'years';
2029
+ const viewDt = DateTime.fromJSDate(this.view.date);
1876
2030
  // When in year view, we need to subtract 9 years to determine if we're past the min date.
1877
2031
  // The year view is 16 years long, and the current date is the 9th year in the view.
1878
2032
  const subCount = this.view.mode === 'year' ? 9 : 1;
1879
- if (moment(this.view.date).subtract(subCount, unit).isBefore(this.minDate, unit)) {
1880
- this.disablePreviousButton = true;
1881
- }
1882
- else {
1883
- this.disablePreviousButton = false;
1884
- }
2033
+ const prev = viewDt.minus({ [pluralUnit]: subCount }).startOf(unit);
2034
+ const minDt = DateTime.fromJSDate(this.minDate).startOf(unit);
2035
+ this.disablePreviousButton = prev < minDt;
1885
2036
  // When in year view, we need to add 8 years to determine if we're past the max date.
1886
2037
  // The year view is 16 years long, and the current date is the 9th year in the view.
1887
2038
  const addCount = this.view.mode === 'year' ? 8 : 1;
1888
- if (moment(this.view.date).add(addCount, unit).isAfter(this.maxDate, unit)) {
1889
- this.disableNextButton = true;
1890
- }
1891
- else {
1892
- this.disableNextButton = false;
1893
- }
2039
+ const next = viewDt.plus({ [pluralUnit]: addCount }).startOf(unit);
2040
+ const maxDt = DateTime.fromJSDate(this.maxDate).startOf(unit);
2041
+ this.disableNextButton = next > maxDt;
1894
2042
  }
1895
2043
  getLastItem() {
1896
2044
  const lastRow = this.calendarItems[this.calendarItems.length - 1];
@@ -5268,7 +5416,7 @@ class DateInputComponent extends FormControlBase {
5268
5416
  * Date parsing formats for user-entered dates. Defaults to month-first formats, but is updated in onInit
5269
5417
  * to use the user's preferred date format.
5270
5418
  */
5271
- this.parseFormats = DateTimeHelper.getMomentParseFormats();
5419
+ this.parseFormats = DateTimeHelper.getLuxonParseFormats();
5272
5420
  }
5273
5421
  ngOnChanges(changes) {
5274
5422
  if (changes.selectionMode && !changes.selectionMode.isFirstChange()) {
@@ -5284,12 +5432,15 @@ class DateInputComponent extends FormControlBase {
5284
5432
  this.onFormModelStatusChanges();
5285
5433
  this.onFormModelValueChanges();
5286
5434
  this.onTextboxValueChanges();
5287
- // Sync the initial disabled status and value of the textbox
5435
+ // Sync the initial disabled status and value of the textbox.
5436
+ // Normalize the form model value so any ISO date strings the host bound to the model
5437
+ // become real Dates before they reach the calendar/stepper logic (see normalizeSelection).
5288
5438
  this.syncTextboxControlDisabledStatus(this.formModel.status);
5289
- const initialDisplayValue = this.selectionStrategy.formatSelection(this.formModel.value);
5439
+ const initialSelection = DateInput.normalizeSelection(this.formModel.value);
5440
+ const initialDisplayValue = this.selectionStrategy.formatSelection(initialSelection);
5290
5441
  this.textboxGroup.patchValue(initialDisplayValue, { emitEvent: false });
5291
5442
  // Update the calendar selection and view based on the initial form model value
5292
- this.calendarSelection = this.formModel.value;
5443
+ this.calendarSelection = initialSelection;
5293
5444
  this.updateCalendars(this.calendarSelection);
5294
5445
  this.updateSteppers(this.calendarSelection);
5295
5446
  }
@@ -5385,7 +5536,7 @@ class DateInputComponent extends FormControlBase {
5385
5536
  goToToday() {
5386
5537
  // If both calendars are visible, show today in the secondary calendar and the month before in the primary calendar.
5387
5538
  if (this.selectionStrategy.showSecondaryCalendar) {
5388
- this.primaryCalendarView = { mode: 'day', date: moment().subtract(1, 'month').toDate() };
5539
+ this.primaryCalendarView = { mode: 'day', date: DateTime.now().minus({ months: 1 }).toJSDate() };
5389
5540
  this.secondaryCalendarView = { mode: 'day', date: new Date() };
5390
5541
  // If we're in a single date mode, show today in the primary calendar.
5391
5542
  }
@@ -5434,11 +5585,14 @@ class DateInputComponent extends FormControlBase {
5434
5585
  // Update the calendar selection textbox value with a formatted date when the form model changes.
5435
5586
  // This is triggered by the user selecting a date from the calendar or when the textbox is blurred.
5436
5587
  this.formModel.valueChanges.pipe(takeUntil(this.componentDestroyed)).subscribe(value => {
5437
- const displayValue = this.selectionStrategy.formatSelection(value);
5588
+ // Normalize first: the host may set an ISO date string on the model (e.g. an API value),
5589
+ // and the calendar logic requires real Dates to avoid an Invalid Date crash.
5590
+ const selection = DateInput.normalizeSelection(value);
5591
+ const displayValue = this.selectionStrategy.formatSelection(selection);
5438
5592
  // Don't emit an event when setting the textbox value to avoid circular updates between the textbox and form model.
5439
5593
  this.textboxGroup.patchValue(displayValue, { emitEvent: false });
5440
- this.updateCalendars(value);
5441
- this.updateSteppers(value);
5594
+ this.updateCalendars(selection);
5595
+ this.updateSteppers(selection);
5442
5596
  });
5443
5597
  }
5444
5598
  onTextboxValueChanges() {
@@ -5518,7 +5672,7 @@ class DateInputComponent extends FormControlBase {
5518
5672
  const { preference } = await lastValueFrom(this.userPreferenceService.getPreferences());
5519
5673
  const dateFormat = preference?.dateFormat;
5520
5674
  this.placeholder = dateFormat ?? this.placeholder;
5521
- this.parseFormats = DateTimeHelper.getMomentParseFormats(dateFormat);
5675
+ this.parseFormats = DateTimeHelper.getLuxonParseFormats(dateFormat);
5522
5676
  }
5523
5677
  /** Focuses the date input. */
5524
5678
  focusInput() {
@@ -5538,10 +5692,10 @@ class DateInputComponent extends FormControlBase {
5538
5692
  // This would cause the dates in the other calendar to be unselectable if we were to
5539
5693
  // update the min/max dates in these cases.
5540
5694
  if (this.primaryCalendarView?.mode === 'day') {
5541
- this.secondaryCalendarMinDate = moment(this.primaryCalendarView?.date).add(1, 'month').startOf('month').toDate();
5695
+ this.secondaryCalendarMinDate = DateTime.fromJSDate(this.primaryCalendarView.date).plus({ months: 1 }).startOf('month').toJSDate();
5542
5696
  }
5543
5697
  if (this.secondaryCalendarView?.mode === 'day') {
5544
- this.primaryCalendarMaxDate = moment(this.secondaryCalendarView?.date).subtract(1, 'month').endOf('month').toDate();
5698
+ this.primaryCalendarMaxDate = DateTime.fromJSDate(this.secondaryCalendarView.date).minus({ months: 1 }).endOf('month').toJSDate();
5545
5699
  }
5546
5700
  }
5547
5701
  updateCalendars(selection) {
@@ -5557,8 +5711,10 @@ class DateInputComponent extends FormControlBase {
5557
5711
  // However, it's possible that the new date is after the secondary calendar's date. In this case,
5558
5712
  // We want to shift the secondary calendar so the calendars stay in order.
5559
5713
  if (shiftSecondary && this.secondaryCalendarView) {
5560
- if (moment(this.primaryCalendarView.date).isSameOrAfter(this.secondaryCalendarView.date, 'month')) {
5561
- let newSecondaryDate = moment(this.primaryCalendarView.date).add(1, 'month').toDate();
5714
+ const primaryDt = DateTime.fromJSDate(this.primaryCalendarView.date);
5715
+ const secondaryDt = DateTime.fromJSDate(this.secondaryCalendarView.date);
5716
+ if (primaryDt.startOf('month') >= secondaryDt.startOf('month')) {
5717
+ const newSecondaryDate = primaryDt.plus({ months: 1 }).toJSDate();
5562
5718
  this.secondaryCalendarView = { mode: this.selectionStrategy.selectionViewMode, date: newSecondaryDate };
5563
5719
  }
5564
5720
  }
@@ -5569,8 +5725,10 @@ class DateInputComponent extends FormControlBase {
5569
5725
  // However, it's possible that the new date is before the primary calendar's date. In this case,
5570
5726
  // We want to shift the primary calendar so the calendars stay in order.
5571
5727
  if (shiftPrimary && this.primaryCalendarView) {
5572
- if (moment(this.primaryCalendarView.date).isSameOrAfter(this.secondaryCalendarView.date, 'month')) {
5573
- let newPrimaryDate = moment(this.secondaryCalendarView.date).subtract(1, 'month').toDate();
5728
+ const primaryDt = DateTime.fromJSDate(this.primaryCalendarView.date);
5729
+ const secondaryDt = DateTime.fromJSDate(this.secondaryCalendarView.date);
5730
+ if (primaryDt.startOf('month') >= secondaryDt.startOf('month')) {
5731
+ const newPrimaryDate = secondaryDt.minus({ months: 1 }).toJSDate();
5574
5732
  this.primaryCalendarView = { mode: this.selectionStrategy.selectionViewMode, date: newPrimaryDate };
5575
5733
  }
5576
5734
  }
@@ -5620,10 +5778,12 @@ class DateInputComponent extends FormControlBase {
5620
5778
  // We're using the view mode as the granularity. We don't want to invalidate
5621
5779
  // the date in the case where you have a month/quarter/year selected and the min
5622
5780
  // or max date is somewhere within that month/quarter/year.
5623
- if (moment(value).isBefore(this.minDate, this.selectionStrategy.selectionViewMode)) {
5781
+ const unit = this.selectionStrategy.selectionViewMode;
5782
+ const valueDt = DateTime.fromJSDate(value).startOf(unit);
5783
+ if (valueDt < DateTime.fromJSDate(this.minDate).startOf(unit)) {
5624
5784
  return { minDate: { minValue: this.minDate } };
5625
5785
  }
5626
- else if (moment(value).isAfter(this.maxDate, this.selectionStrategy.selectionViewMode)) {
5786
+ else if (valueDt > DateTime.fromJSDate(this.maxDate).startOf(unit)) {
5627
5787
  return { maxDate: { maxValue: this.maxDate } };
5628
5788
  }
5629
5789
  }
@@ -11932,7 +12092,9 @@ class DateDisplayPipe2 {
11932
12092
  }
11933
12093
  async transform(date, options) {
11934
12094
  const { showEndOfTime, showTime, showSeconds, displayInUTC } = options || {};
11935
- if (!date || !moment(date).isValid() || (!showEndOfTime && DateTimeHelper.isEndOfTime(date))) {
12095
+ if (date == null)
12096
+ return '';
12097
+ if (!DateTimeHelper.toDateTime(date).isValid || (!showEndOfTime && DateTimeHelper.isEndOfTime(date))) {
11936
12098
  return '';
11937
12099
  }
11938
12100
  const { preference } = await lastValueFrom(this.userPrefs.getPreferences());
@@ -11944,8 +12106,7 @@ class DateDisplayPipe2 {
11944
12106
  }
11945
12107
  formatString += ` ${timeFormat}`;
11946
12108
  }
11947
- // Parse and format the date using moment. If displayInUTC is true, use moment.utc() to shift the date to UTC.
11948
- return (displayInUTC ? moment.utc(date) : moment(date)).format(formatString);
12109
+ return DateTimeHelper.formatDate(date, formatString, displayInUTC);
11949
12110
  }
11950
12111
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: DateDisplayPipe2, deps: [{ token: UserPreferenceService }], target: i0.ɵɵFactoryTarget.Pipe }); }
11951
12112
  static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.4", ngImport: i0, type: DateDisplayPipe2, isStandalone: false, name: "dateDisplay2" }); }
@@ -11958,6 +12119,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
11958
12119
  }]
11959
12120
  }], ctorParameters: () => [{ type: UserPreferenceService }] });
11960
12121
 
12122
+ /**
12123
+ * Same behavior as {@link DateDisplayPipe2}, but always appends the short named
12124
+ * timezone abbreviation (e.g. `EST`, `PDT`, `UTC`) to the output. Use this when
12125
+ * downstream consumers need to disambiguate the timezone of a rendered date.
12126
+ *
12127
+ * @example <span>{{ someDate | dateDisplay3 | async }}</span>
12128
+ */
12129
+ class DateDisplayPipe3 {
12130
+ constructor(userPrefs) {
12131
+ this.userPrefs = userPrefs;
12132
+ }
12133
+ async transform(date, options) {
12134
+ const { showEndOfTime, showTime, showSeconds, displayInUTC } = options || {};
12135
+ if (date == null)
12136
+ return '';
12137
+ if (!DateTimeHelper.toDateTime(date).isValid || (!showEndOfTime && DateTimeHelper.isEndOfTime(date))) {
12138
+ return '';
12139
+ }
12140
+ const { preference } = await lastValueFrom(this.userPrefs.getPreferences());
12141
+ let formatString = preference?.dateFormat || 'MM/DD/YYYY';
12142
+ if (showTime) {
12143
+ let timeFormat = preference.timeFormat;
12144
+ if (!showSeconds) {
12145
+ timeFormat = timeFormat.replace(':ss', '');
12146
+ }
12147
+ formatString += ` ${timeFormat}`;
12148
+ }
12149
+ // Luxon's `ZZZZ` renders the short named offset (e.g. `EST`, `EDT`, `UTC`).
12150
+ // The token passes through `momentToLuxonFormat` unchanged.
12151
+ formatString += ' ZZZZ';
12152
+ return DateTimeHelper.formatDate(date, formatString, !!displayInUTC);
12153
+ }
12154
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: DateDisplayPipe3, deps: [{ token: UserPreferenceService }], target: i0.ɵɵFactoryTarget.Pipe }); }
12155
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.4", ngImport: i0, type: DateDisplayPipe3, isStandalone: false, name: "dateDisplay3" }); }
12156
+ }
12157
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: DateDisplayPipe3, decorators: [{
12158
+ type: Pipe,
12159
+ args: [{
12160
+ name: 'dateDisplay3',
12161
+ standalone: false
12162
+ }]
12163
+ }], ctorParameters: () => [{ type: UserPreferenceService }] });
12164
+
11961
12165
  /**
11962
12166
  * Format a time to the user's preference for display
11963
12167
  */
@@ -11970,11 +12174,10 @@ class TimeDisplayPipe {
11970
12174
  * Format a time for display, accounting for user's display preferences.
11971
12175
  */
11972
12176
  transform(time, showSeconds) {
11973
- let display = '';
11974
12177
  let formatString = '';
11975
12178
  // use user-preferred formats
11976
12179
  this.userPreferenceService.getPreferences().subscribe(result => {
11977
- //if preferences exist then format time to users preference
12180
+ //if preferences exist then format time to users preference
11978
12181
  //otherwise use the last user preference
11979
12182
  if (result.preference) {
11980
12183
  formatString = result.preference.timeFormat;
@@ -11984,8 +12187,7 @@ class TimeDisplayPipe {
11984
12187
  this.lastFormatString = formatString;
11985
12188
  }
11986
12189
  });
11987
- display = moment(time).format(this.lastFormatString);
11988
- return display;
12190
+ return DateTimeHelper.formatDate(time ?? null, this.lastFormatString);
11989
12191
  }
11990
12192
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: TimeDisplayPipe, deps: [{ token: UserPreferenceService }], target: i0.ɵɵFactoryTarget.Pipe }); }
11991
12193
  static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.4", ngImport: i0, type: TimeDisplayPipe, isStandalone: false, name: "timeDisplay" }); }
@@ -12013,21 +12215,21 @@ class RelativeDatePipe {
12013
12215
  * If any of todayLabel, yesterdayLabel is supplied, use the supplied label.
12014
12216
  */
12015
12217
  transform(timeSelected, options) {
12016
- const selected = moment(timeSelected);
12017
- const today = moment();
12018
- const yesterday = moment().subtract(1, 'day');
12218
+ const selected = DateTime.fromISO(timeSelected);
12219
+ const today = DateTime.now();
12220
+ const yesterday = today.minus({ days: 1 });
12019
12221
  const timeDisplay = options?.dateOnly ? `` : ` ${this.timeDisplayPipe.transform(timeSelected)}`;
12020
12222
  let displayValue = null;
12021
12223
  let prefixValue = '';
12022
- if (selected.isSame(today, 'day') && !options?.showTimeForToday) {
12224
+ if (selected.hasSame(today, 'day') && !options?.showTimeForToday) {
12023
12225
  displayValue = this.translateService.instant(options?.todayLabel ? options?.todayLabel : 'today');
12024
12226
  prefixValue = options?.todayPrefix ?? '';
12025
12227
  }
12026
- else if (selected.isSame(today, 'day') && options?.showTimeForToday && !timeDisplay) {
12228
+ else if (selected.hasSame(today, 'day') && options?.showTimeForToday && !timeDisplay) {
12027
12229
  displayValue = this.timeDisplayPipe.transform(timeSelected);
12028
12230
  prefixValue = options?.todayTimePrefix ?? '';
12029
12231
  }
12030
- else if (selected.isSame(yesterday, 'day')) {
12232
+ else if (selected.hasSame(yesterday, 'day')) {
12031
12233
  displayValue = this.translateService.instant(options?.yesterdayLabel ? options?.yesterdayLabel : 'yesterday');
12032
12234
  prefixValue = options?.yesterdayPrefix ?? '';
12033
12235
  }
@@ -12665,6 +12867,7 @@ class ComponentsModule {
12665
12867
  DateInputComponent,
12666
12868
  KeyboardNavContainerDirective,
12667
12869
  DateDisplayPipe2,
12870
+ DateDisplayPipe3,
12668
12871
  MultiselectComponent], imports: [CommonModule,
12669
12872
  FormsModule,
12670
12873
  ReactiveFormsModule,
@@ -12737,7 +12940,8 @@ class ComponentsModule {
12737
12940
  CalendarComponent,
12738
12941
  DateInputComponent,
12739
12942
  KeyboardNavContainerDirective,
12740
- DateDisplayPipe2] }); }
12943
+ DateDisplayPipe2,
12944
+ DateDisplayPipe3] }); }
12741
12945
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ComponentsModule, providers: [
12742
12946
  FormGroupHelper,
12743
12947
  DialogService,
@@ -12747,7 +12951,8 @@ class ComponentsModule {
12747
12951
  RowCountPipe,
12748
12952
  RelativeDatePipe,
12749
12953
  HighlightTextPipe,
12750
- DateDisplayPipe2
12954
+ DateDisplayPipe2,
12955
+ DateDisplayPipe3
12751
12956
  ], imports: [CommonModule,
12752
12957
  FormsModule,
12753
12958
  ReactiveFormsModule,
@@ -12833,6 +13038,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
12833
13038
  DateInputComponent,
12834
13039
  KeyboardNavContainerDirective,
12835
13040
  DateDisplayPipe2,
13041
+ DateDisplayPipe3,
12836
13042
  MultiselectComponent
12837
13043
  ],
12838
13044
  imports: [
@@ -12854,7 +13060,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
12854
13060
  RowCountPipe,
12855
13061
  RelativeDatePipe,
12856
13062
  HighlightTextPipe,
12857
- DateDisplayPipe2
13063
+ DateDisplayPipe2,
13064
+ DateDisplayPipe3
12858
13065
  ],
12859
13066
  exports: [
12860
13067
  ButtonComponent,
@@ -12922,7 +13129,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
12922
13129
  CalendarComponent,
12923
13130
  DateInputComponent,
12924
13131
  KeyboardNavContainerDirective,
12925
- DateDisplayPipe2
13132
+ DateDisplayPipe2,
13133
+ DateDisplayPipe3
12926
13134
  ]
12927
13135
  }]
12928
13136
  }] });
@@ -13918,5 +14126,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
13918
14126
  * Generated bundle index. Do not edit.
13919
14127
  */
13920
14128
 
13921
- export { AppBarComponent, AvatarComponent, BannerComponent, ButtonComponent, CacheService, CalendarComponent, CheckboxComponent, ClickAreaForDirective, CollapsibleToggleComponent, ComboboxComponent, ComponentsModule, ConfirmComponent, ConfirmDialogContext, CopyButtonBaseTestInjectorFactory, CopyButtonDirective, CopyTableButtonDirective, CustomValidators, DateDisplayPipe, DateDisplayPipe2, DateInput, DateInputComponent, DateTimeHelper, DialogCloseDuration, DialogCloseEvent, DialogCloseLatestEvent, DialogComponent, DialogEvent, DialogGroupComponent, DialogOpenDuration, DialogOpenEndEvent, DialogOpenStartEvent, DialogResult, DialogService, DropdownComponent, ErrorService, FileTypeExtensions, FileUploadComponent, FormControlBase, FormControlComponent, FormControlLabelComponent, FormGroupComponent, FormGroupHelper, HelpPopoverComponent, HierarchyBase, HierarchyBaseTestInjectorFactory, HierarchyItem, HierarchyMocks, HierarchyTreeComponent, HighlightTextPipe, IfViewportWidthDirective, ItemDisplayComponent, ItemPickerComponent, ItemPickerSelectableContext, JsonDisplayComponent, JsonHelper, KeyboardNavContainerDirective, LinkButtonComponent, MenuComponent, MockActivatedRoute, MockDateDisplayPipe, MockDialog, MockDialogContent, MockTranslateService, MockTranslationHelperService, MultiselectComponent, NavGroup, NavItemActiveDirective, NumericboxComponent, Overlay, PageBaseComponent, PageBaseComponentTestHelper, PageBaseComponentTestInjectorFactory, PageInitResult, PageStatus, PageStatuses, PageTitleComponent, PageViewComponent, PanelCloseDuration, PanelOpenDuration, PopoverComponent, PopupContainerDirective, RadioButtonComponent, RadioButtonOption, RelativeDatePipe, ResizableBase, ResizableColumnComponent, ResizableComponent, RouterHelper, RowCountPipe, ScrollService, SearchableTableComponent, SelectComponent, SpinnerComponent, SplashComponent, SplashService, SpyFactory, TableComponent, TableLockedColumnComponent, TableMasterHeaderRowComponent, TableMasterRowComponent, TablePaginationComponent, TableSelectableRowComponent, TableSelectableRowContext, TabsComponent, Tag, TagsComponent, TelemetryService, TelemetryTrackerService, TextboxComponent, TimeDisplayPipe, ToastComponent, ToastEvent, ToastService, ToasterComponent, TooltipComponent, TooltipDirective, TooltipService, TreeComponent, UnicodeStrings, UserPreferenceService, ValidationMessageService, ViewOverlayComponent, WindowService, WizardBaseComponent, WizardButtonsComponent, WizardProgressComponent, canadianPostalCodeRegex, clickEvent, dateInputFormatRegex, domainPattern, findAllSpacesPattern, forEachFormControl, getApiError, getControlValue, getDecimalPattern, handleKeyNavigate, integerPattern, isApiError, menuAnimationSpeed, mockRouterFactory, mockRouterHelperFactory, numericboxValidation, orderByIgnoreCase, otherZipCodeRegex, phoneNumberValidationPattern, sortByIgnoreCase, textboxValidation, unitedStatesZipCodeRegex, urlValidationPattern, validateFormGroupValuesAreUnique };
14129
+ export { AppBarComponent, AvatarComponent, BannerComponent, ButtonComponent, CacheService, CalendarComponent, CheckboxComponent, ClickAreaForDirective, CollapsibleToggleComponent, ComboboxComponent, ComponentsModule, ConfirmComponent, ConfirmDialogContext, CopyButtonBaseTestInjectorFactory, CopyButtonDirective, CopyTableButtonDirective, CustomValidators, DateDisplayPipe, DateDisplayPipe2, DateDisplayPipe3, DateInput, DateInputComponent, DateTimeHelper, DialogCloseDuration, DialogCloseEvent, DialogCloseLatestEvent, DialogComponent, DialogEvent, DialogGroupComponent, DialogOpenDuration, DialogOpenEndEvent, DialogOpenStartEvent, DialogResult, DialogService, DropdownComponent, ErrorService, FileTypeExtensions, FileUploadComponent, FormControlBase, FormControlComponent, FormControlLabelComponent, FormGroupComponent, FormGroupHelper, HelpPopoverComponent, HierarchyBase, HierarchyBaseTestInjectorFactory, HierarchyItem, HierarchyMocks, HierarchyTreeComponent, HighlightTextPipe, IfViewportWidthDirective, ItemDisplayComponent, ItemPickerComponent, ItemPickerSelectableContext, JsonDisplayComponent, JsonHelper, KeyboardNavContainerDirective, LinkButtonComponent, MenuComponent, MockActivatedRoute, MockDateDisplayPipe, MockDialog, MockDialogContent, MockTranslateService, MockTranslationHelperService, MultiselectComponent, NavGroup, NavItemActiveDirective, NumericboxComponent, Overlay, PageBaseComponent, PageBaseComponentTestHelper, PageBaseComponentTestInjectorFactory, PageInitResult, PageStatus, PageStatuses, PageTitleComponent, PageViewComponent, PanelCloseDuration, PanelOpenDuration, PopoverComponent, PopupContainerDirective, RadioButtonComponent, RadioButtonOption, RelativeDatePipe, ResizableBase, ResizableColumnComponent, ResizableComponent, RouterHelper, RowCountPipe, ScrollService, SearchableTableComponent, SelectComponent, SpinnerComponent, SplashComponent, SplashService, SpyFactory, TableComponent, TableLockedColumnComponent, TableMasterHeaderRowComponent, TableMasterRowComponent, TablePaginationComponent, TableSelectableRowComponent, TableSelectableRowContext, TabsComponent, Tag, TagsComponent, TelemetryService, TelemetryTrackerService, TextboxComponent, TimeDisplayPipe, ToastComponent, ToastEvent, ToastService, ToasterComponent, TooltipComponent, TooltipDirective, TooltipService, TreeComponent, UnicodeStrings, UserPreferenceService, ValidationMessageService, ViewOverlayComponent, WindowService, WizardBaseComponent, WizardButtonsComponent, WizardProgressComponent, canadianPostalCodeRegex, clickEvent, dateInputFormatRegex, domainPattern, findAllSpacesPattern, forEachFormControl, getApiError, getControlValue, getDecimalPattern, handleKeyNavigate, integerPattern, isApiError, menuAnimationSpeed, mockRouterFactory, mockRouterHelperFactory, numericboxValidation, orderByIgnoreCase, otherZipCodeRegex, phoneNumberValidationPattern, sortByIgnoreCase, textboxValidation, unitedStatesZipCodeRegex, urlValidationPattern, validateFormGroupValuesAreUnique };
13922
14130
  //# sourceMappingURL=energycap-components.mjs.map