У меня есть дата начала и окончания. Я хотел бы вернуть список кортежей с датами начала и окончания каждого месяца.
Input
start_date="2020-10-14"
end_date="2021-03-26"
Output
[('2020-10-14', '2020-10-31'),
('2020-11-01', '2020-11-30'),
('2020-12-01', '2020-12-31'),
('2021-01-01', '2021-01-31'),
('2021-02-01', '2021-02-28'),
('2021-03-01', '2021-03-26')]
Мой код
from datetime import datetime, timedelta, date
import calendar
def get_first_day(year, month):
return f'{year}-{str(month).zfill(2)}-01'
def get_last_day(year, month):
# https://stackoverflow.com/a/23447523/5675288
dt = datetime.strptime(f'{year}-{month}', '%Y-%m')
dt = (dt.replace(day=1) + timedelta(days=32)).replace(day=1) - timedelta(days=1)
return dt.strftime('%Y-%m-%d')
def diff_month(d1, d2):
# https://stackoverflow.com/a/4040338/5675288
return abs((d1.year - d2.year) * 12 + d1.month - d2.month)
def add_months(sourcedate, months):
# https://stackoverflow.com/a/4131114/5675288
month = sourcedate.month - 1 + months
year = sourcedate.year + month // 12
month = month % 12 + 1
day = min(sourcedate.day, calendar.monthrange(year,month)[1])
return date(year, month, day)
def get_dates(start_date, end_date):
"""get list of tuples of dates
[
(start_date, end-of-month),
(start-of-next-month, end-of-next-month),
(start-of-next-next-month, end-of-next-next-month),
...
(start-of-last-month, end_date)
]
"""
sd = datetime.strptime(f'{start_date}', '%Y-%m-%d')
ed = datetime.strptime(f'{end_date}', '%Y-%m-%d')
diff_months = diff_month(sd, ed)
first_days = []
last_days = []
for month in range(diff_months+1):
d = add_months(sd, month)
first_days.append(get_first_day(d.year, d.month))
last_days.append(get_last_day(d.year, d.month))
first_days = [start_date] + first_days[1:]
last_days = last_days[:-1] + [end_date]
return list(zip(first_days, last_days))
start_date="2020-10-14"
end_date="2021-03-26"
print(get_dates(start_date, end_date))
Есть ли для этого более чистый / лучший подход?
1 ответ
Ни один из этих кодов не должен работать со строками, кроме может быть заключительный выходной каскад. Все это можно выбросить в пользу единственной функции генератора, использующей очень полезные dateutil библиотека. Также помогут подсказки типа:
from datetime import date
from typing import Iterable, Tuple
from dateutil.relativedelta import relativedelta
def get_dates(start: date, end: date) -> Iterable[Tuple[date, date]]:
while True:
next_start = start + relativedelta(months=1, day=1)
this_end = next_start - relativedelta(days=1)
if end <= this_end:
yield start, end
break
yield start, this_end
start = next_start
def main() -> None:
start = date.fromisoformat(input('Enter start date: '))
end = date.fromisoformat(input('Enter end date: '))
for range_start, range_end in get_dates(start, end):
print(f'{range_start.isoformat()} - {range_end.isoformat()}')
if __name__ == '__main__':
main()
Выход:
Enter start date: 2020-10-14
Enter end date: 2021-03-26
2020-10-14 - 2020-10-31
2020-11-01 - 2020-11-30
2020-12-01 - 2020-12-31
2021-01-01 - 2021-01-31
2021-02-01 - 2021-02-28
2021-03-01 - 2021-03-26

Спасибо за ответ. Хотя есть смысл, что я не должен делать это со строками, мой вариант использования состоит в том, что я буду получать даты начала и окончания в виде строк от пользователя, а кортежи даты будут использоваться в качестве параметров для запроса из API. Вот почему я тоже возвращал их как струны. Мне действительно нравится ваше решение, возможно, я могу внести небольшие изменения, чтобы получить его в виде строк
— научный_эксплорер
Просто заранее оберните решение вызовами синтаксического анализа и вызовами форматирования после.
— Райндериен