gix_date/parse/
function.rs1use std::{str::FromStr, time::SystemTime};
2
3use jiff::{civil::Date, fmt::rfc2822, tz::TimeZone, Zoned};
4
5use crate::parse::git::parse_git_date_format;
6use crate::parse::raw::parse_raw;
7use crate::{
8 parse::relative,
9 time::format::{DEFAULT, GITOXIDE, ISO8601, ISO8601_STRICT, SHORT},
10 Error, OffsetInSeconds, SecondsSinceUnixEpoch, Time,
11};
12use gix_error::{Exn, ResultExt};
13
14pub fn parse(input: &str, now: Option<SystemTime>) -> Result<Time, Exn<Error>> {
76 Ok(if let Ok(val) = Date::strptime(SHORT.0, input) {
77 let val = val
78 .to_zoned(TimeZone::UTC)
79 .or_raise(|| Error::new_with_input("Timezone conversion failed", input))?;
80 Time::new(val.timestamp().as_second(), val.offset().seconds())
81 } else if let Ok(val) = rfc2822_relaxed(input) {
82 Time::new(val.timestamp().as_second(), val.offset().seconds())
83 } else if let Ok(val) = strptime_relaxed(ISO8601.0, input) {
84 Time::new(val.timestamp().as_second(), val.offset().seconds())
85 } else if let Ok(val) = strptime_relaxed(ISO8601_STRICT.0, input) {
86 Time::new(val.timestamp().as_second(), val.offset().seconds())
87 } else if let Ok(val) = strptime_relaxed(GITOXIDE.0, input) {
88 Time::new(val.timestamp().as_second(), val.offset().seconds())
89 } else if let Ok(val) = strptime_relaxed(DEFAULT.0, input) {
90 Time::new(val.timestamp().as_second(), val.offset().seconds())
91 } else if let Ok(val) = SecondsSinceUnixEpoch::from_str(input) {
92 Time::new(val, 0)
93 } else if let Some(val) = parse_git_date_format(input) {
94 val
95 } else if let Some(val) = relative::parse(input, now).transpose()? {
96 Time::new(val.timestamp().as_second(), val.offset().seconds())
97 } else if let Some(val) = parse_raw(input) {
98 val
100 } else {
101 return Err(Error::new_with_input("Unknown date format", input))?;
102 })
103}
104
105pub fn parse_header(input: &str) -> Option<Time> {
111 pub enum Sign {
112 Plus,
113 Minus,
114 }
115 fn parse_offset(offset: &str) -> Option<OffsetInSeconds> {
116 if (offset.len() != 5) && (offset.len() != 7) {
117 return None;
118 }
119 let sign = match offset.get(..1)? {
120 "-" => Some(Sign::Minus),
121 "+" => Some(Sign::Plus),
122 _ => None,
123 }?;
124 if offset.as_bytes().get(1).is_some_and(|b| !b.is_ascii_digit()) {
125 return None;
126 }
127 let hours: i32 = offset.get(1..3)?.parse().ok()?;
128 let minutes: i32 = offset.get(3..5)?.parse().ok()?;
129 let offset_seconds: i32 = if offset.len() == 7 {
130 offset.get(5..7)?.parse().ok()?
131 } else {
132 0
133 };
134 let mut offset_in_seconds = hours * 3600 + minutes * 60 + offset_seconds;
135 if matches!(sign, Sign::Minus) {
136 offset_in_seconds *= -1;
137 }
138 Some(offset_in_seconds)
139 }
140
141 if input.contains(':') {
142 return None;
143 }
144 let mut split = input.split_whitespace();
145 let seconds = split.next()?;
146 let seconds = match seconds.parse::<SecondsSinceUnixEpoch>() {
147 Ok(s) => s,
148 Err(_err) => {
149 let first_digits: String = seconds.chars().take_while(char::is_ascii_digit).collect();
151 first_digits.parse().ok()?
152 }
153 };
154 let offset = match split.next() {
155 None => 0,
156 Some(offset) => {
157 if split.next().is_some() {
158 0
159 } else {
160 parse_offset(offset).unwrap_or_default()
161 }
162 }
163 };
164 let time = Time { seconds, offset };
165 Some(time)
166}
167
168fn strptime_relaxed(fmt: &str, input: &str) -> std::result::Result<Zoned, jiff::Error> {
173 let mut tm = jiff::fmt::strtime::parse(fmt, input)?;
174 tm.set_weekday(None);
175 tm.to_zoned()
176}
177
178fn rfc2822_relaxed(input: &str) -> std::result::Result<Zoned, jiff::Error> {
181 static P: rfc2822::DateTimeParser = rfc2822::DateTimeParser::new().relaxed_weekday(true);
182 P.parse_zoned(input)
183}