@energycap/components 0.46.15 → 0.46.16

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,22 @@ 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',
597
+ // A lone number is treated as a month; the day defaults to the 1st and the year to the current year
598
+ // (e.g. "11" -> Nov 1 of this year). Listed last so it never shadows the more specific formats above.
599
+ 'M'
598
600
  ]; }
599
601
  /**
600
602
  * User input formats that we will parse into a date
@@ -618,44 +620,58 @@ class DateTimeHelper {
618
620
  "yyyyDDmm"
619
621
  ]; }
620
622
  /**
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'
623
+ * Luxon parse formats for the day-first preference. Separator characters
624
+ * in the input are normalized to a hyphen before matching (see {@link parseWithFormats}).
625
+ */
626
+ static { this.luxonDateFirstParseFormats = [
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',
635
+ // A lone number is treated as a month; the day defaults to the 1st and the year to the current year
636
+ // (e.g. "11" -> Nov 1 of this year). Listed last so it never shadows the more specific formats above.
637
+ 'M'
635
638
  ]; }
636
639
  /**
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',
640
+ * Luxon parse formats for medium/long date strings (e.g. "Jan 5, 2024").
641
+ * Shared by date-first and month-first preferences because the month is
642
+ * not numeric, so the order is unambiguous.
643
+ */
644
+ static { this.luxonMedAndLongDateFormats = [
645
+ // Moment was forgiving with leading zeros (`DD` matched both `9` and `09`);
646
+ // Luxon's `dd` is strict, so single-digit `d` variants are listed explicitly.
647
+ 'd MMM',
648
+ 'dd MMM',
649
+ 'MMM d',
650
+ 'MMM dd',
651
+ 'd MMM yy',
652
+ 'dd MMM yy',
653
+ 'd MMM yyyy',
654
+ 'dd MMM yyyy',
655
+ 'd MMMM yyyy',
656
+ 'dd MMMM yyyy',
657
+ 'MMM d, yy',
658
+ 'MMM d, yyyy',
659
+ 'MMM dd, yyyy',
660
+ 'MMMM d, yyyy',
661
+ 'MMMM dd, yyyy',
662
+ // Space-separated month-day-year without a comma (e.g. "Apr 21 1998", "April 21 1998").
663
+ 'MMM d yy',
664
+ 'MMM dd yy',
665
+ 'MMM d yyyy',
666
+ 'MMM dd yyyy',
667
+ 'MMMM d yyyy',
668
+ 'MMMM dd yyyy',
669
+ 'MMM yy',
670
+ 'MMM yyyy',
671
+ 'MMMM yyyy',
672
+ 'MMM, yy',
673
+ 'MMM, yyyy',
674
+ 'MMMM, yyyy',
659
675
  ]; }
660
676
  /**
661
677
  * Format to display the time portion of a datetime
@@ -664,6 +680,103 @@ class DateTimeHelper {
664
680
  ///////////////////////////////////////////////////////
665
681
  // METHODS
666
682
  ///////////////////////////////////////////////////////
683
+ /**
684
+ * Convert any date-like value into a Luxon {@link DateTime}. Strings are parsed
685
+ * as ISO 8601; numbers are treated as epoch milliseconds. Returns an invalid
686
+ * DateTime when the input is null/undefined.
687
+ */
688
+ static toDateTime(value) {
689
+ if (value === null || value === undefined) {
690
+ return DateTime.invalid('null or undefined');
691
+ }
692
+ if (DateTime.isDateTime(value)) {
693
+ return value;
694
+ }
695
+ if (value instanceof Date) {
696
+ return DateTime.fromJSDate(value);
697
+ }
698
+ if (typeof value === 'number') {
699
+ return DateTime.fromMillis(value);
700
+ }
701
+ return DateTime.fromISO(value);
702
+ }
703
+ /**
704
+ * Translate a moment-style format string (e.g. `MM/DD/YYYY HH:mm:ss A`) into
705
+ * the Luxon equivalent (`MM/dd/yyyy HH:mm:ss a`). Use this when format strings
706
+ * originate from external sources (such as user preferences) that still use
707
+ * moment tokens.
708
+ *
709
+ * Only the tokens currently in use across this library are translated:
710
+ * `Y`→`y`, `D`→`d`, `A`→`a`. `M`, `H`, `h`, `m`, `s` are identical between
711
+ * the two libraries.
712
+ */
713
+ static momentToLuxonFormat(format) {
714
+ return format
715
+ .replace(/Y/g, 'y')
716
+ .replace(/D/g, 'd')
717
+ .replace(/A/g, 'a');
718
+ }
719
+ /**
720
+ * Format a date-like value using a moment-style format string. The format
721
+ * string is automatically translated to Luxon tokens, so callers can keep
722
+ * passing the same preference strings (e.g. `MM/DD/YYYY HH:mm:ss`).
723
+ *
724
+ * When `displayInUTC` is true, string inputs without zone info are parsed
725
+ * as UTC (matching `moment.utc(str)`), and all other inputs are shifted to
726
+ * the UTC zone for display. Returns an empty string for invalid input.
727
+ *
728
+ * Moment's lowercase `a` token produces lowercase `am`/`pm`, but Luxon's
729
+ * `a` is locale-aware and emits uppercase `AM`/`PM` in en-US. When the
730
+ * incoming format uses lowercase `a` (and not uppercase `A`), the meridiem
731
+ * in the output is lowercased to preserve the moment contract for user
732
+ * preferences like `h:mm:ss a` that are stored externally.
733
+ */
734
+ static formatDate(date, momentFormat, displayInUTC = false) {
735
+ let dt;
736
+ if (displayInUTC && typeof date === 'string') {
737
+ dt = DateTime.fromISO(date, { zone: 'utc' });
738
+ }
739
+ else {
740
+ dt = this.toDateTime(date);
741
+ if (displayInUTC) {
742
+ dt = dt.setZone('utc');
743
+ }
744
+ }
745
+ if (!dt.isValid)
746
+ return '';
747
+ let result = dt.toFormat(this.momentToLuxonFormat(momentFormat));
748
+ if (momentFormat.includes('a') && !momentFormat.includes('A')) {
749
+ result = result.replace(/AM|PM/g, m => m.toLowerCase());
750
+ }
751
+ return result;
752
+ }
753
+ /**
754
+ * Parse a string against a list of format patterns. Returns the first
755
+ * successful parse as a {@link DateTime} (which may be invalid if none match).
756
+ *
757
+ * Format strings may use either Luxon or moment tokens — each pattern is
758
+ * run through {@link momentToLuxonFormat} before matching, so callers can
759
+ * keep passing arrays like `['M/D/YYYY']` from previously moment-based code.
760
+ *
761
+ * Mirrors moment's behavior of ignoring separator characters by normalizing
762
+ * `/`, `.` and `-` in both input and patterns before matching.
763
+ */
764
+ static parseWithFormats(value, formats) {
765
+ const normalize = (s) => s.replace(/[\/.\-]/g, '-');
766
+ // Strip surrounding whitespace and separators from the input so a partial entry with a
767
+ // trailing (or leading) separator still matches — e.g. "11/" normalizes to "11", which
768
+ // parses via the month-only format to Nov 1 of the current year. Internal separators are
769
+ // preserved, so full dates like "Apr 21 1998" are unaffected.
770
+ const normalizedValue = normalize(value).replace(/^[\s-]+|[\s-]+$/g, '');
771
+ for (const format of formats) {
772
+ const luxonFormat = this.momentToLuxonFormat(format);
773
+ const dt = DateTime.fromFormat(normalizedValue, normalize(luxonFormat));
774
+ if (dt.isValid) {
775
+ return dt;
776
+ }
777
+ }
778
+ return DateTime.invalid('no matching format');
779
+ }
667
780
  /**
668
781
  * Return true if @see date is equal to the @see endOfTime
669
782
  */
@@ -675,7 +788,7 @@ class DateTimeHelper {
675
788
  * Compare two dates and return true if they represent the same year, month, and day (time is ignored).
676
789
  */
677
790
  static isSameDay(date1, date2) {
678
- return moment(date1).isSame(moment(date2), 'day');
791
+ return this.toDateTime(date1).hasSame(this.toDateTime(date2), 'day');
679
792
  }
680
793
  /**
681
794
  * Get the parse formats based on the given date format. If no date format is
@@ -694,19 +807,19 @@ class DateTimeHelper {
694
807
  return dateFormat ? map[dateFormat] : this.monthFirstParseFormats;
695
808
  }
696
809
  /**
697
- * Get the moment parse formats based on the given date format. If no date format is
810
+ * Get the Luxon parse formats based on the given date format. If no date format is
698
811
  * given, or we don't recognize the format, return the month first parse formats.
699
812
  */
700
- static getMomentParseFormats(dateFormat) {
813
+ static getLuxonParseFormats(dateFormat) {
701
814
  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]
815
+ 'MM/DD/YYYY': [...this.luxonMonthFirstParseFormats, ...this.luxonMedAndLongDateFormats],
816
+ 'MM-DD-YYYY': [...this.luxonMonthFirstParseFormats, ...this.luxonMedAndLongDateFormats],
817
+ 'DD/MM/YYYY': [...this.luxonDateFirstParseFormats, ...this.luxonMedAndLongDateFormats],
818
+ 'DD-MM-YYYY': [...this.luxonDateFirstParseFormats, ...this.luxonMedAndLongDateFormats],
819
+ 'YYYY/MM/DD': [...this.luxonMonthFirstParseFormats, ...this.luxonMedAndLongDateFormats],
820
+ 'YYYY-MM-DD': [...this.luxonMonthFirstParseFormats, ...this.luxonMedAndLongDateFormats]
708
821
  };
709
- return dateFormat ? map[dateFormat] : this.momentMonthFirstParseFormats;
822
+ return dateFormat ? map[dateFormat] : this.luxonMonthFirstParseFormats;
710
823
  }
711
824
  /**
712
825
  * Get relationship start date (favoring date in partialFormState if exists)
@@ -729,28 +842,23 @@ class DateTimeHelper {
729
842
  return null;
730
843
  }
731
844
  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
845
  // If a start date is not provide, we'll use our beginning of time instead
743
846
  startDate = startDate || this.beginningOfTime;
744
847
  // If an end date is not provided, we'll use our end of time instead
745
848
  endDate = endDate || this.endOfTime;
746
- return moment(date).isBetween(startDate, endDate, 'day', inclusivity);
849
+ const d = this.toDateTime(date).startOf('day');
850
+ const s = this.toDateTime(startDate).startOf('day');
851
+ const e = this.toDateTime(endDate).startOf('day');
852
+ const afterStart = includeStart ? d >= s : d > s;
853
+ const beforeEnd = includeEnd ? d <= e : d < e;
854
+ return afterStart && beforeEnd;
747
855
  }
748
856
  static stringToDate(dateString) {
749
857
  if (dateString === null || dateString === undefined) {
750
858
  return dateString;
751
859
  }
752
860
  else {
753
- return moment(dateString).toDate();
861
+ return DateTime.fromISO(dateString).toJSDate();
754
862
  }
755
863
  }
756
864
  }
@@ -764,15 +872,15 @@ class DateInputSelectionStrategyBase {
764
872
  if (!value) {
765
873
  return null;
766
874
  }
767
- const date = moment(value, parseFormats);
768
- if (date.isValid()) {
875
+ let date = DateTimeHelper.parseWithFormats(value, parseFormats);
876
+ if (date.isValid) {
769
877
  // 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
878
  // This prevents the calendars from showing unhelpful dates that are in the distant past before the user finishes typing in the date.
771
879
  // 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());
880
+ if (opts?.shiftToCurrentYearIfBelow && date < DateTime.fromJSDate(opts.shiftToCurrentYearIfBelow)) {
881
+ date = date.set({ year: DateTime.now().year });
774
882
  }
775
- return date.toDate();
883
+ return date.toJSDate();
776
884
  }
777
885
  return null;
778
886
  }
@@ -854,7 +962,7 @@ class DaySelectionStrategy extends DateInputSelectionStrategyBase {
854
962
  getNextSelection(selection) {
855
963
  // Add a single day to the selection
856
964
  if (DateInput.isSelectionSingleDate(selection)) {
857
- return moment(selection).add(1, 'day').toDate();
965
+ return DateTime.fromJSDate(selection).plus({ days: 1 }).toJSDate();
858
966
  }
859
967
  // This shouldn't happen based on current date input flows, but if it does just return the selection as-is.
860
968
  return selection;
@@ -862,7 +970,7 @@ class DaySelectionStrategy extends DateInputSelectionStrategyBase {
862
970
  getPreviousSelection(selection) {
863
971
  // Subtract a single day from the selection
864
972
  if (DateInput.isSelectionSingleDate(selection)) {
865
- return moment(selection).subtract(1, 'day').toDate();
973
+ return DateTime.fromJSDate(selection).minus({ days: 1 }).toJSDate();
866
974
  }
867
975
  // This shouldn't happen based on current date input flows, but if it does just return the selection as-is.
868
976
  return selection;
@@ -893,19 +1001,19 @@ class Last28DaysSelectionStrategy extends DateInputSelectionStrategyBase {
893
1001
  // Update the start and end dates to match a 28-day range, keying off the end date if parseFromEnd is true
894
1002
  // parseFromEnd is true when a date is being entered from the end date textbox
895
1003
  if (opts?.parseFromEnd) {
896
- start = moment(end).subtract(27, 'days').toDate();
1004
+ start = DateTime.fromJSDate(end).minus({ days: 27 }).toJSDate();
897
1005
  }
898
1006
  else {
899
- end = moment(start).add(27, 'days').toDate();
1007
+ end = DateTime.fromJSDate(start).plus({ days: 27 }).toJSDate();
900
1008
  }
901
1009
  }
902
1010
  // If the start date is not provided, we'll use the end date to calculate the start date
903
1011
  if (!start && end && !opts?.preventAutoComplete) {
904
- start = moment(end).subtract(27, 'days').toDate();
1012
+ start = DateTime.fromJSDate(end).minus({ days: 27 }).toJSDate();
905
1013
  }
906
1014
  // If the end date is not provided, we'll use the start date to calculate the end date
907
1015
  if (!end && start && !opts?.preventAutoComplete) {
908
- end = moment(start).add(27, 'days').toDate();
1016
+ end = DateTime.fromJSDate(start).plus({ days: 27 }).toJSDate();
909
1017
  }
910
1018
  if (!start && !end) {
911
1019
  return null;
@@ -916,7 +1024,7 @@ class Last28DaysSelectionStrategy extends DateInputSelectionStrategyBase {
916
1024
  }
917
1025
  getSelectionFromDate(date) {
918
1026
  return {
919
- start: moment(date).subtract(27, 'days').toDate(),
1027
+ start: DateTime.fromJSDate(date).minus({ days: 27 }).toJSDate(),
920
1028
  end: date
921
1029
  };
922
1030
  }
@@ -926,7 +1034,7 @@ class Last28DaysSelectionStrategy extends DateInputSelectionStrategyBase {
926
1034
  if (!previousSelection.end && previousSelection.start) {
927
1035
  return {
928
1036
  start: previousSelection.start,
929
- end: moment(previousSelection.start).add(27, 'days').toDate()
1037
+ end: DateTime.fromJSDate(previousSelection.start).plus({ days: 27 }).toJSDate()
930
1038
  };
931
1039
  // If the range only has an end date, make the 28-day range end on that date
932
1040
  }
@@ -943,9 +1051,10 @@ class Last28DaysSelectionStrategy extends DateInputSelectionStrategyBase {
943
1051
  getNextSelection(selection) {
944
1052
  // Move the selection to the next 28 days
945
1053
  if (DateInput.isSelectionRange(selection) && selection.end) {
1054
+ const end = DateTime.fromJSDate(selection.end);
946
1055
  return {
947
- start: moment(selection.end).add(1, 'day').toDate(),
948
- end: moment(selection.end).add(28, 'days').toDate()
1056
+ start: end.plus({ days: 1 }).toJSDate(),
1057
+ end: end.plus({ days: 28 }).toJSDate()
949
1058
  };
950
1059
  }
951
1060
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
@@ -955,9 +1064,10 @@ class Last28DaysSelectionStrategy extends DateInputSelectionStrategyBase {
955
1064
  getPreviousSelection(selection) {
956
1065
  // Move the selection to the previous 28 days
957
1066
  if (DateInput.isSelectionRange(selection) && selection.start) {
1067
+ const start = DateTime.fromJSDate(selection.start);
958
1068
  return {
959
- start: moment(selection.start).subtract(28, 'days').toDate(),
960
- end: moment(selection.start).subtract(1, 'day').toDate()
1069
+ start: start.minus({ days: 28 }).toJSDate(),
1070
+ end: start.minus({ days: 1 }).toJSDate()
961
1071
  };
962
1072
  }
963
1073
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
@@ -990,19 +1100,19 @@ class Last7DaysSelectionStrategy extends DateInputSelectionStrategyBase {
990
1100
  // Update the start and end dates to match a 7-day range, keying off the end date if parseFromEnd is true
991
1101
  // parseFromEnd is true when a date is being entered from the end date textbox
992
1102
  if (opts?.parseFromEnd) {
993
- start = moment(end).subtract(6, 'days').toDate();
1103
+ start = DateTime.fromJSDate(end).minus({ days: 6 }).toJSDate();
994
1104
  }
995
1105
  else {
996
- end = moment(start).add(6, 'days').toDate();
1106
+ end = DateTime.fromJSDate(start).plus({ days: 6 }).toJSDate();
997
1107
  }
998
1108
  }
999
1109
  // If the start date is not provided, we'll use the end date to calculate the start date
1000
1110
  if (!start && end && !opts?.preventAutoComplete) {
1001
- start = moment(end).subtract(6, 'days').toDate();
1111
+ start = DateTime.fromJSDate(end).minus({ days: 6 }).toJSDate();
1002
1112
  }
1003
1113
  // If the end date is not provided, we'll use the start date to calculate the end date
1004
1114
  if (!end && start && !opts?.preventAutoComplete) {
1005
- end = moment(start).add(6, 'days').toDate();
1115
+ end = DateTime.fromJSDate(start).plus({ days: 6 }).toJSDate();
1006
1116
  }
1007
1117
  if (!start && !end) {
1008
1118
  return null;
@@ -1014,7 +1124,7 @@ class Last7DaysSelectionStrategy extends DateInputSelectionStrategyBase {
1014
1124
  getSelectionFromDate(date) {
1015
1125
  // Base the selection on the given date, moving back 6 days
1016
1126
  return {
1017
- start: moment(date).subtract(6, 'days').toDate(),
1127
+ start: DateTime.fromJSDate(date).minus({ days: 6 }).toJSDate(),
1018
1128
  end: date
1019
1129
  };
1020
1130
  }
@@ -1024,7 +1134,7 @@ class Last7DaysSelectionStrategy extends DateInputSelectionStrategyBase {
1024
1134
  if (!previousSelection.end && previousSelection.start) {
1025
1135
  return {
1026
1136
  start: previousSelection.start,
1027
- end: moment(previousSelection.start).add(6, 'days').toDate()
1137
+ end: DateTime.fromJSDate(previousSelection.start).plus({ days: 6 }).toJSDate()
1028
1138
  };
1029
1139
  // Otherwise, create the new selection based on the end date backwards
1030
1140
  }
@@ -1041,9 +1151,10 @@ class Last7DaysSelectionStrategy extends DateInputSelectionStrategyBase {
1041
1151
  getNextSelection(selection) {
1042
1152
  // Shift the range forward by 7 days
1043
1153
  if (DateInput.isSelectionRange(selection) && selection.end) {
1154
+ const end = DateTime.fromJSDate(selection.end);
1044
1155
  return {
1045
- start: moment(selection.end).add(1, 'day').toDate(),
1046
- end: moment(selection.end).add(7, 'days').toDate()
1156
+ start: end.plus({ days: 1 }).toJSDate(),
1157
+ end: end.plus({ days: 7 }).toJSDate()
1047
1158
  };
1048
1159
  }
1049
1160
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
@@ -1053,9 +1164,10 @@ class Last7DaysSelectionStrategy extends DateInputSelectionStrategyBase {
1053
1164
  getPreviousSelection(selection) {
1054
1165
  // Shift the range back 7 days
1055
1166
  if (DateInput.isSelectionRange(selection) && selection.start) {
1167
+ const start = DateTime.fromJSDate(selection.start);
1056
1168
  return {
1057
- start: moment(selection.start).subtract(7, 'days').toDate(),
1058
- end: moment(selection.start).subtract(1, 'day').toDate()
1169
+ start: start.minus({ days: 7 }).toJSDate(),
1170
+ end: start.minus({ days: 1 }).toJSDate()
1059
1171
  };
1060
1172
  }
1061
1173
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
@@ -1083,7 +1195,7 @@ class MonthSelectionStrategy extends DateInputSelectionStrategyBase {
1083
1195
  // A single date selection shouldn't happen in this mode but it's simple enough to handle
1084
1196
  const date = DateInput.isSelectionRange(selection) ? selection.start : selection;
1085
1197
  return {
1086
- textbox: moment(date).format('MMM, YYYY'),
1198
+ textbox: date ? DateTime.fromJSDate(date).toFormat('MMM, yyyy') : null,
1087
1199
  textbox2: null
1088
1200
  };
1089
1201
  }
@@ -1096,9 +1208,10 @@ class MonthSelectionStrategy extends DateInputSelectionStrategyBase {
1096
1208
  return this.getSelectionFromDate(date);
1097
1209
  }
1098
1210
  getSelectionFromDate(date) {
1211
+ const dt = DateTime.fromJSDate(date);
1099
1212
  return {
1100
- start: moment(date).startOf('month').toDate(),
1101
- end: moment(date).endOf('month').toDate()
1213
+ start: dt.startOf('month').toJSDate(),
1214
+ end: dt.endOf('month').toJSDate()
1102
1215
  };
1103
1216
  }
1104
1217
  getNewSelectionFromExisting(previousSelection) {
@@ -1118,8 +1231,8 @@ class MonthSelectionStrategy extends DateInputSelectionStrategyBase {
1118
1231
  getNextSelection(selection) {
1119
1232
  // Move the selection to the next month
1120
1233
  if (DateInput.isSelectionRange(selection) && selection.start) {
1121
- const date = moment(selection.start).add(1, 'month');
1122
- return this.getSelectionFromDate(date.toDate());
1234
+ const date = DateTime.fromJSDate(selection.start).plus({ months: 1 });
1235
+ return this.getSelectionFromDate(date.toJSDate());
1123
1236
  }
1124
1237
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
1125
1238
  // If it does, just return the selection as-is.
@@ -1128,8 +1241,8 @@ class MonthSelectionStrategy extends DateInputSelectionStrategyBase {
1128
1241
  getPreviousSelection(selection) {
1129
1242
  // Move the selection to the previous month
1130
1243
  if (DateInput.isSelectionRange(selection) && selection.start) {
1131
- const date = moment(selection.start).subtract(1, 'month');
1132
- return this.getSelectionFromDate(date.toDate());
1244
+ const date = DateTime.fromJSDate(selection.start).minus({ months: 1 });
1245
+ return this.getSelectionFromDate(date.toJSDate());
1133
1246
  }
1134
1247
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
1135
1248
  // If it does, just return the selection as-is.
@@ -1147,14 +1260,21 @@ class QuarterSelectionStrategy extends DateInputSelectionStrategyBase {
1147
1260
  this.validViewModes = ['quarter', 'year'];
1148
1261
  this.showSecondaryTextbox = false;
1149
1262
  this.showSecondaryCalendar = false;
1263
+ /**
1264
+ * Format of the quarter range shown in the textbox (see {@link formatSelection}), e.g. "Jan–Mar, 2023".
1265
+ * Included in parsing so the displayed value round-trips (Luxon parsing is strict and would otherwise
1266
+ * reject it, clearing the selection when the textbox is blurred). Both month tokens land in the same
1267
+ * quarter, so parsing keys off whichever the parser resolves.
1268
+ */
1269
+ this.rangeParseFormats = ['MMM–MMM, yyyy', 'MMM–MMM, yy'];
1150
1270
  }
1151
1271
  formatSelection(selection) {
1152
1272
  // Format the the quarter range as "MMM–MMM, YYYY"
1153
1273
  // We only have one textbox in this mode
1154
1274
  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');
1275
+ const start = selection.start ? DateTime.fromJSDate(selection.start).toFormat('MMM') : '';
1276
+ const end = selection.end ? DateTime.fromJSDate(selection.end).toFormat('MMM') : '';
1277
+ const year = selection.end ? DateTime.fromJSDate(selection.end).toFormat('yyyy') : '';
1158
1278
  return {
1159
1279
  textbox: `${start}–${end}, ${year}`,
1160
1280
  textbox2: null
@@ -1164,17 +1284,19 @@ class QuarterSelectionStrategy extends DateInputSelectionStrategyBase {
1164
1284
  return { textbox: null, textbox2: null };
1165
1285
  }
1166
1286
  parseTextboxValues(value, parseFormats, opts) {
1167
- // Only one textbox is used in this mode
1168
- const date = this.parseString(value.textbox, parseFormats, opts);
1287
+ // Only one textbox is used in this mode. Try the quarter-range display format first, then fall back
1288
+ // to the standard single-date formats a user might type (e.g. "Feb 2023").
1289
+ const date = this.parseString(value.textbox, [...this.rangeParseFormats, ...parseFormats], opts);
1169
1290
  if (!date) {
1170
1291
  return null;
1171
1292
  }
1172
1293
  return this.getSelectionFromDate(date);
1173
1294
  }
1174
1295
  getSelectionFromDate(date) {
1296
+ const dt = DateTime.fromJSDate(date);
1175
1297
  return {
1176
- start: moment(date).startOf('quarter').toDate(),
1177
- end: moment(date).endOf('quarter').toDate()
1298
+ start: dt.startOf('quarter').toJSDate(),
1299
+ end: dt.endOf('quarter').toJSDate()
1178
1300
  };
1179
1301
  }
1180
1302
  getNewSelectionFromExisting(previousSelection) {
@@ -1194,8 +1316,8 @@ class QuarterSelectionStrategy extends DateInputSelectionStrategyBase {
1194
1316
  getNextSelection(selection) {
1195
1317
  // Move the selection to the next quarter
1196
1318
  if (DateInput.isSelectionRange(selection) && selection.start) {
1197
- const date = moment(selection.start).add(1, 'quarter');
1198
- return this.getSelectionFromDate(date.toDate());
1319
+ const date = DateTime.fromJSDate(selection.start).plus({ quarters: 1 });
1320
+ return this.getSelectionFromDate(date.toJSDate());
1199
1321
  }
1200
1322
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
1201
1323
  // If it does, just return the selection as-is.
@@ -1204,8 +1326,8 @@ class QuarterSelectionStrategy extends DateInputSelectionStrategyBase {
1204
1326
  getPreviousSelection(selection) {
1205
1327
  // Move the selection to the previous quarter
1206
1328
  if (DateInput.isSelectionRange(selection) && selection.start) {
1207
- const date = moment(selection.start).subtract(1, 'quarter');
1208
- return this.getSelectionFromDate(date.toDate());
1329
+ const date = DateTime.fromJSDate(selection.start).minus({ quarters: 1 });
1330
+ return this.getSelectionFromDate(date.toJSDate());
1209
1331
  }
1210
1332
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
1211
1333
  // If it does, just return the selection as-is.
@@ -1228,13 +1350,13 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1228
1350
  if (!selection) {
1229
1351
  return { textbox: null, textbox2: null };
1230
1352
  }
1231
- // We use the 'll' moment medium format here to make sure the formatted date is formatted according to the user's locale
1353
+ // We use Luxon's DATE_MED preset here (equivalent to moment's 'll') so the formatted date follows the user's locale
1232
1354
  if (DateInput.isSelectionSingleDate(selection)) {
1233
- return { textbox: moment(selection).format('ll'), textbox2: null };
1355
+ return { textbox: DateTime.fromJSDate(selection).toLocaleString(DateTime.DATE_MED), textbox2: null };
1234
1356
  }
1235
1357
  else {
1236
- const start = selection.start ? moment(selection.start).format('ll') : null;
1237
- const end = selection.end ? moment(selection.end).format('ll') : null;
1358
+ const start = selection.start ? DateTime.fromJSDate(selection.start).toLocaleString(DateTime.DATE_MED) : null;
1359
+ const end = selection.end ? DateTime.fromJSDate(selection.end).toLocaleString(DateTime.DATE_MED) : null;
1238
1360
  return { textbox: start, textbox2: end };
1239
1361
  }
1240
1362
  }
@@ -1245,7 +1367,7 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1245
1367
  // 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
1368
  // 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
1369
  // begin/end date they just entered.
1248
- if (start && end && moment(start).isAfter(end)) {
1370
+ if (start && end && DateTime.fromJSDate(start) > DateTime.fromJSDate(end)) {
1249
1371
  if (opts?.parseFromEnd) {
1250
1372
  start = null;
1251
1373
  }
@@ -1263,18 +1385,19 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1263
1385
  getSelectionFromDate(date, existingSelection) {
1264
1386
  if (DateInput.isSelectionRange(existingSelection)) {
1265
1387
  const { start, end } = existingSelection;
1388
+ const dDt = DateTime.fromJSDate(date);
1266
1389
  if (start && !end) {
1267
1390
  // 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 };
1391
+ return dDt < DateTime.fromJSDate(start) ? { start: date, end: start } : { start, end: date };
1269
1392
  }
1270
1393
  if (end && !start) {
1271
1394
  // 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 };
1395
+ return dDt < DateTime.fromJSDate(end) ? { start: date, end } : { start: end, end: date };
1273
1396
  }
1274
1397
  }
1275
1398
  else if (DateInput.isSelectionSingleDate(existingSelection)) {
1276
1399
  // 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 };
1400
+ return DateTime.fromJSDate(date) < DateTime.fromJSDate(existingSelection) ? { start: date, end: existingSelection } : { start: existingSelection, end: date };
1278
1401
  }
1279
1402
  // If no selection exists or we already have a complete selection, start a new one with the selected date as the start date.
1280
1403
  return { start: date, end: null };
@@ -1295,9 +1418,9 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1295
1418
  // If we have a valid range, we'll shift the range to the date provided, using it as the end date
1296
1419
  // The new selection will have the same length as the previous.
1297
1420
  if (DateInput.isSelectionRange(existingSelection) && existingSelection.start && existingSelection.end) {
1298
- const length = moment(existingSelection.end).diff(existingSelection.start, 'days');
1421
+ const length = Math.floor(DateTime.fromJSDate(existingSelection.end).diff(DateTime.fromJSDate(existingSelection.start), 'days').days);
1299
1422
  return {
1300
- start: moment(date).subtract(length, 'days').toDate(),
1423
+ start: DateTime.fromJSDate(date).minus({ days: length }).toJSDate(),
1301
1424
  end: date
1302
1425
  };
1303
1426
  }
@@ -1309,10 +1432,11 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1309
1432
  // We can only determine the next selection if we have a valid range
1310
1433
  if (DateInput.isSelectionRange(selection) && selection.start && selection.end) {
1311
1434
  // 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');
1435
+ const end = DateTime.fromJSDate(selection.end);
1436
+ const length = Math.floor(end.diff(DateTime.fromJSDate(selection.start), 'days').days);
1313
1437
  return {
1314
- start: moment(selection.end).add(1, 'days').toDate(),
1315
- end: moment(selection.end).add(1 + length, 'days').toDate()
1438
+ start: end.plus({ days: 1 }).toJSDate(),
1439
+ end: end.plus({ days: 1 + length }).toJSDate()
1316
1440
  };
1317
1441
  }
1318
1442
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
@@ -1323,10 +1447,11 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1323
1447
  // We can only determine the previous selection if we have a valid range
1324
1448
  if (DateInput.isSelectionRange(selection) && selection.start && selection.end) {
1325
1449
  // 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');
1450
+ const start = DateTime.fromJSDate(selection.start);
1451
+ const length = Math.floor(DateTime.fromJSDate(selection.end).diff(start, 'days').days);
1327
1452
  return {
1328
- start: moment(selection.start).subtract(1 + length, 'days').toDate(),
1329
- end: moment(selection.start).subtract(1, 'days').toDate()
1453
+ start: start.minus({ days: 1 + length }).toJSDate(),
1454
+ end: start.minus({ days: 1 }).toJSDate()
1330
1455
  };
1331
1456
  }
1332
1457
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
@@ -1336,31 +1461,33 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1336
1461
  getPrimaryCalendarView(selection, minDate, maxDate, currentView) {
1337
1462
  let viewDate;
1338
1463
  if (DateInput.isSelectionRange(selection)) {
1339
- let start = selection.start ? moment(selection.start) : null;
1340
- let end = selection.end ? moment(selection.end) : null;
1464
+ const start = selection.start ? DateTime.fromJSDate(selection.start) : null;
1465
+ const end = selection.end ? DateTime.fromJSDate(selection.end) : null;
1466
+ const minDt = DateTime.fromJSDate(minDate);
1467
+ const maxDt = DateTime.fromJSDate(maxDate);
1341
1468
  // Since we're in range mode, the primary calendar is mostly concerned with the start date
1342
1469
  if (start) {
1343
1470
  // 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')) {
1471
+ if (start.startOf('month') < minDt.startOf('month')) {
1345
1472
  viewDate = minDate;
1346
1473
  // If the start is after the max date, shift the view to the max date.
1347
1474
  }
1348
- else if (start.isSameOrAfter(maxDate, 'month')) {
1475
+ else if (start.startOf('month') >= maxDt.startOf('month')) {
1349
1476
  // the secondary calendar will show the max date month.
1350
1477
  // The primary calendar will show the month before the max date.
1351
- viewDate = moment(maxDate).subtract(1, 'month').toDate();
1478
+ viewDate = maxDt.minus({ months: 1 }).toJSDate();
1352
1479
  }
1353
1480
  else {
1354
- viewDate = start.toDate();
1481
+ viewDate = start.toJSDate();
1355
1482
  }
1356
1483
  }
1357
- else if (end && end.isSameOrBefore(minDate, 'month')) {
1484
+ else if (end && end.startOf('month') <= minDt.startOf('month')) {
1358
1485
  // 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
1486
  viewDate = minDate;
1360
1487
  }
1361
- else if (end && end.isSameOrBefore(new Date(), 'month')) {
1488
+ else if (end && end.startOf('month') <= DateTime.now().startOf('month')) {
1362
1489
  // 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();
1490
+ viewDate = end.minus({ months: 1 }).toJSDate();
1364
1491
  }
1365
1492
  else {
1366
1493
  // Otherwise, show the current month.
@@ -1378,32 +1505,34 @@ class RangeSelectionStrategy extends DateInputSelectionStrategyBase {
1378
1505
  getSecondaryCalendarView(selection, minDate, maxDate, currentView) {
1379
1506
  let viewDate;
1380
1507
  if (DateInput.isSelectionRange(selection)) {
1381
- let start = selection.start ? moment(selection.start) : null;
1382
- let end = selection.end ? moment(selection.end) : null;
1508
+ const start = selection.start ? DateTime.fromJSDate(selection.start) : null;
1509
+ const end = selection.end ? DateTime.fromJSDate(selection.end) : null;
1510
+ const minDt = DateTime.fromJSDate(minDate);
1511
+ const maxDt = DateTime.fromJSDate(maxDate);
1383
1512
  // The secondary calendar is primarily concerned with the end date in the selection.
1384
1513
  if (end) {
1385
- if (end.isAfter(maxDate, 'month')) {
1514
+ if (end.startOf('month') > maxDt.startOf('month')) {
1386
1515
  // If the end date is after the max date, shift the view to the max date.
1387
1516
  viewDate = maxDate;
1388
1517
  }
1389
- else if (end.isSameOrBefore(minDate, 'month')) {
1518
+ else if (end.startOf('month') <= minDt.startOf('month')) {
1390
1519
  // If the end date is the same month or before the min date, shift the view to show
1391
1520
  // the month after the min date. The primary calendar will show the min date month.
1392
- viewDate = moment(minDate).add(1, 'month').toDate();
1521
+ viewDate = minDt.plus({ months: 1 }).toJSDate();
1393
1522
  }
1394
1523
  else {
1395
1524
  // Otherwise, show the end date month.
1396
- viewDate = end.toDate();
1525
+ viewDate = end.toJSDate();
1397
1526
  }
1398
1527
  }
1399
- else if (start && start.isSameOrAfter(maxDate, 'month')) {
1528
+ else if (start && start.startOf('month') >= maxDt.startOf('month')) {
1400
1529
  // If we don't have an end date, and the start date is after the max date, show the max date month
1401
1530
  viewDate = maxDate;
1402
1531
  }
1403
- else if (start && start.isSameOrAfter(new Date(), 'month')) {
1532
+ else if (start && start.startOf('month') >= DateTime.now().startOf('month')) {
1404
1533
  // If we don't have an end date, and the start date is on or after the current month,
1405
1534
  // show the month after the start date.
1406
- viewDate = moment(start).add(1, 'month').toDate();
1535
+ viewDate = start.plus({ months: 1 }).toJSDate();
1407
1536
  }
1408
1537
  else {
1409
1538
  // Otherwise, show the current month.
@@ -1438,12 +1567,12 @@ class YearSelectionStrategy extends DateInputSelectionStrategyBase {
1438
1567
  // Only one date is needed for formatting since the ranges span a single year.
1439
1568
  // There shouldn't be any case where there's only a single date but it's covered anyway.
1440
1569
  const date = DateInput.isSelectionRange(selection) ? selection.start : selection;
1441
- return { textbox: moment(date).format('YYYY'), textbox2: null };
1570
+ return { textbox: date ? DateTime.fromJSDate(date).toFormat('yyyy') : null, textbox2: null };
1442
1571
  }
1443
1572
  parseString(value, parseFormats, opts) {
1444
- // Including 'YY' and 'YYYY' as valid formats for year input. These aren't included in the default parse
1573
+ // Including 'yy' and 'yyyy' as valid formats for year input. These aren't included in the default parse
1445
1574
  // formats because they cause issues with the default date parsing.
1446
- return super.parseString(value, ['YY', 'YYYY', ...parseFormats], opts);
1575
+ return super.parseString(value, ['yy', 'yyyy', ...parseFormats], opts);
1447
1576
  }
1448
1577
  parseTextboxValues(value, parseFormats, opts) {
1449
1578
  // Only a single textbox is used in year mode
@@ -1454,9 +1583,10 @@ class YearSelectionStrategy extends DateInputSelectionStrategyBase {
1454
1583
  return this.getSelectionFromDate(date);
1455
1584
  }
1456
1585
  getSelectionFromDate(date) {
1586
+ const dt = DateTime.fromJSDate(date);
1457
1587
  return {
1458
- start: moment(date).startOf('year').toDate(),
1459
- end: moment(date).endOf('year').toDate()
1588
+ start: dt.startOf('year').toJSDate(),
1589
+ end: dt.endOf('year').toJSDate()
1460
1590
  };
1461
1591
  }
1462
1592
  getNewSelectionFromExisting(previousSelection) {
@@ -1479,8 +1609,8 @@ class YearSelectionStrategy extends DateInputSelectionStrategyBase {
1479
1609
  getNextSelection(selection) {
1480
1610
  // Move the selection to the next year
1481
1611
  if (DateInput.isSelectionRange(selection) && selection.start) {
1482
- const date = moment(selection.start).add(1, 'year');
1483
- return this.getSelectionFromDate(date.toDate());
1612
+ const date = DateTime.fromJSDate(selection.start).plus({ years: 1 });
1613
+ return this.getSelectionFromDate(date.toJSDate());
1484
1614
  }
1485
1615
  // This shouldn't happen since the next stepper is disabled if the selection only has one date.
1486
1616
  // If it does, just return the selection as-is.
@@ -1489,8 +1619,8 @@ class YearSelectionStrategy extends DateInputSelectionStrategyBase {
1489
1619
  getPreviousSelection(selection) {
1490
1620
  // Move the selection to the previous year
1491
1621
  if (DateInput.isSelectionRange(selection) && selection.start) {
1492
- const date = moment(selection.start).subtract(1, 'year');
1493
- return this.getSelectionFromDate(date.toDate());
1622
+ const date = DateTime.fromJSDate(selection.start).minus({ years: 1 });
1623
+ return this.getSelectionFromDate(date.toJSDate());
1494
1624
  }
1495
1625
  // This shouldn't happen since the previous stepper is disabled if the selection only has one date.
1496
1626
  // If it does, just return the selection as-is.
@@ -1529,10 +1659,10 @@ class DateDisplayPipe {
1529
1659
  transform(date, showEndOfTime, showTime, showSeconds) {
1530
1660
  let display = '';
1531
1661
  let formatString = '';
1532
- if (date && moment(date).isValid() && (showEndOfTime || !DateTimeHelper.isEndOfTime(date))) {
1662
+ if (date != null && DateTimeHelper.toDateTime(date).isValid && (showEndOfTime || !DateTimeHelper.isEndOfTime(date))) {
1533
1663
  // use user-preferred formats
1534
1664
  this.userPreferenceService.getPreferences().subscribe(result => {
1535
- //if preferences exist then format date to users preference
1665
+ //if preferences exist then format date to users preference
1536
1666
  //otherwise use the last user preference
1537
1667
  if (result.preference) {
1538
1668
  formatString = result.preference.dateFormat;
@@ -1546,7 +1676,7 @@ class DateDisplayPipe {
1546
1676
  this.lastFormatString = formatString;
1547
1677
  }
1548
1678
  });
1549
- display = moment(date).format(this.lastFormatString);
1679
+ display = DateTimeHelper.formatDate(date, this.lastFormatString);
1550
1680
  }
1551
1681
  return display;
1552
1682
  }
@@ -1571,6 +1701,32 @@ var DateInput;
1571
1701
  return !!selection && !isSelectionSingleDate(selection);
1572
1702
  }
1573
1703
  DateInput.isSelectionRange = isSelectionRange;
1704
+ /**
1705
+ * Coerces a selection into one backed by real {@link Date} objects.
1706
+ *
1707
+ * The form model is typed as `Date | Range`, but hosts commonly bind ISO date strings
1708
+ * (e.g. values straight off an API response). The display path tolerates strings via
1709
+ * Luxon parsing, but the calendar view builders pass the value to `DateTime.fromJSDate`,
1710
+ * which only accepts a `Date` — a string yields an Invalid Date and crashes the calendar
1711
+ * (`RangeError: Invalid time value`). Normalizing at the boundary lets all downstream
1712
+ * calendar/stepper logic rely on actual Dates. Invalid/unparseable values become null.
1713
+ */
1714
+ function normalizeSelection(selection) {
1715
+ if (selection === null || selection === undefined) {
1716
+ return null;
1717
+ }
1718
+ if (isSelectionSingleDate(selection)) {
1719
+ const dt = DateTimeHelper.toDateTime(selection);
1720
+ return dt.isValid ? dt.toJSDate() : null;
1721
+ }
1722
+ const start = selection.start ? DateTimeHelper.toDateTime(selection.start) : null;
1723
+ const end = selection.end ? DateTimeHelper.toDateTime(selection.end) : null;
1724
+ return {
1725
+ start: start?.isValid ? start.toJSDate() : null,
1726
+ end: end?.isValid ? end.toJSDate() : null
1727
+ };
1728
+ }
1729
+ DateInput.normalizeSelection = normalizeSelection;
1574
1730
  class SelectionStrategies {
1575
1731
  constructor(dateDisplayPipe) {
1576
1732
  this.dateDisplayPipe = dateDisplayPipe;
@@ -1613,9 +1769,14 @@ class CalendarItemComponent {
1613
1769
  this.today = false;
1614
1770
  this.outsideActiveMonth = false;
1615
1771
  }
1772
+ else if (this.item?.date) {
1773
+ const itemDt = DateTime.fromJSDate(this.item.date);
1774
+ this.today = itemDt.hasSame(DateTime.now(), 'day');
1775
+ this.outsideActiveMonth = !itemDt.hasSame(DateTime.fromJSDate(this.view.date), 'month');
1776
+ }
1616
1777
  else {
1617
- this.today = moment(this.item?.date).isSame(moment(), 'day');
1618
- this.outsideActiveMonth = !moment(this.item?.date).isSame(this.view.date, 'month');
1778
+ this.today = false;
1779
+ this.outsideActiveMonth = false;
1619
1780
  }
1620
1781
  if (this.item?.date) {
1621
1782
  this.hidden = this.isHidden(this.item.date, this.minDate, this.maxDate, this.view.mode);
@@ -1629,24 +1790,30 @@ class CalendarItemComponent {
1629
1790
  if (!date || !selection) {
1630
1791
  return false;
1631
1792
  }
1793
+ const unit = view ?? 'day';
1794
+ const dt = DateTime.fromJSDate(date);
1632
1795
  if (DateInput.isSelectionSingleDate(selection)) {
1633
- return moment(date).isSame(selection, view);
1796
+ return dt.hasSame(DateTime.fromJSDate(selection), unit);
1634
1797
  }
1635
1798
  else {
1636
1799
  if (selection.start && !selection.end) {
1637
- return moment(date).isSame(selection.start, view);
1800
+ return dt.hasSame(DateTime.fromJSDate(selection.start), unit);
1638
1801
  }
1639
1802
  else if (!selection.start && selection.end) {
1640
- return moment(date).isSame(selection.end, view);
1803
+ return dt.hasSame(DateTime.fromJSDate(selection.end), unit);
1641
1804
  }
1642
1805
  else if (selection.start && selection.end) {
1643
- return moment(date).isBetween(selection.start, selection.end, view, '[]');
1806
+ const d = dt.startOf(unit);
1807
+ const s = DateTime.fromJSDate(selection.start).startOf(unit);
1808
+ const e = DateTime.fromJSDate(selection.end).startOf(unit);
1809
+ return d >= s && d <= e;
1644
1810
  }
1645
1811
  }
1646
1812
  return false;
1647
1813
  }
1648
1814
  isHidden(date, minDate, maxDate, view) {
1649
- return moment(date).isBefore(minDate, view) || moment(date).isAfter(maxDate, view);
1815
+ const d = DateTime.fromJSDate(date).startOf(view);
1816
+ return d < DateTime.fromJSDate(minDate).startOf(view) || d > DateTime.fromJSDate(maxDate).startOf(view);
1650
1817
  }
1651
1818
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CalendarItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1652
1819
  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 +1862,12 @@ class CalendarComponent {
1695
1862
  this.viewChange = new EventEmitter();
1696
1863
  /** Array of calendar item rows to display in the calendar. */
1697
1864
  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));
1865
+ /** Weekday labels to show at the top of the calendar in day view. Starts on Sunday to match the calendar grid layout. */
1866
+ this.weekDays = (() => {
1867
+ // Info.weekdays returns Monday-first; reorder so Sunday is first to match the calendar grid.
1868
+ const mondayFirst = Info.weekdays('short');
1869
+ return [mondayFirst[6], ...mondayFirst.slice(0, 6)].map(d => d.slice(0, 1));
1870
+ })();
1700
1871
  this.disablePreviousButton = false;
1701
1872
  this.disableNextButton = false;
1702
1873
  // 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 +1891,19 @@ class CalendarComponent {
1720
1891
  onNextClick() {
1721
1892
  // If we're in day view, we're incrementing by month
1722
1893
  // In month, quarter, and year views we're incrementing by year\
1723
- const unit = this.view.mode === 'day' ? 'month' : 'year';
1894
+ const unit = this.view.mode === 'day' ? 'months' : 'years';
1724
1895
  // In year view, we're moving by 16 years at a time. Otherwise we're just moving by 1 month/year.
1725
1896
  const count = this.view.mode === 'year' ? 16 : 1;
1726
- const goToDate = moment(this.view.date).add(count, unit).toDate();
1897
+ const goToDate = DateTime.fromJSDate(this.view.date).plus({ [unit]: count }).toJSDate();
1727
1898
  this.drawCalendar({ mode: this.view.mode, date: goToDate });
1728
1899
  }
1729
1900
  onPreviousClick() {
1730
1901
  // If we're in day view, we're incrementing by month
1731
1902
  // In month, quarter, and year views we're incrementing by year
1732
- const unit = this.view.mode === 'day' ? 'month' : 'year';
1903
+ const unit = this.view.mode === 'day' ? 'months' : 'years';
1733
1904
  // In year view, we're moving by 16 years at a time. Otherwise we're just moving by 1 month/year.
1734
1905
  const count = this.view.mode === 'year' ? 16 : 1;
1735
- const goToDate = moment(this.view.date).subtract(count, unit).toDate();
1906
+ const goToDate = DateTime.fromJSDate(this.view.date).minus({ [unit]: count }).toJSDate();
1736
1907
  this.drawCalendar({ mode: this.view.mode, date: goToDate });
1737
1908
  }
1738
1909
  /** Switches the calendar to month view. */
@@ -1799,13 +1970,14 @@ class CalendarComponent {
1799
1970
  this.view = view;
1800
1971
  this.viewChange.emit(view);
1801
1972
  }
1802
- this.month = moment(this.view.date).format('MMM');
1803
- this.year = moment(this.view.date).format('YYYY');
1973
+ const viewDt = DateTime.fromJSDate(this.view.date);
1974
+ this.month = viewDt.toFormat('MMM');
1975
+ this.year = viewDt.toFormat('yyyy');
1804
1976
  this.calendarItems = this.getCalendarItems(this.view);
1805
1977
  const startYear = this.calendarItems[0][0].date < this.minDate ? this.minDate : this.calendarItems[0][0].date;
1806
1978
  const lastItem = this.getLastItem();
1807
1979
  const endYear = lastItem.date > this.maxDate ? this.maxDate : lastItem.date;
1808
- this.yearRange = `${moment(startYear).format('YYYY')}&ndash;${moment(endYear).format('YYYY')}`;
1980
+ this.yearRange = `${DateTime.fromJSDate(startYear).toFormat('yyyy')}&ndash;${DateTime.fromJSDate(endYear).toFormat('yyyy')}`;
1809
1981
  this.updateNextPreviousStates();
1810
1982
  }
1811
1983
  getCalendarItems(view) {
@@ -1817,53 +1989,58 @@ class CalendarComponent {
1817
1989
  }
1818
1990
  }
1819
1991
  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');
1992
+ // 6 rows of 7 days = 42 days.
1993
+ // Use the start of the week of the first day of the month, anchored to Sunday to match the calendar grid.
1994
+ // Luxon's startOf('week') is Monday-based per ISO, so shift back to Sunday explicitly.
1995
+ const monthStart = DateTime.fromJSDate(activeDate).startOf('month');
1996
+ const sundayStart = monthStart.weekday === 7 ? monthStart : monthStart.startOf('week').minus({ days: 1 });
1997
+ return range(0, 6).map((r) => {
1998
+ return range(0, 7).map((d) => {
1999
+ const date = sundayStart.plus({ days: r * 7 + d });
1826
2000
  return {
1827
- date: date.toDate(),
1828
- label: date.format('D')
2001
+ date: date.toJSDate(),
2002
+ label: date.toFormat('d')
1829
2003
  };
1830
2004
  });
1831
2005
  });
1832
2006
  }
1833
2007
  getMonthViewItems(activeDate) {
1834
2008
  // 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');
2009
+ const yearStart = DateTime.fromJSDate(activeDate).startOf('year');
2010
+ return range(0, 4).map((r) => {
2011
+ return range(0, 3).map((m) => {
2012
+ const date = yearStart.plus({ months: r * 3 + m });
1838
2013
  return {
1839
- date: date.toDate(),
1840
- label: date.format('MMM')
2014
+ date: date.toJSDate(),
2015
+ label: date.toFormat('MMM')
1841
2016
  };
1842
2017
  });
1843
2018
  });
1844
2019
  }
1845
2020
  getQuarterViewItems(activeDate) {
1846
2021
  // 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');
2022
+ const yearStart = DateTime.fromJSDate(activeDate).startOf('year');
2023
+ return range(0, 2).map((r) => {
2024
+ return range(0, 2).map((q) => {
2025
+ const date = yearStart.plus({ quarters: r * 2 + q });
2026
+ const endDate = date.endOf('quarter');
1851
2027
  return {
1852
- date: date.toDate(),
1853
- label: `${date.format('MMM')}&ndash;${endDate.format('MMM')}`
2028
+ date: date.toJSDate(),
2029
+ label: `${date.toFormat('MMM')}&ndash;${endDate.toFormat('MMM')}`
1854
2030
  };
1855
2031
  });
1856
2032
  });
1857
2033
  }
1858
2034
  getYearViewItems(activeDate) {
1859
2035
  // 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');
2036
+ // Subtracting 8 years to put the current active year at the beginning of the 3rd row.
2037
+ const yearStart = DateTime.fromJSDate(activeDate).startOf('year').minus({ years: 8 });
2038
+ return range(0, 4).map((r) => {
2039
+ return range(0, 4).map((y) => {
2040
+ const date = yearStart.plus({ years: r * 4 + y });
1864
2041
  return {
1865
- date: date.toDate(),
1866
- label: date.format('YYYY')
2042
+ date: date.toJSDate(),
2043
+ label: date.toFormat('yyyy')
1867
2044
  };
1868
2045
  });
1869
2046
  });
@@ -1873,24 +2050,20 @@ class CalendarComponent {
1873
2050
  // We don't want to disable the next/previous buttons if the min or max date is within the
1874
2051
  // next or previous view
1875
2052
  const unit = this.view.mode === 'day' ? 'month' : 'year';
2053
+ const pluralUnit = this.view.mode === 'day' ? 'months' : 'years';
2054
+ const viewDt = DateTime.fromJSDate(this.view.date);
1876
2055
  // When in year view, we need to subtract 9 years to determine if we're past the min date.
1877
2056
  // The year view is 16 years long, and the current date is the 9th year in the view.
1878
2057
  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
- }
2058
+ const prev = viewDt.minus({ [pluralUnit]: subCount }).startOf(unit);
2059
+ const minDt = DateTime.fromJSDate(this.minDate).startOf(unit);
2060
+ this.disablePreviousButton = prev < minDt;
1885
2061
  // When in year view, we need to add 8 years to determine if we're past the max date.
1886
2062
  // The year view is 16 years long, and the current date is the 9th year in the view.
1887
2063
  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
- }
2064
+ const next = viewDt.plus({ [pluralUnit]: addCount }).startOf(unit);
2065
+ const maxDt = DateTime.fromJSDate(this.maxDate).startOf(unit);
2066
+ this.disableNextButton = next > maxDt;
1894
2067
  }
1895
2068
  getLastItem() {
1896
2069
  const lastRow = this.calendarItems[this.calendarItems.length - 1];
@@ -5334,7 +5507,7 @@ class DateInputComponent extends FormControlBase {
5334
5507
  * Date parsing formats for user-entered dates. Defaults to month-first formats, but is updated in onInit
5335
5508
  * to use the user's preferred date format.
5336
5509
  */
5337
- this.parseFormats = DateTimeHelper.getMomentParseFormats();
5510
+ this.parseFormats = DateTimeHelper.getLuxonParseFormats();
5338
5511
  }
5339
5512
  ngOnChanges(changes) {
5340
5513
  if (changes.selectionMode && !changes.selectionMode.isFirstChange()) {
@@ -5350,12 +5523,15 @@ class DateInputComponent extends FormControlBase {
5350
5523
  this.onFormModelStatusChanges();
5351
5524
  this.onFormModelValueChanges();
5352
5525
  this.onTextboxValueChanges();
5353
- // Sync the initial disabled status and value of the textbox
5526
+ // Sync the initial disabled status and value of the textbox.
5527
+ // Normalize the form model value so any ISO date strings the host bound to the model
5528
+ // become real Dates before they reach the calendar/stepper logic (see normalizeSelection).
5354
5529
  this.syncTextboxControlDisabledStatus(this.formModel.status);
5355
- const initialDisplayValue = this.selectionStrategy.formatSelection(this.formModel.value);
5530
+ const initialSelection = DateInput.normalizeSelection(this.formModel.value);
5531
+ const initialDisplayValue = this.selectionStrategy.formatSelection(initialSelection);
5356
5532
  this.textboxGroup.patchValue(initialDisplayValue, { emitEvent: false });
5357
5533
  // Update the calendar selection and view based on the initial form model value
5358
- this.calendarSelection = this.formModel.value;
5534
+ this.calendarSelection = initialSelection;
5359
5535
  this.updateCalendars(this.calendarSelection);
5360
5536
  this.updateSteppers(this.calendarSelection);
5361
5537
  }
@@ -5451,7 +5627,7 @@ class DateInputComponent extends FormControlBase {
5451
5627
  goToToday() {
5452
5628
  // If both calendars are visible, show today in the secondary calendar and the month before in the primary calendar.
5453
5629
  if (this.selectionStrategy.showSecondaryCalendar) {
5454
- this.primaryCalendarView = { mode: 'day', date: moment().subtract(1, 'month').toDate() };
5630
+ this.primaryCalendarView = { mode: 'day', date: DateTime.now().minus({ months: 1 }).toJSDate() };
5455
5631
  this.secondaryCalendarView = { mode: 'day', date: new Date() };
5456
5632
  // If we're in a single date mode, show today in the primary calendar.
5457
5633
  }
@@ -5500,11 +5676,14 @@ class DateInputComponent extends FormControlBase {
5500
5676
  // Update the calendar selection textbox value with a formatted date when the form model changes.
5501
5677
  // This is triggered by the user selecting a date from the calendar or when the textbox is blurred.
5502
5678
  this.formModel.valueChanges.pipe(takeUntil(this.componentDestroyed)).subscribe(value => {
5503
- const displayValue = this.selectionStrategy.formatSelection(value);
5679
+ // Normalize first: the host may set an ISO date string on the model (e.g. an API value),
5680
+ // and the calendar logic requires real Dates to avoid an Invalid Date crash.
5681
+ const selection = DateInput.normalizeSelection(value);
5682
+ const displayValue = this.selectionStrategy.formatSelection(selection);
5504
5683
  // Don't emit an event when setting the textbox value to avoid circular updates between the textbox and form model.
5505
5684
  this.textboxGroup.patchValue(displayValue, { emitEvent: false });
5506
- this.updateCalendars(value);
5507
- this.updateSteppers(value);
5685
+ this.updateCalendars(selection);
5686
+ this.updateSteppers(selection);
5508
5687
  });
5509
5688
  }
5510
5689
  onTextboxValueChanges() {
@@ -5584,7 +5763,7 @@ class DateInputComponent extends FormControlBase {
5584
5763
  const { preference } = await lastValueFrom(this.userPreferenceService.getPreferences());
5585
5764
  const dateFormat = preference?.dateFormat;
5586
5765
  this.placeholder = dateFormat ?? this.placeholder;
5587
- this.parseFormats = DateTimeHelper.getMomentParseFormats(dateFormat);
5766
+ this.parseFormats = DateTimeHelper.getLuxonParseFormats(dateFormat);
5588
5767
  }
5589
5768
  /** Focuses the date input. */
5590
5769
  focusInput() {
@@ -5604,10 +5783,10 @@ class DateInputComponent extends FormControlBase {
5604
5783
  // This would cause the dates in the other calendar to be unselectable if we were to
5605
5784
  // update the min/max dates in these cases.
5606
5785
  if (this.primaryCalendarView?.mode === 'day') {
5607
- this.secondaryCalendarMinDate = moment(this.primaryCalendarView?.date).add(1, 'month').startOf('month').toDate();
5786
+ this.secondaryCalendarMinDate = DateTime.fromJSDate(this.primaryCalendarView.date).plus({ months: 1 }).startOf('month').toJSDate();
5608
5787
  }
5609
5788
  if (this.secondaryCalendarView?.mode === 'day') {
5610
- this.primaryCalendarMaxDate = moment(this.secondaryCalendarView?.date).subtract(1, 'month').endOf('month').toDate();
5789
+ this.primaryCalendarMaxDate = DateTime.fromJSDate(this.secondaryCalendarView.date).minus({ months: 1 }).endOf('month').toJSDate();
5611
5790
  }
5612
5791
  }
5613
5792
  updateCalendars(selection) {
@@ -5623,8 +5802,10 @@ class DateInputComponent extends FormControlBase {
5623
5802
  // However, it's possible that the new date is after the secondary calendar's date. In this case,
5624
5803
  // We want to shift the secondary calendar so the calendars stay in order.
5625
5804
  if (shiftSecondary && this.secondaryCalendarView) {
5626
- if (moment(this.primaryCalendarView.date).isSameOrAfter(this.secondaryCalendarView.date, 'month')) {
5627
- let newSecondaryDate = moment(this.primaryCalendarView.date).add(1, 'month').toDate();
5805
+ const primaryDt = DateTime.fromJSDate(this.primaryCalendarView.date);
5806
+ const secondaryDt = DateTime.fromJSDate(this.secondaryCalendarView.date);
5807
+ if (primaryDt.startOf('month') >= secondaryDt.startOf('month')) {
5808
+ const newSecondaryDate = primaryDt.plus({ months: 1 }).toJSDate();
5628
5809
  this.secondaryCalendarView = { mode: this.selectionStrategy.selectionViewMode, date: newSecondaryDate };
5629
5810
  }
5630
5811
  }
@@ -5635,8 +5816,10 @@ class DateInputComponent extends FormControlBase {
5635
5816
  // However, it's possible that the new date is before the primary calendar's date. In this case,
5636
5817
  // We want to shift the primary calendar so the calendars stay in order.
5637
5818
  if (shiftPrimary && this.primaryCalendarView) {
5638
- if (moment(this.primaryCalendarView.date).isSameOrAfter(this.secondaryCalendarView.date, 'month')) {
5639
- let newPrimaryDate = moment(this.secondaryCalendarView.date).subtract(1, 'month').toDate();
5819
+ const primaryDt = DateTime.fromJSDate(this.primaryCalendarView.date);
5820
+ const secondaryDt = DateTime.fromJSDate(this.secondaryCalendarView.date);
5821
+ if (primaryDt.startOf('month') >= secondaryDt.startOf('month')) {
5822
+ const newPrimaryDate = secondaryDt.minus({ months: 1 }).toJSDate();
5640
5823
  this.primaryCalendarView = { mode: this.selectionStrategy.selectionViewMode, date: newPrimaryDate };
5641
5824
  }
5642
5825
  }
@@ -5686,10 +5869,12 @@ class DateInputComponent extends FormControlBase {
5686
5869
  // We're using the view mode as the granularity. We don't want to invalidate
5687
5870
  // the date in the case where you have a month/quarter/year selected and the min
5688
5871
  // or max date is somewhere within that month/quarter/year.
5689
- if (moment(value).isBefore(this.minDate, this.selectionStrategy.selectionViewMode)) {
5872
+ const unit = this.selectionStrategy.selectionViewMode;
5873
+ const valueDt = DateTime.fromJSDate(value).startOf(unit);
5874
+ if (valueDt < DateTime.fromJSDate(this.minDate).startOf(unit)) {
5690
5875
  return { minDate: { minValue: this.minDate } };
5691
5876
  }
5692
- else if (moment(value).isAfter(this.maxDate, this.selectionStrategy.selectionViewMode)) {
5877
+ else if (valueDt > DateTime.fromJSDate(this.maxDate).startOf(unit)) {
5693
5878
  return { maxDate: { maxValue: this.maxDate } };
5694
5879
  }
5695
5880
  }
@@ -12059,7 +12244,9 @@ class DateDisplayPipe2 {
12059
12244
  }
12060
12245
  async transform(date, options) {
12061
12246
  const { showEndOfTime, showTime, showSeconds, displayInUTC } = options || {};
12062
- if (!date || !moment(date).isValid() || (!showEndOfTime && DateTimeHelper.isEndOfTime(date))) {
12247
+ if (date == null)
12248
+ return '';
12249
+ if (!DateTimeHelper.toDateTime(date).isValid || (!showEndOfTime && DateTimeHelper.isEndOfTime(date))) {
12063
12250
  return '';
12064
12251
  }
12065
12252
  const { preference } = await lastValueFrom(this.userPrefs.getPreferences());
@@ -12071,8 +12258,7 @@ class DateDisplayPipe2 {
12071
12258
  }
12072
12259
  formatString += ` ${timeFormat}`;
12073
12260
  }
12074
- // Parse and format the date using moment. If displayInUTC is true, use moment.utc() to shift the date to UTC.
12075
- return (displayInUTC ? moment.utc(date) : moment(date)).format(formatString);
12261
+ return DateTimeHelper.formatDate(date, formatString, displayInUTC);
12076
12262
  }
12077
12263
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: DateDisplayPipe2, deps: [{ token: UserPreferenceService }], target: i0.ɵɵFactoryTarget.Pipe }); }
12078
12264
  static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.4", ngImport: i0, type: DateDisplayPipe2, isStandalone: false, name: "dateDisplay2" }); }
@@ -12085,6 +12271,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
12085
12271
  }]
12086
12272
  }], ctorParameters: () => [{ type: UserPreferenceService }] });
12087
12273
 
12274
+ /**
12275
+ * Same behavior as {@link DateDisplayPipe2}, but always appends the short named
12276
+ * timezone abbreviation (e.g. `EST`, `PDT`, `UTC`) to the output. Use this when
12277
+ * downstream consumers need to disambiguate the timezone of a rendered date.
12278
+ *
12279
+ * @example <span>{{ someDate | dateDisplay3 | async }}</span>
12280
+ */
12281
+ class DateDisplayPipe3 {
12282
+ constructor(userPrefs) {
12283
+ this.userPrefs = userPrefs;
12284
+ }
12285
+ async transform(date, options) {
12286
+ const { showEndOfTime, showTime, showSeconds, displayInUTC } = options || {};
12287
+ if (date == null)
12288
+ return '';
12289
+ if (!DateTimeHelper.toDateTime(date).isValid || (!showEndOfTime && DateTimeHelper.isEndOfTime(date))) {
12290
+ return '';
12291
+ }
12292
+ const { preference } = await lastValueFrom(this.userPrefs.getPreferences());
12293
+ let formatString = preference?.dateFormat || 'MM/DD/YYYY';
12294
+ if (showTime) {
12295
+ let timeFormat = preference.timeFormat;
12296
+ if (!showSeconds) {
12297
+ timeFormat = timeFormat.replace(':ss', '');
12298
+ }
12299
+ formatString += ` ${timeFormat}`;
12300
+ }
12301
+ // Luxon's `ZZZZ` renders the short named offset (e.g. `EST`, `EDT`, `UTC`).
12302
+ // The token passes through `momentToLuxonFormat` unchanged.
12303
+ formatString += ' ZZZZ';
12304
+ return DateTimeHelper.formatDate(date, formatString, !!displayInUTC);
12305
+ }
12306
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: DateDisplayPipe3, deps: [{ token: UserPreferenceService }], target: i0.ɵɵFactoryTarget.Pipe }); }
12307
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.4", ngImport: i0, type: DateDisplayPipe3, isStandalone: false, name: "dateDisplay3" }); }
12308
+ }
12309
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: DateDisplayPipe3, decorators: [{
12310
+ type: Pipe,
12311
+ args: [{
12312
+ name: 'dateDisplay3',
12313
+ standalone: false
12314
+ }]
12315
+ }], ctorParameters: () => [{ type: UserPreferenceService }] });
12316
+
12088
12317
  /**
12089
12318
  * Format a time to the user's preference for display
12090
12319
  */
@@ -12097,11 +12326,10 @@ class TimeDisplayPipe {
12097
12326
  * Format a time for display, accounting for user's display preferences.
12098
12327
  */
12099
12328
  transform(time, showSeconds) {
12100
- let display = '';
12101
12329
  let formatString = '';
12102
12330
  // use user-preferred formats
12103
12331
  this.userPreferenceService.getPreferences().subscribe(result => {
12104
- //if preferences exist then format time to users preference
12332
+ //if preferences exist then format time to users preference
12105
12333
  //otherwise use the last user preference
12106
12334
  if (result.preference) {
12107
12335
  formatString = result.preference.timeFormat;
@@ -12111,8 +12339,7 @@ class TimeDisplayPipe {
12111
12339
  this.lastFormatString = formatString;
12112
12340
  }
12113
12341
  });
12114
- display = moment(time).format(this.lastFormatString);
12115
- return display;
12342
+ return DateTimeHelper.formatDate(time ?? null, this.lastFormatString);
12116
12343
  }
12117
12344
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: TimeDisplayPipe, deps: [{ token: UserPreferenceService }], target: i0.ɵɵFactoryTarget.Pipe }); }
12118
12345
  static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.4", ngImport: i0, type: TimeDisplayPipe, isStandalone: false, name: "timeDisplay" }); }
@@ -12140,21 +12367,21 @@ class RelativeDatePipe {
12140
12367
  * If any of todayLabel, yesterdayLabel is supplied, use the supplied label.
12141
12368
  */
12142
12369
  transform(timeSelected, options) {
12143
- const selected = moment(timeSelected);
12144
- const today = moment();
12145
- const yesterday = moment().subtract(1, 'day');
12370
+ const selected = DateTime.fromISO(timeSelected);
12371
+ const today = DateTime.now();
12372
+ const yesterday = today.minus({ days: 1 });
12146
12373
  const timeDisplay = options?.dateOnly ? `` : ` ${this.timeDisplayPipe.transform(timeSelected)}`;
12147
12374
  let displayValue = null;
12148
12375
  let prefixValue = '';
12149
- if (selected.isSame(today, 'day') && !options?.showTimeForToday) {
12376
+ if (selected.hasSame(today, 'day') && !options?.showTimeForToday) {
12150
12377
  displayValue = this.translateService.instant(options?.todayLabel ? options?.todayLabel : 'today');
12151
12378
  prefixValue = options?.todayPrefix ?? '';
12152
12379
  }
12153
- else if (selected.isSame(today, 'day') && options?.showTimeForToday && !timeDisplay) {
12380
+ else if (selected.hasSame(today, 'day') && options?.showTimeForToday && !timeDisplay) {
12154
12381
  displayValue = this.timeDisplayPipe.transform(timeSelected);
12155
12382
  prefixValue = options?.todayTimePrefix ?? '';
12156
12383
  }
12157
- else if (selected.isSame(yesterday, 'day')) {
12384
+ else if (selected.hasSame(yesterday, 'day')) {
12158
12385
  displayValue = this.translateService.instant(options?.yesterdayLabel ? options?.yesterdayLabel : 'yesterday');
12159
12386
  prefixValue = options?.yesterdayPrefix ?? '';
12160
12387
  }
@@ -12792,6 +13019,7 @@ class ComponentsModule {
12792
13019
  DateInputComponent,
12793
13020
  KeyboardNavContainerDirective,
12794
13021
  DateDisplayPipe2,
13022
+ DateDisplayPipe3,
12795
13023
  MultiselectComponent], imports: [CommonModule,
12796
13024
  FormsModule,
12797
13025
  ReactiveFormsModule,
@@ -12864,7 +13092,8 @@ class ComponentsModule {
12864
13092
  CalendarComponent,
12865
13093
  DateInputComponent,
12866
13094
  KeyboardNavContainerDirective,
12867
- DateDisplayPipe2] }); }
13095
+ DateDisplayPipe2,
13096
+ DateDisplayPipe3] }); }
12868
13097
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ComponentsModule, providers: [
12869
13098
  FormGroupHelper,
12870
13099
  DialogService,
@@ -12874,7 +13103,8 @@ class ComponentsModule {
12874
13103
  RowCountPipe,
12875
13104
  RelativeDatePipe,
12876
13105
  HighlightTextPipe,
12877
- DateDisplayPipe2
13106
+ DateDisplayPipe2,
13107
+ DateDisplayPipe3
12878
13108
  ], imports: [CommonModule,
12879
13109
  FormsModule,
12880
13110
  ReactiveFormsModule,
@@ -12960,6 +13190,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
12960
13190
  DateInputComponent,
12961
13191
  KeyboardNavContainerDirective,
12962
13192
  DateDisplayPipe2,
13193
+ DateDisplayPipe3,
12963
13194
  MultiselectComponent
12964
13195
  ],
12965
13196
  imports: [
@@ -12981,7 +13212,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
12981
13212
  RowCountPipe,
12982
13213
  RelativeDatePipe,
12983
13214
  HighlightTextPipe,
12984
- DateDisplayPipe2
13215
+ DateDisplayPipe2,
13216
+ DateDisplayPipe3
12985
13217
  ],
12986
13218
  exports: [
12987
13219
  ButtonComponent,
@@ -13049,7 +13281,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
13049
13281
  CalendarComponent,
13050
13282
  DateInputComponent,
13051
13283
  KeyboardNavContainerDirective,
13052
- DateDisplayPipe2
13284
+ DateDisplayPipe2,
13285
+ DateDisplayPipe3
13053
13286
  ]
13054
13287
  }]
13055
13288
  }] });
@@ -14045,5 +14278,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
14045
14278
  * Generated bundle index. Do not edit.
14046
14279
  */
14047
14280
 
14048
- 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 };
14281
+ 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 };
14049
14282
  //# sourceMappingURL=energycap-components.mjs.map