From 62345668748b571939c1c7b441e44664a05a0287 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 21 May 2026 08:32:36 -0300 Subject: [PATCH] core/range: Compute and use timestamps for comparison Using GDateTime utilities is costly and shows up high in profiling. That's because GDateTime has to run timezone offset checks for every comparison. We can amortize this cost by converting these datetimes to unix timestamps upfront, and using them for comparisons. A complication is date-only ranges, for which unix timestamps wouldn't work. For this, add a new kind of timestamp that only accounts for the dates. Add a new test case for date-only comparison. Part-of: --- src/core/gcal-range.c | 135 ++++++++++++++++++++++++++++++------------ tests/test-range.c | 26 ++++++++ 2 files changed, 124 insertions(+), 37 deletions(-) diff --git a/src/core/gcal-range.c b/src/core/gcal-range.c index 5184fba6..f9b4cde8 100644 --- a/src/core/gcal-range.c +++ b/src/core/gcal-range.c @@ -29,21 +29,63 @@ struct _GcalRange GDateTime *range_start; GDateTime *range_end; GcalRangeType range_type; + + int64_t start_unix_timestamp; + int64_t start_date_timestamp; + int64_t end_unix_timestamp; + int64_t end_date_timestamp; }; G_DEFINE_BOXED_TYPE (GcalRange, gcal_range, gcal_range_ref, gcal_range_unref) -typedef int (*CompareDateTimeFunc) (GDateTime *a, - GDateTime *b); - -static CompareDateTimeFunc -get_compare_func (GcalRange *a, - GcalRange *b) +static inline int64_t +date_time_to_date_timestamp (GDateTime *datetime) { + int64_t date_timestamp = 0; + + date_timestamp += g_date_time_get_day_of_month (datetime); + date_timestamp += (g_date_time_get_month (datetime) * 100); + date_timestamp += (g_date_time_get_year (datetime) * 10000); + + return date_timestamp; +} + +static inline void +get_start_and_end_timestamps (GcalRange *a, + GcalRange *b, + int64_t *out_start_timestamp_a, + int64_t *out_end_timestamp_a, + int64_t *out_start_timestamp_b, + int64_t *out_end_timestamp_b) +{ + int64_t start_timestamp_a; + int64_t start_timestamp_b; + int64_t end_timestamp_a; + int64_t end_timestamp_b; + if (a->range_type == GCAL_RANGE_DATE_ONLY || b->range_type == GCAL_RANGE_DATE_ONLY) - return gcal_date_time_compare_date; + { + start_timestamp_a = a->start_date_timestamp; + start_timestamp_b = b->start_date_timestamp; + end_timestamp_a = a->end_date_timestamp; + end_timestamp_b = b->end_date_timestamp; + } else - return (CompareDateTimeFunc) g_date_time_compare; + { + start_timestamp_a = a->start_unix_timestamp; + start_timestamp_b = b->start_unix_timestamp; + end_timestamp_a = a->end_unix_timestamp; + end_timestamp_b = b->end_unix_timestamp; + } + + if (out_start_timestamp_a) + *out_start_timestamp_a = start_timestamp_a; + if (out_start_timestamp_b) + *out_start_timestamp_b = start_timestamp_b; + if (out_end_timestamp_a) + *out_end_timestamp_a = end_timestamp_a; + if (out_end_timestamp_b) + *out_end_timestamp_b = end_timestamp_b; } /** @@ -165,6 +207,11 @@ gcal_range_new_take (GDateTime *range_start, self->range_end = range_end; self->range_type = range_type; + self->start_unix_timestamp = g_date_time_to_unix (range_start); + self->start_date_timestamp = date_time_to_date_timestamp (range_start); + self->end_unix_timestamp = g_date_time_to_unix (range_end); + self->end_date_timestamp = date_time_to_date_timestamp (range_end); + return self; } @@ -242,11 +289,14 @@ gcal_range_calculate_overlap (GcalRange *a, GcalRange *b, GcalRangePosition *out_position) { - CompareDateTimeFunc compare_func; GcalRangePosition position; GcalRangeOverlap overlap; - gint a_start_b_start_diff; - gint a_end_b_end_diff; + int64_t a_start_b_start_diff; + int64_t a_end_b_end_diff; + int64_t start_timestamp_a; + int64_t start_timestamp_b; + int64_t end_timestamp_a; + int64_t end_timestamp_b; g_return_val_if_fail (a && b, GCAL_RANGE_NO_OVERLAP); @@ -308,9 +358,12 @@ gcal_range_calculate_overlap (GcalRange *a, * */ - compare_func = get_compare_func (a, b); - a_start_b_start_diff = compare_func (a->range_start, b->range_start); - a_end_b_end_diff = compare_func (a->range_end, b->range_end); + get_start_and_end_timestamps (a, b, + &start_timestamp_a, &end_timestamp_a, + &start_timestamp_b, &end_timestamp_b); + + a_start_b_start_diff = start_timestamp_a - start_timestamp_b; + a_end_b_end_diff = end_timestamp_a - end_timestamp_b; if (a_start_b_start_diff == 0 && a_end_b_end_diff == 0) { @@ -337,11 +390,8 @@ gcal_range_calculate_overlap (GcalRange *a, } else if (a_end_b_end_diff == 0) { - gint a_start_b_end_diff; - gint a_end_b_start_diff; - - a_start_b_end_diff = compare_func (a->range_start, b->range_end); - a_end_b_start_diff = compare_func (a->range_end, b->range_start); + int64_t a_start_b_end_diff = start_timestamp_a - end_timestamp_b; + int64_t a_end_b_start_diff = end_timestamp_a - start_timestamp_b; if (a_start_b_end_diff >= 0) { @@ -384,11 +434,8 @@ gcal_range_calculate_overlap (GcalRange *a, } else { - gint a_start_b_end_diff; - gint a_end_b_start_diff; - - a_start_b_end_diff = compare_func (a->range_start, b->range_end); - a_end_b_start_diff = compare_func (a->range_end, b->range_start); + int64_t a_start_b_end_diff = start_timestamp_a - end_timestamp_b; + int64_t a_end_b_start_diff = end_timestamp_a - start_timestamp_b; /* No overlap cases */ if (a_start_b_end_diff >= 0) @@ -448,16 +495,22 @@ gint gcal_range_compare (GcalRange *a, GcalRange *b) { - CompareDateTimeFunc compare_func; - gint result; + int64_t start_timestamp_a; + int64_t start_timestamp_b; + int64_t end_timestamp_a; + int64_t end_timestamp_b; + int64_t result; g_return_val_if_fail (a && b, 0); - compare_func = get_compare_func (a, b); - result = compare_func (a->range_start, b->range_start); + get_start_and_end_timestamps (a, b, + &start_timestamp_a, &end_timestamp_a, + &start_timestamp_b, &end_timestamp_b); + + result = start_timestamp_b - start_timestamp_a; if (result == 0) - result = compare_func (a->range_end, b->range_end); + result = end_timestamp_b - end_timestamp_a; return result; } @@ -475,22 +528,27 @@ GcalRange* gcal_range_union (GcalRange *a, GcalRange *b) { - CompareDateTimeFunc compare_func; GcalRangeType range_type; GDateTime *start; GDateTime *end; + int64_t start_timestamp_a; + int64_t start_timestamp_b; + int64_t end_timestamp_a; + int64_t end_timestamp_b; g_return_val_if_fail (a != NULL, NULL); g_return_val_if_fail (b != NULL, NULL); - compare_func = get_compare_func (a, b); + get_start_and_end_timestamps (a, b, + &start_timestamp_a, &end_timestamp_a, + &start_timestamp_b, &end_timestamp_b); - if (compare_func (a->range_start, b->range_start) < 0) + if (start_timestamp_a < start_timestamp_b) start = a->range_start; else start = b->range_start; - if (compare_func (a->range_end, b->range_end) > 0) + if (end_timestamp_a > end_timestamp_b) end = a->range_end; else end = b->range_end; @@ -540,18 +598,21 @@ gboolean gcal_range_contains_datetime (GcalRange *self, GDateTime *datetime) { + int64_t timestamp = 0; + g_return_val_if_fail (self, FALSE); g_return_val_if_fail (datetime, FALSE); switch (self->range_type) { case GCAL_RANGE_DEFAULT: - return g_date_time_compare (datetime, self->range_start) >= 0 && - g_date_time_compare (datetime, self->range_end) < 0; + timestamp = g_date_time_to_unix (datetime); + return timestamp >= self->start_unix_timestamp && timestamp < self->end_unix_timestamp; case GCAL_RANGE_DATE_ONLY: - return gcal_date_time_compare_date (datetime, self->range_start) >= 0 && - gcal_date_time_compare_date (datetime, self->range_end) <= 0; + timestamp = date_time_to_date_timestamp (datetime); + return timestamp >= self->start_date_timestamp && timestamp <= self->end_date_timestamp; + default: g_assert_not_reached (); } diff --git a/tests/test-range.c b/tests/test-range.c index 43a695cb..f26da706 100644 --- a/tests/test-range.c +++ b/tests/test-range.c @@ -231,6 +231,31 @@ range_calculate_overlap (void) /*********************************************************************************************************************/ +static void +range_calculate_overlap_date_only (void) +{ + g_autoptr (GcalRange) range_a = NULL; + g_autoptr (GcalRange) range_b = NULL; + GcalRangePosition position; + GcalRangeOverlap overlap; + + range_a = gcal_range_new_take (g_date_time_new_utc (1921, 12, 31, 0, 0, 0), + g_date_time_new_utc (1922, 1, 1, 0, 0, 0), + GCAL_RANGE_DATE_ONLY); + g_assert_nonnull (range_a); + + range_b = gcal_range_new_take (g_date_time_new_utc (2121, 10, 31, 0, 0, 0), + g_date_time_new_utc (2121, 11, 1, 0, 0, 0), + GCAL_RANGE_DATE_ONLY); + g_assert_nonnull (range_b); + + overlap = gcal_range_calculate_overlap (range_a, range_b, &position); + g_assert_cmpint (overlap, ==, GCAL_RANGE_NO_OVERLAP); + g_assert_cmpint (position, ==, GCAL_RANGE_BEFORE); +} + +/*********************************************************************************************************************/ + gint main (gint argc, gchar *argv[]) @@ -242,6 +267,7 @@ main (gint argc, g_test_add_func ("/range/new", range_new); g_test_add_func ("/range/invalid-ranges", range_invalid); g_test_add_func ("/range/calculate-overlap", range_calculate_overlap); + g_test_add_func ("/range/calculate-overlap-date-only", range_calculate_overlap_date_only); return g_test_run (); }