Shift the date to a month start or end a given number of months away.
(date, months, day_option: DayOption = "start")
| 285 | |
| 286 | |
| 287 | def _shift_month(date, months, day_option: DayOption = "start"): |
| 288 | """Shift the date to a month start or end a given number of months away.""" |
| 289 | _ = attempt_import("cftime") |
| 290 | |
| 291 | has_year_zero = date.has_year_zero |
| 292 | year = date.year + (date.month + months) // 12 |
| 293 | month = (date.month + months) % 12 |
| 294 | |
| 295 | if month == 0: |
| 296 | month = 12 |
| 297 | year -= 1 |
| 298 | |
| 299 | if not has_year_zero: |
| 300 | if date.year < 0 <= year: |
| 301 | year += 1 |
| 302 | elif year <= 0 < date.year: |
| 303 | year -= 1 |
| 304 | |
| 305 | # Silence warnings associated with generating dates with years < 1. |
| 306 | with warnings.catch_warnings(): |
| 307 | warnings.filterwarnings("ignore", message="this date/calendar/year zero") |
| 308 | |
| 309 | if day_option == "start": |
| 310 | day = 1 |
| 311 | elif day_option == "end": |
| 312 | reference = type(date)(year, month, 1, has_year_zero=has_year_zero) |
| 313 | day = reference.daysinmonth |
| 314 | else: |
| 315 | raise ValueError(day_option) |
| 316 | return date.replace(year=year, month=month, day=day) |
| 317 | |
| 318 | |
| 319 | def roll_qtrday( |