Branch: Tag:


2008-02-05 21:34:47 by Martin Stjernholm <>

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/

2226:       static TimeRange _move(int x,YMD step)    { -  if (step->is_year) -  return year()->add(x,step)->place(this,1); +  if (step->is_year) { +  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) -  return month()->add(x,step)->place(this,1); +  if (step->is_month) { +  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)    return week()->add(x,step)->place(this,1);