[glib] datetime: Update modifiers for DST changes



commit 4bac6613cfca4b0676a51e1df0848fa2e4d35120
Author: Emmanuele Bassi <ebassi linux intel com>
Date:   Wed Sep 15 13:55:36 2010 +0100

    datetime: Update modifiers for DST changes
    
    If a DateTime gets modified to cross the DST state from its previous
    state then we want to update the DateTime to compensate for the new
    offset.
    
    In other words, if we have a DateTime defined as:
    
      DateTime({ y: 2009, m: 8, d: 15, hh: 3, mm: 0, tz: 'Europe/London' });
    
    and we add six months to it, the hour must be changed to 60 minutes
    behind, as the DST comes into effect.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=50076

 glib/gdatetime.c       |  270 +++++++++++++++++++++++++++++-------------------
 glib/tests/gdatetime.c |   67 ++++++++++--
 2 files changed, 220 insertions(+), 117 deletions(-)
---
diff --git a/glib/gdatetime.c b/glib/gdatetime.c
index 634f73d..e5b55c6 100644
--- a/glib/gdatetime.c
+++ b/glib/gdatetime.c
@@ -313,111 +313,6 @@ get_weekday_name_abbr (gint day)
   return NULL;
 }
 
-static inline gint
-date_to_proleptic_gregorian (gint year,
-                             gint month,
-                             gint day)
-{
-  gint64 days;
-
-  days = (year - 1) * 365 + ((year - 1) / 4) - ((year - 1) / 100)
-      + ((year - 1) / 400);
-
-  days += days_in_year[0][month - 1];
-  if (GREGORIAN_LEAP (year) && month > 2)
-    day++;
-
-  days += day;
-
-  return days;
-}
-
-static inline void
-g_date_time_add_days_internal (GDateTime *datetime,
-                               gint64     days)
-{
-  datetime->days += days;
-}
-
-static inline void
-g_date_time_add_usec (GDateTime *datetime,
-                      gint64     usecs)
-{
-  gint64 u = datetime->usec + usecs;
-  gint d = u / USEC_PER_DAY;
-
-  if (u < 0)
-    d -= 1;
-
-  if (d != 0)
-    g_date_time_add_days_internal (datetime, d);
-
-  if (u < 0)
-    datetime->usec = USEC_PER_DAY + (u % USEC_PER_DAY);
-  else
-    datetime->usec = u % USEC_PER_DAY;
-}
-
-/*< internal >
- * g_date_time_add_ymd:
- * @datetime: a #GDateTime
- * @years: years to add, in the Gregorian calendar
- * @months: months to add, in the Gregorian calendar
- * @days: days to add, in the Gregorian calendar
- *
- * Updates @datetime by adding @years, @months and @days to it
- *
- * This function modifies the passed #GDateTime so public accessors
- * should make always pass a copy
- */
-static inline void
-g_date_time_add_ymd (GDateTime *datetime,
-                     gint       years,
-                     gint       months,
-                     gint       days)
-{
-  gint y = g_date_time_get_year (datetime);
-  gint m = g_date_time_get_month (datetime);
-  gint d = g_date_time_get_day_of_month (datetime);
-  gint step, i;
-  const guint16 *max_days;
-
-  y += years;
-
-  /* subtract one day for leap years */
-  if (GREGORIAN_LEAP (y) && m == 2)
-    {
-      if (d == 29)
-        d -= 1;
-    }
-
-  /* add months */
-  step = months > 0 ? 1 : -1;
-  for (i = 0; i < ABS (months); i++)
-    {
-      m += step;
-
-      if (m < 1)
-        {
-          y -= 1;
-          m = 12;
-        }
-      else if (m > 12)
-        {
-          y += 1;
-          m = 1;
-        }
-    }
-
-  /* clamp the days */
-  max_days = days_in_months[GREGORIAN_LEAP (y) ? 1 : 0];
-  if (max_days[m] < d)
-    d = max_days[m];
-
-  datetime->days = date_to_proleptic_gregorian (y, m, d);
-  g_date_time_add_days_internal (datetime, days);
-}
-
 #define ZONEINFO_DIR            "zoneinfo"
 #define TZ_MAGIC                "TZif"
 #define TZ_MAGIC_LEN            (strlen (TZ_MAGIC))
@@ -950,6 +845,171 @@ g_time_zone_sink (GTimeZone *time_zone,
     }
 }
 
+static inline gint
+date_to_proleptic_gregorian (gint year,
+                             gint month,
+                             gint day)
+{
+  gint64 days;
+
+  days = (year - 1) * 365 + ((year - 1) / 4) - ((year - 1) / 100)
+      + ((year - 1) / 400);
+
+  days += days_in_year[0][month - 1];
+  if (GREGORIAN_LEAP (year) && month > 2)
+    day++;
+
+  days += day;
+
+  return days;
+}
+
+static inline void g_date_time_add_usec (GDateTime *datetime,
+                                         gint64     usecs);
+
+static inline void
+g_date_time_add_days_internal (GDateTime *datetime,
+                               gint64     days)
+{
+  gboolean was_dst = FALSE;
+  gint64 old_offset = 0;
+
+  if (datetime->tz != NULL && datetime->tz->tz_file != NULL)
+    {
+      was_dst = g_time_zone_get_is_dst (datetime->tz);
+      old_offset = g_time_zone_get_offset (datetime->tz);
+
+      datetime->tz->is_floating = TRUE;
+    }
+
+  datetime->days += days;
+
+  if (datetime->tz != NULL && datetime->tz->tz_file != NULL)
+    {
+      gint64 offset;
+
+      g_time_zone_sink (datetime->tz, datetime);
+
+      if (was_dst == g_time_zone_get_is_dst (datetime->tz))
+        return;
+
+      offset = old_offset - g_time_zone_get_offset (datetime->tz);
+      g_date_time_add_usec (datetime, offset * USEC_PER_SECOND * -1);
+    }
+}
+
+static inline void
+g_date_time_add_usec (GDateTime *datetime,
+                      gint64     usecs)
+{
+  gint64 u = datetime->usec + usecs;
+  gint d = u / USEC_PER_DAY;
+  gboolean was_dst = FALSE;
+  gint64 old_offset = 0;
+
+  /* if we are using a time zone from a zoneinfo we want to
+   * check for changes in the DST and update the DateTime
+   * accordingly in case we change for standard time to DST
+   * and vice versa
+   */
+  if (datetime->tz != NULL && datetime->tz->tz_file != NULL)
+    {
+      was_dst = g_time_zone_get_is_dst (datetime->tz);
+      old_offset = g_time_zone_get_offset (datetime->tz);
+
+      /* force the floating state */
+      datetime->tz->is_floating = TRUE;
+    }
+
+  if (u < 0)
+    d -= 1;
+
+  if (d != 0)
+    g_date_time_add_days_internal (datetime, d);
+
+  if (u < 0)
+    datetime->usec = USEC_PER_DAY + (u % USEC_PER_DAY);
+  else
+    datetime->usec = u % USEC_PER_DAY;
+
+  if (datetime->tz != NULL && datetime->tz->tz_file != NULL)
+    {
+      gint64 offset;
+
+      /* sink the timezone; if there were no changes in the
+       * DST state then bail out; otherwise, apply the change
+       * in the offset to the DateTime
+       */
+      g_time_zone_sink (datetime->tz, datetime);
+
+      if (was_dst == g_time_zone_get_is_dst (datetime->tz))
+        return;
+
+      offset = old_offset - g_time_zone_get_offset (datetime->tz);
+      g_date_time_add_usec (datetime, offset * USEC_PER_SECOND * -1);
+    }
+}
+
+/*< internal >
+ * g_date_time_add_ymd:
+ * @datetime: a #GDateTime
+ * @years: years to add, in the Gregorian calendar
+ * @months: months to add, in the Gregorian calendar
+ * @days: days to add, in the Gregorian calendar
+ *
+ * Updates @datetime by adding @years, @months and @days to it
+ *
+ * This function modifies the passed #GDateTime so public accessors
+ * should make always pass a copy
+ */
+static inline void
+g_date_time_add_ymd (GDateTime *datetime,
+                     gint       years,
+                     gint       months,
+                     gint       days)
+{
+  gint y = g_date_time_get_year (datetime);
+  gint m = g_date_time_get_month (datetime);
+  gint d = g_date_time_get_day_of_month (datetime);
+  gint step, i;
+  const guint16 *max_days;
+
+  y += years;
+
+  /* subtract one day for leap years */
+  if (GREGORIAN_LEAP (y) && m == 2)
+    {
+      if (d == 29)
+        d -= 1;
+    }
+
+  /* add months */
+  step = months > 0 ? 1 : -1;
+  for (i = 0; i < ABS (months); i++)
+    {
+      m += step;
+
+      if (m < 1)
+        {
+          y -= 1;
+          m = 12;
+        }
+      else if (m > 12)
+        {
+          y += 1;
+          m = 1;
+        }
+    }
+
+  /* clamp the days */
+  max_days = days_in_months[GREGORIAN_LEAP (y) ? 1 : 0];
+  if (max_days[m] < d)
+    d = max_days[m];
+
+  datetime->days = date_to_proleptic_gregorian (y, m, d);
+  g_date_time_add_days_internal (datetime, days);
+}
+
 static GDateTime *
 g_date_time_new (void)
 {
diff --git a/glib/tests/gdatetime.c b/glib/tests/gdatetime.c
index fe6808e..fdd62cf 100644
--- a/glib/tests/gdatetime.c
+++ b/glib/tests/gdatetime.c
@@ -484,26 +484,30 @@ test_GDateTime_add_years (void)
 static void
 test_GDateTime_add_months (void)
 {
+  GTimeZone *utc_tz = g_time_zone_new_utc ();
+
 #define TEST_ADD_MONTHS(y,m,d,a,ny,nm,nd) G_STMT_START { \
   GDateTime *dt, *dt2; \
-  dt = g_date_time_new_from_date (y, m, d); \
+  dt = g_date_time_new_full (y, m, d, 0, 0, 0, utc_tz); \
   dt2 = g_date_time_add_months (dt, a); \
   ASSERT_DATE (dt2, ny, nm, nd); \
   g_date_time_unref (dt); \
   g_date_time_unref (dt2); \
 } G_STMT_END
 
-  TEST_ADD_MONTHS (2009, 12, 31, 1, 2010, 1, 31);
-  TEST_ADD_MONTHS (2009, 12, 31, 1, 2010, 1, 31);
-  TEST_ADD_MONTHS (2009, 6, 15, 1, 2009, 7, 15);
-  TEST_ADD_MONTHS (1400, 3, 1, 1, 1400, 4, 1);
-  TEST_ADD_MONTHS (1400, 1, 31, 1, 1400, 2, 28);
-  TEST_ADD_MONTHS (1400, 1, 31, 7200, 2000, 1, 31);
-  TEST_ADD_MONTHS (2008, 2, 29, 12, 2009, 2, 28);
-  TEST_ADD_MONTHS (2000, 8, 16, -5, 2000, 3, 16);
-  TEST_ADD_MONTHS (2000, 8, 16, -12, 1999, 8, 16);
-  TEST_ADD_MONTHS (2011, 2, 1, -13, 2010, 1, 1);
-  TEST_ADD_MONTHS (1776, 7, 4, 1200, 1876, 7, 4);
+  TEST_ADD_MONTHS (2009, 12, 31,    1, 2010, 1, 31);
+  TEST_ADD_MONTHS (2009, 12, 31,    1, 2010, 1, 31);
+  TEST_ADD_MONTHS (2009,  6, 15,    1, 2009, 7, 15);
+  TEST_ADD_MONTHS (1400,  3,  1,    1, 1400, 4,  1);
+  TEST_ADD_MONTHS (1400,  1, 31,    1, 1400, 2, 28);
+  TEST_ADD_MONTHS (1400,  1, 31, 7200, 2000, 1, 31);
+  TEST_ADD_MONTHS (2008,  2, 29,   12, 2009, 2, 28);
+  TEST_ADD_MONTHS (2000,  8, 16,   -5, 2000, 3, 16);
+  TEST_ADD_MONTHS (2000,  8, 16,  -12, 1999, 8, 16);
+  TEST_ADD_MONTHS (2011,  2,  1,  -13, 2010, 1,  1);
+  TEST_ADD_MONTHS (1776,  7,  4, 1200, 1876, 7,  4);
+
+  g_time_zone_free (utc_tz);
 }
 
 static void
@@ -981,6 +985,44 @@ GDateTime *__dt = g_date_time_new_from_date (2009, 10, 24);     \
   TEST_PRINTF ("%Z", dst);
 }
 
+static void
+test_GDateTime_dst (void)
+{
+  GDateTime *dt1, *dt2;
+  GTimeZone *tz;
+
+  tz = g_time_zone_new_for_name ("Europe/London");
+
+  /* this date has the DST state set for Europe/London */
+  dt1 = g_date_time_new_full (2009, 8, 15, 3, 0, 1, tz);
+  g_assert (g_date_time_is_daylight_savings (dt1));
+  g_assert_cmpint (g_date_time_get_utc_offset (dt1) / G_USEC_PER_SEC, ==, 3600);
+  g_assert_cmpint (g_date_time_get_hour (dt1), ==, 3);
+
+  /* add 6 months to clear the DST flag and go back one hour */
+  dt2 = g_date_time_add_months (dt1, 6);
+  g_assert (!g_date_time_is_daylight_savings (dt2));
+  g_assert_cmpint (g_date_time_get_utc_offset (dt2) / G_USEC_PER_SEC, ==, 0);
+  g_assert_cmpint (g_date_time_get_hour (dt2), ==, 2);
+
+  g_date_time_unref (dt2);
+  g_date_time_unref (dt1);
+
+  /* now do the reverse: start with a non-DST state and move to DST */
+  dt1 = g_date_time_new_full (2009, 2, 15, 2, 0, 1, tz);
+  g_assert (!g_date_time_is_daylight_savings (dt1));
+  g_assert_cmpint (g_date_time_get_hour (dt1), ==, 2);
+
+  dt2 = g_date_time_add_months (dt1, 6);
+  g_assert (g_date_time_is_daylight_savings (dt2));
+  g_assert_cmpint (g_date_time_get_hour (dt2), ==, 3);
+
+  g_date_time_unref (dt2);
+  g_date_time_unref (dt1);
+
+  g_time_zone_free (tz);
+}
+
 gint
 main (gint   argc,
       gchar *argv[])
@@ -1029,6 +1071,7 @@ main (gint   argc,
   g_test_add_func ("/GDateTime/to_utc", test_GDateTime_to_utc);
   g_test_add_func ("/GDateTime/today", test_GDateTime_today);
   g_test_add_func ("/GDateTime/utc_now", test_GDateTime_utc_now);
+  g_test_add_func ("/GDateTime/dst", test_GDateTime_dst);
 
   return g_test_run ();
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]