48

I'm using pandas and I'm wondering what's the easiest way to get the business days between a start and end date using pandas?

There are a lot of posts out there regarding doing this in Python (for example), but I would be interested to use directly pandas as I think that pandas can probably handle this quite easy.

Community
  • 1
  • 1
Thomas Kremmel
  • 14,575
  • 26
  • 108
  • 177

7 Answers7

59

You can also use date_range for this purpose.

In [3]: pd.date_range('2011-01-05', '2011-01-09', freq=BDay())

Out[3]: DatetimeIndex(['2011-01-05', '2011-01-06', '2011-01-07'], dtype='datetime64[ns]', freq='B', tz=None)

EDIT

Or even simpler

In [7]: pd.bdate_range('2011-01-05', '2011-01-09')

Out[7]: DatetimeIndex(['2011-01-05', '2011-01-06', '2011-01-07'], dtype='datetime64[ns]', freq='B', tz=None)

Note that both start and end dates are inclusive. Source: http://pandas.pydata.org/pandas-docs/stable/generated/pandas.bdate_range.html

Romain
  • 19,910
  • 6
  • 56
  • 65
35

As of v0.14 you can use holiday calendars.

from pandas.tseries.holiday import USFederalHolidayCalendar
from pandas.tseries.offsets import CustomBusinessDay

us_bd = CustomBusinessDay(calendar=USFederalHolidayCalendar())
print pd.DatetimeIndex(start='2010-01-01',end='2010-01-15', freq=us_bd)

returns:

DatetimeIndex(['2010-01-04', '2010-01-05', '2010-01-06', '2010-01-07',
               '2010-01-08', '2010-01-11', '2010-01-12', '2010-01-13',
               '2010-01-14', '2010-01-15'],
              dtype='datetime64[ns]', freq='C')
Ivelin
  • 12,293
  • 5
  • 37
  • 35
  • 4
    if you want the number of days between the date range, you can get this as `pd.DatetimeIndex(start='2010-01-01',end='2010-01-15',freq=us_bd).shape[0]` – tsando Sep 15 '17 at 15:52
  • 1
    The "start" and "end" arguments from the DatetimeIndex have been deprecated, but you can use `len(pd.date_range(start='2010-01-01',end='2010-01-15',freq=us_bd))` https://pandas.pydata.org/docs/reference/api/pandas.date_range.html – robinch Apr 06 '21 at 13:49
33

Just be careful when using bdate_range or BDay() - the name might mislead you to think that it is a range of business days, whereas in reality it's just calendar days with weekends stripped out (ie. it doesn't take holidays into account).

Lars Larsson
  • 566
  • 5
  • 10
  • 2
    despite this not really being a full answer to the Q... this is a very important comment that belongs in the accepted answer - the name `BDay` is misleading. is christmas a business day? It is according to BDay if it's not on a weekend. – Tommy Oct 19 '21 at 00:12
16

Use BDay() to get the business days in range.

from pandas.tseries.offsets import *

In [185]: s
Out[185]: 
2011-01-01   -0.011629
2011-01-02   -0.089666
2011-01-03   -1.314430
2011-01-04   -1.867307
2011-01-05    0.779609
2011-01-06    0.588950
2011-01-07   -2.505803
2011-01-08    0.800262
2011-01-09    0.376406
2011-01-10   -0.469988
Freq: D

In [186]: s.asfreq(BDay())
Out[186]: 
2011-01-03   -1.314430
2011-01-04   -1.867307
2011-01-05    0.779609
2011-01-06    0.588950
2011-01-07   -2.505803
2011-01-10   -0.469988
Freq: B

With slicing:

In [187]: x=datetime(2011, 1, 5)

In [188]: y=datetime(2011, 1, 9)

In [189]: s.ix[x:y]
Out[189]: 
2011-01-05    0.779609
2011-01-06    0.588950
2011-01-07   -2.505803
2011-01-08    0.800262
2011-01-09    0.376406
Freq: D

In [190]: s.ix[x:y].asfreq(BDay())
Out[190]: 
2011-01-05    0.779609
2011-01-06    0.588950
2011-01-07   -2.505803
Freq: B

and count()

In [191]: s.ix[x:y].asfreq(BDay()).count()
Out[191]: 3
root
  • 76,608
  • 25
  • 108
  • 120
  • would it be possible to use the same technique when my data has a granularity of hours? So I want to pull out all the hours that are on business days. I know how to then pull out just the working hours of the day after that – Luka Vlaskalic Oct 24 '18 at 14:01
  • I figured out that you can just use this .asfreq(freq='BH') – Luka Vlaskalic Oct 24 '18 at 14:07
  • 1
    What is `s`? Please make a full example. – suimizu Apr 30 '19 at 13:19
  • @root I think this answer, as the accepted one, should include @Lars comment. This name is misleading and people may find themselves in a gotcha. The name `BDay` is misleading. is christmas a business day? It is according to BDay if it's not on a weekend. BDay really stands for "weekday" and not "business day" in most business logic applications. – Tommy Oct 19 '21 at 00:13
11

On top of this answer and xone, we can write a short function to return the trading days of US exchange:

from xone import calendar

def business_dates(start, end):
    us_cal = calendar.USTradingCalendar()
    kw = dict(start=start, end=end)
    return pd.bdate_range(**kw).drop(us_cal.holidays(**kw))

In [1]: business_dates(start='2018-12-20', end='2018-12-31')
Out[1]: DatetimeIndex(['2018-12-20', '2018-12-21', '2018-12-24', '2018-12-26',
                       '2018-12-27', '2018-12-28', '2018-12-31'],
                      dtype='datetime64[ns]', freq=None)

Edit March 2019:

Replace DatetimeIndex with bdate_range for pandas 0.24.0 update:

  • Creating a TimedeltaIndex, DatetimeIndex, or PeriodIndex by passing range arguments start, end, and periods is deprecated in favor of timedelta_range(), date_range(), or period_range()
Alpha
  • 2,372
  • 3
  • 21
  • 23
5

We can use pd.bdate_range.

Example:

In [1]: pd.bdate_range("2020-01-01", "2020-01-06")
Out[1]: DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq='B')
saubhik
  • 155
  • 2
  • 6
3

If you also work on Saturdays or have an unusual working week, you also want to exclude public holidays in your country.

import pandas as pd  
from datetime import datetime

weekmask = 'Sun Mon Tue Wed Thu'
exclude = [pd.datetime(2020, 5, 1),
           pd.datetime(2020, 5, 2),
           pd.datetime(2020, 5, 3)]

pd.bdate_range('2020/4/30','2020/5/26',
               freq='C',
               weekmask = weekmask,
               holidays=exclude )
Wojciech Moszczyński
  • 2,893
  • 21
  • 27