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: <https://gitlab.gnome.org/GNOME/gnome-calendar/-/merge_requests/770>
This commit is contained in:
Georges Basile Stavracas Neto
2026-06-13 23:22:01 +02:00
parent 686abd1da6
commit 6234566874
2 changed files with 124 additions and 37 deletions
+98 -37
View File
@@ -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 ();
}
+26
View File
@@ -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 ();
}