From 7a6a13d7e0bc4ededda326202e5493f3e403ed84 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Mon, 15 Feb 2016 19:50:15 -0200 Subject: [PATCH] event: fix timezone handling Using libical with libecal means that the dates are always stored as UTF dates, and we receive the timezone as the ECalComponentDateTime::tzid value. This fact was net being considered when adding date support on GcalEvent, and was causing various issues when displaying dates. Fix that by properly handling timezones in GcalEvent. --- src/gcal-event.c | 132 +++++++++++++++++++++++------------------- src/gcal-month-view.c | 10 ++-- src/gcal-utils.c | 35 +++++++---- src/gcal-utils.h | 5 +- src/gcal-year-view.c | 5 +- 5 files changed, 107 insertions(+), 80 deletions(-) diff --git a/src/gcal-event.c b/src/gcal-event.c index 56f50bd5..937edb35 100644 --- a/src/gcal-event.c +++ b/src/gcal-event.c @@ -63,25 +63,69 @@ enum { N_PROPS }; +static GTimeZone* +get_timezone_from_ical (ECalComponentDateTime *comp) +{ + GTimeZone *tz; + + if (comp->value->is_date) + { + /* + * When loading an all day event, the timezone must not be taken into account + * when loading the date. Since all events in ical are UTC, we match it by + * setting the timezone as UTC as well. + */ + tz = g_time_zone_new_utc (); + } + else if (icaltime_get_timezone (*comp->value)) + { + gchar *tzid; + gint offset; + + offset = icaltimezone_get_utc_offset ((icaltimezone*) icaltime_get_timezone (*comp->value), + comp->value, NULL); + tzid = format_utc_offset (offset); + tz = g_time_zone_new (tzid); + + g_free (tzid); + } + else if (comp->tzid) + { + tz = g_time_zone_new (comp->tzid); + } + else + { + tz = g_time_zone_new_utc (); + } + + return tz; +} + static ECalComponentDateTime* build_component_from_datetime (GcalEvent *self, GDateTime *dt) { ECalComponentDateTime *comp_dt; - const gchar *tzid; + GDateTime *utf_dt; if (!dt) return NULL; + utf_dt = g_date_time_to_utc (dt); + comp_dt = g_new0 (ECalComponentDateTime, 1); comp_dt->value = NULL; comp_dt->tzid = NULL; - tzid = g_date_time_get_timezone_abbreviation (dt); - - comp_dt->value = datetime_to_icaltime (dt); + comp_dt->value = datetime_to_icaltime (utf_dt); + comp_dt->value->zone = NULL; comp_dt->value->is_date = self->all_day; - comp_dt->tzid = g_strdup (tzid ? tzid : "UTC"); + + /* All day events have no timezone */ + if (!self->all_day) + comp_dt->tzid = format_utc_offset (g_date_time_get_utc_offset (dt)); + + g_clear_pointer (&utf_dt, g_date_time_unref); return comp_dt; } @@ -138,31 +182,40 @@ gcal_event_set_component_internal (GcalEvent *self, ECalComponentDateTime start; ECalComponentDateTime end; GDateTime *date_start; + GTimeZone *zone_start; GDateTime *date_end; - GTimeZone *tz; + GTimeZone *zone_end; + GDateTime *aux; + gboolean start_is_all_day, end_is_all_day; gchar *description; /* Setup start date */ e_cal_component_get_dtstart (component, &start); - date_start = icaltime_to_datetime (start.value, &tz); + zone_start = get_timezone_from_ical (&start); + aux = icaltime_to_datetime (start.value); + date_start = g_date_time_to_timezone (aux, zone_start); + start_is_all_day = datetime_is_date (aux); - gcal_event_set_date_start (self, date_start); + self->dt_start = g_date_time_ref (date_start); + + g_clear_pointer (&aux, g_date_time_unref); + + /* The timezone of the event is the timezone of the start date */ + self->timezone = g_time_zone_ref (zone_start); /* Setup end date */ e_cal_component_get_dtend (component, &end); - date_end = icaltime_to_datetime (end.value, NULL); + zone_end = get_timezone_from_ical (&end); + aux = icaltime_to_datetime (end.value); + date_end = g_date_time_to_timezone (aux, zone_end); + end_is_all_day = datetime_is_date (aux); - gcal_event_set_date_end (self, date_end); + self->dt_end = g_date_time_ref (date_end); - /* - * Setup timezone. We use the timezone from the start - * date as the timezone of the event, and the end date - * is adjusted if necessary. - */ - gcal_event_set_timezone (self, tz); + g_clear_pointer (&aux, g_date_time_unref); /* Setup all day */ - gcal_event_set_all_day (self, datetime_is_date (date_start) && datetime_is_date (date_end)); + self->all_day = start_is_all_day && end_is_all_day; /* Setup description */ description = get_desc_from_component (component, "\n\n"); @@ -176,7 +229,9 @@ gcal_event_set_component_internal (GcalEvent *self, g_object_notify (G_OBJECT (self), "summary"); g_clear_pointer (&date_start, g_date_time_unref); + g_clear_pointer (&zone_start, g_time_zone_unref); g_clear_pointer (&date_end, g_date_time_unref); + g_clear_pointer (&zone_end, g_time_zone_unref); g_clear_pointer (&description, g_free); } } @@ -583,48 +638,7 @@ gcal_event_set_all_day (GcalEvent *self, self->all_day = all_day; g_object_notify (G_OBJECT (self), "all-day"); - - /* - * If we just set an all day event, the hour & minute fields - * from the start & end date should be set to 0. - */ - if (self->all_day) - { - GDateTime *dt; - - /* Update start date */ - if (self->dt_start) - { - dt = g_date_time_new (self->timezone, - g_date_time_get_year (self->dt_start), - g_date_time_get_month (self->dt_start), - g_date_time_get_day_of_month (self->dt_start), - self->all_day ? 0 : g_date_time_get_hour (self->dt_start), - self->all_day ? 0 : g_date_time_get_minute (self->dt_start), - 0); - - gcal_event_set_date_start (self, dt); - - g_clear_pointer (&dt, g_date_time_unref); - } - - /* Update end date */ - if (self->dt_end) - { - dt = g_date_time_new (self->timezone, - g_date_time_get_year (self->dt_end), - g_date_time_get_month (self->dt_end), - g_date_time_get_day_of_month (self->dt_end), - self->all_day ? 0 : g_date_time_get_hour (self->dt_end), - self->all_day ? 0 : g_date_time_get_minute (self->dt_end), - 0); - - gcal_event_set_date_end (self, dt); - - g_clear_pointer (&dt, g_date_time_unref); - } - } - } + } } /** diff --git a/src/gcal-month-view.c b/src/gcal-month-view.c index 1055ed84..a9357ffb 100644 --- a/src/gcal-month-view.c +++ b/src/gcal-month-view.c @@ -488,6 +488,8 @@ rebuild_popover_for_day (GcalMonthView *view, PangoFontDescription *ofont_desc; gint font_height, padding_bottom; + GTimeZone *tz; + priv = gcal_month_view_get_instance_private (view); ppriv = GCAL_SUBSCRIBER_VIEW (view)->priv; @@ -499,6 +501,7 @@ rebuild_popover_for_day (GcalMonthView *view, gtk_container_foreach (GTK_CONTAINER (priv->events_list_box), (GtkCallback) gtk_widget_destroy, NULL); l = g_hash_table_lookup (ppriv->overflow_cells, GINT_TO_POINTER (priv->pressed_overflow_indicator)); + tz = g_time_zone_new_local (); /* Setup the start & end dates of the events as the begin & end of day */ if (l != NULL) @@ -506,9 +509,7 @@ rebuild_popover_for_day (GcalMonthView *view, GDateTime *current_date; GDateTime *dt_start; GDateTime *dt_end; - GTimeZone *tz; - - current_date = icaltime_to_datetime (priv->date, &tz); + current_date = icaltime_to_datetime (priv->date); dt_start = g_date_time_new (tz, g_date_time_get_year (current_date), g_date_time_get_month (current_date), @@ -536,9 +537,10 @@ rebuild_popover_for_day (GcalMonthView *view, g_clear_pointer (¤t_date, g_date_time_unref); g_clear_pointer (&dt_start, g_date_time_unref); g_clear_pointer (&dt_end, g_date_time_unref); - g_clear_pointer (&tz, g_time_zone_unref); } + g_clear_pointer (&tz, g_time_zone_unref); + /* placement calculation */ start_grid_y = get_start_grid_y (GTK_WIDGET (view)); shown_rows = ceil ((priv->days_delay + icaltime_days_in_month (priv->date->month, priv->date->year)) / 7.0); diff --git a/src/gcal-utils.c b/src/gcal-utils.c index 552de36c..9ac6a287 100644 --- a/src/gcal-utils.c +++ b/src/gcal-utils.c @@ -110,8 +110,7 @@ datetime_to_icaltime (GDateTime *dt) } GDateTime* -icaltime_to_datetime (const icaltimetype *date, - GTimeZone **timezone) +icaltime_to_datetime (const icaltimetype *date) { GDateTime *dt; GTimeZone *tz; @@ -125,10 +124,7 @@ icaltime_to_datetime (const icaltimetype *date, date->is_date ? 0 : date->minute, date->is_date ? 0 : date->second); - if (timezone) - *timezone = tz; - else - g_clear_pointer (&tz, g_time_zone_unref); + g_clear_pointer (&tz, g_time_zone_unref); return dt; } @@ -427,15 +423,11 @@ build_component_from_details (const gchar *summary, ECalComponent *event; ECalComponentDateTime dt; ECalComponentText summ; - GTimeZone *tz; - - tz = g_time_zone_new_local (); event = e_cal_component_new (); e_cal_component_set_new_vtype (event, E_CAL_COMPONENT_EVENT); dt.value = (icaltimetype*) initial_date; - dt.tzid = g_time_zone_get_abbreviation (tz, 0); e_cal_component_set_dtstart (event, &dt); if (final_date != NULL) @@ -458,8 +450,6 @@ build_component_from_details (const gchar *summary, e_cal_component_commit_sequence (event); - g_clear_pointer (&tz, g_time_zone_unref); - return event; } @@ -785,3 +775,24 @@ get_source_parent_name_color (GcalManager *manager, *color = gdk_rgba_to_string (&c); } } + +gchar* +format_utc_offset (gint offset) +{ + const char *sign = "+"; + gint hours, minutes, seconds; + + if (offset < 0) { + offset = -offset; + sign = "-"; + } + + hours = offset / 3600; + minutes = (offset % 3600) / 60; + seconds = offset % 60; + + if (seconds > 0) + return g_strdup_printf ("%s%02i%02i%02i", sign, hours, minutes, seconds); + else + return g_strdup_printf ("%s%02i%02i", sign, hours, minutes); +} diff --git a/src/gcal-utils.h b/src/gcal-utils.h index 303e1a00..0ac27fcb 100644 --- a/src/gcal-utils.h +++ b/src/gcal-utils.h @@ -59,8 +59,7 @@ icaltimetype* datetime_to_icaltime (GDateTime gboolean datetime_is_date (GDateTime *dt); -GDateTime* icaltime_to_datetime (const icaltimetype *date, - GTimeZone **timezone); +GDateTime* icaltime_to_datetime (const icaltimetype *date); icaltimetype* gcal_dup_icaltime (const icaltimetype *date); @@ -122,4 +121,6 @@ void get_source_parent_name_color (GcalManager gchar **name, gchar **color); +gchar* format_utc_offset (gint offset); + #endif // __GCAL_UTILS_H__ diff --git a/src/gcal-year-view.c b/src/gcal-year-view.c index f202d9b3..3870a596 100644 --- a/src/gcal-year-view.c +++ b/src/gcal-year-view.c @@ -272,7 +272,6 @@ add_event_to_day_array (GcalYearView *year_view, GDateTime *dt_start, *dt_end; GDateTime *date, *second_date; - GTimeZone *tz; gint i; @@ -284,8 +283,8 @@ add_event_to_day_array (GcalYearView *year_view, dt_end = gcal_event_get_date_end (event); /* normalize date on each new event */ - date = icaltime_to_datetime (year_view->start_selected_date, &tz); - second_date = g_date_time_new (tz, + date = icaltime_to_datetime (year_view->start_selected_date); + second_date = g_date_time_new (gcal_event_get_timezone (event), g_date_time_get_year (date), g_date_time_get_month (date), g_date_time_get_day_of_month (date) + 1,