1+ use crate :: Time ;
2+ use smallvec:: SmallVec ;
3+ use std:: str:: FromStr ;
4+
15#[ derive( thiserror:: Error , Debug , Clone ) ]
26#[ allow( missing_docs) ]
37pub enum Error {
@@ -11,6 +15,61 @@ pub enum Error {
1115 MissingCurrentTime ,
1216}
1317
18+ /// A container for just enough bytes to hold the largest-possible [`time`](Time) instance.
19+ /// It's used in conjunction with
20+ #[ derive( Default , Clone ) ]
21+ pub struct TimeBuf {
22+ buf : SmallVec < [ u8 ; Time :: MAX . size ( ) ] > ,
23+ }
24+
25+ impl TimeBuf {
26+ /// Represent this instance as standard string, serialized in a format compatible with
27+ /// signature fields in Git commits, also known as anything parseable as [raw format](function::parse_header()).
28+ pub fn as_str ( & self ) -> & str {
29+ // SAFETY: We know that serialized times are pure ASCII, a subset of UTF-8.
30+ // `buf` and `len` are written only by time-serialization code.
31+ let time_bytes = self . buf . as_slice ( ) ;
32+ #[ allow( unsafe_code) ]
33+ unsafe {
34+ std:: str:: from_utf8_unchecked ( time_bytes)
35+ }
36+ }
37+
38+ /// Clear the previous content.
39+ pub fn clear ( & mut self ) {
40+ self . buf . clear ( ) ;
41+ }
42+ }
43+
44+ impl std:: io:: Write for TimeBuf {
45+ fn write ( & mut self , buf : & [ u8 ] ) -> std:: io:: Result < usize > {
46+ self . buf . write ( buf)
47+ }
48+
49+ fn flush ( & mut self ) -> std:: io:: Result < ( ) > {
50+ self . buf . flush ( )
51+ }
52+ }
53+
54+ impl Time {
55+ /// Serialize this instance into `buf`, exactly as it would appear in the header of a Git commit,
56+ /// and return `buf` as `&str` for easy consumption.
57+ pub fn to_str < ' a > ( & self , buf : & ' a mut TimeBuf ) -> & ' a str {
58+ buf. clear ( ) ;
59+ self . write_to ( buf)
60+ . expect ( "write to memory of just the right size cannot fail" ) ;
61+ buf. as_str ( )
62+ }
63+ }
64+
65+ impl FromStr for Time {
66+ type Err = Error ;
67+
68+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
69+ crate :: parse_header ( s) . ok_or_else ( || Error :: InvalidDateString { input : s. into ( ) } )
70+ }
71+ }
72+
1473pub ( crate ) mod function {
1574 use std:: { str:: FromStr , time:: SystemTime } ;
1675
@@ -25,7 +84,67 @@ pub(crate) mod function {
2584 SecondsSinceUnixEpoch , Time ,
2685 } ;
2786
28- #[ allow( missing_docs) ]
87+ /// Parse `input` as any time that Git can parse when inputting a date.
88+ ///
89+ /// ## Examples
90+ ///
91+ /// ### 1. SHORT Format
92+ ///
93+ /// * `2018-12-24`
94+ /// * `1970-01-01`
95+ /// * `1950-12-31`
96+ /// * `2024-12-31`
97+ ///
98+ /// ### 2. RFC2822 Format
99+ ///
100+ /// * `Thu, 18 Aug 2022 12:45:06 +0800`
101+ /// * `Mon Oct 27 10:30:00 2023 -0800`
102+ ///
103+ /// ### 3. GIT_RFC2822 Format
104+ ///
105+ /// * `Thu, 8 Aug 2022 12:45:06 +0800`
106+ /// * `Mon Oct 27 10:30:00 2023 -0800` (Note the single-digit day)
107+ ///
108+ /// ### 4. ISO8601 Format
109+ ///
110+ /// * `2022-08-17 22:04:58 +0200`
111+ /// * `1970-01-01 00:00:00 -0500`
112+ ///
113+ /// ### 5. ISO8601_STRICT Format
114+ ///
115+ /// * `2022-08-17T21:43:13+08:00`
116+ ///
117+ /// ### 6. UNIX Timestamp (Seconds Since Epoch)
118+ ///
119+ /// * `123456789`
120+ /// * `0` (January 1, 1970 UTC)
121+ /// * `-1000`
122+ /// * `1700000000`
123+ ///
124+ /// ### 7. Commit Header Format
125+ ///
126+ /// * `1745582210 +0200`
127+ /// * `1660874655 +0800`
128+ /// * `-1660874655 +0800`
129+ ///
130+ /// See also the [`parse_header()`].
131+ ///
132+ /// ### 8. GITOXIDE Format
133+ ///
134+ /// * `Thu Sep 04 2022 10:45:06 -0400`
135+ /// * `Mon Oct 27 2023 10:30:00 +0000`
136+ ///
137+ /// ### 9. DEFAULT Format
138+ ///
139+ /// * `Thu Sep 4 10:45:06 2022 -0400`
140+ /// * `Mon Oct 27 10:30:00 2023 +0000`
141+ ///
142+ /// ### 10. Relative Dates (e.g., "2 minutes ago", "1 hour from now")
143+ ///
144+ /// These dates are parsed *relative to a `now` timestamp*. The examples depend entirely on the value of `now`.
145+ /// If `now` is October 27, 2023 at 10:00:00 UTC:
146+ /// * `2 minutes ago` (October 27, 2023 at 09:58:00 UTC)
147+ /// * `3 hours ago` (October 27, 2023 at 07:00:00 UTC)
29148 pub fn parse ( input : & str , now : Option < SystemTime > ) -> Result < Time , Error > {
30149 // TODO: actual implementation, this is just to not constantly fail
31150 if input == "1979-02-26 18:30:00" {
@@ -50,7 +169,7 @@ pub(crate) mod function {
50169 } else if let Ok ( val) = SecondsSinceUnixEpoch :: from_str ( input) {
51170 // Format::Unix
52171 Time :: new ( val, 0 )
53- } else if let Some ( val) = parse_raw ( input) {
172+ } else if let Some ( val) = parse_header ( input) {
54173 // Format::Raw
55174 val
56175 } else if let Some ( val) = relative:: parse ( input, now) . transpose ( ) ? {
@@ -60,8 +179,9 @@ pub(crate) mod function {
60179 } )
61180 }
62181
63- #[ allow( missing_docs) ]
64- pub fn parse_raw ( input : & str ) -> Option < Time > {
182+ /// Unlike [`parse()`] which handles all kinds of input, this function only parses the commit-header format
183+ /// like `1745582210 +0200`.
184+ pub fn parse_header ( input : & str ) -> Option < Time > {
65185 let mut split = input. split_whitespace ( ) ;
66186 let seconds: SecondsSinceUnixEpoch = split. next ( ) ?. parse ( ) . ok ( ) ?;
67187 let offset = split. next ( ) ?;
0 commit comments