summaryrefslogtreecommitdiffstats
path: root/src/plist.c
diff options
context:
space:
mode:
authorGravatar Flavio Toffalini2026-10-26 09:03:57 +0000
committerGravatar Nikias Bassen2026-11-06 10:29:06 +0100
commit1d3b54c784da260f71d885f5f560d69c211a645e (patch)
tree6aa7b84ae67249d740bd4c43500a349e403ca2e6 /src/plist.c
parent9fdbf585f47501ba19a53fd4bb382d5b533e3fdd (diff)
downloadlibplist-1d3b54c784da260f71d885f5f560d69c211a645e.tar.gz
libplist-1d3b54c784da260f71d885f5f560d69c211a645e.tar.bz2
plist: fix the (sec, usec) date API round trip
plist_set_date_val stores val = sec + usec / 1000000, but plist_get_date_val splits it with a truncating cast and fabs(): *sec = (int32_t)val; val = fabs((val - (int64_t)val) * 1000000); *usec = (int32_t)val; Two results are wrong. The product is truncated, so set(5, 6) reads back as (5, 5) - 29.3% of round trips over sec in [0, 3] come back a microsecond short. And for a negative value, truncation toward zero plus fabs() give a pair that does not denote the same instant: set(-3, 250000) stores -2.75 and reads back as (-2, 750000), which the setter would store as -1.25. Every round trip with sec < 0 and usec != 0 is wrong. (int32_t)val is also undefined when the stored value is outside the int32_t range. Round the seconds down, round the fraction to microseconds carrying 1000000 into the seconds, and clamp the seconds before converting, so sec + usec / 1000000 == val to the microsecond with 0 <= usec < 1000000 - the convention the setter uses. Add test/date_val.c covering fixed cases and a sweep over sec in [-2, 2]. It fails on an unmodified tree - set(5, 6), set(-3, 250000) and set(-1, 1) all come back as different instants - and passes with this change, with make check at 44/44. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Arn4YaWtcQBDsQuJtyBmFP
Diffstat (limited to 'src/plist.c')
-rw-r--r--src/plist.c20
1 files changed, 15 insertions, 5 deletions
diff --git a/src/plist.c b/src/plist.c
index 677e94e..85c2338 100644
--- a/src/plist.c
+++ b/src/plist.c
@@ -1889,13 +1889,23 @@ void plist_get_date_val(plist_t node, int32_t * sec, int32_t * usec)
return;
plist_get_type_and_value(node, &type, (void *) &val, &length);
assert(length == sizeof(double));
+ /* Inverse of plist_set_date_val (val = sec + usec / 1000000):
+ * whole seconds rounded down, the fraction rounded to microseconds. */
+ double whole = floor(val);
+ double frac_usec = round((val - whole) * 1000000);
+ if (frac_usec >= 1000000) {
+ whole += 1;
+ frac_usec -= 1000000;
+ }
+ /* keep the int32_t conversion defined */
+ if (whole > INT32_MAX)
+ whole = INT32_MAX;
+ else if (whole < INT32_MIN)
+ whole = INT32_MIN;
if (sec)
- *sec = (int32_t)val;
+ *sec = (int32_t)whole;
if (usec)
- {
- val = fabs((val - (int64_t)val) * 1000000);
- *usec = (int32_t)val;
- }
+ *usec = (int32_t)frac_usec;
}
void plist_get_unix_date_val(plist_t node, int64_t *sec)