From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 62A0D1FF183 for ; Wed, 17 Dec 2025 11:29:33 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id E28B3253AB; Wed, 17 Dec 2025 11:30:20 +0100 (CET) From: Dominik Csapak To: yew-devel@lists.proxmox.com Date: Wed, 17 Dec 2025 11:30:10 +0100 Message-ID: <20251217103017.1103164-1-d.csapak@proxmox.com> X-Mailer: git-send-email 2.47.3 MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.029 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_SHORT 0.001 Use of a URL Shortener for very short URL RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [php.net] Subject: [yew-devel] [PATCH yew-widget-toolkit 1/2] plain date: improve format parser X-BeenThere: yew-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Yew framework devel list at Proxmox List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Yew framework devel list at Proxmox Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: yew-devel-bounces@lists.proxmox.com Sender: "yew-devel" by using more idiomatic rust code, and also fixes some bugs that are now tested with additional test cases. In particular this changes the parser from being purely index based to using a mutable slice for proceeding, and an iterator + match for handling the different format characters. The new tests are for doing early error checking e.g. in case the format string contains multiple variants of the same type, and requires valid values for those. Signed-off-by: Dominik Csapak --- src/widget/form/date_field/plain_date.rs | 202 ++++++++++------------- 1 file changed, 91 insertions(+), 111 deletions(-) diff --git a/src/widget/form/date_field/plain_date.rs b/src/widget/form/date_field/plain_date.rs index 80c036a..e7d8a97 100644 --- a/src/widget/form/date_field/plain_date.rs +++ b/src/widget/form/date_field/plain_date.rs @@ -1,5 +1,5 @@ use js_sys::Date; -use std::fmt; +use std::{fmt, str::FromStr}; /// A date without time information (Year, Month, Day). /// Months are 0-based (0 = January, 11 = December). @@ -161,133 +161,102 @@ impl PlainDate { /// - d: Day with leading zeros /// - j: Day without leading zeros /// Reference: https://www.php.net/manual/en/datetime.format.php - pub fn from_format(input: &str, fmt: &str) -> Result { + pub fn from_format(mut input: &str, fmt: &str) -> Result { // Implementation: - let mut year = None; - let mut month = None; - let mut day = None; + let mut year: Option = None; + let mut month: Option = None; + let mut day: Option = None; - // Convert to look-ahead friendly chars - let fmt_chars: Vec = fmt.chars().collect(); - let input_chars: Vec = input.chars().collect(); - - let mut f_idx = 0; - let mut i_idx = 0; + fn parse_num(input: &str, err: String) -> Result { + input.parse().map_err(|_| err) + } - while f_idx < fmt_chars.len() { - if i_idx >= input_chars.len() { - return Err("Input too short".to_string()); + fn find_num_length(input: &str) -> usize { + let mut chars = input.chars(); + if let Some(c1) = chars.next() { + if c1.is_ascii_digit() { + if ('1'..='3').contains(&c1) { + if let Some(c2) = chars.next() { + if c2.is_ascii_digit() { + return 2; + } + } + } + return 1; + } } + 0 + } - // Check tokens - if fmt[f_idx..].starts_with("Y") { - // PHP Y: 4 digit year - if i_idx + 4 > input_chars.len() { - return Err("Input ends before Year".into()); - } - let y_str: String = input_chars[i_idx..i_idx + 4].iter().collect(); - year = y_str.parse::().ok(); - f_idx += 1; - i_idx += 4; - } else if fmt[f_idx..].starts_with("y") { - // PHP y: 2 digit year - if i_idx + 2 > input_chars.len() { - return Err("Input ends before Year".into()); + for f_char in fmt.chars() { + let took = match f_char { + 'Y' => { + // PHP Y: 4 digit year + if input.len() < 4 { + return Err("Input ends before Year".into()); + } + year = Some(parse_num(&input[..4], "Invalid year".to_string())?); + 4 } - let y_str: String = input_chars[i_idx..i_idx + 2].iter().collect(); - if let Ok(y2) = y_str.parse::() { - // Pivot strategy: 2000-2099 defaults - year = Some(2000 + y2); + 'y' => { + // PHP y: 2 digit year + if input.len() < 2 { + return Err("Input ends before Year".into()); + } + let y: i32 = parse_num(&input[..2], "Invalid year".to_string())?; + year = Some(y + 2000); + 2 } - f_idx += 1; - i_idx += 2; - } else if fmt[f_idx..].starts_with("m") { - // PHP m: Month with leading zeros (01-12) - if i_idx + 2 > input_chars.len() { - return Err("Input ends before Month".into()); + 'm' => { + // PHP m: Month with leading zeros (01-12) + if input.len() < 2 { + return Err("Input ends before Month".into()); + } + month = Some(parse_num(&input[..2], "Invalid month".to_string())?); + 2 } - let m_str: String = input_chars[i_idx..i_idx + 2].iter().collect(); - month = m_str.parse::().ok(); - f_idx += 1; - i_idx += 2; - } else if fmt[f_idx..].starts_with("d") { - // PHP d: Day with leading zeros (01-31) - if i_idx + 2 > input_chars.len() { - return Err("Input ends before Day".into()); + 'd' => { + // PHP d: Day with leading zeros (01-31) + if input.len() < 2 { + return Err("Input ends before Day".into()); + } + day = Some(parse_num(&input[..2], "Invalid day".to_string())?); + 2 } - let d_str: String = input_chars[i_idx..i_idx + 2].iter().collect(); - day = d_str.parse::().ok(); - f_idx += 1; - i_idx += 2; - } else if fmt[f_idx..].starts_with("n") { - // PHP n: Month without leading zeros (1-12) - let next_sep = fmt_chars.get(f_idx + 1); - let mut took = 0; - if let Some(c1) = input_chars.get(i_idx) { - if c1.is_ascii_digit() { - took = 1; - if let Some(c2) = input_chars.get(i_idx + 1) { - if c2.is_ascii_digit() { - // Variable length logic - if let Some(sep) = next_sep { - if *sep != *c2 { - took = 2; - } - } else { - took = 2; - } - } - } + 'n' => { + // PHP n: Month without leading zeros (1-12) + let took = find_num_length(input); + if took == 0 { + return Err("Expected digit for Month".into()); } + month = Some(parse_num(&input[..took], "Invalid month".to_string())?); + took } - if took == 0 { - return Err("Expected digit for Month".into()); + 'j' => { + // PHP j: Day without leading zeros (1-31) + let took = find_num_length(input); + if took == 0 { + return Err("Expected digit for Day".into()); + } + day = Some(parse_num(&input[..took], "Invalid day".to_string())?); + took } - let m_str: String = input_chars[i_idx..i_idx + took].iter().collect(); - month = m_str.parse::().ok(); - f_idx += 1; - i_idx += took; - } else if fmt[f_idx..].starts_with("j") { - // PHP j: Day without leading zeros (1-31) - let next_sep = fmt_chars.get(f_idx + 1); - let mut took = 0; - if let Some(c1) = input_chars.get(i_idx) { - if c1.is_ascii_digit() { - took = 1; - if let Some(c2) = input_chars.get(i_idx + 1) { - if c2.is_ascii_digit() { - if let Some(sep) = next_sep { - if *sep != *c2 { - took = 2; - } - } else { - took = 2; - } - } + _ => { + // Literal match + if let Some(c) = input.chars().next() { + if f_char != c { + return Err(format!("Expected '{}', found '{}'", f_char, c)); } + } else { + return Err("Input ends too soon.".to_string()); } + 1 } - if took == 0 { - return Err("Expected digit for Day".into()); - } - let d_str: String = input_chars[i_idx..i_idx + took].iter().collect(); - day = d_str.parse::().ok(); - f_idx += 1; - i_idx += took; - } else { - // Literal match - if fmt_chars[f_idx] != input_chars[i_idx] { - return Err(format!( - "Expected '{}', found '{}'", - fmt_chars[f_idx], input_chars[i_idx] - )); - } - f_idx += 1; - i_idx += 1; - } + }; + input = &input[took..]; } - if i_idx != input_chars.len() { + if !input.is_empty() { return Err("Input longer than format".into()); } @@ -401,6 +370,17 @@ mod tests { let d4 = PlainDate::from_format("01/02/2023", "m/d/Y").unwrap(); assert_eq!(d4.month(), 0); assert_eq!(d4.day(), 2); + + assert!(PlainDate::from_format("01/02/zzzz", "m/d/Y").is_err()); + assert!(PlainDate::from_format("01/yy/2023", "m/d/Y").is_err()); + assert!(PlainDate::from_format("xx/02/zzzz", "m/d/Y").is_err()); + assert!(PlainDate::from_format("01/yy/zzzz", "m/d/Y").is_err()); + assert!(PlainDate::from_format("xx/02/zzzz", "m/d/Y").is_err()); + assert!(PlainDate::from_format("xx/yy/2023", "m/d/Y").is_err()); + assert!(PlainDate::from_format("xx/yy/zzzz", "m/d/Y").is_err()); + + assert!(PlainDate::from_format("xx01/02/2023", "mm/d/Y").is_err()); + assert!(PlainDate::from_format("1022023", "nmY").is_err()) } #[test] -- 2.47.3 _______________________________________________ yew-devel mailing list yew-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/yew-devel