@@ -93,9 +93,19 @@ fn deserialize_date<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
9393where
9494 D : Deserializer < ' de > ,
9595{
96- let s = String :: deserialize ( deserializer) ?;
96+ let mut s = String :: deserialize ( deserializer) ?;
9797
98- serde_json:: from_str :: < DateTime < Utc > > ( & format ! ( "\" {s}\" " ) )
98+ if let Some ( ( _, offset) ) = s. rsplit_once ( [ '+' , '-' ] )
99+ && matches ! (
100+ offset. as_bytes( ) ,
101+ [ b'0' ..=b'9' , b':' , b'0' ..=b'9' , b'0' ..=b'9' ]
102+ )
103+ {
104+ s. insert ( s. len ( ) - offset. len ( ) , '0' ) ;
105+ }
106+
107+ DateTime :: parse_from_rfc3339 ( & s)
108+ . map ( |date| date. with_timezone ( & Utc ) )
99109 . or_else ( |_| {
100110 NaiveDateTime :: parse_from_str ( & s, "%Y-%m-%dT%H:%M:%S%.f" )
101111 . map ( |date| date. and_utc ( ) )
@@ -291,3 +301,55 @@ pub struct LoaderVersion {
291301 /// Whether the loader is stable or not
292302 pub stable : bool ,
293303}
304+
305+ #[ cfg( test) ]
306+ mod tests {
307+ use super :: PartialVersionInfo ;
308+ use chrono:: { DateTime , Utc } ;
309+ use serde_json:: json;
310+
311+ fn deserialize_version (
312+ date : & str ,
313+ ) -> serde_json:: Result < PartialVersionInfo > {
314+ serde_json:: from_value ( json ! ( {
315+ "id" : "1.21.11-forge-61.1.7" ,
316+ "inheritsFrom" : "1.21.11" ,
317+ "time" : date,
318+ "releaseTime" : date,
319+ "libraries" : [ ] ,
320+ "type" : "release"
321+ } ) )
322+ }
323+
324+ #[ test]
325+ fn deserialize_loader_timestamps ( ) {
326+ for ( input, expected) in [
327+ ( "2026-05-27T14:13:59+0:00" , "2026-05-27T14:13:59Z" ) ,
328+ ( "2026-05-27T14:13:59+5:30" , "2026-05-27T08:43:59Z" ) ,
329+ ( "2026-05-27T14:13:59-5:30" , "2026-05-27T19:43:59Z" ) ,
330+ ( "2026-05-27T14:13:59.123+0:00" , "2026-05-27T14:13:59.123Z" ) ,
331+ ( "2026-05-27T14:13:59+00:00" , "2026-05-27T14:13:59Z" ) ,
332+ ( "2026-05-27T14:13:59+05:30" , "2026-05-27T08:43:59Z" ) ,
333+ ( "2026-05-27T14:13:59Z" , "2026-05-27T14:13:59Z" ) ,
334+ ( "2026-05-27T14:13:59" , "2026-05-27T14:13:59Z" ) ,
335+ ( "2026-05-27T14:13:59.123" , "2026-05-27T14:13:59.123Z" ) ,
336+ ] {
337+ let version = deserialize_version ( input) . unwrap ( ) ;
338+ let expected = expected. parse :: < DateTime < Utc > > ( ) . unwrap ( ) ;
339+ assert_eq ! ( version. time, expected, "{input}" ) ;
340+ assert_eq ! ( version. release_time, expected, "{input}" ) ;
341+ }
342+ }
343+
344+ #[ test]
345+ fn reject_invalid_loader_timestamps ( ) {
346+ for input in [
347+ "not a date" ,
348+ "2026-05-27T14:13:59+0:60" ,
349+ "2026-05-27T14:13:59+0:00junk" ,
350+ "2026-05-27T14:13:59\" " ,
351+ ] {
352+ assert ! ( deserialize_version( input) . is_err( ) , "{input}" ) ;
353+ }
354+ }
355+ }
0 commit comments