Skip to content

Commit

Permalink
Support pandas 3.0 dev
Browse files Browse the repository at this point in the history
Changes provide support for default value of pandas
copy_on_write option changing to True in 3.0. See issue #379.

Methods that previously overwrote Index objects in place now
return revised copies which are in turn assigned to the
corresponding calendar attribute.

Required specific changes to XKRX and XHKG calendars.
  • Loading branch information
maread99 committed Jun 24, 2024
1 parent a97bb2f commit 3db631f
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 40 deletions.
46 changes: 23 additions & 23 deletions exchange_calendars/exchange_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,11 +357,15 @@ def __init__(
_special_opens = self._calculate_special_opens(start, end)
_special_closes = self._calculate_special_closes(start, end)

# Overwrite the special opens and closes on top of the standard ones.
_overwrite_special_dates(_all_days, self._opens, _special_opens)
_overwrite_special_dates(_all_days, self._closes, _special_closes)
_remove_breaks_for_special_dates(_all_days, self._break_starts, _special_closes)
_remove_breaks_for_special_dates(_all_days, self._break_ends, _special_closes)
# Adjust for special opens and closes.
self._opens = _adjust_special_dates(_all_days, self._opens, _special_opens)
self._closes = _adjust_special_dates(_all_days, self._closes, _special_closes)
self._break_starts = _remove_breaks_for_special_dates(
_all_days, self._break_starts, _special_closes
)
self._break_ends = _remove_breaks_for_special_dates(
_all_days, self._break_ends, _special_closes
)

break_starts = None if self._break_starts is None else self._break_starts
break_ends = None if self._break_ends is None else self._break_ends
Expand Down Expand Up @@ -2869,18 +2873,18 @@ def scheduled_special_times(
)


def _overwrite_special_dates(
def _adjust_special_dates(
session_labels: pd.DatetimeIndex,
standard_times: pd.DatetimeIndex,
special_times: pd.Series,
) -> None:
"""Overwrite standard times of a session bound with special times.
) -> pd.DatetimeIndex:
"""Adjust standard times of a session bound with special times.
`session_labels` required for alignment.
"""
# Short circuit when nothing to apply.
if special_times.empty:
return
return standard_times

len_m, len_oc = len(session_labels), len(standard_times)
if len_m != len_oc:
Expand All @@ -2900,33 +2904,31 @@ def _overwrite_special_dates(
bad_dates = list(special_times[indexer == -1])
raise ValueError(f"Special dates {bad_dates} are not sessions.")

# NOTE: This is a slightly dirty hack. We're in-place overwriting the
# internal data of an Index, which is conceptually immutable. Since we're
# maintaining sorting, this should be ok, but this is a good place to
# sanity check if things start going haywire with calendar computations.
standard_times.values[indexer] = special_times.values
srs = standard_times.to_series()
srs.iloc[indexer] = special_times
return pd.DatetimeIndex(srs)


def _remove_breaks_for_special_dates(
session_labels: pd.DatetimeIndex,
standard_break_times: pd.DatetimeIndex | None,
special_times: pd.Series,
) -> None:
) -> pd.DatetimeIndex | None:
"""Remove standard break times for sessions with special times."
Overwrites standard break times with NaT for sessions with speical
Replaces standard break times with NaT for sessions with speical
times. Anticipated that `special_times` will be special times for
'opens' or 'closes'.
`session_labels` required for alignment.
"""
# Short circuit when we have no breaks
if standard_break_times is None:
return
return None

# Short circuit when nothing to apply.
if special_times.empty:
return
return standard_break_times

len_m, len_oc = len(session_labels), len(standard_break_times)
if len_m != len_oc:
Expand All @@ -2946,8 +2948,6 @@ def _remove_breaks_for_special_dates(
bad_dates = list(special_times[indexer == -1])
raise ValueError(f"Special dates {bad_dates} are not trading days.")

# NOTE: This is a slightly dirty hack. We're in-place overwriting the
# internal data of an Index, which is conceptually immutable. Since we're
# maintaining sorting, this should be ok, but this is a good place to
# sanity check if things start going haywire with calendar computations.
standard_break_times.values[indexer] = NP_NAT
srs = standard_break_times.to_series()
srs.iloc[indexer] = np.nan
return pd.DatetimeIndex(srs)
10 changes: 6 additions & 4 deletions exchange_calendars/exchange_calendar_xhkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,9 @@ def process_queen_birthday(dt):
# on the Monday (2022-09-12). In the past they don't seem to have followed
# this pattern. We'll have to wait and see before we generalise this into a rule.
pd.Timestamp("2022-09-12"),
pd.Timestamp("2023-07-17"), # 8号台风泰利, 全天休市 https://www.hkex.com.hk/News/Market-Communications/2023/2307172news?sc_lang=en
pd.Timestamp(
"2023-07-17"
), # 8号台风泰利, 全天休市 https://www.hkex.com.hk/News/Market-Communications/2023/2307172news?sc_lang=en
]


Expand Down Expand Up @@ -359,7 +361,7 @@ def adhoc_holidays(self):

qingming_festival = vectorized_sunday_to_monday(
qingming_festival_dates,
).values
).values.copy() # copy so that array is writeable
years = qingming_festival.astype("M8[Y]")
easter_monday = EasterMonday.dates(years[0], years[-1] + 1)
# qingming gets observed one day later if easter monday is on the same
Expand All @@ -370,7 +372,7 @@ def adhoc_holidays(self):
# conflicts with national day, then national day is observed on the
# second, though we don't encode that in the regular holiday, so
# instead we pretend that the mid autumn festival would be delayed.
mid_autumn_festival = day_after_mid_autumn_festival_dates.values
mid_autumn_festival = day_after_mid_autumn_festival_dates.values.copy()
mid_autumn_festival[
(day_after_mid_autumn_festival_dates.month == 10)
& (day_after_mid_autumn_festival_dates.day == 1)
Expand Down Expand Up @@ -435,7 +437,7 @@ def special_closes(self):
@property
def special_closes_adhoc(self):
lunar_new_years_eve = (chinese_lunar_new_year_dates - pd.Timedelta(days=1))[
np.in1d(
np.isin(
chinese_lunar_new_year_dates.weekday,
[TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY],
)
Expand Down
22 changes: 10 additions & 12 deletions exchange_calendars/exchange_calendar_xkrx.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ def special_offsets_adhoc(
),
]

def _overwrite_special_offsets(
def _adjust_special_offsets(
self,
session_labels: pd.DatetimeIndex,
standard_times: pd.DatetimeIndex | None,
Expand All @@ -229,7 +229,7 @@ def _overwrite_special_offsets(
):
# Short circuit when nothing to apply.
if standard_times is None or not len(standard_times):
return
return standard_times

len_m, len_oc = len(session_labels), len(standard_times)
if len_m != len_oc:
Expand Down Expand Up @@ -271,13 +271,11 @@ def _overwrite_special_offsets(

# Short circuit when nothing to apply.
if not len(special_opens_or_closes):
return
return standard_times

# NOTE: This is a slightly dirty hack. We're in-place overwriting the
# internal data of an Index, which is conceptually immutable. Since we're
# maintaining sorting, this should be ok, but this is a good place to
# sanity check if things start going haywire with calendar computations.
standard_times.values[indexer] = special_opens_or_closes.values
srs = standard_times.to_series()
srs.iloc[indexer] = special_opens_or_closes
return pd.DatetimeIndex(srs)

def apply_special_offsets(
self,
Expand Down Expand Up @@ -314,31 +312,31 @@ def apply_special_offsets(
(t[3], t[-1]) for t in _special_offsets_adhoc if t[3] is not None
]

self._overwrite_special_offsets(
self._opens = self._adjust_special_offsets(
session_labels,
self._opens,
_special_open_offsets,
_special_open_offsets_adhoc,
start,
end,
)
self._overwrite_special_offsets(
self._break_starts = self._adjust_special_offsets(
session_labels,
self._break_starts,
_special_break_start_offsets,
_special_break_start_offsets_adhoc,
start,
end,
)
self._overwrite_special_offsets(
self._break_ends = self._adjust_special_offsets(
session_labels,
self._break_ends,
_special_break_end_offsets,
_special_break_end_offsets_adhoc,
start,
end,
)
self._overwrite_special_offsets(
self._closes = self._adjust_special_offsets(
session_labels,
self._closes,
_special_close_offsets,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_exchange_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -1364,7 +1364,7 @@ def _trading_minute_to_break_minute(
for session, break_session in zip(sessions[mask], break_sessions[mask]):
break_minutes = self.get_session_break_minutes(break_session)
trading_minutes = self.get_session_minutes(session)[0]
bv = np.in1d(trading_minutes.time, break_minutes.time)
bv = np.isin(trading_minutes.time, break_minutes.time)
minutes.append([trading_minutes[bv][-1], session, break_session])
return minutes

Expand Down

0 comments on commit 3db631f

Please sign in to comment.