First workday of month (except holidays)

TrustyTony 1 Tallied Votes 4K Views Share

This code determines if datetime object represent first workday of month, but it does not consider the special days like New Year (this year weekend but last year Friday).

Reaction to Basic efforts in: http://www.daniweb.com/software-development/legacy-and-other-languages/threads/362098

There algorithm idea given by jon.kiparsky

based docstring says 'workday' when it should be 'weekday':

''' Determine first workday based on day of month and weekday (0 == Monday) '''
from datetime import datetime

def first_workday(theday):
    ''' Determine first workday based on day of month and workday (0 == Monday) '''
    return ((theday.day == 3 and theday.weekday() == 0) or
            (theday.day == 2 and theday.weekday() == 0) or
            (theday.day == 1 and theday.weekday() < 5))


for test in (datetime.today(), datetime(2011, 5, 2), datetime(2010, 5, 2), datetime(2011, 5, 1),
             datetime(2011, 4, 1), datetime(2011, 3, 1), datetime(2011, 1, 1), datetime(2012, 9, 1),
            datetime(2012, 9, 3), datetime(2010, 1, 1)): # wrong as New Year is Holiday
        print 'The day is' + ('' if first_workday(test) else ' not'), 'first workday:',  test.strftime('%Y/%m/%d, %A')
    
'''Output:
The day is not first workday: 2011/05/07, Saturday
The day is first workday: 2011/05/02, Monday
The day is not first workday: 2010/05/02, Sunday
The day is not first workday: 2011/05/01, Sunday
The day is first workday: 2011/04/01, Friday
The day is first workday: 2011/03/01, Tuesday
The day is not first workday: 2011/01/01, Saturday
The day is not first workday: 2012/09/01, Saturday
The day is first workday: 2012/09/03, Monday
The day is first workday: 2010/01/01, Friday
'''

Dani AI

Generated

The weekday-only check above is a good start, but it will mark statutory holidays as workdays (e.g., Jan 1 on a Friday) and miss observed days (e.g., when Jan 1 falls on Saturday, many calendars observe Dec 31 as the holiday). A simple, reliable approach is to compute the first business day by scanning forward from the 1st while skipping weekends and a supplied holiday set. This keeps the logic clear, works for any country/region, and avoids hardcoding Monday-specific cases.

from datetime import date, timedelta

def first_business_day(year, month, holidays=None):
    """Return the first business day of (year, month), skipping weekends and holidays."""
    holidays = holidays or set()
    d = date(year, month, 1)
    while d.weekday() >= 5 or d in holidays:  # 5=Sat, 6=Sun
        d += timedelta(days=1)
    return d

def is_first_business_day(d, holidays=None):
    """True if d is the first business day of its month given the holiday set."""
    return d == first_business_day(d.year, d.month, holidays)

Populate holidays from your calendar rules. For example, to handle US New Year edge cases: include date(2010, 1, 1) (Friday) and add date(2010, 12, 31) when Jan 1, 2011 falls on Saturday and is observed on the preceding Friday. With this, first_business_day(2011, 1, ...) becomes Monday Jan 3, and first_business_day(2010, 1, ...) becomes Monday Jan 4. This complements the calendar suggestion by isolating holiday logic and keeping your predicate a single, readable line.

vegaseat 1,735 DaniWeb's Hypocrite Team Colleague
TrustyTony 888 ex-Moderator Team Colleague Featured Poster

Using date and one less or branch (using in), if you feel five and/ors and six comparisons are too much:

from datetime import date

def isfirst_workday(theday):
    ''' Determine first workday based on day of month and weekday (0 == Monday) '''
    return ((theday.day in (2,3) and theday.weekday() == 0) or
            (theday.day == 1 and theday.weekday() < 5))

if __name__ == '__main__':
    for test in (date.today(), date(2011, 5, 2), date(2010, 5, 2), date(2011, 5, 1),
             date(2011, 4, 1), date(2011, 3, 1), date(2011, 1, 1), date(2012, 9, 1),
            date(2012, 9, 3), date(2010, 1, 1)): # wrong as New Year is Holiday
        print 'The day is' + ('' if isfirst_workday(test) else ' not'), 'first workday:',  test.strftime('%Y/%m/%d, %A')
    
'''Output:
The day is not first workday: 2011/05/07, Saturday
The day is first workday: 2011/05/02, Monday
The day is not first workday: 2010/05/02, Sunday
The day is not first workday: 2011/05/01, Sunday
The day is first workday: 2011/04/01, Friday
The day is first workday: 2011/03/01, Tuesday
The day is not first workday: 2011/01/01, Saturday
The day is not first workday: 2012/09/01, Saturday
The day is first workday: 2012/09/03, Monday
The day is first workday: 2010/01/01, Friday
'''
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.