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:

rateslib.scheduling.Cal(holidays, week_mask)

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:

rateslib.scheduling.Adjuster(args)

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:

rateslib.scheduling.NamedCal(name)

A wrapped UnionCal constructed with a string parsing syntax.

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:

rateslib.scheduling.UnionCal(calendars[, ...])

A calendar defined by a business day intersection of multiple Cal objects.

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:

rateslib.scheduling.Imm(item)

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

rateslib.scheduling.get_imm([month, year, ...])

Return an IMM date for a specified month.

rateslib.scheduling.next_imm(start[, definition])

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)