【问题标题】:Number of days between 2 dates, excluding weekends2 个日期之间的天数,不包括周末
【发布时间】:2011-04-06 15:13:40
【问题描述】:

如何计算两个日期之间的天数(不包括周末)?

【问题讨论】:

  • 你是如何表示日期的?
  • 是否还要排除公共假期?

标签: python datetime


【解决方案1】:

对于那些还想在不手动指定公共假期的情况下排除公共假期的人,可以使用holidays 包以及来自numpybusday_count

from datetime import date 

import numpy as np
import holidays

np.busday_count(
    begindates=date(2021, 1, 1),
    enddates=date(2021, 3, 20),
    holidays=list(
        holidays.US(state="CA", years=2021).keys()
    ),
)

【讨论】:

    【解决方案2】:

    对于 Python 3; xrange() 仅适用于 Python 2。基于 Dave Webb 的回答,并包含显示包括周末在内的日期的代码

    import datetime
    
    start_date = datetime.date(2014, 1, 1)
    end_date = datetime.date(2014, 1, 16)
    
    delta_days = (end_date - start_date).days
    delta_days # 13
    
    day_generator = (start_date + datetime.timedelta(x + 1) for x in range((end_date - start_date).days))
    delta_days = sum(1 for day in day_generator if day.weekday() < 5)
    delta_days # 10
    

    【讨论】:

      【解决方案3】:

      这是我用于管理脚本的内容,它考虑了假期,无论您在哪个国家/地区(使用网络服务获取特定国家/地区的假期数据)。需要进行一些效率重构,但除此之外,它还有效。

      from dateutil import rrule
      from datetime import datetime
      import pytz
      
      timezone_manila = pytz.timezone('Asia/Manila')
      
      class Holidays(object):
          
          def __init__(self, holidaydata):
              self.holidaydata = holidaydata
              
          def isHoliday(self,dateobj):
              for holiday in self.holidaydata:
                  d = datetime(holiday['date']['year'], holiday['date']['month'], holiday['date']['day'], tzinfo=timezone_manila)            
                  if d == dateobj:
                      return True
              
              return False
      
      def pullHolidays(start, end):
          import urllib.request, json
          urlstring = "https://kayaposoft.com/enrico/json/v2.0/?action=getHolidaysForDateRange&fromDate=%s&toDate=%s&country=phl&region=dc&holidayType=public_holiday" % (start.strftime("%d-%m-%Y"),end.strftime("%d-%m-%Y")) 
          
          with urllib.request.urlopen(urlstring) as url:
              holidaydata = json.loads(url.read().decode())
          
          return Holidays(holidaydata)
      
      
      def countWorkDays(start, end):
          workdays=0
          holidayData=pullHolidays(start,end)
          for dt in rrule.rrule(rrule.DAILY, dtstart=start, until=end):
              if dt.weekday() < 5:
                  if holidayData.isHoliday(dt) == False:
                      workdays+=1
          
          return workdays
      

      【讨论】:

        【解决方案4】:

        到目前为止,我发现所提供的解决方案都不令人满意。要么存在对我不想要的库的依赖,要么存在低效的循环算法,或者存在不适用于所有情况的算法。不幸的是,@neil 提供的那个不能很好地工作。 @vekerdyb 的回答更正了这一点,不幸的是,这也不适用于所有情况(例如,在同一个周末选择周六或周日......)。
        所以我坐下来,尽力想出一个适用于所有输入日期的解决方案。它小巧高效。当然,您也可以随意发现其中的错误。开始和结束都包含在内(例如,一周中的周一至周二为 2 个工作日)。

        def get_workdays(from_date: datetime, to_date: datetime):
            # if the start date is on a weekend, forward the date to next Monday
            if from_date.weekday() > 4:
                from_date = from_date + timedelta(days=7 - from_date.weekday())
            # if the end date is on a weekend, rewind the date to the previous Friday
            if to_date.weekday() > 4:
                to_date = to_date - timedelta(days=to_date.weekday() - 4)
            if from_date > to_date:
                return 0
            # that makes the difference easy, no remainders etc
            diff_days = (to_date - from_date).days + 1
            weeks = int(diff_days / 7)
            return weeks * 5 + (to_date.weekday() - from_date.weekday()) + 1
        

        【讨论】:

          【解决方案5】:

          首先将numpy 导入为np。函数np.busday_count计算两个日期之间的有效天数,不包括结束日期的那一天。

          如果结束日期早于开始日期,则计数将为负数。有关np.busday_count 的更多信息,请阅读文档here

          import numpy as np
          np.busday_count('2018-04-10', '2018-04-11')
          

          请注意,该函数接受字符串,在调用该函数之前无需实例化datetime 对象。

          还支持特定的有效日期以及添加假期的选项。

          import numpy as np
          np.busyday_count('2019-01-21','2020-03-28',weekmask=[1,1,1,1,1,0,0],holidays=['2020-01-01'])
          

          weekmask 格式 = [Mon,Tue,Wed....Sat,Sun]

          【讨论】:

          • 祝贺你的第一篇文章。不幸的是,大约 3 1/2 年前,用户 Ben 已经提供了这个答案。请在自己提供之前阅读其他答案以避免此类重复
          • 如果您从系列中提取数据,请确保将日期转换为字符串。示例 df['mydatecol'].dt.strftime('%Y-%m-%d').
          • 懒人用holidays:pypi.org/project/holidays
          【解决方案6】:

          您可以使用https://pypi.org/project/python-networkdays/ 该包没有依赖项,没有 NumPy 或 pandas 来计算日期。 ;)

          In [3]: import datetime
          
          In [4]: from networkdays import networkdays
          
          In [5]: HOLIDAYS  = { datetime.date(2020, 12, 25),}
          
          In [6]: days = networkdays.Networkdays(datetime.date(2020, 12, 1),datetime.date(2020, 12, 31), holidays=HOLIDAYS, weekdaysoff={6,7})
          
          In [7]: days.networkdays()
          Out[7]:
          [datetime.date(2020, 12, 1),
           datetime.date(2020, 12, 2),
           datetime.date(2020, 12, 3),
           datetime.date(2020, 12, 4),
           datetime.date(2020, 12, 7),
           datetime.date(2020, 12, 8),
           datetime.date(2020, 12, 9),
           datetime.date(2020, 12, 10),
           datetime.date(2020, 12, 11),
           datetime.date(2020, 12, 14),
           datetime.date(2020, 12, 15),
           datetime.date(2020, 12, 16),
           datetime.date(2020, 12, 17),
           datetime.date(2020, 12, 18),
           datetime.date(2020, 12, 21),
           datetime.date(2020, 12, 22),
           datetime.date(2020, 12, 23),
           datetime.date(2020, 12, 24),
           datetime.date(2020, 12, 28),
           datetime.date(2020, 12, 29),
           datetime.date(2020, 12, 30),
           datetime.date(2020, 12, 31)]
          

          【讨论】:

            【解决方案7】:

            您可以使用以下万无一失的函数来获取任意两个给定日期之间的工作日数:

            import datetime
            
            def working_days(start_dt,end_dt):
                num_days = (end_dt -start_dt).days +1
                num_weeks =(num_days)//7
                a=0
                #condition 1
                if end_dt.strftime('%a')=='Sat':
                    if start_dt.strftime('%a') != 'Sun':
                        a= 1
                #condition 2
                if start_dt.strftime('%a')=='Sun':
                    if end_dt.strftime('%a') !='Sat':
                        a =1
                #condition 3
                if end_dt.strftime('%a')=='Sun':
                    if start_dt.strftime('%a') not in ('Mon','Sun'):
                        a =2
                #condition 4        
                if start_dt.weekday() not in (0,6):
                    if (start_dt.weekday() -end_dt.weekday()) >=2:
                        a =2
                working_days =num_days -(num_weeks*2)-a
            
                return working_days
            

            使用示例:

            start_dt = datetime.date(2019,6,5)
            end_dt = datetime.date(2019,6,21)
            
            working_days(start_dt,end_dt)
            
            

            在这里,开始日期和结束日期都包括在内,不包括所有周末。
            希望这会有所帮助!

            【讨论】:

            • 与已经给出的所有其他答案相比,这种方法有什么优势?
            • 这种方法非常有效且万无一失。它考虑了所有可能的场景,并且运行时间最短,因为它不需要任何循环。试试看!!
            • 这是万无一失的,直到圣诞节在星期六和星期一变成非工作日。
            • 哈哈@JeffUK 是的,这有点打破了魔咒!
            【解决方案8】:

            我的解决方案也是计算最后一天。因此,如果 start 和 end 设置为同一工作日,则 asnwer 将为 1(例如 10 月 17 日)。 如果开始和结束是连续 2 个工作日,则答案将为 2(例如 10 月 17 日和 18 日)。 它计算整个星期(每个星期我们将有 2 个周末),然后检查提醒天是否包含周末。

            import datetime
            
            def getWeekdaysNumber(start,end):
                numberOfDays = (end-start).days+1
                numberOfWeeks = numberOfDays // 7
                reminderDays = numberOfDays % 7
                numberOfDays -= numberOfWeeks *2
                if reminderDays:
                    #this line is creating a set of weekdays for remainder days where 7 and 0 will be Saturday, 6 and -1 will be Sunday
                    weekdays = set(range(end.isoweekday(), end.isoweekday() - reminderDays, -1))
                    numberOfDays -= len(weekdays.intersection([7,6,0,-1])
                return numberOfDays 
            

            用法示例:

            start = date(2018,10,10)
            end = date (2018,10,17)
            result = getWeekdaysNumber(start,end)`
            

            【讨论】:

              【解决方案9】:

              请注意,@neil 的(否则很棒)代码将在周日至周四的间隔内失败。这是一个修复:

              def working_days_in_range(from_date, to_date):
                  from_weekday = from_date.weekday()
                  to_weekday = to_date.weekday()
                  # If start date is after Friday, modify it to Monday
                  if from_weekday > 4:
                      from_weekday = 0
                  day_diff = to_weekday - from_weekday
                  whole_weeks = ((to_date - from_date).days - day_diff) / 7
                  workdays_in_whole_weeks = whole_weeks * 5
                  beginning_end_correction = min(day_diff, 5) - (max(to_weekday - 4, 0) % 5)
                  working_days = workdays_in_whole_weeks + beginning_end_correction
                  # Final sanity check (i.e. if the entire range is weekends)
                  return max(0, working_days)
              

              【讨论】:

              【解决方案10】:

              在 PyPi 中使用这个名为 business-duration 的包。

              示例代码:

              from business_duration import businessDuration
              
              import pandas as pd
              
              import datetime
              
              start = pd.to_datetime("2010-1-1 00:00:00")
              
              end = pd.to_datetime("2010-3-31 00:00:00")
              
              businessDuration(startdate=start,enddate=end,unit='day')
              

              输出[6]:62.99927083333333

              【讨论】:

                【解决方案11】:

                这是我实现的一个函数,用于测量跨分支集成代码需要多少工作日。它不需要像其他解决方案那样在整个中间天进行迭代,而只需要在第一周进行。

                这个问题可以分解成两个不同的问题:

                1. 计算区间内的整数周数:对于整数周,周末天数始终为 2。这是一个微不足道的整数除法:(todate - fromdate)/7

                2. 计算剩余区间的周末天数:这可以通过计数方法(类似map-reduce)轻松解决:sum(map(is_weekend, rem_days))

                  李>
                def count_working_days(fromdate, todate):
                    from datetime import timedelta as td
                    def is_weekend(d): return d.weekday() > 4
                
                    # 1st problem
                    num_weeks = (todate - fromdate).days/7
                
                    # 2nd problem
                    rem_days = (todate - fromdate).days%7
                    rem_weekend_days = sum(is_weekend(fromdate + td(days=i+1)) for i in range(rem_days))
                
                    return (todate - fromdate).days - 2*num_weeks - rem_weekend_days
                

                及其工作示例:

                >>> for i in range(10): latency(datetime.now(), datetime.now() + timedelta(days=i))
                ...
                0    1    1    1    2    3    4    5    6    6
                

                【讨论】:

                  【解决方案12】:

                  到目前为止给出的答案都有效,但如果日期相距很远(由于循环),效率会非常低。

                  这应该可行:

                  import datetime
                  
                  start = datetime.date(2010,1,1)
                  end = datetime.date(2010,3,31)
                  
                  daydiff = end.weekday() - start.weekday()
                  
                  days = ((end-start).days - daydiff) / 7 * 5 + min(daydiff,5) - (max(end.weekday() - 4, 0) % 5)
                  

                  这会将其转换为整周(有 5 个工作日),然后处理剩余的天数。

                  【讨论】:

                  • b-a 未定义,应该是 end-start 吗?
                  • 好点 - 我用 a 和 b 写了它,然后改成了有意义的名字。我看到你已经处理了我可能错过的边缘情况。如果我有时间,我可能会重新审视它并检查它是否防水。
                  • 请注意,@neil 的代码将在周日至周四的时间间隔内失败。这是一个修复stackoverflow.com/a/32698089/1617748(编辑:新答案和链接以获得更好的格式)
                  • @vekerdyb 不幸的是,你的也不适用于所有情况。看我的回答stackoverflow.com/a/62835010/865695
                  【解决方案13】:

                  我认为最干净的解决方案是使用 numpy 函数busday_count

                  import numpy as np
                  import datetime as dt
                  
                  start = dt.date( 2014, 1, 1 )
                  end = dt.date( 2014, 1, 16 )
                  
                  days = np.busday_count( start, end )
                  

                  【讨论】:

                    【解决方案14】:

                    懒惰的方法是pip install workdays 获取执行此操作的python 包。

                    https://pypi.python.org/pypi/workdays/

                    【讨论】:

                    • 我正在使用这个包,但是它缺少其他功能,例如返回单个日期列表
                    • github.com/seatgeek/businesstime 如果您想获得时间增量形式的天+小时,效果很好。
                    【解决方案15】:

                    我将 Dave Webb 的答案改编成一个函数并添加了一些测试用例:

                    import datetime
                    
                    def weekdays_between(start, end):
                        return sum([1 for daydelta in xrange(1, (end - start).days + 1)
                                    if (start + datetime.timedelta(daydelta)).weekday() < 5])
                    
                    assert 7 == weekdays_between(
                        datetime.date(2014,2,19),
                        datetime.date(2014,3,1))
                    
                    assert 1 == weekdays_between(
                        datetime.date(2014,2,19),
                        datetime.date(2014,2,20))
                    
                    assert 2 == weekdays_between(
                        datetime.date(2014,2,19),
                        datetime.date(2014,2,22))
                    
                    assert 2 == weekdays_between(
                        datetime.date(2014,2,19),
                        datetime.date(2014,2,23))
                    
                    assert 3 == weekdays_between(
                        datetime.date(2014,2,19),
                        datetime.date(2014,2,24))
                    
                    assert 1 == weekdays_between(
                        datetime.date(2014,2,21),
                        datetime.date(2014,2,24))
                    
                    assert 1 == weekdays_between(
                        datetime.date(2014,2,22),
                        datetime.date(2014,2,24))
                    
                    assert 2 == weekdays_between(
                        datetime.date(2014,2,23),
                        datetime.date(2014,2,25))
                    

                    【讨论】:

                      【解决方案16】:

                      我尝试了前两个答案(Dave Webb 和 Neil 的),但由于某种原因,我从两个答案中都得到了不正确的答案。这可能是我的一个错误,但我选择了一个现有的库,因为它可能具有更多功能并且针对边缘情况进行了更好的测试:

                      https://bitbucket.org/shelldweller/python-bizdatetime

                      【讨论】:

                        【解决方案17】:

                        固定周六至周日在同一周末正常运行。

                        from __future__ import print_function
                        from datetime import date, timedelta
                        
                        def workdaycount(startdate,enddate):
                            if startdate.year != enddate.year:
                                raise ValueError("Dates to workdaycount must be during same year")
                            if startdate == enddate:
                                return int(startdate.weekday() < 5)
                            elif (enddate - startdate).days == 1 and enddate.weekday() == 6: # Saturday and Sunday same weekend
                                return 0
                            first_week_workdays = min(startdate.weekday(), 4) + 1
                            last_week_workdays = min(enddate.weekday(), 4) + 1
                            workweeks = int(enddate.strftime('%W')) - int(startdate.strftime('%W'))
                            return (5 * workweeks)  + last_week_workdays - first_week_workdays + 1
                        
                        for comment, start,end in (
                             ("Two dates same weekend:", date(2010,9,18), date(2010,9,19)),
                             ("Same dates during weekend:", date(2010,9,19), date(2010,9,19)),
                             ("Same dates during week", date(2010,9,16), date(2010,9,16)),
                             ("Dates during same week", date(2010,9,13), date(2010,9,16)),
                             ("Dates during following weeks", date(2010,9,7), date(2010,9,16)),
                             ("Dates after two weeks", date(2010,9,7), date(2010,9,24)),
                             ("Dates from other solution", date(2010,1, 1), date(2010, 3,31))):
                        
                            daydiff = end.weekday() - start.weekday()
                            days = ((end-start).days - daydiff) / 7 * 5 + min(daydiff,5)
                            daygenerator = (start + timedelta(x + 1) for x in xrange((end - start).days))
                            gendays = sum(day.weekday() < 5 for day in daygenerator)
                        
                            print(comment,start,end,workdaycount(start,end))
                            print('Other formula:', days, '. Generator formula: ', gendays)
                        

                        【讨论】:

                          【解决方案18】:
                          >>> from datetime import date,timedelta
                          >>> fromdate = date(2010,1,1)
                          >>> todate = date(2010,3,31)
                          >>> daygenerator = (fromdate + timedelta(x + 1) for x in xrange((todate - fromdate).days))
                          >>> sum(1 for day in daygenerator if day.weekday() < 5)
                          63
                          

                          这个creates a generator using a generator expression 哪个will yield the list of daysfromdatetodate

                          然后我们可以从生成器创建一个列表,使用the weekday() function 过滤掉周末,列表的大小给出了我们想要的天数。但是,为了将整个列表保存在内存中,如果日期相隔很长时间,这可能会成为问题,我们使用另一个生成器表达式,它过滤掉周末但返回 1 而不是每个日期。 We can then just add all these 1s together to get the length 无需存储整个列表。

                          注意,如果fromdate == todate 这计算的是 0 而不是 1。

                          【讨论】:

                          • 您的注释表明这最多但不包括todate。只需将 xrange 表达式更改为 xrange((todate-fromdate).days + 1),这将为您提供包含 todate 的日生成器。另外,顺便说一句,但由于 True = 1,您的 sum 表达式可以简单地读取(如果您关心的话)sum(day.weekday() &lt; 5 for day in daygenerator)。也许有点聪明,但这不是一个糟糕的习惯用法,用于计算符合条件的事物的数量。
                          • 这是生成器表达式的伟大用途!
                          【解决方案19】:
                          import datetime
                          
                          # some givens
                          dateB = datetime.date(2010, 8, 31)
                          dateA = datetime.date(2010, 7, 8)
                          delta = datetime.timedelta(1)
                          
                          # number of days
                          days = 0
                          
                          while dateB != dateA:
                              #subtract a day
                              dateB -= delta
                          
                              # if not saturday or sunday, add to count
                              if dateB.isoweekday() not in (6, 7):
                                  days += 1
                          

                          我认为这样的事情应该可行。我现在没有测试它的工具。

                          【讨论】:

                            猜你喜欢
                            • 2019-04-15
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2012-04-03
                            • 1970-01-01
                            相关资源
                            最近更新 更多