From 6afb5cf3a5447e30486a37b6049a468c4399a0dd Mon Sep 17 00:00:00 2001 From: Daniel Garcia Moreno Date: Mon, 6 Nov 2023 13:56:31 +0100 Subject: [PATCH] view: Fix daylight saving issue in week view This patch fixes an issue when creating new events in week view the week after the daylight saving change. The problem was in the calc of the selected date, that's done couting the 30 minutes slots and adding that minutes to the week_start datetime. When there's a daylight saving change, the real date to select should be adjusted. This patch adds a new util function to make it easy to add minutes ignoring the tz so we can make this calc directly with gcal_date_time_add_floating_minutes. This also includes a new test file with some checks for the new util function and spring / autumn changes. This new function could be used in other places to fix specific issues that could be caused by the same logic error, calculate a selection or event position based on GDateTime operations. Fix https://gitlab.gnome.org/GNOME/gnome-calendar/-/issues/482 --- src/gui/views/gcal-week-grid.c | 8 +- src/utils/gcal-date-time-utils.c | 28 +++++++ src/utils/gcal-date-time-utils.h | 4 + tests/meson.build | 1 + tests/test-daylight-saving.c | 125 +++++++++++++++++++++++++++++++ 5 files changed, 162 insertions(+), 4 deletions(-) create mode 100644 tests/test-daylight-saving.c diff --git a/src/gui/views/gcal-week-grid.c b/src/gui/views/gcal-week-grid.c index c04f1a78..877f84ca 100644 --- a/src/gui/views/gcal-week-grid.c +++ b/src/gui/views/gcal-week-grid.c @@ -294,8 +294,8 @@ on_click_gesture_released_cb (GtkGestureClick *click_gesture, if (ltr) { - start = g_date_time_add_minutes (week_start, start_cell * 30); - end = g_date_time_add_minutes (week_start, (end_cell + 1) * 30); + start = gcal_date_time_add_floating_minutes (week_start, start_cell * 30); + end = gcal_date_time_add_floating_minutes (week_start, (end_cell + 1) * 30); } else { @@ -306,8 +306,8 @@ on_click_gesture_released_cb (GtkGestureClick *click_gesture, rtl_start_cell = start_cell + (rtl_column - column) * 48; rtl_end_cell = (rtl_column * MINUTES_PER_DAY + minute) / 30; - start = g_date_time_add_minutes (week_start, rtl_start_cell * 30); - end = g_date_time_add_minutes (week_start, (rtl_end_cell + 1) * 30); + start = gcal_date_time_add_floating_minutes (week_start, rtl_start_cell * 30); + end = gcal_date_time_add_floating_minutes (week_start, (rtl_end_cell + 1) * 30); } local_x = round ((column + 0.5) * (alloc.width / 7.0)); diff --git a/src/utils/gcal-date-time-utils.c b/src/utils/gcal-date-time-utils.c index 358ace8e..09fd1e4e 100644 --- a/src/utils/gcal-date-time-utils.c +++ b/src/utils/gcal-date-time-utils.c @@ -258,3 +258,31 @@ gcal_timezone_to_icaltimezone (GTimeZone *tz) return ical_tz; } +/** + * gcal_date_time_add_floating_minutes: + * @initial: Initial #GDateTime to add minutes + * @minutes: The number of minutes to add + * + * Calculate the datetime initial + minutes without caring about dayligh saving + * changes, so if it's 02:30 + 30 minutes, the result will be 03:00, always, + * independently of the daylight saving logic. + * + * Returns: (transfer full): A new #GDateTime with initial + minutes + */ +GDateTime* +gcal_date_time_add_floating_minutes (GDateTime *initial, + gint minutes) +{ + g_autoptr (GDateTime) result = NULL; + GTimeSpan initial_offset = 0; + GTimeSpan result_offset = 0; + GTimeSpan offset = 0; + + result = g_date_time_add_minutes (initial, minutes); + initial_offset = g_date_time_get_utc_offset (initial); + result_offset = g_date_time_get_utc_offset (result); + offset = initial_offset - result_offset; + // offset is in microseconds, convert to seconds + offset = offset / 1000000; + return g_date_time_add_seconds (result, offset); +} diff --git a/src/utils/gcal-date-time-utils.h b/src/utils/gcal-date-time-utils.h index 50d9707e..635338f8 100644 --- a/src/utils/gcal-date-time-utils.h +++ b/src/utils/gcal-date-time-utils.h @@ -53,4 +53,8 @@ GDateTime* gcal_date_time_from_icaltime (const ICalTime ICalTimezone* gcal_timezone_to_icaltimezone (GTimeZone *tz); + +GDateTime* gcal_date_time_add_floating_minutes (GDateTime *initial, + gint minutes); + G_END_DECLS diff --git a/tests/meson.build b/tests/meson.build index 62d3ba04..e5d0a5cb 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -33,6 +33,7 @@ libgcal_test_dep = declare_dependency( tests = [ 'server', + 'daylight-saving', 'discoverer', 'event', 'range', diff --git a/tests/test-daylight-saving.c b/tests/test-daylight-saving.c new file mode 100644 index 00000000..9a0352a7 --- /dev/null +++ b/tests/test-daylight-saving.c @@ -0,0 +1,125 @@ +/* test-daylight-saving.c + * + * Copyright © 2023 Daniel García Moreno + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include "gcal-utils.h" + +/*********************************************************************************************************************/ + +static void +floating_minutes (void) +{ + g_autoptr (GDateTime) week_start = NULL; + g_autoptr (GDateTime) start = NULL; + g_autoptr (GDateTime) end = NULL; + g_autoptr (GTimeZone) cest = NULL; + + int start_cell = 3 * 2; // three hours (daylight time starts at 3:00 AM) + int end_cell = start_cell; + + g_test_bug ("482"); + + cest = g_time_zone_new_identifier ("Europe/Madrid"); + + // Without daylight saving change + week_start = g_date_time_new (cest, 2023, 11, 5, 0, 0, 0); + g_assert_cmpstr (g_date_time_format_iso8601 (week_start), ==, "2023-11-05T00:00:00+01"); + g_assert_false (g_date_time_is_daylight_savings (week_start)); + + start = gcal_date_time_add_floating_minutes (week_start, start_cell * 30); + end = gcal_date_time_add_floating_minutes (week_start, (end_cell + 1) * 30); + + g_assert_false (g_date_time_is_daylight_savings (start)); + g_assert_false (g_date_time_is_daylight_savings (end)); + + g_assert_cmpstr (g_date_time_format_iso8601 (start), ==, "2023-11-05T03:00:00+01"); + g_assert_cmpstr (g_date_time_format_iso8601 (end), ==, "2023-11-05T03:30:00+01"); +} + + +static void +floating_minutes_october (void) +{ + g_autoptr (GDateTime) week_start = NULL; + g_autoptr (GDateTime) start = NULL; + g_autoptr (GDateTime) end = NULL; + g_autoptr (GTimeZone) cest = NULL; + + int start_cell = 3 * 2; // three hours (daylight time starts at 3:00 AM) + int end_cell = start_cell; + + g_test_bug ("482"); + + cest = g_time_zone_new_identifier ("Europe/Madrid"); + + // With daylight saving change + week_start = g_date_time_new (cest, 2023, 10, 29, 0, 0, 0); + g_assert_cmpstr (g_date_time_format_iso8601 (week_start), ==, "2023-10-29T00:00:00+02"); + g_assert_true (g_date_time_is_daylight_savings (week_start)); + + start = gcal_date_time_add_floating_minutes (week_start, start_cell * 30); + end = gcal_date_time_add_floating_minutes (week_start, (end_cell + 1) * 30); + + g_assert_cmpstr (g_date_time_format_iso8601 (start), ==, "2023-10-29T03:00:00+01"); + g_assert_cmpstr (g_date_time_format_iso8601 (end), ==, "2023-10-29T03:30:00+01"); +} + +static void +floating_minutes_march (void) +{ + g_autoptr (GDateTime) week_start = NULL; + g_autoptr (GDateTime) start = NULL; + g_autoptr (GDateTime) end = NULL; + g_autoptr (GTimeZone) cest = NULL; + + int start_cell = 3 * 2; // three hours (daylight time starts at 3:00 AM) + int end_cell = start_cell; + + g_test_bug ("482"); + + cest = g_time_zone_new_identifier ("Europe/Madrid"); + + // With daylight saving change + week_start = g_date_time_new (cest, 2023, 3, 26, 0, 0, 0); + g_assert_cmpstr (g_date_time_format_iso8601 (week_start), ==, "2023-03-26T00:00:00+01"); + g_assert_false (g_date_time_is_daylight_savings (week_start)); + + start = gcal_date_time_add_floating_minutes (week_start, start_cell * 30); + end = gcal_date_time_add_floating_minutes (week_start, (end_cell + 1) * 30); + + g_assert_cmpstr (g_date_time_format_iso8601 (start), ==, "2023-03-26T03:00:00+02"); + g_assert_cmpstr (g_date_time_format_iso8601 (end), ==, "2023-03-26T03:30:00+02"); +} + +/*********************************************************************************************************************/ + +gint +main (gint argc, + gchar *argv[]) +{ + g_setenv ("TZ", "UTC", TRUE); + + g_test_init (&argc, &argv, NULL); + g_test_bug_base ("https://gitlab.gnome.org/GNOME/gnome-calendar/-/issues/"); + + g_test_add_func ("/daylight/floating-minutes", floating_minutes); + g_test_add_func ("/daylight/floating-minutes-october", floating_minutes_october); + g_test_add_func ("/daylight/floating-minutes-march", floating_minutes_march); + + return g_test_run (); +}