Skip to content

Commit 76fe854

Browse files
committed
parse time only strings
1 parent cdc1b64 commit 76fe854

File tree

2 files changed

+128
-0
lines changed

2 files changed

+128
-0
lines changed

src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use std::fmt::{self, Display};
1616
mod parse_relative_time;
1717
mod parse_timestamp;
1818

19+
mod parse_time_only_str;
1920
mod parse_weekday;
2021

2122
use chrono::{
@@ -237,6 +238,11 @@ pub fn parse_datetime_at_date<S: AsRef<str> + Clone>(
237238
}
238239
}
239240

241+
// parse time only dates
242+
if let Some(date_time) = parse_time_only_str::parse_time_only(date, s.as_ref()) {
243+
return Ok(date_time);
244+
}
245+
240246
// Default parse and failure
241247
s.as_ref()
242248
.parse()

src/parse_time_only_str.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
use chrono::{DateTime, FixedOffset, Local, NaiveTime, TimeZone};
2+
use regex::Regex;
3+
4+
mod time_only_formats {
5+
pub const HH_MM: &str = "%R";
6+
pub const HH_MM_SS: &str = "%T";
7+
pub const TWELVEHOUR: &str = "%r";
8+
}
9+
10+
pub(crate) fn parse_time_only(date: DateTime<Local>, s: &str) -> Option<DateTime<FixedOffset>> {
11+
let re =
12+
Regex::new(
13+
r"^(?<time>.*?)(?:(?<sign>\+|-)(?<h>[0-9]{1,2}):?(?<m>[0-9]{0,2}))?$"
14+
).unwrap();
15+
let captures = re.captures(s)?;
16+
17+
let parsed_offset = match captures.name("h") {
18+
Some(hours) if !(hours.as_str().is_empty()) => {
19+
let mut offset_in_sec = hours.as_str().parse::<i32>().unwrap() * 3600;
20+
match captures.name("m") {
21+
Some(minutes) if !(minutes.as_str().is_empty()) => {
22+
offset_in_sec += minutes.as_str().parse::<i32>().unwrap() * 60;
23+
}
24+
_ => (),
25+
};
26+
offset_in_sec *= if &captures["sign"] == "-" { -1 } else { 1 };
27+
FixedOffset::east_opt(offset_in_sec)
28+
}
29+
_ => None,
30+
};
31+
32+
for fmt in [
33+
time_only_formats::HH_MM,
34+
time_only_formats::HH_MM_SS,
35+
time_only_formats::TWELVEHOUR,
36+
] {
37+
if let Ok(parsed) = NaiveTime::parse_from_str(&captures["time"].trim(), fmt) {
38+
let parsed_dt = date.date_naive().and_time(parsed);
39+
let offset = match parsed_offset {
40+
Some(offset) => offset,
41+
None => *date.offset(),
42+
};
43+
return offset.from_local_datetime(&parsed_dt).single();
44+
}
45+
}
46+
47+
None
48+
}
49+
50+
#[cfg(test)]
51+
mod tests {
52+
use std::env;
53+
use crate::parse_time_only_str::parse_time_only;
54+
use chrono::{DateTime, Local, TimeZone};
55+
56+
fn get_test_date() -> DateTime<Local> {
57+
Local.with_ymd_and_hms(2024, 03, 03, 0, 0, 0).unwrap()
58+
}
59+
60+
#[test]
61+
fn test_time_only() {
62+
env::set_var("TZ", "UTC");
63+
let parsed_time = parse_time_only(get_test_date(), "21:04")
64+
.unwrap()
65+
.timestamp();
66+
assert_eq!(parsed_time, 1709499840)
67+
}
68+
69+
#[test]
70+
fn test_time_with_offset() {
71+
env::set_var("TZ", "UTC");
72+
let parsed_time = parse_time_only(get_test_date(), "21:04 +0530")
73+
.unwrap()
74+
.timestamp();
75+
assert_eq!(parsed_time, 1709480040);
76+
}
77+
78+
#[test]
79+
fn test_time_with_hour_only_offset() {
80+
env::set_var("TZ", "UTC");
81+
let parsed_time = parse_time_only(get_test_date(), "22:04 +01")
82+
.unwrap()
83+
.timestamp();
84+
assert_eq!(parsed_time, 1709499840);
85+
}
86+
87+
#[test]
88+
fn test_time_with_hour_only_neg_offset() {
89+
env::set_var("TZ", "UTC");
90+
let parsed_time = parse_time_only(get_test_date(), "17:04 -04")
91+
.unwrap()
92+
.timestamp();
93+
assert_eq!(parsed_time, 1709499840);
94+
}
95+
96+
#[test]
97+
fn test_time_with_seconds() {
98+
env::set_var("TZ", "UTC");
99+
let parsed_time = parse_time_only(get_test_date(), "21:04:30")
100+
.unwrap()
101+
.timestamp();
102+
assert_eq!(parsed_time, 1709499870)
103+
}
104+
105+
#[test]
106+
fn test_time_with_seconds_with_offset() {
107+
env::set_var("TZ", "UTC");
108+
let parsed_time = parse_time_only(get_test_date(), "21:04:30 +0530")
109+
.unwrap()
110+
.timestamp();
111+
assert_eq!(parsed_time, 1709480070)
112+
}
113+
114+
#[test]
115+
fn test_twelve_hour_time() {
116+
env::set_var("TZ", "UTC");
117+
let parsed_time = parse_time_only(get_test_date(), "9:04:00 PM")
118+
.unwrap()
119+
.timestamp();
120+
assert_eq!(parsed_time, 1709499840)
121+
}
122+
}

0 commit comments

Comments
 (0)