Bizcal is a simple business calendar package with three unique features:
- Compatibility:
bizcal.Date
is a drop-in replacement fordatetime.date
. - Pythonic: intuitive syntax.
- Holiday-aware: not just when not trading, but also why.
Bizcal is listed on PyPI.
Bizcal abstracts dates with Date
class, a direct subclass of the standard
datetime.date
, allowing it to seamlessly integrate into legacy codebases as
a drop-in replacement for datetime.date
.
Additionally, Date
includes several convenient methods and properties designed to simplify your code.
Code snippets speak for themselves. Check more in unit tests.
from bizcal import Calendar
cne = Calendar([
'2023: 0101-2, 0121-29, 0405, 0429-0503, 0622-25, 0929-1008',
'2024: 0101, 0209-18, 0404-7, 0501-5, 0608-10, 0914-17, 1001-7',
])
day = cne('20240210')
# shift trading day with >> and <<
while not day:
day = day >> 1
prev_trading_day = day << 1
next_trading_day = day >> 1
# shift calendar day with + and -
yesterday = day - 1
tomorrow = day + 1
# period abstraction
period = cne[20240101, 20240131]
print(len(period), 'trading days')
# whole period
print(sum(1 for d in cne['*'].days if d.open), 'trading days')
# iterate trading days
for day in period:
print(f'{day} is a trading day')
# iterate calendar days
for day in period.days:
flag = day and 'trading' or (day.holiday and 'holiday' or 'weekend')
print(f'{day} is {flag}')
Certain exchanges, like SHFE and DCE, adjust their trading hours based on holiday schedules. Bizcal not only identifies business (open) and non-business (closed) days but also specifies if a non-business day is a holiday.
from bizcal import Calendar
cal = Calendar('/path/to/calendar.file')
cal = Calendar([
'2023: 0101-2, 0121-29, 0405, 0429-0503, 0622-25, 0929-1008',
'2024: 0101, 0209-18, 0404-7, 0501-5, 0608-10, 0914-17, 1001-7',
])
The Calendar(spec)
constructor accepts spec
as either a string or a list
of strings.
- If
spec
is a string, it is treated as a path to a calendar definition file. Each line in the file defines the holidays (and potentially weekends) for a specific year. - If
spec
is a list of strings, each string represents a line of calendar definitions for one year. - The year line format is
YYYY: holidays, holidays, ...
, whereholidays
are specified as date ranges in the formatMMDD
,MMDD-DD
, orMMDD-MMDD
.
Date
objects can only be created from a Calendar
object using call
syntax. Multiple input formats are supported.
cal = Calendar([...])
day = cal(20240501) # integer
day = cal('20240501') # YYYYMMDD (string)
day = cal('2024-05-01') # YYYY-MM-DD
day = cal('2024.05.01') # YYYY.MM.DD
day = cal(2024, 5, 1) # year, month, day
day = cal((2024, 5, 1)) # tuple
day = cal([2024, 5, 1]) # list
day = cal(date) # date or even Date object
day = cal([2025, 1, 1]) # error, as date is out of calendar scope
A Date
object can be shifted both as calendar days and business days. The
+
and -
operators shift the date as calendar days, while the >>
and <<
operators shift the date as business days.
cal = Calendar([...])
day = cal(20240501)
day + 10 # 2024-05-11
day - 1 # 2024-04-30
day >> 10 # 2024-05-17
day << 10 # 2024-04-17
Date
is a subclass of datetime.date
, so all date
methods are supported.
Date.open
: Indicates if it is a business day (boolean).Date.__bool__()
: Boolean operator, returns if it is a business day.
Date.holiday
: Indicates if it is a holiday (boolean).Date.num
: Returns the date as anYYYYMMDD
integer.Date.str
: Returns the date as a"YYYYMMDD"
string.Date.eom
: Indicates if it is the end of the month (boolean).
Date.spec(sep='')
: Returns the date as aYYYY{sep}MM{sep}DD
string.Date.shift(days)
: Performs an explicit business day shift.
A period of dates can be defined using the indexing (brackets) syntax.
cal = Calendar([...])
period = cal[20240101, 20240131]
jan = cal['20240101-31']
q1 = cal['202401-3']
h1 = cal['202401-06']
years = cal['2023-4']
period = cal[cal('20240101'), cal(2024, 1, 1) + 30]
print(len(jan)) # count trading days
print(jan.spec) # period spec string
The length of a period (len(period)
) is the number of business days in that
period.
The primary use case of a period is date iteration. Two generator properties,
bizdays
and days
, are implemented for business day and calendar day
iteration, respectively. The default iteration method (__iter__
) is for
business days.
# iterate business days
for day in cal['202401']:
print(f'{day} is a trading day')
# iterate calendar days
for day in cal['20240215-20'].days:
flag = day and 'trading' or (day.holiday and 'holiday' or 'weekend')
print(f'{day} is {flag}')
Static method Calendar.span(spec: str)
parses a string spec into
(beg, end)
tuple of dates. datetime.date
objects are returned.
from bizcal import Calendar
beg, end = Calendar.span('202402')