2025, Dec 04 23:00

Pandas is_year_end: why calendar vs business year end depends on your DatetimeIndex frequency

Learn how Pandas is_year_end works with business-day vs daily DatetimeIndex frequency. See examples and tips to choose calendar or business year end correctly.

When you work with Pandas time series, properties like is_year_end look deceptively simple. You might expect them to mark December 31 as the end of the year. Then you switch your DateTimeIndex to business days and suddenly the result changes: the last business day becomes the “year end.” The behavior is correct—and intentional—but it’s easy to miss why it happens.

Reproducing the situation

The following code builds two ranges covering the same period. The first uses business-day frequency (B). The second uses the default daily frequency (D). The logic is identical, but the outcome differs because of the index frequency.

from datetime import datetime
import pandas as pd

ver = pd.__version__
# ver is '2.0.0'

# Business-day index
biz_idx = pd.date_range(start='2017-01-01', end='2018-01-01', freq='B')

# Inspect the tail and the is_year_end flags
last_10_biz = biz_idx[-10:]
flags_biz = last_10_biz.is_year_end
end_biz = biz_idx[biz_idx.is_year_end]

# Daily index
day_idx = pd.date_range(start='2017-01-01', end='2018-01-01')

# Inspect the tail and the is_year_end flags
last_10_day = day_idx[-10:]
flags_day = last_10_day.is_year_end
end_day = day_idx[day_idx.is_year_end]

With business-day frequency, the selection yields 2017-12-29, which indeed was the last business day of the year. With daily frequency, the selection yields 2017-12-31, the last calendar day of the year. Same property, different result, entirely due to the DatetimeIndex frequency.

Why this happens

The official description explains the behavior succinctly:

is_year_end Logical indicating if last day of year (defined by frequency)

“Defined by frequency” is the key. If your index is composed of business days, the accessor does not check whether a timestamp is December 31. Instead, it checks whether a timestamp is the last business day in that year. If the index is daily, it checks the last calendar day. This aligns with real-world use cases where the meaningful “year end” depends on the cadence of the series. As practitioners pointed out, not every “year” closes on December 31. A university might consider 06/30 as its year end; some retail organizations close on 01/31. A frequency-aware property supports these fiscal calendars.

The fix: choose the frequency that matches your intent

If what you want is the last calendar day of the year, use a daily-frequency index. If your analysis revolves around business days and you care about the last trading or working day of the year, use a business-day index. The property is_year_end will follow the frequency of the index you created.

import pandas as pd

# Calendar-year end (last calendar day)
cal_idx = pd.date_range(start='2017-01-01', end='2018-01-01')
calendar_year_end = cal_idx[cal_idx.is_year_end]
# Result: DatetimeIndex(['2017-12-31'], dtype='datetime64[ns]', freq='D')

# Business-year end (last business day in the year)
work_idx = pd.date_range(start='2017-01-01', end='2018-01-01', freq='B')
business_year_end = work_idx[work_idx.is_year_end]
# Result: DatetimeIndex(['2017-12-29'], dtype='datetime64[ns]', freq='B')

No extra parameters are required. The same accessor adapts its logic to the index frequency.

Why you should care

Small differences in index frequency can change the semantics of your filters and flags. If you assume calendar semantics while your data is indexed by business days, you’ll quietly select the wrong dates. Understanding that is_year_end is frequency-aware helps avoid subtle bugs and makes your intent explicit: calendar year end versus last business day of the year.

Takeaways

is_year_end is not a hardcoded check for December 31. It’s a frequency-aware property: on a business-day index it marks the last business day; on a daily index it marks the last calendar day. Choose the index frequency that matches your definition of “year,” whether calendar or fiscal, and the accessor will do the right thing. If the behavior seems surprising, verify the DatetimeIndex frequency first—it’s the source of truth for how time-based accessors interpret their logic.