Have you compared a date column to a timestamp and been told the comparison is invalid?
d = pd.Series(pd.to_datetime(['2026-01-05', '2026-06-01']))
d > pd.Timestamp('2026-03-01', tz='UTC')
TypeError: Invalid comparison between dtype=datetime64[us] and Timestamp
One side knows its timezone and the other does not. A naive datetime is a wall-clock reading with no location attached, so "is this before that" genuinely has no answer — it depends on where the naive one was recorded, and nothing in the data says.
Two honest fixes, and which is right depends on what your data means.
If the column is already UTC and simply never said so, attach the timezone:
d.dt.tz_localize('UTC') > pd.Timestamp('2026-03-01', tz='UTC')
0 False
1 True
dtype: bool
If the column is genuinely local wall-clock time, drop the timezone from the other side instead:
d > pd.Timestamp('2026-03-01')
0 False
1 True
dtype: bool
localize and convert are different operations
This is the part worth getting right, because both compile and only one is correct for a given frame.
tz_localize attaches a timezone to a naive timestamp without moving it. 2026-01-05 09:00 localized to UTC stays 09:00 and is now understood as 09:00 UTC.
tz_convert moves an already-aware timestamp into another zone. 09:00 UTC converted to America/New_York becomes 04:00.
So the sequence for "this column is New York local time, and I want UTC" is localize then convert:
d.dt.tz_localize('America/New_York').dt.tz_convert('UTC')
Calling tz_convert on a naive column raises, and calling tz_localize on an aware one raises too. The error text names which you have.
Where the mismatch comes from
Usually from mixing sources. A CSV of dates parses naive. A database with TIMESTAMP WITH TIME ZONE comes back aware. An API returning ISO-8601 with a Z or an offset parses aware, and one without parses naive — so two rows from the same endpoint can differ.
pd.to_datetime(s, utc=True) is the blunt instrument that makes everything aware and in UTC, including mixed input. If your analysis does not care about local wall-clock time, that is usually the right call at load time, and it removes this whole category.
Daylight saving is the reason to be careful
tz_localize on a local zone can fail in two specific ways, and both are real rather than pedantic. The hour that daylight saving skips does not exist, and the hour it repeats is ambiguous. Pandas raises rather than guessing, and ambiguous= and nonexistent= let you say what to do. Storing and computing in UTC and converting only for display avoids meeting either.
The mistakes I see most often
Using tz_convert to add a timezone. It moves the clock rather than labeling it, and on a naive column it raises.
Localizing to a local zone when the data is UTC. No error, and every value is now wrong by the offset.
Comparing to datetime.now(). That is naive. pd.Timestamp.now(tz='UTC') is the aware version.
Practice
See to_datetime in Pandas for parsing, and .dt.date in Pandas for pulling pieces out of a datetime column.