Возвращает список кортежей диапазонов дат

У меня есть дата начала и окончания. Я хотел бы вернуть список кортежей с датами начала и окончания каждого месяца.

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 ответ
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. Вот почему я тоже возвращал их как струны. Мне действительно нравится ваше решение, возможно, я могу внести небольшие изменения, чтобы получить его в виде строк

    — научный_эксплорер

  • 1

    Просто заранее оберните решение вызовами синтаксического анализа и вызовами форматирования после.

    — Райндериен

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *