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:
+98
-37
@@ -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 ();
|
||||
}
|
||||
|
||||
@@ -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 ();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user