计算日期之间的天数,忽略周末

Moh*_*eem 49 python

如何计算忽略周末的两个日期之间的天数?

Ben*_*Ben 70

我认为最干净的解决方案是使用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 )
Run Code Online (Sandbox Code Playgroud)


Dav*_*ebb 50

>>> 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
Run Code Online (Sandbox Code Playgroud)

将创建使用生成器表达式生成器,其将产生的天列表从获取fromdatetodate.

然后我们可以从生成器创建一个列表,使用weekday()函数过滤掉周末,列表的大小给出我们想要的天数.但是,为了节省将整个列表存储在内存中如果日期相隔很长时间可能会出现问题,我们使用另一个生成器表达式来过滤掉周末但返回1而不是每个日期. 然后我们可以将所有这些1组合在一起以获得长度而无需存储整个列表.

注意,如果fromdate == todate这个计算0不是1.

  • 您的注释表明这可以计入但不包括`todate`.只需将xrange表达式更改为`xrange((todate-fromdate).days + 1)`,这将为您提供包含`todate`的daygenerator.另外,只是旁边,但是因为True = 1,你的总和表达式可以简单地读取(如果你关心),`sum(day.weekday()<day for day in daygenerator)`.也许有点聪明,但这不是一个可怕的习惯用于计算符合条件的事物的数量. (5认同)

nei*_*eil 10

到目前为止给出的答案将起作用,但如果日期相隔很远(由于循环),则效率非常低.

这应该工作:

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)
Run Code Online (Sandbox Code Playgroud)

这将它变成整整一周(有5个工作日),然后处理剩下的几天.

  • 请注意,@neil 的代码将在周日至周四的时间间隔内失败。这是一个修复 http://stackoverflow.com/a/32698089/1617748(编辑:新答案和链接以获得更好的格式) (2认同)

小智 9

首先导入numpynp.该函数np.busday_count计算两个日期之间的有效天数,不包括结束日期的日期.

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

import numpy as np
np.busday_count('2018-04-10', '2018-04-11')
Run Code Online (Sandbox Code Playgroud)

请注意,该函数接受字符串,datetime在调用函数之前不必实例化对象.

  • 祝贺你的第一篇文章。不幸的是,大约 3 1/2 年前,用户 Ben 已经提供了这个答案。请在自己提供之前阅读其他答案以避免此类重复 (4认同)

Mat*_*ten 8

懒惰的方法是pip install workdays获得完全正确的python包.

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


Seb*_*ian 7

到目前为止,我发现所提供的解决方案都不令人满意。要么依赖于我不想要的库,要么存在低效的循环算法,或者存在不适用于所有情况的算法。不幸的是@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
Run Code Online (Sandbox Code Playgroud)


vvk*_*sov 5

1. 使用外部服务获得公共假期/额外工作日

\n

非常感谢 @Renan 介绍了我现在在自己的项目中使用的这个令人惊叹的 API。这是他通过轻微清洁+测试得到的答案。

\n
import urllib.request\nimport json\nfrom typing import Dict\n\nfrom dateutil import rrule\nfrom datetime import date\n\n\nWEEKDAY_FRIDAY = 4  # date.weekday() starts with 0\n\n\nclass CountryCalendar(object):\n\n    def __init__(self, special_dates: Dict[date, str]):\n        self.special_dates = special_dates\n\n    def is_working_day(self, dt: date):\n        date_type = self.special_dates.get(dt)\n        if date_type == "extra_working_day":\n            return True\n        if date_type == "public_holiday":\n            return False\n        return dt.weekday() <= WEEKDAY_FRIDAY\n\n\ndef load_calendar(\n    country: str,\n    region: str,\n    start_date: date,\n    end_date: date\n) -> CountryCalendar:\n    """\n    Access Enrico Service 2.0 JSON\n    https://kayaposoft.com/enrico/\n\n    Response format (for country=rus):\n\n    [\n        {\n            holidayType: "public_holiday",\n            date: {\n                day: 2,\n                month: 1,\n                year: 2022,\n                dayOfWeek: 7\n            },\n            name: [\n                {lang: "ru", text: "\xd0\x9d\xd0\xbe\xd0\xb2\xd0\xbe\xd0\xb3\xd0\xbe\xd0\xb4\xd0\xbd\xd0\xb8\xd0\xb5 \xd0\xba\xd0\xb0\xd0\xbd\xd0\xb8\xd0\xba\xd1\x83\xd0\xbb\xd1\x8b"},\n                {lang: "en", text: "New Year\xe2\x80\x99s Holiday"}\n            ]\n        },\n        ...\n    ]\n    """\n    urlstring = (\n        f"https://kayaposoft.com/enrico/json/v2.0/"\n        f"?action=getHolidaysForDateRange"\n        f"&fromDate={start_date:%d-%m-%Y}"\n        f"&toDate={end_date:%d-%m-%Y}"\n        f"&country={country}"\n        f"&region={region}"\n        f"&holidayType=all"\n    )\n\n    with urllib.request.urlopen(urlstring) as url:\n        payload = json.loads(url.read().decode())\n\n    return CountryCalendar({\n        date(\n            special_date["date"]["year"],\n            special_date["date"]["month"],\n            special_date["date"]["day"],\n        ): special_date["holidayType"]\n        for special_date in payload\n    })\n\n\ndef count_work_days(\n    start_date: date,\n    end_date: date,\n    country: str,\n    region: str,\n):\n    """\n    Get working days specific for country\n    using public holidays internet provider\n    """\n    if start_date > end_date:\n        return 0\n\n    try:\n        country_calendar = load_calendar(country, region, start_date, end_date)\n    except Exception as exc:\n        print(f"Error accessing calendar of country: {country}. Exception: {exc}")\n        raise\n\n    workdays = 0\n    for dt in rrule.rrule(rrule.DAILY, dtstart=start_date, until=end_date):\n        if country_calendar.is_working_day(dt):\n            workdays += 1\n\n    return workdays\n\n\ndef test(test_name: str, start_date: date, end_date: date, expected: int):\n    print(f"Running: {test_name}... ", end="")\n    params = dict(\n        start_date=start_date,\n        end_date=end_date,\n        country="rus",\n        region=""\n    )\n    assert expected == count_work_days(**params), dict(\n        expected=expected,\n        actual=count_work_days(**params),\n        **params\n    )\n    print("ok")\n\n\n# Start on Mon\ntest("Mon - Mon", date(2022, 4, 4), date(2022, 4, 4), 1)\ntest("Mon - Tue", date(2022, 4, 4), date(2022, 4, 5), 2)\ntest("Mon - Wed", date(2022, 4, 4), date(2022, 4, 6), 3)\ntest("Mon - Thu", date(2022, 4, 4), date(2022, 4, 7), 4)\ntest("Mon - Fri", date(2022, 4, 4), date(2022, 4, 8), 5)\ntest("Mon - Sut", date(2022, 4, 4), date(2022, 4, 9), 5)\ntest("Mon - Sun", date(2022, 4, 4), date(2022, 4, 10), 5)\ntest("Mon - next Mon", date(2022, 4, 4), date(2022, 4, 11), 6)\ntest("Mon - next Tue", date(2022, 4, 4), date(2022, 4, 12), 7)\n\n\n# Start on Fri\ntest("Fri - Sut", date(2022, 4, 1), date(2022, 4, 2), 1)\ntest("Fri - Sun", date(2022, 4, 1), date(2022, 4, 3), 1)\ntest("Fri - Mon", date(2022, 4, 1), date(2022, 4, 4), 2)\ntest("Fri - Tue", date(2022, 4, 1), date(2022, 4, 5), 3)\ntest("Fri - Wed", date(2022, 4, 1), date(2022, 4, 6), 4)\ntest("Fri - Thu", date(2022, 4, 1), date(2022, 4, 7), 5)\ntest("Fri - next Fri", date(2022, 4, 1), date(2022, 4, 8), 6)\ntest("Fri - next Sut", date(2022, 4, 1), date(2022, 4, 9), 6)\ntest("Fri - next Sun", date(2022, 4, 1), date(2022, 4, 10), 6)\ntest("Fri - next Mon", date(2022, 4, 1), date(2022, 4, 11), 7)\n\n\n# Some edge cases\ntest("start > end", date(2022, 4, 2), date(2022, 4, 1), 0)\ntest("Sut - Sun", date(2022, 4, 2), date(2022, 4, 3), 0)\ntest("Sut - Mon", date(2022, 4, 2), date(2022, 4, 4), 1)\ntest("Sut - Fri", date(2022, 4, 2), date(2022, 4, 8), 5)\ntest("Thu - Fri", date(2022, 3, 31), date(2022, 4, 8), 7)\n\n
Run Code Online (Sandbox Code Playgroud)\n

2. 简单的数学:公共假期/额外工作日不使用服务

\n

即使@Sebastian的答案在很多情况下都不能应用,因为它没有考虑公共假期和额外的工作日,我仍然觉得它很棒,因为它可以完成工作,并决定修复一个错误(基本上只改变了他的最后一行)。

\n
from datetime import date, timedelta\n\n\nWEEKDAY_FRIDAY = 4  # date.weekday() starts with 0\n\n\ndef count_work_days(start_date: date, end_date: date):\n    """\n    Math function to get workdays between 2 dates.\n    Can be used only as fallback as it doesn\'t know\n    about specific country holidays or extra working days.\n    """\n    # if the start date is on a weekend, forward the date to next Monday\n\n    if start_date.weekday() > WEEKDAY_FRIDAY:\n        start_date = start_date + timedelta(days=7 - start_date.weekday())\n\n    # if the end date is on a weekend, rewind the date to the previous Friday\n    if end_date.weekday() > WEEKDAY_FRIDAY:\n        end_date = end_date - timedelta(days=end_date.weekday() - WEEKDAY_FRIDAY)\n\n    if start_date > end_date:\n        return 0\n    # that makes the difference easy, no remainders etc\n    diff_days = (end_date - start_date).days + 1\n    weeks = int(diff_days / 7)\n\n    remainder = end_date.weekday() - start_date.weekday() + 1\n\n    if remainder != 0 and end_date.weekday() < start_date.weekday():\n        remainder = 5 + remainder\n\n    return weeks * 5 + remainder\n\n\ndef test(test_name: str, start_date: date, end_date: date, expected: int):\n    print(f"Running: {test_name}... ", end="")\n    params = dict(\n        start_date=start_date,\n        end_date=end_date,\n    )\n    assert expected == count_work_days(**params), dict(\n        expected=expected,\n        actual=count_work_days(**params),\n        **params\n    )\n    print("ok")\n\n\n# Start on Mon\ntest("Mon - Mon", date(2022, 4, 4), date(2022, 4, 4), 1)\ntest("Mon - Tue", date(2022, 4, 4), date(2022, 4, 5), 2)\ntest("Mon - Wed", date(2022, 4, 4), date(2022, 4, 6), 3)\ntest("Mon - Thu", date(2022, 4, 4), date(2022, 4, 7), 4)\ntest("Mon - Fri", date(2022, 4, 4), date(2022, 4, 8), 5)\ntest("Mon - Sut", date(2022, 4, 4), date(2022, 4, 9), 5)\ntest("Mon - Sun", date(2022, 4, 4), date(2022, 4, 10), 5)\ntest("Mon - next Mon", date(2022, 4, 4), date(2022, 4, 11), 6)\ntest("Mon - next Tue", date(2022, 4, 4), date(2022, 4, 12), 7)\n\n\n# Start on Fri\ntest("Fri - Sut", date(2022, 4, 1), date(2022, 4, 2), 1)\ntest("Fri - Sun", date(2022, 4, 1), date(2022, 4, 3), 1)\ntest("Fri - Mon", date(2022, 4, 1), date(2022, 4, 4), 2)\ntest("Fri - Tue", date(2022, 4, 1), date(2022, 4, 5), 3)\ntest("Fri - Wed", date(2022, 4, 1), date(2022, 4, 6), 4)\ntest("Fri - Thu", date(2022, 4, 1), date(2022, 4, 7), 5)\ntest("Fri - next Fri", date(2022, 4, 1), date(2022, 4, 8), 6)\ntest("Fri - next Sut", date(2022, 4, 1), date(2022, 4, 9), 6)\ntest("Fri - next Sun", date(2022, 4, 1), date(2022, 4, 10), 6)\ntest("Fri - next Mon", date(2022, 4, 1), date(2022, 4, 11), 7)\n\n\n# Some edge cases\ntest("start > end", date(2022, 4, 2), date(2022, 4, 1), 0)\ntest("Sut - Sun", date(2022, 4, 2), date(2022, 4, 3), 0)\ntest("Sut - Mon", date(2022, 4, 2), date(2022, 4, 4), 1)\ntest("Sut - Fri", date(2022, 4, 2), date(2022, 4, 8), 5)\ntest("Thu - Fri", date(2022, 3, 31), date(2022, 4, 8), 7)\n
Run Code Online (Sandbox Code Playgroud)\n


Cod*_*ict 3

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
Run Code Online (Sandbox Code Playgroud)

我认为类似的事情应该有效。我现在没有工具来测试它。