[glib/wip/date-time-fixes: 3/5] datetime: Update modifiers for DST changes
- From: Emmanuele Bassi <ebassi src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/wip/date-time-fixes: 3/5] datetime: Update modifiers for DST changes
- Date: Wed, 15 Sep 2010 15:30:43 +0000 (UTC)
commit bc6bfb4fcc70fd49602fa6fa7ffeac1fb224e1fa
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]