@@ -129,7 +129,7 @@ pub const Jar = struct {
129129
130130fn isCookieExpired (cookie : * const Cookie , now : i64 ) bool {
131131 const ce = cookie .expires orelse return false ;
132- return ce <= now ;
132+ return ce <= @as ( f64 , @floatFromInt ( now )) ;
133133}
134134
135135fn areCookiesEqual (a : * const Cookie , b : * const Cookie ) bool {
@@ -174,7 +174,7 @@ pub const Cookie = struct {
174174 value : []const u8 ,
175175 domain : []const u8 ,
176176 path : []const u8 ,
177- expires : ? i64 ,
177+ expires : ? f64 ,
178178 secure : bool = false ,
179179 http_only : bool = false ,
180180 same_site : SameSite = .none ,
@@ -226,7 +226,7 @@ pub const Cookie = struct {
226226 var secure : ? bool = null ;
227227 var max_age : ? i64 = null ;
228228 var http_only : ? bool = null ;
229- var expires : ? DateTime = null ;
229+ var expires : ? [] const u8 = null ;
230230 var same_site : ? Cookie.SameSite = null ;
231231
232232 var it = std .mem .splitScalar (u8 , rest , ';' );
@@ -258,7 +258,7 @@ pub const Cookie = struct {
258258 .domain = > domain = value ,
259259 .secure = > secure = true ,
260260 .@"max-age" = > max_age = std .fmt .parseInt (i64 , value , 10 ) catch continue ,
261- .expires = > expires = DateTime . parse ( value , .rfc822 ) catch continue ,
261+ .expires = > expires = value ,
262262 .httponly = > http_only = true ,
263263 .samesite = > {
264264 same_site = std .meta .stringToEnum (Cookie .SameSite , std .ascii .lowerString (& scrap , value )) orelse continue ;
@@ -278,13 +278,25 @@ pub const Cookie = struct {
278278 const owned_path = try parsePath (aa , uri , path );
279279 const owned_domain = try parseDomain (aa , uri , domain );
280280
281- var normalized_expires : ? i64 = null ;
281+ var normalized_expires : ? f64 = null ;
282282 if (max_age ) | ma | {
283- normalized_expires = std .time .timestamp () + ma ;
283+ normalized_expires = @floatFromInt ( std .time .timestamp () + ma ) ;
284284 } else {
285285 // max age takes priority over expires
286- if (expires ) | e | {
287- normalized_expires = e .sub (DateTime .now (), .seconds );
286+ if (expires ) | expires_ | {
287+ var exp_dt = DateTime .parse (expires_ , .rfc822 ) catch null ;
288+ if (exp_dt == null ) {
289+ if ((expires_ .len > 11 and expires_ [7 ] == '-' and expires_ [11 ] == '-' )) {
290+ // Replace dashes and try again
291+ const output = try aa .dupe (u8 , expires_ );
292+ output [7 ] = ' ' ;
293+ output [11 ] = ' ' ;
294+ exp_dt = DateTime .parse (output , .rfc822 ) catch null ;
295+ }
296+ }
297+ if (exp_dt ) | dt | {
298+ normalized_expires = @floatFromInt (dt .unix (.seconds ));
299+ } else std .debug .print ("Invalid cookie expires value: {s}\n " , .{expires_ });
288300 }
289301 }
290302
@@ -838,7 +850,8 @@ test "Cookie: parse expires" {
838850 try expectAttribute (.{ .expires = null }, null , "b;expires=13.22" );
839851 try expectAttribute (.{ .expires = null }, null , "b;expires=33" );
840852
841- try expectAttribute (.{ .expires = 1918798080 - std .time .timestamp () }, null , "b;expires=Wed, 21 Oct 2030 07:28:00 GMT" );
853+ try expectAttribute (.{ .expires = 1918798080 }, null , "b;expires=Wed, 21 Oct 2030 07:28:00 GMT" );
854+ try expectAttribute (.{ .expires = 1784275395 }, null , "b;expires=Fri, 17-Jul-2026 08:03:15 GMT" );
842855 // max-age has priority over expires
843856 try expectAttribute (.{ .expires = std .time .timestamp () + 10 }, null , "b;Max-Age=10; expires=Wed, 21 Oct 2030 07:28:00 GMT" );
844857}
@@ -858,7 +871,7 @@ test "Cookie: parse all" {
858871 .http_only = true ,
859872 .secure = true ,
860873 .domain = ".lightpanda.io" ,
861- .expires = std .time .timestamp () + 30 ,
874+ .expires = @floatFromInt ( std .time .timestamp () + 30 ) ,
862875 }, "https://lightpanda.io/cms/users" , "user-id=9000; HttpOnly; Max-Age=30; Secure; path=/; Domain=lightpanda.io" );
863876
864877 try expectCookie (.{
@@ -869,7 +882,7 @@ test "Cookie: parse all" {
869882 .secure = false ,
870883 .domain = ".localhost" ,
871884 .same_site = .lax ,
872- .expires = std .time .timestamp () + 7200 ,
885+ .expires = @floatFromInt ( std .time .timestamp () + 7200 ) ,
873886 }, "http://localhost:8000/login" , "app_session=123; Max-Age=7200; path=/; domain=localhost; httponly; samesite=lax" );
874887}
875888
@@ -896,7 +909,7 @@ const ExpectedCookie = struct {
896909 value : []const u8 ,
897910 path : []const u8 ,
898911 domain : []const u8 ,
899- expires : ? i64 = null ,
912+ expires : ? f64 = null ,
900913 secure : bool = false ,
901914 http_only : bool = false ,
902915 same_site : Cookie.SameSite = .lax ,
@@ -915,7 +928,7 @@ fn expectCookie(expected: ExpectedCookie, url: []const u8, set_cookie: []const u
915928 try testing .expectEqual (expected .path , cookie .path );
916929 try testing .expectEqual (expected .domain , cookie .domain );
917930
918- try testing .expectDelta (expected .expires , cookie .expires , 2 );
931+ try testing .expectDelta (expected .expires , cookie .expires , 2.0 );
919932}
920933
921934fn expectAttribute (expected : anytype , url : ? []const u8 , set_cookie : []const u8 ) ! void {
@@ -925,7 +938,10 @@ fn expectAttribute(expected: anytype, url: ?[]const u8, set_cookie: []const u8)
925938
926939 inline for (@typeInfo (@TypeOf (expected )).@"struct" .fields ) | f | {
927940 if (comptime std .mem .eql (u8 , f .name , "expires" )) {
928- try testing .expectDelta (expected .expires , cookie .expires , 1 );
941+ switch (@typeInfo (@TypeOf (expected .expires ))) {
942+ .int , .comptime_int = > try testing .expectDelta (@as (f64 , @floatFromInt (expected .expires )), cookie .expires , 1.0 ),
943+ else = > try testing .expectDelta (expected .expires , cookie .expires , 1.0 ),
944+ }
929945 } else {
930946 try testing .expectEqual (@field (expected , f .name ), @field (cookie , f .name ));
931947 }
0 commit comments