У меня есть такой вариант использования в Python: найти первый рабочий день и последний рабочий день предыдущего месяца по дате. Например, если дата 2024-06-10
first_business_day = '2024-05-01'
last_business_day = '2024-05-31'
Я пробовал, как показано ниже
run_date = '2024-06-10'
from datetime import datetime, timedelta
d = datetime.strptime(run_date, '%Y-%m-%d').date()
previous_month_first_business_day = (d - timedelta(days=d.day)).replace(day=1).strftime("%Y-%m-%d")
previous_month_last_business_day = (d - timedelta(days=d.day)).strftime("%Y-%m-%d")
Результат:
previous_month_first_business_day = '2024-05-01'
previous_month_last_business_day = '2024-05-31'
Это работает нормально в мае, но когда я хочу тот же результат в июне, то, используя приведенное выше, я получаю:
previous_month_first_business_day = '2024-06-01' # This should be '2024-06-03'
previous_month_last_business_day = '2024-06-30' # This should be '2024-06-29'
Что мне делать, чтобы добиться нужного результата?
Как в вашем случае определяются первый и последний рабочий день? Учитывается ли это банковскими/национальными праздниками? Работают ли рабочие по выходным?
«Это должно быть «29 июня 2024 г.» — Но 29-е число — суббота. Являются ли субботы рабочими днями в этой системе?
Какие исследования вы провели? Это помогает? Первая деловая дата месяца Python
У @wjandrea есть возможность использовать Pandas! особенно коллекция pandas USFederalHolidayCalendar может оказаться всем, что вам нужно, чтобы пропустить праздничные даты.






Взгляните на встроенный календарь вместо просто datetime
import calendar
import datetime
def month_start_end_work(date_src, fmt_date = "%Y-%m-%d"):
dt = datetime.datetime.strptime(date_src, fmt_date)
# roll around January -> December
month = 12 if dt.month == 1 else (dt.month - 1)
year = dt.year if month != 12 else (dt.year - 1)
# discover last day of last month
for day in (31, 30, 29, 28): # handle Feb cases
try: # already the correct year for leap
month_start = calendar.weekday(year, month, day)
except ValueError: # day is out of range for month
continue # month has fewer days (always decreases)
if month_start == calendar.SATURDAY:
day -= 1
elif month_start == calendar.SUNDAY:
day -= 2
break # last day name to be used
else: # did not solve and break
raise RuntimeError("BUG: impossible code path reached")
day_last = (year, month, day) # TODO may want to make a datetime.datetime
# discover first day of last month
day = 1 # all months begin at 1
month_start = calendar.weekday(year, month, day)
if month_start == calendar.SATURDAY:
day += 2
elif month_start == calendar.SUNDAY:
day += 1
day_first = (year, month, day)
return day_first, day_last
>>> month_start_end_work("2024-06-10")
((2024, 5, 1), (2024, 5, 31))
>>> month_start_end_work("2024-07-10")
((2024, 6, 3), (2024, 6, 28))
>>> month_start_end_work("2022-01-01")
((2021, 12, 1), (2021, 12, 31))
Если у вас есть особые дни, которые, как вы знаете, являются выходными (вероятно, 6–12 в год), соберите их в список и просто продолжайте уменьшать/увеличивать для точных совпадений.
Здесь праздничные дни считаются рабочими днями, нет удобного места для добавления проверки на праздничные дни, а функциональность, которая у него есть (проверка только на будние дни), неоправданно длинная и сложная.
действительно, хотя праздники - это всегда больная тема.. праздники , как вы предлагаете, отличный выбор вместе с таймсериями Панд (предложено по ссылке SO в комментариях) .. но в связи с регионом и спецификой работы, от всего сердца Компаниям предлагается опубликовать (внутреннюю) пару API для многолетнего окна праздников по региону и снова для всех известных временных интервалов компании, возможно, в виде статического ответа JSON, чтобы дополнить или переопределить общедоступные данные при запуске или сборке. время
имея некоторый источник дополнительных данных о праздниках, пользователи могут преобразовать его в набор кортежей для сравнения, а также увеличить или уменьшить свой день в
Вы были на правильном пути, получив первый и последний день предыдущего месяца. Следующим шагом будет увеличение/уменьшение, если эти дни являются выходными, что вы можете проверить с помощью метода weekday(): неделя начинается с 0 с понедельника и заканчивается в 6 с воскресенья. Убедившись, что этот день не является выходным с помощью weekday(), вы должны убедиться, что это также не выходной, используя библиотеку, например праздники. Например, чтобы проверить праздники в США:
from datetime import datetime, timedelta
import holidays
def previous_month_business_day_endpoints(d, h=holidays.US()):
d = datetime.strptime(d, '%Y-%m-%d').date()
end = d - timedelta(days=d.day)
start = end.replace(day=1)
while start.weekday() > 4 or start in h:
start += timedelta(days=1)
while end.weekday() > 4 or end in h:
end -= timedelta(days=1)
return start, end
Это будет правильно обрабатывать входные данные, такие как '2024-2-1', поскольку первый будний день января 2024 года приходится на Новый год.
Вы специально заменили день месяца на первый, без чего-либо, связанного с тем, каким будет рабочий день, поэтому вы получаете первое число месяца, как и ожидалось.