From fbbaa23842dc948f19728980da3fc9215a35b72f Mon Sep 17 00:00:00 2001 From: Yorhel Date: Mon, 24 Feb 2025 11:51:43 +0100 Subject: [PATCH] pg: Add date type & httpdate tests ...I was hoping not to have to implement the date type, because date conversions suck, but it turns out manned.org actually needs it. (Only to then convert it into a Unix timestamp again, hmm, maybe this string conversion isn't useful at all?) --- FU/Pg.pm | 7 ++++++- c/pgtypes.c | 30 +++++++++++++++++++++++++++++- t/httpdate.t | 13 +++++++++++++ t/pgtypes.t | 8 ++++++++ 4 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 t/httpdate.t diff --git a/FU/Pg.pm b/FU/Pg.pm index ecf17e0..25fa5b1 100644 --- a/FU/Pg.pm +++ b/FU/Pg.pm @@ -570,6 +570,11 @@ represent that without loss for dates that are near enough to the epoch (still seems to be fine in 2025, at least), but this conversion may be lossy for dates far beyond or before the epoch. +=item date + +Converted between strings in C format. Postgres accepts a bunch of +alternative date formats, this module does not. + =item json / jsonb These types are converted through C and C from @@ -599,7 +604,7 @@ These are converted to and from hashrefs. =item money -=item date / time / timetz +=item time / timetz =item bit / varbit diff --git a/c/pgtypes.c b/c/pgtypes.c index 6e33ff9..6a215ac 100644 --- a/c/pgtypes.c +++ b/c/pgtypes.c @@ -511,6 +511,34 @@ SENDFN(timestamp) { fustr_writebeI(64, out, ts); } +RECVFN(date) { + RLEN(4); + time_t ts = ((time_t)fu_frombeI(32, buf)) * 86400 + UNIX_PG_EPOCH; + struct tm tm; + gmtime_r(&ts, &tm); + return newSVpvf("%04d-%02d-%02d", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday); +} + +SENDFN(date) { + int year, month, day; + if (sscanf(SvPV_nolen(val), "%4d-%2d-%2d", &year, &month, &day) != 3) SERR("invalid date format"); + /* Can't use mktime() hackery here because libc has no UTC variant. Code + * below is adapted from PostgreSQL date2j() instead. */ + if (month > 2) { + month += 1; + year += 4800; + } else { + month += 13; + year += 4799; + } + int century = year / 100; + int v = year * 365 - 32167; + v += year / 4 - century + century / 4; + v += 7834 * month / 256 + day; + v -= 2451545; /* Julian -> Postgres */ + fustr_writebeI(32, out, v); +} + #undef UNIX_PG_EPOCH #undef SIV @@ -615,7 +643,7 @@ SENDFN(timestamp) { A( 1041, "_inet", 869 )\ B( 1042, "bpchar", text )\ B( 1043, "varchar", text )\ - /* 1082 date */\ + B( 1082, "date", date )\ /* 1083 time */\ B( 1114, "timestamp", timestamp)\ A( 1115, "_timestamp", 1114 )\ diff --git a/t/httpdate.t b/t/httpdate.t new file mode 100644 index 0000000..0b4f863 --- /dev/null +++ b/t/httpdate.t @@ -0,0 +1,13 @@ +use v5.38; +use Test::More; +use FU::Util 'httpdate_format', 'httpdate_parse'; + +is httpdate_format(0), 'Thu, 01 Jan 1970 00:00:00 GMT'; +is httpdate_format(1740325942), 'Sun, 23 Feb 2025 15:52:22 GMT'; + +is httpdate_parse('Thu, 01 Jan 1970 00:00:00 GMT'), 0; +is httpdate_parse('Sun, 23 Feb 2025 15:52:22 GMT'), 1740325942; +is httpdate_parse('Sub, 23 Feb 2025 15:52:22 GMT'), undef; +is httpdate_parse('Sun, 3 Feb 2025 15:52:22 GMT'), undef; + +done_testing; diff --git a/t/pgtypes.t b/t/pgtypes.t index 22b1f6e..7960d39 100644 --- a/t/pgtypes.t +++ b/t/pgtypes.t @@ -115,6 +115,14 @@ v timestamptz => 1740133814.705915, undef, '2025-02-21 10:30:14.705915+00'; v timestamp => 0, undef, '1970-01-01 00:00:00'; v timestamp => 1740133814.705915, undef, '2025-02-21 10:30:14.705915'; +v date => '1970-01-01'; +v date => '1999-01-08'; +v date => '2025-02-24'; +f date => ''; +f date => '2025-'; +f date => '2025-02-'; +f date => '1999-Jan-08'; + v 'int[]', [], undef, '{}'; v 'int[]', [1], undef, '{1}'; v 'int[]', [1,-3,undef,3], undef, '{1,-3,NULL,3}';