Repository: jsonata-js/jsonata (js)

Issue:
Issue with time conversion (string -->ISO) - deviation from intended time when using to/from millis
Working with latest version. When trying to convert a local timeformat (YYYYMMDDHHmmss), I get an issue of misconversion;
For the input string:

```
"20240101123849"
``` 

I want to recieve:
```
"2024-01-01T12:38:49Z",
```

However, using the code:

```
$fromMillis($toMillis($,"[Y0000][M00][D00][H00][m00][s00]"))
``` 

results in:
```
 "2024-02-21T14:04:09.000Z"
```

using the "brute force" approach with 
```
(
    $substring($,0,4) & "-" & 
    $substring($,4,2) & "-" & 
    $substring($,6,2) & "T" & 
    $substring($,8,2) & ":" & 
    $substring($,10,2) & ":" & 
    $substring($,12,2) & "Z";
  );
```
results in the right time.

I've noticed that the original seconds segment is translated to the minutes and seconds in the wrong output. changing the hour affects the day/month (depending on value). looks like an issue with the way the string is translated, however chaging the PICTURE parameter didn't seem to solve the isse.

Pull request description:
Improve regex generation for DateTime parser
If there are two adjacent integer definitions for date/time fields in the picture string without any extra markup between them, then the generated regex must precisely define the number of digits in the first field.

Resolves #727 

Patch:
diff --git a/src/datetime.js b/src/datetime.js
index 0121765..1d9b2d9 100644
--- a/src/datetime.js
+++ b/src/datetime.js
@@ -612,7 +612,7 @@ const dateTime = (function () {
                             def.integerFormat.mandatoryDigits = def.width.min;
                         }
                     }
-                    if ('YMD'.indexOf(def.component) !== -1) {
+                    if (def.component === 'Y') {
                         // §9.8.4.4
                         def.n = -1;
                         if (def.width && def.width.max !== undefined) {
@@ -625,6 +625,11 @@ const dateTime = (function () {
                             }
                         }
                     }
+                    // if the previous part is also an integer with no intervening markup, then its width for parsing must be precisely defined
+                    const previousPart = spec[spec.length - 1];
+                    if (previousPart && previousPart.integerFormat) {
+                        previousPart.integerFormat.parseWidth = previousPart.integerFormat.mandatoryDigits;
+                    }
                 }
                 if (def.component === 'Z' || def.component === 'z') {
                     def.integerFormat = analyseIntegerPicture(def.presentation1);
@@ -990,7 +995,6 @@ const dateTime = (function () {
                         return offsetHours * 60 + offsetMinutes;
                     };
                 } else if (part.integerFormat) {
-                    part.integerFormat.n = part.n;
                     res = generateRegex(part.integerFormat);
                 } else {
                     // must be a month or day name
@@ -1035,16 +1039,6 @@ const dateTime = (function () {
         } else { // type === 'integer'
             matcher.type = 'integer';
             const isUpper = formatSpec.case === tcase.UPPER;
-            let occurrences;
-            if(formatSpec.n && formatSpec.n > 0){
-                if(formatSpec.optionalDigits === 0){
-                    occurrences = `{${formatSpec.n}}`;
-                } else {
-                    occurrences = `{${formatSpec.n - formatSpec.optionalDigits},${formatSpec.n}}`;
-                }
-            } else {
-                occurrences = '+';
-            }
 
             switch (formatSpec.primary) {
                 case formats.LETTERS:
@@ -1066,7 +1060,12 @@ const dateTime = (function () {
                     };
                     break;
                 case formats.DECIMAL:
-                    matcher.regex = `[0-9]${occurrences}`;
+                    matcher.regex = '[0-9]';
+                    if (formatSpec.parseWidth) {
+                        matcher.regex += `{${formatSpec.parseWidth}}`;
+                    } else {
+                        matcher.regex += '+';
+                    }
                     if (formatSpec.ordinal) {
                         // ordinals
                         matcher.regex += '(?:th|st|nd|rd)';


Test patch:
diff --git a/test/test-suite/groups/function-tomillis/case013.json b/test/test-suite/groups/function-tomillis/case013.json
new file mode 100644
index 0000000..0e085d1
--- /dev/null
+++ b/test/test-suite/groups/function-tomillis/case013.json
@@ -0,0 +1,6 @@
+{
+    "expr": "$toMillis('2024-01-01T12:38:49Z') = $toMillis('20240101123849', '[Y0000][M00][D00][H00][m00][s00]')",
+    "data": null,
+    "bindings": {},
+    "result": true
+}