diff --git a/glib/gdatetime.c b/glib/gdatetime.c index 634f73d15..e5b55c6be 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 fe6808e3f..fdd62cf7d 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 (); }