botant / py-business-calendar

Business days and custom calendar calculations for Python
MIT License
26 stars 11 forks source link

bug has been identified. #3

Open lukshap opened 7 years ago

lukshap commented 7 years ago

Hi! Could you please check why the code below calculates with error: import business-calendar cal = Calendar() date1=datetime.datetime(2017, 1, 20, 14, 43, 45) date2=datetime.datetime(2017, 1, 28, 14, 40, 45) print cal.workdaycount(date1, date2) >>> 0

sevendaybeard commented 7 years ago

Hi @lukshap ,

you are right, I've been able to reproduce this error and it affects the busdaycount method too:

import datetime
import business_calendar as bc
cal = bc.Calendar()
date1 = datetime.datetime(2017, 1, 20, 14, 43, 45)
date2 = datetime.datetime(2017, 1, 28, 14, 40, 45)
print(cal.busdaycount(date1, date2))
>>> 0

The issue results from a combination of the facts that

If the following day of date1 would however be a workday (respectively businessday) we would still get an incorrect value as the result would be one less than the correct answer. With the current setup the code subtracts exactly 24 hours from date2 so both days are on a Friday. As we then go mod 7 we get a remainder of 6 for which we then count how often we need to add a workday until both days are the same weekday. However, as both already are fridays this leads to the issue you described.

Anyway, as date1 is observed COB we could simply add date1 = date1.replace(hour=0, minute=0, second=0) in the beginning of the _workdaycount method and would then get:

    def _workdaycount(self, date1, date2):`
        """
        (PRIVATE) Count work days between two dates, ignoring holidays.
        """
        assert date2 >= date1
        date1 = date1.replace(hour=0, minute=0, second=0)
        date1wd = date1.weekday()
        date2wd = date2.weekday()
        if not self.weekdaymap[date2wd].isworkday:
            date2 += datetime.timedelta(days=self.weekdaymap[date2wd].offsetprev)
            date2wd = self.weekdaymap[date2wd].prevworkday
        if date2 <= date1:
            return 0

        nw, nd = divmod((date2 - date1).days, 7)
        ndays = nw * len(self.workdays)
        if nd > 0:
            date1wd = date1.weekday()
            date2wd = date2.weekday()
            while date1wd != date2wd:
                ndays += 1
                date1wd = self.weekdaymap[date1wd].nextworkday
        return ndays`

This should fix the issue for the _workdaycount method but I haven't yet come to test this on the busdaycount method too.

Kind regards