mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-02-25 19:52:10 +01:00
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
This commit is contained in:
parent
0746f74036
commit
4bac6613cf
270
glib/gdatetime.c
270
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)
|
||||
{
|
||||
|
@ -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 ();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user