@zelgadis87/utils-core 5.3.5 → 5.3.6

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/.rollup/index.cjs CHANGED
@@ -2345,8 +2345,48 @@ function normalizeMonthName(name) {
2345
2345
  // Trim whitespace
2346
2346
  .trim();
2347
2347
  }
2348
- const PATTERN_REGEX = /(M|y|d|D|h|H|m|s|S|G|Z|P|a)+/g;
2349
2348
  const ESCAPE_REGEX = /\\"|"((?:\\"|[^"])*)"/g;
2349
+ /**
2350
+ * Tokenize a date/time pattern by detecting character changes.
2351
+ *
2352
+ * This function uses a character-change detection algorithm to split a pattern into tokens:
2353
+ * 1. Iterates through the pattern character by character
2354
+ * 2. Detects when the character changes (e.g., 'y' → 'M' → 'd')
2355
+ * 3. Creates tokens based on consecutive identical characters
2356
+ * 4. Marks tokens as pattern tokens only if they consist of repeated pattern characters
2357
+ *
2358
+ * This approach works correctly for patterns with or without separators:
2359
+ * - "yyyy-MM-dd" → [{token:"yyyy", type:"y"}, {token:"-", type:null}, {token:"MM", type:"M"}, {token:"-", type:null}, {token:"dd", type:"d"}]
2360
+ * - "yyyyMMdd" → [{token:"yyyy", type:"y"}, {token:"MM", type:"M"}, {token:"dd", type:"d"}]
2361
+ *
2362
+ * @param pattern - The date/time pattern to tokenize
2363
+ * @returns Array of tokens with their types (null for separators/literals, pattern char for pattern tokens)
2364
+ */
2365
+ function tokenizePattern(pattern) {
2366
+ const result = [];
2367
+ let currentToken = '';
2368
+ let currentChar = '';
2369
+ for (const char of pattern) {
2370
+ if (char === currentChar) {
2371
+ // Same character, extend current token
2372
+ currentToken += char;
2373
+ }
2374
+ else {
2375
+ // Character changed, end current token and start new one
2376
+ if (currentToken) {
2377
+ // Mark the token with its character (caller will decide if it's a valid pattern)
2378
+ result.push({ token: currentToken, type: currentChar });
2379
+ }
2380
+ currentToken = char;
2381
+ currentChar = char;
2382
+ }
2383
+ }
2384
+ // Don't forget the last token
2385
+ if (currentToken) {
2386
+ result.push({ token: currentToken, type: currentChar });
2387
+ }
2388
+ return result;
2389
+ }
2350
2390
  // Formatter configuration mapping: token pattern -> configuration with optional extraction rules
2351
2391
  const formatterConfigs = {
2352
2392
  // Year
@@ -2428,11 +2468,16 @@ function formatTimeInstant(instant, pattern, config = {}) {
2428
2468
  if (index % 2 !== 0) {
2429
2469
  return sub;
2430
2470
  }
2431
- return sub.replace(PATTERN_REGEX, (match) => {
2432
- const type = match.charAt(0);
2433
- const length = match.length;
2434
- return formatType(type, length, date, locale, timeZone) || match;
2435
- });
2471
+ // Tokenize the pattern by detecting character changes
2472
+ const tokens = tokenizePattern(sub);
2473
+ // Format each token and join the results
2474
+ return tokens.map(({ token, type }) => {
2475
+ // If type is null or formatting fails, keep token as-is
2476
+ if (!type)
2477
+ return token;
2478
+ const formatted = formatType(type, token.length, date, locale, timeZone);
2479
+ return formatted !== undefined ? formatted : token;
2480
+ }).join('');
2436
2481
  })
2437
2482
  .join('');
2438
2483
  }
@@ -2460,53 +2505,62 @@ function parseTimeInstantComponents(dateString, pattern, config = {}) {
2460
2505
  if (index % 2 !== 0) {
2461
2506
  return sub.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Escape special regex chars
2462
2507
  }
2463
- return sub.replace(PATTERN_REGEX, (match) => {
2464
- const type = match.charAt(0);
2465
- tokens.push({ type, length: match.length, position: position++ });
2508
+ // Tokenize the pattern by detecting character changes
2509
+ const patternTokens = tokenizePattern(sub);
2510
+ // Build regex pattern from tokens
2511
+ return patternTokens.map(({ token, type }) => {
2512
+ // Check if this is a valid pattern by looking it up in formatterConfigs
2513
+ const isValidPattern = type && formatterConfigs[token];
2514
+ if (!isValidPattern) {
2515
+ // This is a literal/separator, escape it for regex
2516
+ return token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2517
+ }
2518
+ // This is a pattern token, track it and create regex
2519
+ tokens.push({ type, length: token.length, position: position++ });
2466
2520
  // Create appropriate regex for each token type
2467
2521
  switch (type) {
2468
2522
  case 'y':
2469
- return match.length === 2 ? '(\\d{2})' : '(\\d{4})';
2523
+ return token.length === 2 ? '(\\d{2})' : '(\\d{4})';
2470
2524
  case 'M':
2471
- if (match.length === 1)
2525
+ if (token.length === 1)
2472
2526
  return '(\\d{1,2})';
2473
- if (match.length === 2)
2527
+ if (token.length === 2)
2474
2528
  return '(\\d{2})';
2475
- if (match.length === 3)
2529
+ if (token.length === 3)
2476
2530
  return '([A-Za-z.]{1,7})';
2477
2531
  return '([A-Za-z]+)';
2478
2532
  case 'd':
2479
- return match.length === 1 ? '(\\d{1,2})' : '(\\d{2})';
2533
+ return token.length === 1 ? '(\\d{1,2})' : '(\\d{2})';
2480
2534
  case 'H':
2481
2535
  case 'h':
2482
- return match.length === 1 ? '(\\d{1,2})' : '(\\d{2})';
2536
+ return token.length === 1 ? '(\\d{1,2})' : '(\\d{2})';
2483
2537
  case 'm':
2484
2538
  case 's':
2485
- return match.length === 1 ? '(\\d{1,2})' : '(\\d{2})';
2539
+ return token.length === 1 ? '(\\d{1,2})' : '(\\d{2})';
2486
2540
  case 'S':
2487
- return `(\\d{${match.length}})`;
2541
+ return `(\\d{${token.length}})`;
2488
2542
  case 'a':
2489
2543
  return '([aApP][mM])';
2490
2544
  case 'D':
2491
- if (match.length === 1)
2545
+ if (token.length === 1)
2492
2546
  return '([A-Za-z])';
2493
- if (match.length === 2)
2547
+ if (token.length === 2)
2494
2548
  return '([A-Za-z]{3})';
2495
2549
  return '([A-Za-z]+)';
2496
2550
  case 'G':
2497
- if (match.length === 1)
2551
+ if (token.length === 1)
2498
2552
  return '([A-Za-z])';
2499
- if (match.length === 2)
2553
+ if (token.length === 2)
2500
2554
  return '([A-Za-z]{2})';
2501
2555
  return '([A-Za-z\\s]+)';
2502
2556
  case 'Z':
2503
- return match.length === 1 ? '([A-Za-z0-9+\\-:]+)' : '([A-Za-z\\s]+)';
2557
+ return token.length === 1 ? '([A-Za-z0-9+\\-:]+)' : '([A-Za-z\\s]+)';
2504
2558
  case 'P':
2505
2559
  return '([A-Za-z\\s]+)';
2506
2560
  default:
2507
- return match;
2561
+ return token;
2508
2562
  }
2509
- });
2563
+ }).join('');
2510
2564
  }).join('');
2511
2565
  const regex = new RegExp('^' + regexPattern + '$');
2512
2566
  const matches = dateString.match(regex);