@zelgadis87/utils-core 5.2.8 → 5.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@zelgadis87/utils-core",
4
- "version": "5.2.8",
4
+ "version": "5.2.10",
5
5
  "author": "Zelgadis87",
6
6
  "license": "ISC",
7
7
  "private": false,
@@ -30,8 +30,5 @@
30
30
  "typescript": "5.8.3",
31
31
  "typescript-eslint": "8.43.0",
32
32
  "vitest": "3.2.4"
33
- },
34
- "dependencies": {
35
- "small-date": "^2.0.1"
36
33
  }
37
34
  }
package/src/Optional.ts CHANGED
@@ -221,7 +221,7 @@ export type TOptional<T> = {
221
221
 
222
222
  ifEmpty( callback: TVoidFunction ): void;
223
223
  ifPresent( callback: TConsumer<T> ): void;
224
- apply<RP = void, RE = void>( callbackIfPresent: TFunction<T, RP>, callbackIfEmpty: TProducer<RE> ): void;
224
+ apply<RP = void, RE = void>( callbackIfPresent: TFunction<T, RP>, callbackIfEmpty: TProducer<RE> ): RP | RE;
225
225
 
226
226
  throwIfPresent: ( errorGenerator: TFunction<T, Error> ) => TEmptyOptional<T>;
227
227
 
@@ -255,11 +255,13 @@ export type TEmptyOptional<T> = TOptional<T> & {
255
255
  get(): never;
256
256
  isEmpty(): true;
257
257
  isPresent(): false;
258
+ apply<RP = void, RE = void>( callbackIfPresent: TFunction<T, RP>, callbackIfEmpty: TProducer<RE> ): RE;
258
259
  }
259
260
  export type TPresentOptional<T> = TOptional<T> & {
260
261
  get(): T;
261
262
  isEmpty(): false;
262
263
  isPresent(): true;
264
+ apply<RP = void, RE = void>( callbackIfPresent: TFunction<T, RP>, callbackIfEmpty: TProducer<RE> ): RP;
263
265
  }
264
266
 
265
267
  export class ErrorGetEmptyOptional extends Error {
@@ -1,11 +1,10 @@
1
- import { format } from "small-date";
2
1
  import { ICancelable, ICancelablePromise } from "../async/Deferred.js";
3
2
  import { TComparisonResult } from "../sorting/_index.js";
4
3
  import TimeBase from "./TimeBase";
5
4
  import TimeDuration from "./TimeDuration";
6
5
  import { TTimeInstantBuilder, TTimeInstantCreationParameters, createTimeInstantFromParameters, timeInstantBuilder } from "./TimeInstantBuilder.js";
7
6
  import { TimeUnit } from "./TimeUnit";
8
- import { TDayOfMonth, TDayOfWeek, TIso8601DateString, TIso8601DateUtcString, TMonth, TWeekNumber } from "./types";
7
+ import { TDayOfMonth, TDayOfWeek, THourOfDay, TIso8601DateString, TIso8601DateUtcString, TMillisecondOfSecond, TMinuteOfHour, TMonth, TSecondOfMinute, TWeekNumber } from "./types";
9
8
 
10
9
  export class TimeInstant extends TimeBase<TimeInstant> {
11
10
 
@@ -32,7 +31,7 @@ export class TimeInstant extends TimeBase<TimeInstant> {
32
31
  public distanceFromNow(): TimeDuration {
33
32
  return TimeDuration.fromMs( Math.abs( this.ms - Date.now() ) );
34
33
  }
35
-
34
+
36
35
  public distanceFromUnixTimestamp( timestamp: number ): TimeDuration {
37
36
  return TimeDuration.ms( this.ms - timestamp );
38
37
  }
@@ -96,6 +95,8 @@ export class TimeInstant extends TimeBase<TimeInstant> {
96
95
  /**
97
96
  * Formats this instant using the given pattern. The pattern can contain the following tokens:
98
97
  *
98
+ * Note: Implementation inspired by the small-date library (https://github.com/robinweser/small-date).
99
+ *
99
100
  * | Token | Description | Example |
100
101
  * |:------|:--------------------------------|:------------------------------|
101
102
  * | D | Weekday, 1 letter | W |
@@ -135,7 +136,30 @@ export class TimeInstant extends TimeBase<TimeInstant> {
135
136
  * @returns a string, formatted using the given pattern, at the given timeZone with the given locale.
136
137
  */
137
138
  public format( pattern: string, config: { locale?: string, timeZone?: string } = {} ) {
138
- return format( this.toDate(), pattern, config );
139
+ return formatTimeInstant( this, pattern, config );
140
+ }
141
+
142
+ /**
143
+ * Parses a date string using the given pattern and creates a TimeInstant.
144
+ * This method is the inverse of format() - parsing a formatted string should recreate the original instant.
145
+ *
146
+ * For partial patterns (e.g., time-only or date-only), the missing components are taken from the base instant.
147
+ * For example, parsing "14:30" with base set to yesterday at midnight will result in yesterday at 14:30.
148
+ *
149
+ * Note: Currently performs basic validation (e.g., month 1-12, day 1-31) but does not validate
150
+ * calendar-specific constraints (e.g., February 30th, April 31st). Invalid dates may be
151
+ * normalized by the underlying Date constructor.
152
+ *
153
+ * @param dateString The date string to parse
154
+ * @param pattern The pattern used to parse the string (same tokens as format())
155
+ * @param base The base TimeInstant to use for partial patterns (defaults to now)
156
+ * @param config An optional locale and timeZone definition to use during parsing
157
+ * @returns A TimeInstant parsed from the string
158
+ * @throws Error if the string doesn't match the pattern or contains invalid basic values
159
+ * @todo Add calendar-aware validation to reject dates like February 30th, April 31st
160
+ */
161
+ public static parse( dateString: string, pattern: string, base: TimeInstant = TimeInstant.now(), config: { locale?: string, timeZone?: string } = {} ): TimeInstant {
162
+ return parseTimeInstant( dateString, pattern, base, config );
139
163
  }
140
164
 
141
165
  /**
@@ -241,16 +265,58 @@ export class TimeInstant extends TimeBase<TimeInstant> {
241
265
  return timeInstantBuilder();
242
266
  }
243
267
 
244
- public static fromIso8601( str: TIso8601DateString ) {
245
- return TimeInstant.fromUnixTimestamp( new Date( str ).getTime() );
246
- }
268
+ public static fromIso8601( str: string ) {
269
+ // Regex to capture: YYYY-MM-DDTHH:mm:ss.sssZ or YYYY-MM-DDTHH:mm:ss.sss±HH:mm
270
+ const iso8601Regex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3})(Z|[+-]\d{2}:\d{2})?$/;
271
+ const match = str.match( iso8601Regex );
272
+
273
+ if ( !match ) {
274
+ throw new Error( 'Invalid ISO 8601 date format: ' + str );
275
+ }
247
276
 
248
- public static tryFromIso8601( str: string ) {
249
- if ( !str.match( /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:.\d{3}|)(?:Z|[+-]\d{2}:\d{2})?$/ ) )
250
- throw new Error( 'Invalid date given: ' + str );
251
- return TimeInstant.fromUnixTimestamp( new Date( str ).getTime() );
277
+ const [ , yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr, millisecondStr ] = match;
278
+
279
+ // Parse strings to numbers
280
+ const year = parseInt( yearStr, 10 );
281
+ const month = parseInt( monthStr, 10 );
282
+ const date = parseInt( dayStr, 10 );
283
+ const hours = parseInt( hourStr, 10 );
284
+ const minutes = parseInt( minuteStr, 10 );
285
+ const seconds = parseInt( secondStr, 10 );
286
+ const milliseconds = parseInt( millisecondStr, 10 );
287
+
288
+ // Validate each component using standalone validation functions
289
+ if ( !isValidYear( year ) )
290
+ throw new Error( 'Invalid year in: ' + str );
291
+ if ( !isValidMonth( month ) )
292
+ throw new Error( 'Invalid month in: ' + str );
293
+ if ( !isValidDayOfMonth( date ) )
294
+ throw new Error( 'Invalid day in: ' + str );
295
+ if ( !isValidHour( hours ) )
296
+ throw new Error( 'Invalid hour in: ' + str );
297
+ if ( !isValidMinute( minutes ) )
298
+ throw new Error( 'Invalid minute in: ' + str );
299
+ if ( !isValidSecond( seconds ) )
300
+ throw new Error( 'Invalid second in: ' + str );
301
+ if ( !isValidMillisecond( milliseconds ) )
302
+ throw new Error( 'Invalid millisecond in: ' + str );
303
+
304
+ return TimeInstant.fromParameters( {
305
+ year,
306
+ month,
307
+ date,
308
+ hours,
309
+ minutes,
310
+ seconds,
311
+ milliseconds,
312
+ } );
252
313
  }
253
314
 
315
+ /**
316
+ * @deprecated [2025.10.19]: Use fromIso8601 instead.
317
+ */
318
+ public static tryFromIso8601 = this.fromIso8601;
319
+
254
320
  public static now(): TimeInstant {
255
321
  return TimeInstant.fromUnixTimestamp( Date.now() );
256
322
  }
@@ -286,19 +352,19 @@ export class TimeInstant extends TimeBase<TimeInstant> {
286
352
  }
287
353
 
288
354
  public get dayOfMonth(): TDayOfMonth {
289
- return this.toDate().getDate() as TDayOfMonth;
355
+ return this.toDate().getUTCDate() as TDayOfMonth;
290
356
  }
291
357
 
292
358
  public get dayOfWeek(): TDayOfWeek {
293
- return this.toDate().getDay() + 1 as TDayOfWeek;
359
+ return this.toDate().getUTCDay() + 1 as TDayOfWeek;
294
360
  }
295
361
 
296
362
  public get month(): TMonth {
297
- return this.toDate().getMonth() + 1 as TMonth;
363
+ return this.toDate().getUTCMonth() + 1 as TMonth;
298
364
  }
299
365
 
300
366
  public get year(): number {
301
- return this.toDate().getFullYear();
367
+ return this.toDate().getUTCFullYear();
302
368
  }
303
369
 
304
370
  /**
@@ -344,3 +410,343 @@ export function isTimeInstant( x: unknown ): x is TimeInstant {
344
410
  }
345
411
 
346
412
  export default TimeInstant;
413
+
414
+
415
+ // Utility functions for date formatting and parsing
416
+
417
+ // Standalone validation functions
418
+ // TODO: Add calendar-aware validation to check month-specific day limits
419
+ // (e.g., reject February 30th, April 31st, February 29th in non-leap years)
420
+ function isValidYear( num: number ): boolean {
421
+ return num >= 0 && num <= 9999;
422
+ }
423
+
424
+ function isValidMonth( num: number ): num is TMonth {
425
+ return num >= 1 && num <= 12;
426
+ }
427
+
428
+ function isValidDayOfMonth( num: number ): num is TDayOfMonth {
429
+ return num >= 1 && num <= 31;
430
+ }
431
+
432
+ function isValidHour( num: number ): num is THourOfDay {
433
+ return num >= 0 && num <= 23;
434
+ }
435
+
436
+ function isValidMinute( num: number ): num is TMinuteOfHour {
437
+ return num >= 0 && num <= 59;
438
+ }
439
+
440
+ function isValidSecond( num: number ): num is TSecondOfMinute {
441
+ return num >= 0 && num <= 59;
442
+ }
443
+
444
+ function isValidMillisecond( num: number ): num is TMillisecondOfSecond {
445
+ return num >= 0 && num <= 999;
446
+ }
447
+
448
+ const PATTERN_REGEX = /(M|y|d|D|h|H|m|s|S|G|Z|P|a)+/g;
449
+ const ESCAPE_REGEX = /\\"|"((?:\\"|[^"])*)"/g;
450
+
451
+ const optionNames = {
452
+ y: 'year',
453
+ M: 'month',
454
+ d: 'day',
455
+ D: 'weekday',
456
+ S: 'fractionalSecondDigits',
457
+ G: 'era',
458
+ Z: 'timeZoneName',
459
+ P: 'dayPeriod',
460
+ a: 'hour12',
461
+ h: 'hour',
462
+ H: 'hour',
463
+ m: 'minute',
464
+ s: 'second',
465
+ } as const;
466
+
467
+ const values = {
468
+ y: [ 'numeric', '2-digit', undefined, 'numeric' ],
469
+ M: [ 'narrow', '2-digit', 'short', 'long' ],
470
+ d: [ 'numeric', '2-digit' ],
471
+ D: [ 'narrow', 'short', 'long' ],
472
+ S: [ 1, 2, 3 ],
473
+ G: [ 'narrow', 'short', 'long' ],
474
+ Z: [ 'short', 'long' ],
475
+ P: [ 'narrow', 'short', 'long' ],
476
+ a: [ true ],
477
+ h: [ "numeric", "2-digit" ],
478
+ H: [ "numeric", "2-digit" ],
479
+ m: [ "numeric", "2-digit" ],
480
+ s: [ "numeric", "2-digit" ],
481
+ } as const;
482
+
483
+ function padIf( condition: boolean, value: string | number, length: number ): string {
484
+ return condition && length === 2 && Number( value ) / 10 < 1 ? '0' + value : String( value );
485
+ }
486
+
487
+ function formatType( instant: TimeInstant, type: string, length: number, { locale, timeZone }: { locale?: string, timeZone?: string } = {} ): string | undefined {
488
+ // For timezone-sensitive formatting, we still need to use Intl.DateTimeFormat with a Date
489
+ // But for basic numeric formatting, we can use the TimeInstant's properties directly
490
+
491
+ // Handle basic numeric formatting directly from TimeInstant properties (UTC-based)
492
+ if ( type === 'y' ) {
493
+ const year = instant.year;
494
+ if ( length === 2 ) {
495
+ return String( year ).slice( -2 );
496
+ }
497
+ return String( year );
498
+ }
499
+
500
+ if ( type === 'M' && ( length === 1 || length === 2 ) ) {
501
+ const month = instant.month;
502
+ return length === 2 && month < 10 ? '0' + month : String( month );
503
+ }
504
+
505
+ if ( type === 'd' && ( length === 1 || length === 2 ) ) {
506
+ const day = instant.dayOfMonth;
507
+ return length === 2 && day < 10 ? '0' + day : String( day );
508
+ }
509
+
510
+ // Handle time components directly from UTC Date to avoid timezone conversion
511
+ if ( type === 'H' && ( length === 1 || length === 2 ) ) {
512
+ const hours = instant.toDate().getUTCHours();
513
+ return length === 2 && hours < 10 ? '0' + hours : String( hours );
514
+ }
515
+
516
+ if ( type === 'h' && ( length === 1 || length === 2 ) ) {
517
+ let hours = instant.toDate().getUTCHours();
518
+ hours = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
519
+ return length === 2 && hours < 10 ? '0' + hours : String( hours );
520
+ }
521
+
522
+ if ( type === 'm' && ( length === 1 || length === 2 ) ) {
523
+ const minutes = instant.toDate().getUTCMinutes();
524
+ return length === 2 && minutes < 10 ? '0' + minutes : String( minutes );
525
+ }
526
+
527
+ if ( type === 's' && ( length === 1 || length === 2 ) ) {
528
+ const seconds = instant.toDate().getUTCSeconds();
529
+ return length === 2 && seconds < 10 ? '0' + seconds : String( seconds );
530
+ }
531
+
532
+ if ( type === 'S' ) {
533
+ const ms = instant.toDate().getUTCMilliseconds();
534
+ if ( length === 1 ) return String( Math.floor( ms / 100 ) );
535
+ if ( length === 2 ) return String( Math.floor( ms / 10 ) ).padStart( 2, '0' );
536
+ if ( length === 3 ) return String( ms ).padStart( 3, '0' );
537
+ return String( ms );
538
+ }
539
+
540
+ // For timezone-sensitive and locale-sensitive formatting, use Date with Intl
541
+ const date = instant.toDate();
542
+ const option = optionNames[ type as keyof typeof optionNames ];
543
+ const value = values[ type as keyof typeof values ][ length - 1 ];
544
+
545
+ if ( !value ) {
546
+ return;
547
+ }
548
+
549
+ const options = {
550
+ [ option ]: value,
551
+ timeZone,
552
+ } as Intl.DateTimeFormatOptions;
553
+
554
+ if ( type === 'a' ) {
555
+ return Intl.DateTimeFormat( locale, {
556
+ ...options,
557
+ hour: 'numeric',
558
+ } )
559
+ .formatToParts( date )
560
+ .pop()?.value;
561
+ }
562
+
563
+ if ( type === 'G' || type === 'Z' ) {
564
+ return Intl.DateTimeFormat( locale, options ).formatToParts( date ).pop()?.value;
565
+ }
566
+
567
+ // For other complex formatting, fall back to Intl
568
+ return Intl.DateTimeFormat( locale, options ).format( date );
569
+ }
570
+
571
+ function formatTimeInstant( instant: TimeInstant, pattern: string, config: { locale?: string, timeZone?: string } = {} ): string {
572
+ return pattern
573
+ .split( ESCAPE_REGEX )
574
+ .filter( ( sub ) => sub !== undefined )
575
+ .map( ( sub, index ) => {
576
+ // keep escaped strings as is
577
+ if ( index % 2 !== 0 ) {
578
+ return sub;
579
+ }
580
+
581
+ return sub.replace( PATTERN_REGEX, ( match ) => {
582
+ const type = match.charAt( 0 );
583
+ return formatType( instant, type, match.length, config ) || match;
584
+ } );
585
+ } )
586
+ .join( '' );
587
+ }
588
+
589
+ function parseTimeInstant( dateString: string, pattern: string, base: TimeInstant, config: { locale?: string, timeZone?: string } = {} ): TimeInstant {
590
+ // Create a regex pattern from the format pattern
591
+ let regexPattern = pattern;
592
+ const tokens: { type: string; length: number; position: number }[] = [];
593
+ let position = 0;
594
+
595
+ // Split by escape regex first, like the original implementation
596
+ const parts = pattern.split( ESCAPE_REGEX ).filter( ( sub ) => sub !== undefined );
597
+
598
+ // Rebuild pattern and track tokens
599
+ regexPattern = parts.map( ( sub, index ) => {
600
+ // Skip escaped strings (odd indices after split)
601
+ if ( index % 2 !== 0 ) {
602
+ return sub.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ); // Escape special regex chars
603
+ }
604
+
605
+ return sub.replace( PATTERN_REGEX, ( match ) => {
606
+ const type = match.charAt( 0 );
607
+ tokens.push( { type, length: match.length, position: position++ } );
608
+
609
+ // Create appropriate regex for each token type
610
+ switch ( type ) {
611
+ case 'y':
612
+ return match.length === 2 ? '(\\d{2})' : '(\\d{4})';
613
+ case 'M':
614
+ if ( match.length === 1 ) return '(\\d{1,2})';
615
+ if ( match.length === 2 ) return '(\\d{2})';
616
+ if ( match.length === 3 ) return '([A-Za-z]{3})';
617
+ return '([A-Za-z]+)';
618
+ case 'd':
619
+ return match.length === 1 ? '(\\d{1,2})' : '(\\d{2})';
620
+ case 'H':
621
+ case 'h':
622
+ return match.length === 1 ? '(\\d{1,2})' : '(\\d{2})';
623
+ case 'm':
624
+ case 's':
625
+ return match.length === 1 ? '(\\d{1,2})' : '(\\d{2})';
626
+ case 'S':
627
+ return `(\\d{${match.length}})`;
628
+ case 'a':
629
+ return '([aApP][mM])';
630
+ case 'D':
631
+ if ( match.length === 1 ) return '([A-Za-z])';
632
+ if ( match.length === 2 ) return '([A-Za-z]{3})';
633
+ return '([A-Za-z]+)';
634
+ case 'G':
635
+ if ( match.length === 1 ) return '([A-Za-z])';
636
+ if ( match.length === 2 ) return '([A-Za-z]{2})';
637
+ return '([A-Za-z\\s]+)';
638
+ case 'Z':
639
+ return match.length === 1 ? '([A-Za-z0-9+\\-:]+)' : '([A-Za-z\\s]+)';
640
+ case 'P':
641
+ return '([A-Za-z\\s]+)';
642
+ default:
643
+ return match;
644
+ }
645
+ } );
646
+ } ).join( '' );
647
+
648
+ const regex = new RegExp( '^' + regexPattern + '$' );
649
+ const matches = dateString.match( regex );
650
+
651
+ if ( !matches ) {
652
+ throw new Error( `Date string "${dateString}" does not match pattern "${pattern}"` );
653
+ }
654
+
655
+ // Extract values from matches using base instant as default
656
+ let year = base.year;
657
+ let month = base.month;
658
+ let day = base.dayOfMonth;
659
+ let hour = base.toDate().getUTCHours();
660
+ let minute = base.toDate().getUTCMinutes();
661
+ let second = base.toDate().getUTCSeconds();
662
+ let millisecond = base.toDate().getUTCMilliseconds();
663
+ let isPM = false;
664
+ let is12Hour = false;
665
+
666
+ tokens.forEach( ( token, index ) => {
667
+ const value = matches[ index + 1 ];
668
+
669
+ switch ( token.type ) {
670
+ case 'y':
671
+ if ( token.length === 2 ) {
672
+ const shortYear = parseInt( value, 10 );
673
+ year = shortYear < 50 ? 2000 + shortYear : 1900 + shortYear;
674
+ } else {
675
+ year = parseInt( value, 10 );
676
+ }
677
+ break;
678
+ case 'M':
679
+ if ( token.length <= 2 ) {
680
+ month = parseInt( value, 10 ) as TMonth;
681
+ } else {
682
+ // Handle month names - simplified approach
683
+ const monthNames = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
684
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];
685
+ const longMonthNames = [ 'January', 'February', 'March', 'April', 'May', 'June',
686
+ 'July', 'August', 'September', 'October', 'November', 'December' ];
687
+
688
+ let monthIndex = monthNames.findIndex( name =>
689
+ name.toLowerCase() === value.toLowerCase() );
690
+ if ( monthIndex === -1 ) {
691
+ monthIndex = longMonthNames.findIndex( name =>
692
+ name.toLowerCase() === value.toLowerCase() );
693
+ }
694
+ month = ( monthIndex !== -1 ? monthIndex + 1 : 1 ) as TMonth;
695
+ }
696
+ break;
697
+ case 'd':
698
+ day = parseInt( value, 10 ) as TDayOfMonth;
699
+ break;
700
+ case 'H':
701
+ hour = parseInt( value, 10 );
702
+ break;
703
+ case 'h':
704
+ hour = parseInt( value, 10 );
705
+ is12Hour = true;
706
+ break;
707
+ case 'm':
708
+ minute = parseInt( value, 10 );
709
+ break;
710
+ case 's':
711
+ second = parseInt( value, 10 );
712
+ break;
713
+ case 'S':
714
+ let ms = parseInt( value, 10 );
715
+ // Normalize to milliseconds based on length
716
+ if ( token.length === 1 ) ms *= 100;
717
+ else if ( token.length === 2 ) ms *= 10;
718
+ millisecond = ms;
719
+ break;
720
+ case 'a':
721
+ isPM = value.toLowerCase().includes( 'p' );
722
+ break;
723
+ }
724
+ } );
725
+
726
+ // Handle 12-hour format
727
+ if ( is12Hour && isPM && hour < 12 ) {
728
+ hour += 12;
729
+ } else if ( is12Hour && !isPM && hour === 12 ) {
730
+ hour = 0;
731
+ }
732
+
733
+ // Validate each component using standalone validation functions
734
+ if ( !isValidYear( year ) ) throw new Error( `Invalid year in date string: ${dateString}` );
735
+ if ( !isValidMonth( month ) ) throw new Error( `Invalid month in date string: ${dateString}` );
736
+ if ( !isValidDayOfMonth( day ) ) throw new Error( `Invalid day in date string: ${dateString}` );
737
+ if ( !isValidHour( hour ) ) throw new Error( `Invalid hour in date string: ${dateString}` );
738
+ if ( !isValidMinute( minute ) ) throw new Error( `Invalid minute in date string: ${dateString}` );
739
+ if ( !isValidSecond( second ) ) throw new Error( `Invalid second in date string: ${dateString}` );
740
+ if ( !isValidMillisecond( millisecond ) ) throw new Error( `Invalid millisecond in date string: ${dateString}` );
741
+
742
+ // Use TimeInstant.fromParameters instead of creating a Date
743
+ return TimeInstant.fromParameters( {
744
+ year,
745
+ month: month as TMonth,
746
+ date: day as TDayOfMonth,
747
+ hours: hour as THourOfDay,
748
+ minutes: minute as TMinuteOfHour,
749
+ seconds: second as TSecondOfMinute,
750
+ milliseconds: millisecond as TMillisecondOfSecond,
751
+ } );
752
+ }
@@ -66,7 +66,7 @@ const timeInstantResolveValue = ( getFromDate: TFunction<Date, number>, referenc
66
66
  };
67
67
  const getFromDate = {
68
68
  year: d => d.getFullYear(),
69
- month: d => d.getMonth(),
69
+ month: d => d.getMonth() + 1,
70
70
  date: d => d.getDate(),
71
71
  hours: d => d.getHours(),
72
72
  minutes: d => d.getMinutes(),
@@ -84,7 +84,7 @@ export function createTimeInstantFromParameters( aParameters: Partial<TTimeInsta
84
84
  const referenceDate = toReferenceDate( aReferenceDate );
85
85
  const timestamp = Date.UTC(
86
86
  timeInstantResolveValue( getFromDate.year, referenceDate, year ),
87
- timeInstantResolveValue( getFromDate.month, referenceDate, month ),
87
+ timeInstantResolveValue( getFromDate.month, referenceDate, month ) - 1, // Convert from 1-based to 0-based for Date.UTC()
88
88
  timeInstantResolveValue( getFromDate.date, referenceDate, date ),
89
89
  timeInstantResolveValue( getFromDate.hours, referenceDate, hours ),
90
90
  timeInstantResolveValue( getFromDate.minutes, referenceDate, minutes ),
@@ -17,3 +17,5 @@ export type TIsEmptyObject<T> = keyof T extends never ? true : false;
17
17
  * ```
18
18
  */
19
19
  export type TConditionalOptionalType<T> = TIsEmptyObject<T> extends true ? [] : [ T ];
20
+
21
+ export type TEmptyObject = Record<string, never>;