Parse fixed offset strings like "+05:30", "-04:00", "+0530", "-0400"
(offset_str: &str)
| 58 | |
| 59 | /// Parse fixed offset strings like "+05:30", "-04:00", "+0530", "-0400" |
| 60 | fn parse_fixed_offset(offset_str: &str) -> Result<FixedOffset, String> { |
| 61 | let trimmed = offset_str.trim(); |
| 62 | |
| 63 | if trimmed.len() < 3 { |
| 64 | return Err("Invalid offset format".to_string()); |
| 65 | } |
| 66 | |
| 67 | let sign = match trimmed.chars().next() { |
| 68 | Some('+') => 1, |
| 69 | Some('-') => -1, |
| 70 | _ => return Err("Offset must start with + or -".to_string()), |
| 71 | }; |
| 72 | |
| 73 | let offset_part = &trimmed[1..]; |
| 74 | |
| 75 | // Handle formats like "05:30" or "0530" |
| 76 | let (hours, minutes) = if offset_part.contains(':') { |
| 77 | let parts: Vec<&str> = offset_part.split(':').collect(); |
| 78 | if parts.len() != 2 { |
| 79 | return Err("Invalid offset format".to_string()); |
| 80 | } |
| 81 | ( |
| 82 | parts[0].parse::<i32>().map_err(|_| "Invalid hours")?, |
| 83 | parts[1].parse::<i32>().map_err(|_| "Invalid minutes")?, |
| 84 | ) |
| 85 | } else if offset_part.len() == 4 { |
| 86 | // Format like "0530" |
| 87 | let hours_str = &offset_part[0..2]; |
| 88 | let minutes_str = &offset_part[2..4]; |
| 89 | ( |
| 90 | hours_str.parse::<i32>().map_err(|_| "Invalid hours")?, |
| 91 | minutes_str.parse::<i32>().map_err(|_| "Invalid minutes")?, |
| 92 | ) |
| 93 | } else { |
| 94 | return Err("Invalid offset format".to_string()); |
| 95 | }; |
| 96 | |
| 97 | if hours > 23 || minutes > 59 { |
| 98 | return Err("Invalid offset values".to_string()); |
| 99 | } |
| 100 | |
| 101 | let total_seconds = sign * (hours * 3600 + minutes * 60); |
| 102 | FixedOffset::east_opt(total_seconds).ok_or_else(|| "Invalid offset".to_string()) |
| 103 | } |
| 104 | |
| 105 | /// Timezone type enum for handling both named timezones and fixed offsets |
| 106 | #[derive(Debug, Clone)] |
no test coverage detected