Calendars & Date Adjustment#
Calendars allow rateslib to recognise the following types of date, and perform operations on them:
Business and non-business days.
Settleable business days and also non-business but potentially settleable days.
Dates#
Rateslib uses Python’s native datetime.datetime object as a date coordinator.
Cal#
The main calendar class is:
|
A business day calendar defined by weekends and a holiday list. |
This class allows business days to be defined as not non-business days where the latter
might result as being a weekend, indicated by a week_mask, or one of specific holidays,
directly associated with the class.
In [1]: cal = Cal(
...: holidays=[dt(2026, 1, 26), dt(2026, 1, 27), dt(2026, 1, 28)],
...: week_mask=[5, 6] # <- standard Sa, Su weekend
...: )
...:
In [2]: cal.is_bus_day(dt(2026, 1, 26))
Out[2]: False
In [3]: cal.is_non_bus_day(dt(2026, 1, 26))
Out[3]: True
Calendar objects in rateslib have handy print()
functions to convey datetypes.
In [4]: print(cal.print(2026, 1))
January 2026
Su Mo Tu We Th Fr Sa
1 2 .
. 5 6 7 8 9 .
. 12 13 14 15 16 .
. 19 20 21 22 23 .
. * * * 29 30 .
Warning
Calendars are not mutable objects. So you cannot extend or pop items from
their week mask or list of holidays. To edit a calendar you must extract the
relevant week_mask and holidays attributes, amend those and then
reconstitute a new calendar object.
Adjustment#
When a Cal is combined with an:
Enumerable type for date adjustment rules. |
it provides the ability to modify dates according to some rule.
For simple date rolling (which rateslib defines as moving a non-business day to a business day under a common convention) the operation is:
In [5]: cal.adjust(date=dt(2026, 1, 24), adjuster=Adjuster.Following())
Out[5]: datetime.datetime(2026, 1, 29, 0, 0)
The Adjuster also implements these methods directly, and
contains a reverse method to imply dates that give a specific result.
In [6]: Adjuster.Following().reverse(date=dt(2026, 1, 29), calendar=cal)
Out[6]:
[datetime.datetime(2026, 1, 29, 0, 0),
datetime.datetime(2026, 1, 28, 0, 0),
datetime.datetime(2026, 1, 27, 0, 0),
datetime.datetime(2026, 1, 26, 0, 0),
datetime.datetime(2026, 1, 25, 0, 0),
datetime.datetime(2026, 1, 24, 0, 0)]
More complex Adjuster also exist to handle esoteric cases.
The BusDaysLagSettle will be used to determine FX spot in later examples by
adjusting a date two business days forward with extra settlement rules. The
FollowingExLast rule is useful for CDS whose ISDA
definitions for scheduling adopt Following for every accrual date except the last.
This requires a vector adjusts() method.
In [7]: Adjuster.FollowingExLast().adjusts(dates=[dt(2026, 1, 11), dt(2026, 1, 18), dt(2026, 1, 25)], calendar=cal)
Out[7]:
[datetime.datetime(2026, 1, 12, 0, 0),
datetime.datetime(2026, 1, 19, 0, 0),
datetime.datetime(2026, 1, 25, 0, 0)]
Implemented Calendars#
At a Rust extension level, rateslib has implemented some common calendars, the list
of which can be seen in the default calendars. At a Python
level all of these are added to the defaults object at startup. These values are accessible
directly from defaults.calendars or the get_calendar() method
can be used.
In [8]: print(get_calendar("tgt").print(2026))
January 2026 April 2026 July 2026 October 2026
Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
* 2 . 1 2 * . 1 2 3 . 1 2 .
. 5 6 7 8 9 . . * 7 8 9 10 . . 6 7 8 9 10 . . 5 6 7 8 9 .
. 12 13 14 15 16 . . 13 14 15 16 17 . . 13 14 15 16 17 . . 12 13 14 15 16 .
. 19 20 21 22 23 . . 20 21 22 23 24 . . 20 21 22 23 24 . . 19 20 21 22 23 .
. 26 27 28 29 30 . . 27 28 29 30 . 27 28 29 30 31 . 26 27 28 29 30 .
February 2026 May 2026 August 2026 November 2026
Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
. 2 3 4 5 6 . * . . . 2 3 4 5 6 .
. 9 10 11 12 13 . . 4 5 6 7 8 . . 3 4 5 6 7 . . 9 10 11 12 13 .
. 16 17 18 19 20 . . 11 12 13 14 15 . . 10 11 12 13 14 . . 16 17 18 19 20 .
. 23 24 25 26 27 . . 18 19 20 21 22 . . 17 18 19 20 21 . . 23 24 25 26 27 .
. 25 26 27 28 29 . . 24 25 26 27 28 . . 30
. . 31
March 2026 June 2026 September 2026 December 2026
Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
. 2 3 4 5 6 . 1 2 3 4 5 . 1 2 3 4 . 1 2 3 4 .
. 9 10 11 12 13 . . 8 9 10 11 12 . . 7 8 9 10 11 . . 7 8 9 10 11 .
. 16 17 18 19 20 . . 15 16 17 18 19 . . 14 15 16 17 18 . . 14 15 16 17 18 .
. 23 24 25 26 27 . . 22 23 24 25 26 . . 21 22 23 24 25 . . 21 22 23 24 * .
. 30 31 . 29 30 . 28 29 30 . 28 29 30 31
Legend:
'1-31': Settleable business day 'X': Non-settleable business day
'.': Non-business weekend '*': Non-business day
Bespoke Calendars#
It is possible to overwrite these named calendars in defaults and they
will then be directly used when some string name is given to calendar
arguments - and therefore default Instrument
spec will continue to work with your updated calendars. Of course you can customise
spec too if that is your preference.
In [9]: defaults.calendars["bjs"] = Cal(holidays=[], week_mask=[0, 2, 4])
In [10]: print(get_calendar("bjs").print(2026, 1))
January 2026
Su Mo Tu We Th Fr Sa
1 * 3
4 * 6 * 8 * 10
11 * 13 * 15 * 17
18 * 20 * 22 * 24
25 * 27 * 29 * 31
It is also possible to directly populate the defaults with your own bespoke
calendar names, for example:
In [11]: defaults.calendars["polynesia"] = Cal(holidays=[], week_mask=[0,1,2,3,4])
In [12]: get_calendar("polynesia")
Out[12]: <rl.Cal at 0x12d8829e0>
NamedCal#
An additional class:
A wrapped |
allows the Rust implemented calendars to be directly accessed. These objects are serialized differently, in a more minimal way, than other calendars which must record and exchange all the holiday data, but they rely on the hard-coded underlying implementation to be correct.
Note
Think of the named flag as referring to rateslib own named calendars, which
are not user editable.
In [13]: base_implemented_tgt = NamedCal("tgt")
In [14]: base_implemented_tgt.to_json()
Out[14]: '{"NamedCal":{"name":"tgt"}}'
In [15]: user_custom_cal = Cal(holidays=[dt(2026, 1, 1)], week_mask=[5,6])
In [16]: user_custom_cal.to_json()
Out[16]: '{"Cal":{"holidays":["2026-01-01T00:00:00"],"week_mask":["Sun","Sat"]}}'
Combination and Union Calendars#
The:
|
A calendar defined by a business day intersection of multiple |
is provided as the mechanism to combine calendars and it offers two fundamental principles:
Defining business days as the union of business days of each contained calendar.
Defining settleable business days where one or more calendars are used for settlement validation. This second principle is novel compared to other library’s approaches. Typically other libraries need additional date adjustment functions to handle specific cases.
Rateslib uses a string parsing syntax to combine calendars that have been prepopulated to
defaults (or exist in the Rust base implementation). A UnionCal
has inputs for its calendars, and its settlement_calendars. Each list is separable by
a pipe (‘|’) operator, and each calendar within each list is separated by a comma (‘,’).
To create two user calendars and combine them within a UnionCal,
the practical method is to use get_calendar():
In [17]: defaults.calendars["mondays-off"] = Cal([], [0, 5, 6])
In [18]: defaults.calendars["fridays-off"] = Cal([], [4, 5, 6])
In [19]: print(get_calendar("mondays-off, fridays-off").print(2026, 1))
January 2026
Su Mo Tu We Th Fr Sa
1 * .
. * 6 7 8 * .
. * 13 14 15 * .
. * 20 21 22 * .
. * 27 28 29 * .
In [20]: type(get_calendar("mondays-off, fridays-off"))
Out[20]: rateslib.rs.UnionCal
Why use settlement_calendars#
Without settlement_calendars, business day logic alone is not sufficient to support common
financial date manipulation rules. But with them you can encode a lot of business logic into
simple, consistent Adjuster.
For example, the excellent ‘Foreign Exchange Option Pricing: A Practitioner’s Guide’, describes an FX spot settlement rules algorithm:
if T+2 settlement
if CCY1 or CCY2 is in ['MXN', 'ARS', 'CLP'] // special LatAm currencies
-> advance one business day skipping CCY1, CCY2 and USD holidays
else
-> advance one business day skipping holidays in:
- CCY1, unless it is USD
- CCY2, unless it is USD
if T+1 or T+2 settlement
-> advance one business day skipping holidays in CCY1, CCY2 and USD
When the currency is not a special LatAm currency this boils down to performing date logic in the local calendar and validating the calculated date is USD settleable.
Rateslib handles this in the following way: for a currency pair, say EURUSD, define the TARGET calendar settleable against the FED calendar and perform business day adjustment.
In [21]: eurusd_cal = get_calendar("tgt|fed") # calendar: tgt, settlement_calendar: fed
In [22]: print(eurusd_cal.print(2026, 5))
May 2026
Su Mo Tu We Th Fr Sa
* .
. 4 5 6 7 8 .
. 11 12 13 14 15 .
. 18 19 20 21 22 .
. X 26 27 28 29 .
.
In [23]: Adjuster.BusDaysLagSettle(2).adjust(dt(2026, 5, 21), eurusd_cal)
Out[23]: datetime.datetime(2026, 5, 26, 0, 0)
In [24]: Adjuster.BusDaysLagSettle(2).adjust(dt(2026, 5, 22), eurusd_cal)
Out[24]: datetime.datetime(2026, 5, 26, 0, 0)
The subtle difference being the 25th May 2026 is in fact a business day but it is non-settleable.
For the LatAm case the FED calendar is used to also perform business day logic. So the correct approach here does not require settlement calendars:
In [25]: usdmxn_cal = get_calendar("mex,fed") # calendar: [mex, fed], settlement_calendar: None
In [26]: print(usdmxn_cal.print(2026, 5))
May 2026
Su Mo Tu We Th Fr Sa
* .
. 4 5 6 7 8 .
. 11 12 13 14 15 .
. 18 19 20 21 22 .
. * 26 27 28 29 .
.
In [27]: Adjuster.BusDaysLagSettle(2).adjust(dt(2026, 5, 21), usdmxn_cal)
Out[27]: datetime.datetime(2026, 5, 26, 0, 0)
In [28]: Adjuster.BusDaysLagSettle(2).adjust(dt(2026, 5, 22), usdmxn_cal)
Out[28]: datetime.datetime(2026, 5, 27, 0, 0)
Special Dates#
Many dates are specifically defined within financial markets. They are often referred to as IMM dates, but they don’t have to be. And in different contexts real IMM dates might be defined differently, anyway. Rateslib uses the following enum:
|
Enumerable type for International Money-Market (IMM) date definitions. |
For example the IMM.Wed3 refers to the 3rd Wednesday of any month. IMM.Wed3_HMUZ refers to the third Wedneday of March, June, Sep and Dec only, and IMM.Wed1_Post9 is the first Wednesday after the 9th of any month. IMM.Leap is any 29th Feb. These definitions can be used in scheduling or Instrument construction.
The following methods
|
Return an IMM date for a specified month. |
|
Return the next IMM date after the given start date. |
provide some of these mechanisms for working with dates, including using standard futures codes.
In [29]: get_imm(code="H20", definition="wed1_post9")
Out[29]: datetime.datetime(2020, 3, 11, 0, 0)
In [30]: next_imm(dt(2020, 1, 1), definition="wed3_hmuz")
Out[30]: datetime.datetime(2020, 3, 18, 0, 0)