Parse a UTC timestamp from various input formats. Supports: - Unix epoch microseconds: `"1710509400000000"` - Unix epoch seconds: `"1710509400"` - ISO 8601: `"2024-03-15T14:30:00Z"` (basic parsing, no full chrono dependency)
(input: &str)
| 99 | /// - Unix epoch seconds: `"1710509400"` |
| 100 | /// - ISO 8601: `"2024-03-15T14:30:00Z"` (basic parsing, no full chrono dependency) |
| 101 | pub fn parse_utc_timestamp(input: &str) -> crate::Result<u64> { |
| 102 | let trimmed = input.trim(); |
| 103 | |
| 104 | // Try parsing as integer (epoch micros or seconds). |
| 105 | if let Ok(n) = trimmed.parse::<u64>() { |
| 106 | // Heuristic: values > 1e15 are microseconds, otherwise seconds. |
| 107 | if n > 1_000_000_000_000_000 { |
| 108 | return Ok(n); // Already microseconds. |
| 109 | } |
| 110 | return Ok(n * 1_000_000); // Convert seconds to microseconds. |
| 111 | } |
| 112 | |
| 113 | // Try ISO 8601 basic parsing: "YYYY-MM-DDTHH:MM:SSZ" |
| 114 | // This is a simplified parser — production should use chrono or time crate. |
| 115 | if trimmed.len() >= 19 && trimmed.contains('T') { |
| 116 | let date_part = &trimmed[..10]; // "YYYY-MM-DD" |
| 117 | let time_part = &trimmed[11..19]; // "HH:MM:SS" |
| 118 | |
| 119 | let parts: Vec<u64> = date_part |
| 120 | .split('-') |
| 121 | .chain(time_part.split(':')) |
| 122 | .filter_map(|s| s.parse().ok()) |
| 123 | .collect(); |
| 124 | |
| 125 | if parts.len() == 6 { |
| 126 | let (year, month, day, hour, min, sec) = |
| 127 | (parts[0], parts[1], parts[2], parts[3], parts[4], parts[5]); |
| 128 | |
| 129 | // Days from Unix epoch to the given date. |
| 130 | // Leap year: divisible by 4, except centuries unless divisible by 400. |
| 131 | let leap_days = |y: u64| -> u64 { |
| 132 | if y == 0 { |
| 133 | return 0; |
| 134 | } |
| 135 | let y = y - 1; // count leap years before this year |
| 136 | y / 4 - y / 100 + y / 400 - (1969 / 4 - 1969 / 100 + 1969 / 400) |
| 137 | }; |
| 138 | let is_leap = |
| 139 | |y: u64| y.is_multiple_of(4) && (!y.is_multiple_of(100) || y.is_multiple_of(400)); |
| 140 | let leap_adj = if is_leap(year) && month > 2 { 1 } else { 0 }; |
| 141 | let days_since_epoch = |
| 142 | (year - 1970) * 365 + leap_days(year) + month_to_days(month) + leap_adj + day - 1; |
| 143 | let epoch_secs = days_since_epoch * 86400 + hour * 3600 + min * 60 + sec; |
| 144 | return Ok(epoch_secs * 1_000_000); |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | Err(crate::Error::BadRequest { |
| 149 | detail: format!( |
| 150 | "cannot parse UTC timestamp: '{trimmed}'. Expected epoch micros, epoch seconds, or ISO 8601" |
| 151 | ), |
| 152 | }) |
| 153 | } |
| 154 | |
| 155 | /// Approximate days from Jan 1 to the start of the given month (non-leap year). |
| 156 | fn month_to_days(month: u64) -> u64 { |
no test coverage detected