@zelgadis87/utils-core 5.3.3 → 5.3.4

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.
@@ -4,7 +4,7 @@ import TimeBase from "./TimeBase";
4
4
  import TimeDuration from "./TimeDuration";
5
5
  import { TTimeInstantBuilder, TTimeInstantCreationParameters, createTimeInstantFromParameters, timeInstantBuilder, type TTimeInstantParameters } from "./TimeInstantBuilder.js";
6
6
  import { TimeUnit } from "./TimeUnit";
7
- import { TDayOfMonth, TDayOfWeek, THourOfDay, TIso8601DateString, TIso8601DateUtcString, TMillisecondOfSecond, TMinuteOfHour, TMonth, TSecondOfMinute, TWeekNumber, type TFourDigitsYear, type TYear } from "./types";
7
+ import { TDayOfMonth, THourOfDay, TIso8601DateUtcString, TMillisecondOfSecond, TMinuteOfHour, TMonth, TSecondOfMinute, TWeekNumber, type TYear } from "./types";
8
8
 
9
9
  export class TimeInstant extends TimeBase<TimeInstant> {
10
10
 
@@ -287,8 +287,8 @@ export class TimeInstant extends TimeBase<TimeInstant> {
287
287
  }
288
288
 
289
289
  public static fromIso8601( str: string ) {
290
- // Regex to capture: YYYY-MM-DDTHH:mm:ss.sssZ or YYYY-MM-DDTHH:mm:ss.sss±HH:mm
291
- const iso8601Regex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3})(Z|[+-]\d{2}:\d{2})?$/;
290
+ // Regex to capture: YYYY-MM-DDTHH:mm:ss[.sss]Z or YYYY-MM-DDTHH:mm:ss[.sss]±HH:mm
291
+ const iso8601Regex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{3})|)(Z|[+-]\d{2}:\d{2})?$/;
292
292
  const match = str.match( iso8601Regex );
293
293
 
294
294
  if ( !match ) {
@@ -304,7 +304,7 @@ export class TimeInstant extends TimeBase<TimeInstant> {
304
304
  const hours = parseInt( hourStr, 10 );
305
305
  const minutes = parseInt( minuteStr, 10 );
306
306
  const seconds = parseInt( secondStr, 10 );
307
- const milliseconds = parseInt( millisecondStr, 10 );
307
+ const milliseconds = millisecondStr ? parseInt( millisecondStr, 10 ) : 0;
308
308
 
309
309
  // Validate each component using standalone validation functions
310
310
  if ( !isValidYear( year ) )
@@ -641,7 +641,7 @@ function parseTimeInstant( dateString: string, pattern: string, base: TimeInstan
641
641
  return TimeInstant.fromParameters( params );
642
642
  }
643
643
 
644
- function parseTimeInstantComponents( dateString: string, pattern: string, config: { locale?: string, timeZone?: string } = {} ): Partial<TTimeInstantParameters> {
644
+ export function parseTimeInstantComponents( dateString: string, pattern: string, config: { locale?: string, timeZone?: string } = {} ): Partial<TTimeInstantParameters> {
645
645
  // Create a regex pattern from the format pattern
646
646
  let regexPattern = pattern;
647
647
  const tokens: { type: string; length: number; position: number }[] = [];
@@ -663,39 +663,39 @@ function parseTimeInstantComponents( dateString: string, pattern: string, config
663
663
 
664
664
  // Create appropriate regex for each token type
665
665
  switch ( type ) {
666
- case 'y':
667
- return match.length === 2 ? '(\\d{2})' : '(\\d{4})';
668
- case 'M':
669
- if ( match.length === 1 ) return '(\\d{1,2})';
670
- if ( match.length === 2 ) return '(\\d{2})';
671
- if ( match.length === 3 ) return '([A-Za-z.]{1,7})';
672
- return '([A-Za-z]+)';
673
- case 'd':
674
- return match.length === 1 ? '(\\d{1,2})' : '(\\d{2})';
675
- case 'H':
676
- case 'h':
677
- return match.length === 1 ? '(\\d{1,2})' : '(\\d{2})';
678
- case 'm':
679
- case 's':
680
- return match.length === 1 ? '(\\d{1,2})' : '(\\d{2})';
681
- case 'S':
682
- return `(\\d{${match.length}})`;
683
- case 'a':
684
- return '([aApP][mM])';
685
- case 'D':
686
- if ( match.length === 1 ) return '([A-Za-z])';
687
- if ( match.length === 2 ) return '([A-Za-z]{3})';
688
- return '([A-Za-z]+)';
689
- case 'G':
690
- if ( match.length === 1 ) return '([A-Za-z])';
691
- if ( match.length === 2 ) return '([A-Za-z]{2})';
692
- return '([A-Za-z\\s]+)';
693
- case 'Z':
694
- return match.length === 1 ? '([A-Za-z0-9+\\-:]+)' : '([A-Za-z\\s]+)';
695
- case 'P':
696
- return '([A-Za-z\\s]+)';
697
- default:
698
- return match;
666
+ case 'y':
667
+ return match.length === 2 ? '(\\d{2})' : '(\\d{4})';
668
+ case 'M':
669
+ if ( match.length === 1 ) return '(\\d{1,2})';
670
+ if ( match.length === 2 ) return '(\\d{2})';
671
+ if ( match.length === 3 ) return '([A-Za-z.]{1,7})';
672
+ return '([A-Za-z]+)';
673
+ case 'd':
674
+ return match.length === 1 ? '(\\d{1,2})' : '(\\d{2})';
675
+ case 'H':
676
+ case 'h':
677
+ return match.length === 1 ? '(\\d{1,2})' : '(\\d{2})';
678
+ case 'm':
679
+ case 's':
680
+ return match.length === 1 ? '(\\d{1,2})' : '(\\d{2})';
681
+ case 'S':
682
+ return `(\\d{${match.length}})`;
683
+ case 'a':
684
+ return '([aApP][mM])';
685
+ case 'D':
686
+ if ( match.length === 1 ) return '([A-Za-z])';
687
+ if ( match.length === 2 ) return '([A-Za-z]{3})';
688
+ return '([A-Za-z]+)';
689
+ case 'G':
690
+ if ( match.length === 1 ) return '([A-Za-z])';
691
+ if ( match.length === 2 ) return '([A-Za-z]{2})';
692
+ return '([A-Za-z\\s]+)';
693
+ case 'Z':
694
+ return match.length === 1 ? '([A-Za-z0-9+\\-:]+)' : '([A-Za-z\\s]+)';
695
+ case 'P':
696
+ return '([A-Za-z\\s]+)';
697
+ default:
698
+ return match;
699
699
  }
700
700
  } );
701
701
  } ).join( '' );
@@ -718,70 +718,70 @@ function parseTimeInstantComponents( dateString: string, pattern: string, config
718
718
  const value = matches[ index + 1 ];
719
719
 
720
720
  switch ( token.type ) {
721
- case 'y':
722
- if ( token.length === 2 ) {
723
- const shortYear = parseInt( value, 10 );
724
- result.year = shortYear < 50 ? 2000 + shortYear : 1900 + shortYear;
725
- } else {
726
- result.year = parseInt( value, 10 );
727
- }
728
- break;
729
- case 'M':
730
- switch ( token.length ) {
731
- case 1:
732
- case 2:
733
- result.month = parseInt( value, 10 ) as TMonth;
721
+ case 'y':
722
+ if ( token.length === 2 ) {
723
+ const shortYear = parseInt( value, 10 );
724
+ result.year = shortYear < 50 ? 2000 + shortYear : 1900 + shortYear;
725
+ } else {
726
+ result.year = parseInt( value, 10 );
727
+ }
734
728
  break;
735
- case 3: {
736
- const normalizedValue = normalizeMonthName( value );
737
- const monthIndex = getMonthNames( locale ).short.findIndex( name =>
738
- name.toLowerCase() === normalizedValue.toLowerCase()
739
- );
740
- if ( monthIndex === -1 )
741
- throw new Error( `Invalid short month name in date string: ${dateString}` );
742
- result.month = ( monthIndex + 1 ) as TMonth;
729
+ case 'M':
730
+ switch ( token.length ) {
731
+ case 1:
732
+ case 2:
733
+ result.month = parseInt( value, 10 ) as TMonth;
734
+ break;
735
+ case 3: {
736
+ const normalizedValue = normalizeMonthName( value );
737
+ const monthIndex = getMonthNames( locale ).short.findIndex( name =>
738
+ name.toLowerCase() === normalizedValue.toLowerCase()
739
+ );
740
+ if ( monthIndex === -1 )
741
+ throw new Error( `Invalid short month name in date string: ${dateString}` );
742
+ result.month = ( monthIndex + 1 ) as TMonth;
743
+ break;
744
+ }
745
+ case 4: {
746
+ const normalizedValue = normalizeMonthName( value );
747
+ const monthIndex = getMonthNames( locale ).long.findIndex( name =>
748
+ name.toLowerCase() === normalizedValue.toLowerCase()
749
+ );
750
+ if ( monthIndex === -1 )
751
+ throw new Error( `Invalid full month name in date string: ${dateString}` );
752
+ result.month = ( monthIndex + 1 ) as TMonth;
753
+ break;
754
+ }
755
+ default:
756
+ throw new Error( `Invalid month pattern: ${token}` );
757
+ }
743
758
  break;
744
- }
745
- case 4: {
746
- const normalizedValue = normalizeMonthName( value );
747
- const monthIndex = getMonthNames( locale ).long.findIndex( name =>
748
- name.toLowerCase() === normalizedValue.toLowerCase()
749
- );
750
- if ( monthIndex === -1 )
751
- throw new Error( `Invalid full month name in date string: ${dateString}` );
752
- result.month = ( monthIndex + 1 ) as TMonth;
759
+ case 'd':
760
+ result.date = parseInt( value, 10 ) as TDayOfMonth;
761
+ break;
762
+ case 'H':
763
+ result.hours = parseInt( value, 10 ) as THourOfDay;
764
+ break;
765
+ case 'h':
766
+ hourValue = parseInt( value, 10 );
767
+ is12Hour = true;
768
+ break;
769
+ case 'm':
770
+ result.minutes = parseInt( value, 10 ) as TMinuteOfHour;
771
+ break;
772
+ case 's':
773
+ result.seconds = parseInt( value, 10 ) as TSecondOfMinute;
774
+ break;
775
+ case 'S':
776
+ let ms = parseInt( value, 10 );
777
+ // Normalize to milliseconds based on length
778
+ if ( token.length === 1 ) ms *= 100;
779
+ else if ( token.length === 2 ) ms *= 10;
780
+ result.milliseconds = ms as TMillisecondOfSecond;
781
+ break;
782
+ case 'a':
783
+ isPM = value.toLowerCase().includes( 'p' );
753
784
  break;
754
- }
755
- default:
756
- throw new Error( `Invalid month pattern: ${token}` );
757
- }
758
- break;
759
- case 'd':
760
- result.date = parseInt( value, 10 ) as TDayOfMonth;
761
- break;
762
- case 'H':
763
- result.hours = parseInt( value, 10 ) as THourOfDay;
764
- break;
765
- case 'h':
766
- hourValue = parseInt( value, 10 );
767
- is12Hour = true;
768
- break;
769
- case 'm':
770
- result.minutes = parseInt( value, 10 ) as TMinuteOfHour;
771
- break;
772
- case 's':
773
- result.seconds = parseInt( value, 10 ) as TSecondOfMinute;
774
- break;
775
- case 'S':
776
- let ms = parseInt( value, 10 );
777
- // Normalize to milliseconds based on length
778
- if ( token.length === 1 ) ms *= 100;
779
- else if ( token.length === 2 ) ms *= 10;
780
- result.milliseconds = ms as TMillisecondOfSecond;
781
- break;
782
- case 'a':
783
- isPM = value.toLowerCase().includes( 'p' );
784
- break;
785
785
  }
786
786
  } );
787
787
 
@@ -821,3 +821,276 @@ function parseTimeInstantComponents( dateString: string, pattern: string, config
821
821
 
822
822
  return result;
823
823
  }
824
+
825
+ // Basic patterns that don't require Intl (24-hour format only)
826
+ export type TBasicTimePattern =
827
+ // Year patterns
828
+ | 'y' | 'yy' | 'yyyy'
829
+ // Month patterns (numeric only)
830
+ | 'M' | 'MM'
831
+ // Day patterns
832
+ | 'd' | 'dd'
833
+ // Hour patterns (24-hour only)
834
+ | 'H' | 'HH'
835
+ // Minute patterns
836
+ | 'm' | 'mm'
837
+ // Second patterns
838
+ | 's' | 'ss'
839
+ // Millisecond patterns
840
+ | 'S' | 'SS' | 'SSS'
841
+ // Combination patterns (basic only)
842
+ | 'yyyy-MM-dd'
843
+ | 'yyyy-MM-dd HH:mm'
844
+ | 'yyyy-MM-dd HH:mm:ss'
845
+ | 'yyyy-MM-dd HH:mm:ss.SSS'
846
+ | 'yyyy/MM/dd'
847
+ | 'yyyy/MM/dd HH:mm'
848
+ | 'yyyy/MM/dd HH:mm:ss'
849
+ | 'yyyy/MM/dd HH:mm:ss.SSS'
850
+ | 'MM/dd/yyyy'
851
+ | 'MM/dd/yyyy HH:mm'
852
+ | 'MM/dd/yyyy HH:mm:ss'
853
+ | 'MM/dd/yyyy HH:mm:ss.SSS'
854
+ | 'dd/MM/yyyy'
855
+ | 'dd/MM/yyyy HH:mm'
856
+ | 'dd/MM/yyyy HH:mm:ss'
857
+ | 'dd/MM/yyyy HH:mm:ss.SSS'
858
+ | 'HH:mm:ss'
859
+ | 'HH:mm';
860
+
861
+ /**
862
+ * Parses a date string using only basic patterns that don't require Intl.
863
+ * This is a safer alternative to parseTimeInstantComponents that works even when Intl is not available.
864
+ *
865
+ * @param dateString The date string to parse
866
+ * @param pattern A basic pattern that doesn't require Intl (strongly typed)
867
+ * @returns Partial time instant parameters that were parsed from the string
868
+ * @throws Error if the string doesn't match the pattern or contains invalid values
869
+ */
870
+ export function parseTimeInstantBasicComponents(
871
+ dateString: string,
872
+ pattern: TBasicTimePattern
873
+ ): Partial<TTimeInstantParameters> {
874
+
875
+ // Check if Intl is available, if so warn the user about the existing function
876
+ const isIntlAvailable = typeof Intl !== 'undefined' && typeof Intl.DateTimeFormat !== 'undefined';
877
+ if ( isIntlAvailable )
878
+ console.warn( 'Intl is available, use parseTimeInstantComponents instead of parseTimeInstantBasicComponents.' );
879
+
880
+ const result: Partial<TTimeInstantParameters> = {};
881
+
882
+ let patternIndex = 0;
883
+ let dateStringIndex = 0;
884
+
885
+ // Helper function to check if a character is a pattern token
886
+ const isPatternChar = ( char: string ): boolean => [ 'y', 'M', 'd', 'H', 'm', 's', 'S' ].includes( char );
887
+
888
+ // Token extraction rules dictionary
889
+ const tokenRules: Record<string, {
890
+ maxLength?: number;
891
+ validator?: (value: string) => boolean;
892
+ description?: string;
893
+ }> = {
894
+ '': { maxLength: undefined }, // Default rule
895
+ 'H': {
896
+ maxLength: 2,
897
+ validator: (value) => /^\d{1,2}$/.test(value) && parseInt(value, 10) >= 0 && parseInt(value, 10) <= 23,
898
+ description: 'Hour (0-23, 1-2 digits)'
899
+ },
900
+ 'HH': {
901
+ maxLength: 2,
902
+ validator: (value) => /^\d{2}$/.test(value) && parseInt(value, 10) >= 0 && parseInt(value, 10) <= 23,
903
+ description: 'Hour (00-23, exactly 2 digits)'
904
+ },
905
+ 'M': {
906
+ maxLength: 2,
907
+ validator: (value) => /^\d{1,2}$/.test(value) && parseInt(value, 10) >= 1 && parseInt(value, 10) <= 12,
908
+ description: 'Month (1-12, 1-2 digits)'
909
+ },
910
+ 'MM': {
911
+ maxLength: 2,
912
+ validator: (value) => /^\d{2}$/.test(value) && parseInt(value, 10) >= 1 && parseInt(value, 10) <= 12,
913
+ description: 'Month (01-12, exactly 2 digits)'
914
+ },
915
+ 'd': {
916
+ maxLength: 2,
917
+ validator: (value) => /^\d{1,2}$/.test(value) && parseInt(value, 10) >= 1 && parseInt(value, 10) <= 31,
918
+ description: 'Day (1-31, 1-2 digits)'
919
+ },
920
+ 'dd': {
921
+ maxLength: 2,
922
+ validator: (value) => /^\d{2}$/.test(value) && parseInt(value, 10) >= 1 && parseInt(value, 10) <= 31,
923
+ description: 'Day (01-31, exactly 2 digits)'
924
+ },
925
+ 'm': {
926
+ maxLength: 2,
927
+ validator: (value) => /^\d{1,2}$/.test(value) && parseInt(value, 10) >= 0 && parseInt(value, 10) <= 59,
928
+ description: 'Minutes (0-59, 1-2 digits)'
929
+ },
930
+ 'mm': {
931
+ maxLength: 2,
932
+ validator: (value) => /^\d{2}$/.test(value) && parseInt(value, 10) >= 0 && parseInt(value, 10) <= 59,
933
+ description: 'Minutes (00-59, exactly 2 digits)'
934
+ },
935
+ 's': {
936
+ maxLength: 2,
937
+ validator: (value) => /^\d{1,2}$/.test(value) && parseInt(value, 10) >= 0 && parseInt(value, 10) <= 59,
938
+ description: 'Seconds (0-59, 1-2 digits)'
939
+ },
940
+ 'ss': {
941
+ maxLength: 2,
942
+ validator: (value) => /^\d{2}$/.test(value) && parseInt(value, 10) >= 0 && parseInt(value, 10) <= 59,
943
+ description: 'Seconds (00-59, exactly 2 digits)'
944
+ },
945
+ 'S': {
946
+ maxLength: 3,
947
+ validator: (value) => /^\d{1,3}$/.test(value) && parseInt(value, 10) >= 0 && parseInt(value, 10) <= 999,
948
+ description: 'Milliseconds (0-999, 1-3 digits)'
949
+ },
950
+ 'SS': {
951
+ maxLength: 2,
952
+ validator: (value) => /^\d{2}$/.test(value) && parseInt(value, 10) >= 0 && parseInt(value, 10) <= 99,
953
+ description: 'Milliseconds (0-99, exactly 2 digits)'
954
+ },
955
+ 'SSS': {
956
+ maxLength: 3,
957
+ validator: (value) => /^\d{3}$/.test(value) && parseInt(value, 10) >= 0 && parseInt(value, 10) <= 999,
958
+ description: 'Milliseconds (000-999, exactly 3 digits)'
959
+ },
960
+ 'y': {
961
+ maxLength: 4,
962
+ validator: (value) => /^\d{1,4}$/.test(value) && parseInt(value, 10) >= 0 && parseInt(value, 10) <= 9999,
963
+ description: 'Year (0-9999, 1-4 digits)'
964
+ },
965
+ 'yy': {
966
+ maxLength: 2,
967
+ validator: (value) => /^\d{2}$/.test(value),
968
+ description: 'Year (2 digits)'
969
+ },
970
+ 'yyyy': {
971
+ maxLength: 4,
972
+ validator: (value) => /^\d{4}$/.test(value),
973
+ description: 'Year (4 digits)'
974
+ }
975
+ };
976
+
977
+ // Helper function to extract a token value from the date string using token rules
978
+ const extractValue = ( tokenType: string, tokenLength: number ): string => {
979
+ const tokenKey = tokenType.repeat( tokenLength );
980
+ const rule = tokenRules[ tokenKey ] || tokenRules[ '' ];
981
+
982
+ // Extract consecutive digits up to maxLength
983
+ let endIndex = dateStringIndex;
984
+ let digitCount = 0;
985
+ const maxChars = rule.maxLength || tokenLength;
986
+
987
+ while ( endIndex < dateString.length && /\d/.test( dateString[ endIndex ] ) && digitCount < maxChars ) {
988
+ endIndex++;
989
+ digitCount++;
990
+ }
991
+
992
+ const value = dateString.substring( dateStringIndex, endIndex );
993
+ dateStringIndex = endIndex;
994
+
995
+ // Validate the extracted value if a validator is provided
996
+ if ( rule.validator && !rule.validator( value ) ) {
997
+ // Extract field name from description for better error messages
998
+ const fieldName = (rule.description || tokenKey).split(' ')[0].toLowerCase();
999
+ throw new Error( `Invalid ${fieldName} value: "${value}"` );
1000
+ }
1001
+
1002
+ return value;
1003
+ };
1004
+
1005
+ // Helper function to parse and validate a numeric value
1006
+ const parseNumericValue = ( value: string, min: number, max: number, field: string ): number => {
1007
+ const num = parseInt( value, 10 );
1008
+ if ( isNaN( num ) || num < min || num > max )
1009
+ throw new Error( `Invalid ${field} value: ${value}` );
1010
+ return num;
1011
+ };
1012
+
1013
+ while ( patternIndex < pattern.length ) {
1014
+ const patternChar = pattern[ patternIndex ];
1015
+
1016
+ if ( isPatternChar( patternChar ) ) {
1017
+
1018
+ // Start of a token - determine its full length
1019
+ let tokenLength = 0;
1020
+ while ( patternIndex + tokenLength < pattern.length &&
1021
+ pattern[ patternIndex + tokenLength ] === patternChar ) {
1022
+ tokenLength++;
1023
+ }
1024
+
1025
+ // Extract and parse the corresponding value from the date string
1026
+ const value = extractValue( patternChar, tokenLength );
1027
+
1028
+ // Check if we got enough characters for this token (after extraction)
1029
+ if ( value.length < tokenLength ) {
1030
+ throw new Error( `Date string "${dateString}" does not match pattern "${pattern}"` );
1031
+ }
1032
+
1033
+ // Debug: log pattern processing (commented out for production)
1034
+ // console.log( `Processing patternChar="${patternChar}" with tokenLength=${tokenLength}, value="${value}"` );
1035
+
1036
+ switch ( patternChar ) {
1037
+ case 'y': {
1038
+ if ( tokenLength === 2 ) {
1039
+ // 2-digit year
1040
+ const shortYear = parseNumericValue( value, 0, 99, 'year' );
1041
+ result.year = shortYear < 50 ? 2000 + shortYear : 1900 + shortYear;
1042
+ } else {
1043
+ // 4-digit year
1044
+ result.year = parseNumericValue( value, 0, 9999, 'year' );
1045
+ }
1046
+ break;
1047
+ }
1048
+ case 'M':
1049
+ result.month = parseNumericValue( value, 1, 12, 'month' ) as TMonth;
1050
+ break;
1051
+ case 'd':
1052
+ result.date = parseNumericValue( value, 1, 31, 'day' ) as TDayOfMonth;
1053
+ break;
1054
+ case 'H':
1055
+ result.hours = parseNumericValue( value, 0, 23, 'hours' ) as THourOfDay;
1056
+ break;
1057
+ case 'm':
1058
+ result.minutes = parseNumericValue( value, 0, 59, 'minutes' ) as TMinuteOfHour;
1059
+ break;
1060
+ case 's':
1061
+ result.seconds = parseNumericValue( value, 0, 59, 'seconds' ) as TSecondOfMinute;
1062
+ break;
1063
+ case 'S': {
1064
+ let ms = parseInt( value, 10 );
1065
+ if ( isNaN( ms ) )
1066
+ throw new Error( `Invalid milliseconds value: ${value}` );
1067
+
1068
+ // Normalize to milliseconds based on length
1069
+ if ( tokenLength === 1 ) ms *= 100;
1070
+ else if ( tokenLength === 2 ) ms *= 10;
1071
+ result.milliseconds = ms as TMillisecondOfSecond;
1072
+ break;
1073
+ }
1074
+ default:
1075
+ throw new Error( `Unsupported pattern character: ${patternChar}` );
1076
+ }
1077
+
1078
+ patternIndex += tokenLength;
1079
+ } else {
1080
+ // Non-pattern character (separator like '-', '/', ':', ' ', '.')
1081
+ if ( dateStringIndex >= dateString.length ||
1082
+ dateString[ dateStringIndex ] !== patternChar ) {
1083
+ throw new Error( `Date string "${dateString}" does not match pattern "${pattern}"` );
1084
+ }
1085
+ patternIndex++;
1086
+ dateStringIndex++;
1087
+ }
1088
+ }
1089
+
1090
+ // After processing the entire pattern, check if we consumed the entire date string
1091
+ if ( dateStringIndex < dateString.length ) {
1092
+ throw new Error( `Date string "${dateString}" does not match pattern "${pattern}"` );
1093
+ }
1094
+
1095
+ return result;
1096
+ }