From 8902db4887548bcc0a464c10f1289a39b451a57f Mon Sep 17 00:00:00 2001 From: Bruno-Vdr Date: Wed, 25 Feb 2026 13:49:43 +0100 Subject: [PATCH] time: parse_http_header_string (#26636) * Implemented rfc2616 parse_http_header_string. * Corrected typos. * Update vlib/time/parse_test.v * Fixed YY date issue. If parsed year exceed current year, it's considered to be from the previous century. --------- Co-authored-by: Swastik Baranwal --- vlib/time/parse.c.v | 61 ++++++++++++++++++++++++++++++++++++++++++ vlib/time/parse_test.v | 35 ++++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/vlib/time/parse.c.v b/vlib/time/parse.c.v index e802a7ac9..75aaf0061 100644 --- a/vlib/time/parse.c.v +++ b/vlib/time/parse.c.v @@ -126,6 +126,67 @@ fn check_and_extract_date(s string) !(int, int, int) { return year, month, day } +// Convert header string formatted as RFC 2616 to Time. +pub fn parse_http_header_string(s string) !Time { + return parse_rfc2616(s) +} + +// parse_rfc2616 returns the time from a date string in RFC 3339 datetime format. +// Wed, 06 Nov 2024 08:49:37 GMT ; RFC 822, updated by RFC 1123 +// Wednesday, 06-Nov-24 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 +// Wed Nov 6 08:49:37 2024 ; ANSI C's asctime() format +pub fn parse_rfc2616(s string) !Time { + if s == '' { + return error_invalid_time(0, 'datetime string is empty') + } + + // Remove or Replace unwanted tokens. + rmv := ['GMT', '', 'Monday', '', 'Tuesday', '', 'Wednesday', '', 'Thursday', '', 'Friday', + '', 'Saturday', '', 'Sunday', '', 'Mon', '', 'Tue', '', 'Wed', '', 'Thu', '', 'Fri', '', + 'Sat', '', 'Sun', '', '-', ' ', ',', ''] + + mut f := s.replace_each(rmv) + f = remove_consecutive_spaces(f) + + if r := parse_format(f, 'DD MMM YYYY HH:mm:ss') { + return r + } + + // parse_format maps YY to this century (94 maps to 2094, 20 to 2020). + // if parsed year > current year, the date belongs to previous century. + if r := parse_format(f, 'DD MMM YY HH:mm:ss') { + return if r.year > now().year { + r.add_days(-(days_per_100_years + 1)) + } else { + r + } + } + if r := parse_format(f, 'MMM D HH:mm:ss YYYY') { + return r + } + return error('unable to parse date: "${f}"') +} + +// Remove consecutive spaces, only keep one. +fn remove_consecutive_spaces(s string) string { + mut t := s.trim_space() + mut r := '' + mut sp := false + + for c in t { + if c == u8(` `) { + if !sp { + r += ' ' + sp = true + } + } else { + r += c.ascii_str() + sp = false + } + } + return r +} + // parse_rfc3339 returns the time from a date string in RFC 3339 datetime format. // See also https://ijmacd.github.io/rfc3339-iso8601/ for a visual reference of // the differences between ISO-8601 and RFC 3339. diff --git a/vlib/time/parse_test.v b/vlib/time/parse_test.v index c2e1205c8..e95118e14 100644 --- a/vlib/time/parse_test.v +++ b/vlib/time/parse_test.v @@ -243,6 +243,41 @@ fn test_parse_rfc3339_offset() { } } +fn test_parse_rfc2616() { + mut r := time.parse_rfc2616('Wed, 06 Nov 2024 08:49:37 GMT')! + assert r.unix() == 1730882977 + + r = time.parse_rfc2616('Wednesday, 06-Nov-24 08:49:37 GMT')! + assert r.unix() == 1730882977 + + r = time.parse_rfc2616('Wed Nov 6 08:49:37 2024')! + assert r.unix() == 1730882977 + + r = time.parse_rfc2616('Thu, 19 Feb 2026 11:07:09 GMT')! + assert r.unix() == 1771499229 + + r = time.parse_rfc2616('Tuesday, 19-Feb-26 11:07:09 GMT')! + assert r.unix() == 1771499229 + + r = time.parse_rfc2616('Thu Feb 19 11:07:09 2026')! + assert r.unix() == 1771499229 + + // This should map to 1994, not 2094. + r = time.parse_rfc2616('Tuesday, 06-Nov-94 11:07:09 GMT')! + assert r.unix() == 784120029 + + // This should map to 2020, not 1920. + r = time.parse_rfc2616('Friday, 06-Nov-20 08:49:37 GMT')! + assert r.unix() == 1604652577 +} + +fn test_parse_http_header_string() { + t := time.now() + header := t.http_header_string() + back_time := time.parse_http_header_string(header)! + assert t.str() == back_time.str() +} + fn test_ad_second_to_parse_result_in_2001() { now_tm := time.parse('2001-01-01 04:00:00')! future_tm := now_tm.add_seconds(60) -- 2.39.5