Skip to content

GH-47400: [C++] Support adding/subtracting intervals to/from timestamps - #53325

Open
DeviousCardi wants to merge 1 commit into
apache:mainfrom
DeviousCardi:GH-47400-interval-arithmetic
Open

DeviousCardi wants to merge 1 commit into
apache:mainfrom
DeviousCardi:GH-47400-interval-arithmetic

Conversation

@DeviousCardi

Copy link
Copy Markdown

Rationale for this change

There is currently no kernel for timestamp + interval, so e.g. "add one day to a zoned timestamp, keeping the wall clock time" requires a round trip through pandas (see #47400).

What changes are included in this PR?

New kernels for add, add_checked, subtract and subtract_checked:

  • timestamp(unit, tz) ± month_interval / day_time_interval / month_day_nano_interval -> timestamp(unit, tz), for all four units, with or without a timezone
  • add / add_checked also accept the interval as the first argument

Semantics follow the discussion in the issue (Pandas' DateOffset):

  • Months and days are calendar units applied to the local time of the timestamp, so 2025-03-08 09:30 EST + 1 day = 2025-03-09 09:30 EDT.
  • The sub-day part (milliseconds of a day_time_interval, nanoseconds of a month_day_nano_interval) is a fixed duration, so 2025-03-08 09:30 EST + 24h = 2025-03-09 10:30 EDT.
  • add applies months, then days, then the sub-day part. subtract applies the sub-day part first, so subtracting "1 day 24 hours" undoes adding it (the mixed-interval examples in the issue). Months are applied before days in both directions, as in DateOffset and PostgreSQL.
  • Adding months clamps the day of month (Jan 31 + 1 month = Feb 28/29).
  • If the resulting local time is ambiguous or doesn't exist (DST overlap or gap), an Invalid error is returned, with the same messages as the existing ZonedLocalizer::ConvertLocalToSys. This is also what pandas does for a tz-aware Series/DatetimeIndex + DateOffset (a scalar Timestamp silently resolves instead). The issue doesn't settle this point. If wanted, a follow-up could add an option to choose earliest/latest/shift-forward like AssumeTimezoneOptions.
  • A sub-day part that isn't a multiple of the timestamp unit (e.g. 1 ns added to timestamp[s]) returns Invalid.
  • The _checked variants return Invalid on int64 overflow; the unchecked variants wrap around like the other arithmetic kernels.

Calendar arithmetic uses 64-bit year arithmetic, so the full range of timestamp[s] is supported. The timezone lookups use the existing LocateZone/time_zone::get_info machinery from temporal_internal.h.

ArithmeticFunction::DispatchBest no longer applies implicit temporal promotion when one of the arguments is an interval. Otherwise date32 + interval would be silently cast to timestamp[s] + interval. Dates are not supported yet; I'd suggest doing them in a follow-up once we agree on what date + interval with a sub-day part should return.

Are these changes tested?

Yes:

  • C++ (scalar_temporal_test.cc): month-end clamping, leap years, negative intervals, nulls, all units and all three interval types, both argument orders. DST transitions are covered in America/New_York, Europe/London and Australia/Lord_Howe (30 min DST), including results that land just after a gap or overlap and results that fall inside one (errors), plus a fixed-offset timezone. Error tests cover sub-unit and overflow cases. The expected DST values were checked against pandas 3.0.6.
  • Python (test_compute.py): parity with pandas.DateOffset + Timedelta across timezones and units, and the same DST gap/overlap errors as pandas.

Are there any user-facing changes?

Yes, new kernels for add/subtract (documented in compute.rst). Existing kernels are unchanged.


AI usage disclosure: I used an AI coding assistant (Claude) for this PR. It drafted most of the kernel code, the C++ and Python tests, and the docs paragraph. I reviewed all of it, and the expected DST values in the tests were checked against pandas independently of the kernel. The 64-bit calendar helpers (DaysFromCivil/CivilFromDays) follow Howard Hinnant's public-domain date algorithms, which are linked in a comment.

…mestamps

Add add, add_checked, subtract and subtract_checked kernels for
timestamp +/- {month_interval, day_time_interval, month_day_nano_interval},
with and without a timezone (add also accepts the interval first).

Months and days are calendar units applied to the local time of the
timestamp, while the sub-day part of the interval is a fixed duration,
following pandas.DateOffset. add applies months, days, then the sub-day
part; subtract applies the sub-day part first. Adding months clamps the
day of month. Ambiguous or nonexistent resulting local times return an
error, and the checked variants detect overflow.

Implicit temporal promotion is no longer applied in arithmetic dispatch
when an argument is an interval, so that e.g. date32 + interval is not
silently cast to a timestamp.
@github-actions github-actions Bot added the awaiting review Awaiting review label Oct 12, 2026
@github-actions

Copy link
Copy Markdown

⚠️ GitHub issue #47400 has been automatically assigned in GitHub to PR creator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant