pandas-dev/pandas · error · TypeError

Start and end cannot both be tz-aware with different timezon

Error message

Start and end cannot both be tz-aware with different timezones

What it means

Raised by _infer_tz_from_endpoints (used by date_range and similar generators) when both `start` and `end` are tz-aware but their tzinfos disagree. The function calls timezones.infer_tzinfo which raises an AssertionError on mismatch; that is caught and re-raised as a TypeError with this message. The function's Raises contract documents this behavior.

Source

Thrown at pandas/core/arrays/datetimes.py:3087

    Parameters
    ----------
    start : Timestamp
    end : Timestamp
    tz : tzinfo or None

    Returns
    -------
    tz : tzinfo or None

    Raises
    ------
    TypeError : if start and end timezones do not agree
    """
    try:
        inferred_tz = timezones.infer_tzinfo(start, end)
    except AssertionError as err:
        # infer_tzinfo raises AssertionError if passed mismatched timezones
        raise TypeError(
            "Start and end cannot both be tz-aware with different timezones"
        ) from err

    inferred_tz = timezones.maybe_get_tz(inferred_tz)
    tz = timezones.maybe_get_tz(tz)

    if tz is not None and inferred_tz is not None:
        if not timezones.tz_compare(inferred_tz, tz):
            raise AssertionError("Inferred time zone not equal to passed time zone")

    elif inferred_tz is not None:
        tz = inferred_tz

    return tz


def _maybe_normalize_endpoints(
    start: _TimestampNoneT1, end: _TimestampNoneT2, normalize: bool

View on GitHub (pinned to 3b7651241d)

Solutions

  1. Align both endpoints to the same tz before calling date_range: `end = end.tz_convert(start.tz)`.
  2. Drop tz from both sides if you want a tz-naive range.
  3. Construct the range in UTC and convert the result: `pd.date_range(..., tz='UTC').tz_convert(target)`.

Example fix

// before
rng = pd.date_range(start=ts_est, end=ts_utc)

// after
rng = pd.date_range(start=ts_est, end=ts_utc.tz_convert(ts_est.tz))
Defensive patterns

Strategy: validation

Validate before calling

import pandas as pd

def aligned_endpoints(start, end):
    stz = getattr(start, 'tzinfo', None) if start is not None else None
    etz = getattr(end, 'tzinfo', None) if end is not None else None
    if stz is not None and etz is not None and stz != etz:
        end = end.tz_convert(stz)
    return start, end

Type guard

def endpoints_tz_compatible(start, end) -> bool:
    stz = getattr(start, 'tzinfo', None) if start is not None else None
    etz = getattr(end, 'tzinfo', None) if end is not None else None
    return stz is None or etz is None or stz == etz

Try / catch

try:
    rng = pd.date_range(start=start, end=end)
except TypeError as e:
    if 'different timezones' in str(e):
        end = end.tz_convert(start.tz)
        rng = pd.date_range(start=start, end=end)
    else:
        raise

Prevention

When it happens

Trigger: Calling `pd.date_range(start=pd.Timestamp('2020-01-01', tz='US/Eastern'), end=pd.Timestamp('2020-01-02', tz='UTC'))`. Any API that internally calls _infer_tz_from_endpoints with mismatched-aware endpoints.

Common situations: Building endpoints from different sources (one from a DB in UTC, one user-supplied in local tz). Hardcoded start tz vs. dynamic end tz. Misconfigured tz on one side of a date window.

Related errors


AI-assisted analysis of pandas-dev/pandas@3b7651241d (2026-08-11). Data as JSON: /api/errors/7c32a1b9b3b37322. Report an issue: GitHub.