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