One function
daterange in mahmoud/boltons
The author described this change as “test and fix year step and december handling in date_range, fixes #319”. It counts as a record because the check below fails on the code as it stood at 6c7fa3e05 and passes on 8ba3a7fa0, with nothing else changed between the two runs.
Projectmahmoud/boltons
Fix saved2022-12-08
Sharing licenceBSD-2-Clause · LICENSE
Change size+6 −4
What the code was meant to do, written into the code itself as a docstring
In the spirit of :func:`range` and :func:`xrange`, the `daterange` generator that yields a sequence of :class:`~datetime.date` objects, starting at *start*, incrementing by *step*, until *stop* is reached. When *inclusive* is True, the final date may be *stop*, **if** *step* falls evenly on it. By default, *step* is one day. See details below for many more details. Args: start (datetime.date): The starting date The first value in the sequence. stop (datetime.date): The stopping date. By default not included in return. Can be `None` to yield an infinite sequence. step (int): The value to increment *start* by to reach *stop*. Can be an :class:`int` number of days, a :class:`datetime.timedelta`, or a :class:`tuple` of integers, `(year, month, day)`. Positive and negative *step* values are supported. inclusive (bool): Whether or not the *stop* date can be returned. *stop* is only returned when a *step* falls evenly on it. *Be careful when using stop=None, as this will yield an infinite sequence of dates.*
The change
| 66 | 66 | else: | |
| 67 | 67 | raise ValueError('step expected int, timedelta, or tuple' | |
| 68 | 68 | ' (year, month, day), not: %r' % step) | |
| 69 | + | ||
| 70 | + | m_step += y_step * 12 | |
| 69 | 71 | ||
| 70 | 72 | if stop is None: | |
| 71 | 73 | finished = lambda now, stop: False | |
| ⋯ | |||
| 77 | 79 | ||
| 78 | 80 | while not finished(now, stop): | |
| 79 | 81 | yield now | |
| 80 | - | if y_step or m_step: | |
| 81 | - | m_y_step, cur_month = divmod(now.month + m_step, 12) | |
| 82 | - | now = now.replace(year=now.year + y_step + m_y_step, | |
| 83 | - | month=cur_month or 12) | |
| 82 | + | if m_step: | |
| 83 | + | m_y_step, cur_month = divmod((now.month - 1) + m_step, 12) | |
| 84 | + | now = now.replace(year=now.year + m_y_step, | |
| 85 | + | month=(cur_month + 1)) | |
| 84 | 86 | now = now + d_step | |
| 85 | 87 | return | |
The check that tells the two apart
fail→pass·tests/test_timeutils.py::test_daterange_years_step
Check file tests/test_timeutils.py, taken without changes from the fix and copied onto the older code, so the exact same check runs against both versions.
Origin and history
The code before it6c7fa3e05659fac6515f661153c89d4ad02df168
Broken version dated2022-04-29
Moduleboltons.timeutils
Units changeddaterange
Fingerprinteab4eb7a07abf56b
Checked2026-08-18 by goldset/0.1
Every field above is generated by our program. None of it is written by hand.
Other bugs found in mahmoud/boltons
- 2026-08-06Bits
- 2026-08-06JSONLIterator
- 2026-08-06tableutils: fix Table.to_text crashes on degenerate tables and headers
- 2026-07-18backoff_iter
- 2026-07-17singularize
- 2026-07-17fix(fileutils): accept os.PathLike in AtomicSaver