adamjstewart / fiscalyear

:calendar: Utilities for managing the fiscal calendar
MIT License
52 stars 13 forks source link

Support for week number of a date in a FiscalYear #10

Open aravinds502 opened 4 years ago

aravinds502 commented 4 years ago

Is there any way to get the Week number for given date in a fiscal year or any plans to support this API?

Ex: import fiscalyear fiscalyear.START_MONTH = 7 fiscalyear.START_DAY = 1 cur_date = fiscalyear.FiscalDate(2019, 7, 1) print(cur_date.week) -> should give 1

adamjstewart commented 4 years ago

@aravinds502 there is currently no way to do this (aside from manually calculating it), but I think that would be a great addition. We already have cur_date.fiscal_year and cur_date.fiscal_quarter, and I can definitely see a use in having cur_date.fiscal_month, cur_date.fiscal_week, and cur_date.fiscal_day. The real question is if we also want FiscalMonth, FiscalWeek, and FiscalDay objects.

If you would like to take a stab at implementing this, PRs are always welcome. Otherwise, if there's enough demand, I can take a look at this when I get a chance.

adamjstewart commented 4 years ago

Note that #11 adds a cur_date.fiscal_month attribute.

nicmendoza commented 4 years ago

@adamjstewart if there were a FiscalMonth class (for example), would you expect it to have year and month properties that returned the calendar year and month like the example below? So for a fiscal year that starts prior year, October:

>>> f = FiscalMonth(2020,1)
>>> f.month
10
>>> f.year
2019

Translating back from fiscal year and month to date is, naturally, my project's next requirement.

adamjstewart commented 4 years ago

I would expect FiscalMonth to be almost identical to FiscalYear. In order to avoid confusion, I would name the attributes fiscal_year and fiscal_month. I didn't do this for FiscalQuarter because Python's builtin datetime objects don't have a quarter attribute, although we can consider renaming it to fiscal_quarter if you think that would be more consistent.

nicmendoza commented 4 years ago

Got it, so for my use case (where I'm actually specifically interested in getting the calendar month and year back out from a FiscalMonth) it would look like this, right?:

>>> f = FiscalMonth(2020,1)
>>> f.start.month
10
>>> f.start.year
2019
adamjstewart commented 4 years ago

Yep, that looks correct.

nicmendoza commented 3 years ago

Hey @adamjstewart, hopefully continuing the conversation here makes sense. Plus I think eventually someone could readily build the original .fiscal_week request from this issue based on FiscalDay.

The definition of a "fiscal day" seems a little less universally-defined from my limited research, but would you expect Fiscal Day to work like the following? This is basically what I need for my own use case so I thought it could potentially fit into the fiscalyear library:

>>> f = FiscalDay(2020,1)
>>> f.year
2019
>>> f.month
10
>>>f.day
1
>>>f.fiscal_year
2020
>>>f.fiscal_quarter
1
>>>f.fiscal_month
1

And then right on up to >>> FiscalDay(2020,364)

Or something else?

adamjstewart commented 3 years ago

I think that API makes sense. I'm trying to think about the easiest way to implement this feature (and other features). Since the start of the fiscal year is a well defined point in time, we could simply use FiscalYear(year) + datetime.timedelta(days=days) to calculate it.

nicmendoza commented 3 years ago

Opened #13 for FiscalDay and fiscal_day.

davedavis commented 3 years ago

I calculate the week number in the Fiscal Quarter in a separate function and combine it with Fiscal quarter, but native support would be fantastic.

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

def get_week_in_quarter(dt):
    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

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

    return week

Happy to add this properly when the FiscalDay PR is merged if there's interest. @aravinds502 This should work for you in the interim.

adamjstewart commented 3 years ago

The FiscalDay and fiscal_day PR has been merged, feel free to submit a PR for FiscalWeek and fiscal_week.

adamjstewart commented 3 years ago

Okay, support for fiscal months and days is merged and documented. Would anyone like to submit a PR to add fiscal weeks, or should I cut a new release at this point?

nicmendoza commented 3 years ago

@adamjstewart ready to release?

adamjstewart commented 3 years ago

Yeah, I can cut a new release if no one wants to work on fiscal week at the moment.

nicmendoza commented 3 years ago

Didn't see anything from @davedavis on this, so I put together a quick PR (#17) to cover the original request in this Issue. If anyone thinks of any missing test cases, please let me know.

adamjstewart commented 3 years ago

@aravinds502 @davedavis how do you define week number? Is it the number of weeks since the start of the fiscal year, or do you also need to consider the days of the week? For example, if the fiscal year starts on a Monday, and you consider weeks to go from Monday to Sunday, then both definitions are equal, but if the fiscal year starts in the middle of a week, would you add 1 to the week count?

nicmendoza commented 3 years ago

In support of @adamjstewart 's question, I found this example US fiscal calendar (although it notably doesn't have week numbers on it unlike 4-4-5 calendars I've sen) which standardizes on a Sunday-Saturday week without being a 4-4-5 calendar. Helpful for the visually oriented.

Aside from the central point Adam has raised here, it does beg the question of how to best handle the contains logic for checking if a week is in a month, since a week can straddle two. Relevant question regardless of which approach we take.

davedavis commented 3 years ago

Sorry guys, got swamped with another PR so unable to do this. So thanks @nicmendoza for going ahead.

@adamjstewart I just used relativedelta from dateutil. My requirement is basically that the first week in each quarter is quarter week 1. From the first day of Q1 is the same, but fiscal week 1. Each quarter quarter week resets but fiscal week doesn't.

This is not how other organizations do it so I wasn't going to mess around.

adamjstewart commented 3 years ago

I looked to see if there is a standard for counting weeks and it looks like ISO has one: https://en.wikipedia.org/wiki/ISO_week_date

Weeks start with Monday. Each week's year is the Gregorian year in which the Thursday falls. The first week of the year, hence, always contains 4 January. ISO week year numbering therefore usually deviates by 1 from the Gregorian for some days close to 1 January.

I think we should stick to ISO whenever possible. Any objections?

nicmendoza commented 3 years ago

Excel also follows ISO 8601, although it's not the default behavior of their weeknum function. I guess because the default follows normal-people calendar conventions. No objections to following ISO 8601 in principle.

What I haven't wrapped my head around is how to butt this up against the current implementation of fiscalyear. The first day of the year won't necessarily line up to October 1 or April 6 most year. Would FiscalYear(2016,1) have a different start date than FiscalWeek(2016,1)?

Poking around for examples in the wild a bit, I came across Zap BI. I've never heard of them but they seem to talk a bit about their approach to this

Tableau's docs seem to gloss over it.

Lots of conversations online about how to get Excel to spit out a fiscal week

Also this: https://pypi.org/project/isoweek/