This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch 
gh-readonly-queue/main/pr-11179-16310a9f7b94463e583dc7b70c812333e2cba75d
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git

commit fafa22b7c57b8e802dccd6e7fcb65afcc654fc4d
Author: Amit Vijapur <[email protected]>
AuthorDate: Tue Oct 27 00:07:51 2026 +0000

    fix(arrow-cast): parse ISO 8601 timestamps without seconds (#11179)
    
    # Which issue does this PR close?
    
    Part of #8219. This covers omission of seconds only; hour-only and other
    ISO 8601 forms remain separate follow-ups.
    
    # Rationale for this change
    
    `string_to_datetime` documents RFC 3339 / ISO 8601 support, but
    `TimestampParser::time` only accepts `HH:MM:SS` and `HHMMSS`, so an ISO
    8601 time without seconds such as `2025-08-14T00:00Z` fails with `error
    parsing time`. @alamb agreed on the issue that this form should parse,
    provided performance is not hurt; the new arm sits after the existing
    `HH:MM:SS` and `HHMMSS` arms, so those forms match before its guard
    runs. @klion26 offered to fix it on 2025-09-08 and no PR followed in the
    year since, so I picked it up.
    
    # What changes are included in this PR?
    
    One more arm in `TimestampParser::time` accepts `HH:MM` when byte 16 is
    neither a digit nor `:` nor `.`, so `HH:MM:SS`, `HH:MM:` and `HH:MM.f`
    keep their current handling and errors. Seconds and nanoseconds are zero
    and the timezone suffix is parsed as before. The doc list of accepted
    inputs gains the new form.
    
    One consequence: `Date32Type::parse("2020-09-08 01:02")` now succeeds,
    since the string is a valid timestamp. The `parse_date32` test moves
    that input from the error cases to the parsed ones.
    
    # Are these changes tested?
    
    `string_to_timestamp_without_seconds` checks four inputs (with `Z`, no
    suffix, `+05:30`, and a lowercase `t` with `-08:00`) against their `:00`
    forms. `string_to_timestamp_invalid` gains `09:2Z`, `09:26:Z`,
    `09:26.5Z` and `09:61Z`, all still `error parsing time`. `cargo test -p
    arrow-cast` passes.
    
    # Are there any user-facing changes?
    
    Timestamps whose time has no seconds now parse in `string_to_datetime`,
    and through it in the string to timestamp and date casts.
    
    Codex prepared the documentation follow-up and these review
    explanations. The documentation-only follow-up passed all 387 arrow-cast
    library tests and workspace formatting on October 15. Codex ran the
    mask/guard mutation checks on October 12 and reverted both mutations. No
    performance benchmark was rerun for the documentation follow-up.
    
    ---------
    
    Signed-off-by: Amit Vijapur <[email protected]>
---
 arrow-cast/src/parse.rs | 33 ++++++++++++++++++++++++++++++++-
 1 file changed, 32 insertions(+), 1 deletion(-)

diff --git a/arrow-cast/src/parse.rs b/arrow-cast/src/parse.rs
index a571368d63..78b1d7dc36 100644
--- a/arrow-cast/src/parse.rs
+++ b/arrow-cast/src/parse.rs
@@ -83,6 +83,7 @@ impl TimestampParser {
     }
 
     /// Parses a time of any of forms
+    /// - `09:26`
     /// - `09:26:56`
     /// - `09:26:56.123`
     /// - `09:26:56.123456`
@@ -135,6 +136,16 @@ impl TimestampParser {
                 let time = time(hour, minute, second, 0)?;
                 Some((time, 17))
             }
+            // 09:26
+            m if m & 0b111111 == 0b011011
+                && self.test(13, b':')
+                && !self.test(16, b':')
+                && !self.test(16, b'.') =>
+            {
+                let hour = self.digits[11] * 10 + self.digits[12];
+                let minute = self.digits[14] * 10 + self.digits[15];
+                Some((time(hour, minute, 0, 0)?, 16))
+            }
             _ => None,
         }
     }
@@ -149,6 +160,7 @@ impl TimestampParser {
 /// Examples of accepted inputs:
 /// * `1997-01-31T09:26:56.123Z`        # RCF3339
 /// * `1997-01-31T09:26:56.123-05:00`   # RCF3339
+/// * `1997-01-31T09:26Z`               # ISO8601 without seconds
 /// * `1997-01-31 09:26:56.123-05:00`   # close to RCF3339 but with a space 
rather than T
 /// * `2023-01-01 04:05:06.789 -08`     # close to RCF3339, no fractional 
seconds or time separator
 /// * `1997-01-31T09:26:56.123`         # close to RCF3339 but no timezone 
offset specified
@@ -1945,6 +1957,20 @@ mod tests {
         }
     }
 
+    #[test]
+    fn string_to_timestamp_without_seconds() {
+        let cases = [
+            ("2025-08-14T00:00Z", (2025, 8, 14, 0, 0)),
+            ("2025-08-14T09:26", (2025, 8, 14, 9, 26)),
+            ("2025-08-14 09:26+05:30", (2025, 8, 14, 3, 56)),
+            ("2025-08-14t23:59-08:00", (2025, 8, 15, 7, 59)),
+        ];
+        for (case, (y, m, d, h, min)) in cases {
+            let expected = Utc.with_ymd_and_hms(y, m, d, h, min, 0).unwrap();
+            assert_eq!(string_to_datetime(&Utc, case).unwrap(), expected)
+        }
+    }
+
     #[test]
     fn string_to_timestamp_invalid() {
         // Test parsing invalid formats
@@ -1968,6 +1994,10 @@ mod tests {
             ("1997-01-31T092656.123Z", "error parsing time"),
             ("1997-01-10T12:00:06.", "error parsing time"),
             ("1997-01-10T12:00:06. ", "error parsing time"),
+            ("1997-01-10T09:2Z", "error parsing time"),
+            ("1997-01-10T09:26:Z", "error parsing time"),
+            ("1997-01-10T09:26.5Z", "error parsing time"),
+            ("1997-01-10T09:61Z", "error parsing time"),
         ];
 
         for (s, ctx) in cases {
@@ -2060,11 +2090,13 @@ mod tests {
             "2020-12-1",
             "1690-2-5",
             "2020-09-08 01:02:03",
+            "2020-09-08 01:02",
         ];
         for case in cases {
             let v = 
date32_to_datetime(Date32Type::parse(case).unwrap()).unwrap();
             let expected = NaiveDate::parse_from_str(case, "%Y-%m-%d")
                 .or_else(|_| NaiveDate::parse_from_str(case, "%Y-%m-%d 
%H:%M:%S"))
+                .or_else(|_| NaiveDate::parse_from_str(case, "%Y-%m-%d %H:%M"))
                 .unwrap();
             assert_eq!(v.date(), expected);
         }
@@ -2078,7 +2110,6 @@ mod tests {
             "2020--04-03",
             "2020--",
             "2020-09-08 01",
-            "2020-09-08 01:02",
             "2020-09-08 01-02-03",
             "2020-9-8 01:02:03",
             "2020-09-08 1:2:3",

Reply via email to