Skip to content
Snippets Groups Projects
Commit 7a51709e authored by Martin Stjernholm's avatar Martin Stjernholm
Browse files

Fixed stepping of days by months or years to work in a more useful way.

Previously e.g. Calendar.ISO.Day(2000,1,31)+Calendar.ISO.Month() would
return March 2nd since there aren't 31 days in February and the Calendar
module would instead step 30 days forward from February 1st.

This is counterintuitive and not useful in most cases; practice shows that
it's clearly better to keep the month correct and accept an offset in the
day-of-month, i.e. to get February 29th instead in the example above.

The same situation also exists when standing on a leap day and stepping by
years to a non-leap year. This patch fixes that case too.

It does however not fix stepping weeks by years, which currently works in an
inconsistent way wrt to days. That will be fixed in versions >= 7.7 with
compat goo.

NOTE: This change is not strictly compatible, but given the alternatives of
introducing a theoretical incompatibility and solving a very real and
repeatedly encountered problem, the choice isn't difficult.

Rev: lib/modules/Calendar.pmod/YMD.pike:1.28
Rev: lib/modules/Calendar.pmod/testsuite.in:1.16
parent 7b64ef07
No related branches found
No related tags found
No related merge requests found
...@@ -2226,11 +2226,27 @@ class cDay ...@@ -2226,11 +2226,27 @@ class cDay
static TimeRange _move(int x,YMD step) static TimeRange _move(int x,YMD step)
{ {
if (step->is_year) if (step->is_year) {
return year()->add(x,step)->place(this,1); TimeRange stepped = year()->add(x,step);
if (TimeRange placed = stepped->place(this,0))
return placed;
// If we couldn't place our day in the target year it means
// we're on a leap day and the target year doesn't have any.
// We return the closest day in the same month.
TimeRange placed = stepped->place (month());
if (md == CALUNKNOWN) make_month();
return placed->day (md < placed->number_of_days() ? md : -1);
}
if (step->is_month) if (step->is_month) {
return month()->add(x,step)->place(this,1); TimeRange stepped = month()->add(x,step);
if (TimeRange placed = stepped->place(this,0))
return placed;
// The target month is shorter and our date doesn't exist in
// it. We return the closest (i.e. last) day of the target
// month.
return stepped->day (-1);
}
if (step->is_week) if (step->is_week)
return week()->add(x,step)->place(this,1); return week()->add(x,step)->place(this,1);
......
START_MARKER START_MARKER
dnl $Id: testsuite.in,v 1.15 2008/01/23 15:23:54 mast Exp $ dnl $Id: testsuite.in,v 1.16 2008/02/05 21:34:47 mast Exp $
dnl NOTE: dnl NOTE:
dnl *every* time the Calendar tests have failed, it's not the dnl *every* time the Calendar tests have failed, it's not the
...@@ -139,4 +139,22 @@ test_eq([[Calendar.ISO.Week (2008, 1)->set_size (Calendar.ISO.Day())->year_no()] ...@@ -139,4 +139,22 @@ test_eq([[Calendar.ISO.Week (2008, 1)->set_size (Calendar.ISO.Day())->year_no()]
test_eq([[Calendar.ISO.Week (2008, 1)->year_no()]], 2008) test_eq([[Calendar.ISO.Week (2008, 1)->year_no()]], 2008)
test_eq([[Calendar.ISO.Year (Calendar.ISO.Week (2008, 1))->year_no()]], 2008) test_eq([[Calendar.ISO.Year (Calendar.ISO.Week (2008, 1))->year_no()]], 2008)
test_eq([[Calendar.ISO.Day (2007, 5, 31)->add (1, Calendar.ISO.Month())]], Calendar.ISO.Day (2007, 6, 30))
test_eq([[Calendar.ISO.Day (2007, 5, 31)->add (2, Calendar.ISO.Month())]], Calendar.ISO.Day (2007, 7, 31))
test_eq([[Calendar.ISO.Day (2007, 5, 31)->add (1, Calendar.ISO.Month())->add (1, Calendar.ISO.Month())]], Calendar.ISO.Day (2007, 7, 30))
test_eq([[Calendar.ISO.Day (2007, 5, 31)->add (-1, Calendar.ISO.Month())]], Calendar.ISO.Day (2007, 4, 30))
test_eq([[Calendar.ISO.Day (2007, 5, 31)->add (-2, Calendar.ISO.Month())]], Calendar.ISO.Day (2007, 3, 31))
test_eq([[Calendar.ISO.Day (2007, 5, 31)->add (-1, Calendar.ISO.Month())->add (1, Calendar.ISO.Month())]], Calendar.ISO.Day (2007, 5, 30))
test_eq([[Calendar.ISO.Day (1900, 1, 31)->add (1, Calendar.ISO.Month())]], Calendar.ISO.Day (1900, 2, 28))
test_eq([[Calendar.ISO.Day (2000, 1, 31)->add (1, Calendar.ISO.Month())]], Calendar.ISO.Day (2000, 2, 29))
test_eq([[Calendar.ISO.Day (2004, 1, 31)->add (1, Calendar.ISO.Month())]], Calendar.ISO.Day (2004, 2, 29))
test_eq([[Calendar.ISO.Day (2004, 2, 29)->add (1, Calendar.ISO.Year())]], Calendar.ISO.Day (2005, 2, 28))
test_eq([[Calendar.ISO.Day (2004, 2, 29)->add (4, Calendar.ISO.Year())]], Calendar.ISO.Day (2008, 2, 29))
test_eq([[Calendar.ISO.Day (2004, 2, 29)->add (2, Calendar.ISO.Year())->add (2, Calendar.ISO.Year())]], Calendar.ISO.Day (2008, 2, 28))
test_eq([[Calendar.ISO.Day (2004, 2, 29)->add (-1, Calendar.ISO.Year())]], Calendar.ISO.Day (2003, 2, 28))
test_eq([[Calendar.ISO.Day (2004, 2, 29)->add (-4, Calendar.ISO.Year())]], Calendar.ISO.Day (2000, 2, 29))
test_eq([[Calendar.ISO.Day (2004, 2, 29)->add (-2, Calendar.ISO.Year())->add (2, Calendar.ISO.Year())]], Calendar.ISO.Day (2004, 2, 28))
END_MARKER END_MARKER
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment