Как получить номер недели текущего квартала в Python? - PullRequest
1 голос
/ 09 июля 2020

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

Я пытаюсь получить номер недели текущего финансового квартала. Каждый квартал начинается 1 января, апреля, июля или октября.

Учитывая дату (строка или объект, не имеет значения), мне нужно иметь возможность вычислить номер недели финансового квартал, в котором он находится.

Чтобы немного усложнить ситуацию, финансовый год начинается в апреле.

Так, например, сегодня 9 июля 2020 года - это вторая неделя этого финансового квартала (Q2 ), потому что квартал начинается в апреле. Точно так же 29 и 30 июня 2020 года - это 14 неделя 1 квартала.

В большинстве библиотек форматирования времени и даже в стандартных библиотеках есть такие методы, как дата ISO, где я могу извлечь номер недели в порядке. Но это номер недели с 1-го дня года.

Я не могу использовать arithmeti c, чтобы просто удалить количество недель до текущей даты, поскольку в каждом квартале разное количество недель . Кварталы могут иметь 12, 13 или 14 недель в зависимости от года.

Самое близкое, что я нашел, - это использовать библиотеку FiscalYear, которая великолепна, поскольку в ней есть класс Fiscal Quarter. К сожалению, унаследованный метод isoformat () к нему не применим. Только класс FiscalDate, который не дает мне нужного квартала.

Кто-нибудь сталкивался с этим? Может ли кто-нибудь указать мне правильное направление?

Я бы разместил фрагменты кода, но в Python есть всего 100 способов получить текущий номер недели (на сегодняшний день это 28).

Я пробовал использовать rrules и deltas в dateutils, но самое близкое, что я могу получить, - это номер недели 1-го квартала с использованием смещений. Во втором квартале он разваливается.

Я счастлив использовать pandas или любую другую стороннюю библиотеку, если это поможет мне избежать жесткого кодирования дат кварталов или, не дай бог, номера недели для дат сопоставления.

Любая помощь в правильном направлении будет очень признательна.

Изменить: все три ответа ниже решили эту проблему для меня по-разному. Я боролся с тем, на какой из них дать правильный ответ, но я дал его на ответ @Pol, поскольку это был тот, за которым я мог бы больше всего следить как человек, не являющийся старшим. Это также был ответ, который соответствовал моему личному варианту использования (о котором я не упоминал), который заключался в получении объекта datetime и получении результатов. Так что это дало ему преимущество. Извините других, кто дал потрясающие ответы. Я очень рад, что получил код, и все, на что я надеялся, было толчком в правильном направлении. Всем спасибо.

Ответы [ 3 ]

2 голосов
/ 10 июля 2020

Если это не очень распространенный способ расчета нумерации недель, я не знаю, собираетесь ли вы найти библиотеку, которая сделает это именно за вас, но это достаточно легко выполнить sh с помощью dateutil ' s relativedelta и немного логи c. Вот простая реализация, возвращающая кортеж (quarter, week). Поскольку вы сказали, что Q1 начинается 1 апреля, я предполагаю, что период с 1 января по 1 апреля называется Q0:

from datetime import date, datetime, timedelta
import typing

from dateutil import relativedelta

NEXT_MONDAY = relativedelta.relativedelta(weekday=relativedelta.MO)
LAST_MONDAY = relativedelta.relativedelta(weekday=relativedelta.MO(-1))
ONE_WEEK = timedelta(weeks=1)


def week_in_quarter(dt: datetime) -> typing.Tuple[int, int]:
    d: date = dt.date()
    year = d.year

    # Q0 = January 1, Q1 = April 1, Q2 = July 1, Q3 = October 1
    quarter = ((d.month - 1) // 3)
    quarter_start = date(year, (quarter * 3) + 1, 1)
    quarter_week_2_monday = quarter_start + NEXT_MONDAY

    if d < quarter_week_2_monday:
        week = 1
    else:
        cur_week_monday = d + LAST_MONDAY
        week = int((cur_week_monday - quarter_week_2_monday) / ONE_WEEK) + 2

    return quarter, week

Что возвращает:

$ python week_in_quarter.py 
2020-01-01: Q0-W01
2020-02-01: Q0-W05
2020-02-29: Q0-W09
2020-03-01: Q0-W09
2020-06-30: Q1-W14
2020-07-01: Q2-W01
2020-09-04: Q2-W10
2020-12-31: Q3-W14

Если я неправильно понял первый квартал календарного года, и на самом деле 1 января - 1 апреля года X считается четвертым кварталом года X-1, тогда вы можете изменить строку return quarter, week в конце этого (и изменить аннотация типа возврата):

if quarter == 0:
    year -= 1
    quarter = 4

return year, quarter, week

Что изменяет возвращаемые значения на:

$ python week_in_quarter.py 
2020-01-01: FY2019-Q4-W01
2020-02-01: FY2019-Q4-W05
2020-02-29: FY2019-Q4-W09
2020-03-01: FY2019-Q4-W09
2020-06-30: FY2020-Q1-W14
2020-07-01: FY2020-Q2-W01
2020-09-04: FY2020-Q2-W10
2020-12-31: FY2020-Q3-W14

Если это что-то, что является узким местом скорости, вероятно, будет легко написать оптимизированную версию из этого, который не использует dateutil.relativedelta, но вместо этого вычисляет это на основе дня недели, дня года и того, является ли это високосным годом (календарные вычисления в Python обычно go быстрее, если вы можете превратить его в целочисленные операции как можно раньше), но я подозреваю, что в большинстве случаев эта версия должна быть самой простой для чтения и понимания.

Если вы хотите избежать e зависимости от dateutil, вы можете заменить NEXT_MONDAY и LAST_MONDAY простыми функциями:

def next_monday(dt: date) -> date:
    weekday = dt.weekday()
    return dt + timedelta(days=(7 - weekday) % 7)

def last_monday(dt: date) -> date:
    weekday = dt.weekday()
    return dt - timedelta(days=weekday)

В этом случае вы должны назначить две переменные _monday как quarter_week_2_monday = next_monday(quarter_start) и cur_week_monday = last_monday(dt) , соответственно.

В качестве примечания: если бы я писал эту функцию, я бы, вероятно, не получил бы, чтобы она возвращала чистый кортеж целых чисел, а вместо этого использовал бы attrs или класс данных , чтобы создать простой класс для этой цели, например:

import attr

@attr.s(auto_attribs=True, frozen=True, slots=True)
class QuarterInWeek:
    year: int
    quarter: int
    week: int

    def __str__(self):
        return f"FY{self.year}-Q{self.quarter}-W{self.week:02d}"

(Обратите внимание, что slots=True не является обязательным, и я думаю, что он недоступен, если вы используете dataclasses.dataclass вместо этого - просто это - простая структура, и я предпочитаю использовать классы слотов для простых структур).

1 голос
/ 09 июля 2020

Вот простое решение с использованием библиотеки isocalendar python для поиска номера недели:

Примечание: неделя начинается в понедельник.

from datetime import datetime

FISCAL_QUARTERS = [4, 7, 10, 1]  # April, July, October, January
FISCAL_PERIOD = 3

def _calc_quarter_week(day, month, year):
    fiscal_quarter = None
    # Find which quarter the given date falls in
    for fiscal_index in range(len(FISCAL_QUARTERS)):
        f_month = FISCAL_QUARTERS[fiscal_index]
        if month >= f_month and month < f_month + FISCAL_PERIOD:
            fiscal_quarter = fiscal_index + 1
            break

    quarter_start_day = datetime(
        year=year, month=FISCAL_QUARTERS[fiscal_quarter-1], day=1)
    # Quarter week number
    _, q_week_no, _ = quarter_start_day.isocalendar()

    given_date = datetime(year=year, month=month, day=day)
    # Given week number
    _, given_week_no, _ = given_date.isocalendar()

    return fiscal_quarter, given_week_no - q_week_no + 1


day, month, year = map(int, input('Day Month Year\n').strip().split())
fiscal_quarter, week_count = _calc_quarter_week(day, month, year)
print('Fiscal quarter: {}, Week: {}'.format(fiscal_quarter, week_count))

Вывод:

Day Month Year
29 6 2020
Fiscal quarter: 1, Week: 14
Day Month Year
9 7 2020
Fiscal quarter: 2, Week: 2
1 голос
/ 09 июля 2020

Думаю, это то, что вам нужно (или, по крайней мере, очень хорошее начало):

import datetime as dt

def quarter(date):
    return (date.month-1)//3 + 1 
    
def week_in_q(d):
    year=d.year
    soq={1:dt.date(year,1,1),
         2:dt.date(year,4,1),
         3:dt.date(year,7,1),
         4:dt.date(year,10,1)}
    for i, sow in enumerate(soq[quarter(d)]+dt.timedelta(weeks=x) for x in range(5*3)):
        if sow>=d: 
            return i+1
date=dt.date(2020, 1, 1)    

for d in (date+dt.timedelta(weeks=x) for x in range(53)):
    print(f"date: {d}, quarter: {quarter(d)}, week in that quarter: {week_in_q(d)}")

Распечатывает:

date: 2020-01-01, quarter: 1, week in that quarter: 1
date: 2020-01-08, quarter: 1, week in that quarter: 2
date: 2020-01-15, quarter: 1, week in that quarter: 3
date: 2020-01-22, quarter: 1, week in that quarter: 4
date: 2020-01-29, quarter: 1, week in that quarter: 5
date: 2020-02-05, quarter: 1, week in that quarter: 6
date: 2020-02-12, quarter: 1, week in that quarter: 7
date: 2020-02-19, quarter: 1, week in that quarter: 8
date: 2020-02-26, quarter: 1, week in that quarter: 9
date: 2020-03-04, quarter: 1, week in that quarter: 10
date: 2020-03-11, quarter: 1, week in that quarter: 11
date: 2020-03-18, quarter: 1, week in that quarter: 12
date: 2020-03-25, quarter: 1, week in that quarter: 13
date: 2020-04-01, quarter: 2, week in that quarter: 1
date: 2020-04-08, quarter: 2, week in that quarter: 2
date: 2020-04-15, quarter: 2, week in that quarter: 3
date: 2020-04-22, quarter: 2, week in that quarter: 4
date: 2020-04-29, quarter: 2, week in that quarter: 5
date: 2020-05-06, quarter: 2, week in that quarter: 6
date: 2020-05-13, quarter: 2, week in that quarter: 7
date: 2020-05-20, quarter: 2, week in that quarter: 8
date: 2020-05-27, quarter: 2, week in that quarter: 9
date: 2020-06-03, quarter: 2, week in that quarter: 10
date: 2020-06-10, quarter: 2, week in that quarter: 11
date: 2020-06-17, quarter: 2, week in that quarter: 12
date: 2020-06-24, quarter: 2, week in that quarter: 13
date: 2020-07-01, quarter: 3, week in that quarter: 1
date: 2020-07-08, quarter: 3, week in that quarter: 2
date: 2020-07-15, quarter: 3, week in that quarter: 3
date: 2020-07-22, quarter: 3, week in that quarter: 4
date: 2020-07-29, quarter: 3, week in that quarter: 5
date: 2020-08-05, quarter: 3, week in that quarter: 6
date: 2020-08-12, quarter: 3, week in that quarter: 7
date: 2020-08-19, quarter: 3, week in that quarter: 8
date: 2020-08-26, quarter: 3, week in that quarter: 9
date: 2020-09-02, quarter: 3, week in that quarter: 10
date: 2020-09-09, quarter: 3, week in that quarter: 11
date: 2020-09-16, quarter: 3, week in that quarter: 12
date: 2020-09-23, quarter: 3, week in that quarter: 13
date: 2020-09-30, quarter: 3, week in that quarter: 14
date: 2020-10-07, quarter: 4, week in that quarter: 2
date: 2020-10-14, quarter: 4, week in that quarter: 3
date: 2020-10-21, quarter: 4, week in that quarter: 4
date: 2020-10-28, quarter: 4, week in that quarter: 5
date: 2020-11-04, quarter: 4, week in that quarter: 6
date: 2020-11-11, quarter: 4, week in that quarter: 7
date: 2020-11-18, quarter: 4, week in that quarter: 8
date: 2020-11-25, quarter: 4, week in that quarter: 9
date: 2020-12-02, quarter: 4, week in that quarter: 10
date: 2020-12-09, quarter: 4, week in that quarter: 11
date: 2020-12-16, quarter: 4, week in that quarter: 12
date: 2020-12-23, quarter: 4, week in that quarter: 13
date: 2020-12-30, quarter: 4, week in that quarter: 14
...