chrono_node/core/utils/
js.rs1pub fn is_js_whitespace(c: char) -> bool {
8 matches!(
9 c,
10 '\t' | '\n' | '\u{B}' | '\u{C}' | '\r' | ' ' | '\u{A0}' | '\u{1680}' | '\u{2000}'
11 ..='\u{200A}' | '\u{2028}' | '\u{2029}' | '\u{202F}' | '\u{205F}' | '\u{3000}' | '\u{FEFF}'
12 )
13}
14
15pub fn js_trim(s: &str) -> &str {
17 s.trim_matches(is_js_whitespace)
18}
19
20pub fn js_parse_int(s: &str) -> Option<i64> {
22 let s = s.trim_start_matches(is_js_whitespace);
23 let (negative, digits) = match s.as_bytes().first() {
24 Some(b'-') => (true, &s[1..]),
25 Some(b'+') => (false, &s[1..]),
26 _ => (false, s),
27 };
28 let end = digits.bytes().position(|b| !b.is_ascii_digit()).unwrap_or(digits.len());
29 if end == 0 {
30 return None;
31 }
32 let mut value: i64 = 0;
33 for b in digits[..end].bytes() {
34 value = value.saturating_mul(10).saturating_add((b - b'0') as i64);
35 }
36 Some(if negative { -value } else { value })
37}
38
39pub fn js_parse_float(s: &str) -> f64 {
41 let s = s.trim_start_matches(is_js_whitespace);
42 let bytes = s.as_bytes();
43 let mut i = 0;
44 if matches!(bytes.first(), Some(b'+') | Some(b'-')) {
45 i = 1;
46 }
47 if s[i..].starts_with("Infinity") {
48 return if bytes[0] == b'-' {
49 f64::NEG_INFINITY
50 } else {
51 f64::INFINITY
52 };
53 }
54 let int_start = i;
55 while i < bytes.len() && bytes[i].is_ascii_digit() {
56 i += 1;
57 }
58 let mut digits = i - int_start;
59 if i < bytes.len() && bytes[i] == b'.' {
60 let frac_start = i + 1;
61 let mut j = frac_start;
62 while j < bytes.len() && bytes[j].is_ascii_digit() {
63 j += 1;
64 }
65 digits += j - frac_start;
66 if digits > 0 {
67 i = j;
68 }
69 }
70 if digits == 0 {
71 return f64::NAN;
72 }
73 if i < bytes.len() && (bytes[i] == b'e' || bytes[i] == b'E') {
74 let mut j = i + 1;
75 if j < bytes.len() && (bytes[j] == b'+' || bytes[j] == b'-') {
76 j += 1;
77 }
78 let exp_start = j;
79 while j < bytes.len() && bytes[j].is_ascii_digit() {
80 j += 1;
81 }
82 if j > exp_start {
83 i = j;
84 }
85 }
86 s[..i].parse().unwrap_or(f64::NAN)
87}
88
89pub fn utf16_to_byte_offset(s: &str, units: usize) -> usize {
92 let mut seen = 0;
93 for (byte, c) in s.char_indices() {
94 if seen >= units {
95 return byte;
96 }
97 seen += c.len_utf16();
98 }
99 s.len()
100}
101
102pub fn js_substring_from(s: &str, start_utf16: usize) -> &str {
104 &s[utf16_to_byte_offset(s, start_utf16)..]
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn parse_int_like_js() {
113 assert_eq!(js_parse_int("12px"), Some(12));
114 assert_eq!(js_parse_int(" -7"), Some(-7));
115 assert_eq!(js_parse_int("x1"), None);
116 assert_eq!(js_parse_int("007"), Some(7));
117 }
118
119 #[test]
120 fn parse_float_like_js() {
121 assert_eq!(js_parse_float("2.5 days"), 2.5);
122 assert_eq!(js_parse_float(".5"), 0.5);
123 assert_eq!(js_parse_float("3."), 3.0);
124 assert_eq!(js_parse_float("1e3x"), 1000.0);
125 assert_eq!(js_parse_float("1ex"), 1.0);
126 assert!(js_parse_float("few").is_nan());
127 assert!(js_parse_float(".").is_nan());
128 }
129
130 #[test]
131 fn trim_and_substring() {
132 assert_eq!(js_trim("\u{FEFF} a \u{3000}"), "a");
133 assert_eq!(js_substring_from("😀ab", 2), "ab");
134 assert_eq!(js_substring_from("ab", 5), "");
135 }
136}