How to create a 4-4-5 fiscal calendar using python?

Viewed 1187

I would like to have a dataframe consisting of the corresponding week numbers and month every date from 1/1/21 to 31/12/50 would belong to. For instance,

  date  |   day   | week_no | yyyy-mm | quarter
01/01/21| Friday  |    40   | 2021-01 | Q4 2021
02/01/21| Saturday|    40   | 2021-01 | Q4 2021
.
.
24/01/21| Sunday  |    44   | 2021-02 | Q4 2021
.
.
01/04/21| Thursday|    53   | 2021-03 | Q4 2021
.
04/04/21| Sunday  |    1    | 2021-04 | Q1 2022

I found a wrapper class in Pandas called FY5253Quarter and fiscal445 in python, however, I am not sure how I can use this class to achieve what I require. Thanks in advance for all the help :)

2 Answers

Creating calendar table could be done with SQL. Here example using PostgreSQL:

WITH RECURSIVE cte AS (
   SELECT CAST('2021-01-01' AS date) d
   UNION ALL
   SELECT d+1
   FROM cte
   WHERE d < CAST('2030-12-31' AS DATE)
)
SELECT 
  d AS "date",
  to_char(d, 'DAY') AS day,
  to_char(d, 'iw') AS week_no,
  to_char(d, 'yyyy-mm') AS "yyyy-mm",
  to_char(d, '"Q"q yyyy') AS "quarter"
FROM cte;

db<>fiddle demo

How it works:

  1. Recursive part generate date rows from 2021-01-01 to defined end date
  2. Main query generate columns with proper string formatting

Now by having the query you could materialize it with CREATE TABLE myCalendar AS ... if needed.

If you are using different RDBMS, the pattern is basically the same - the only difference is using dialect specific functions.

For instance PostgreSQL has its own function generate_series:

SELECT * /* format functions here */
FROM generate_series(date '2021-01-01', date '2030-12-31', '1 day');

EDIT:

How can I specify that I want a 4-4-5 calendar because from what I understand it seems you're creating a normal calendar?

Yes, here it is a normal calendar. The logic could be rewritten, but you already found a working solution.

Also I found a link to PostgreSQL for fiscal 4-4-5 github.com/sqlsunday/calendar/blob/boss/CalendarFunctions.sql but I always meet with an error running the script for function Calendar.Fiscal_4_4_5

The point is it's written for SQL Server in TSQL as table-valued function. To call it use:

SELECT *
FROM  Calendar.Fiscal_4_4_5 ('2021-01-01', '2030-12-31', '2021-01-01','445',1);

db<>fiddle demo

Okay Second try. I like learning new things so thank you for bringing the 4-4-5 fiscal calender to my life. ;)

Since I think you only need to create this calender once and than use the finished calender as reference I thought of a stupid idea to create such a calender. Hardcoded loops.

The following code has a function that creates a 4-4-5 fiscal calender based on a start date and the number of years that should be created. Since according to Wikipedia there is a leap week every 5 or 6 years I added the leap_week_year for you to choose in which year the leep week should go. Also a leap_week_quarter to choose which quarter should be 4-4-6.

import pandas as pd
import datetime as dt


def create_445_calender(start_date: str, years: int, leap_week_year = 5, leap_week_quarter = 4):

    output = []

    tmp_datetime = dt.datetime.strptime(start_date,'%d/%m/%Y')
    
    for year in range(1,years+1):
        month_of_year = 1
        week_of_year = 1
        # 4 quarters
        for quarters in range(1,5):
            # 4 weeks
            for weeks in range(1,5):
                # 7 days
                for days in range(1,8):
                    tmp_datestr = dt.datetime.strftime(tmp_datetime,'%d/%m/%Y')
                    tmp_weekday = dt.datetime.strftime(tmp_datetime,'%A')
                    tmp_monthstr = str(month_of_year) if month_of_year >=  10 else '0' + str(month_of_year)
                    tmp_yyyy_mm = dt.datetime.strftime(tmp_datetime,'%Y') + '-' + tmp_monthstr
                    tmp_quarter = 'Q' + str(quarters) + ' ' + dt.datetime.strftime(tmp_datetime,'%Y')
                    output.append([tmp_datestr,tmp_weekday, week_of_year, tmp_yyyy_mm, tmp_quarter])
                    tmp_datetime = tmp_datetime + dt.timedelta(days=1)
                week_of_year += 1
            month_of_year += 1
            # 4 weeks
            for weeks in range(1,5):
                # 7 days
                for days in range(1,8):
                    tmp_datestr = dt.datetime.strftime(tmp_datetime,'%d/%m/%Y')
                    tmp_weekday = dt.datetime.strftime(tmp_datetime,'%A')
                    tmp_monthstr = str(month_of_year) if month_of_year >=  10 else '0' + str(month_of_year)
                    tmp_yyyy_mm = dt.datetime.strftime(tmp_datetime,'%Y') + '-' + tmp_monthstr
                    tmp_quarter = 'Q' + str(quarters) + ' ' + dt.datetime.strftime(tmp_datetime,'%Y')
                    output.append([tmp_datestr,tmp_weekday, week_of_year, tmp_yyyy_mm, tmp_quarter])
                    tmp_datetime = tmp_datetime + dt.timedelta(days=1)
                week_of_year += 1
            month_of_year += 1
            # 5 / 6 weeks
            # 5 years
            if (year % leap_week_year == 0 ) and (quarters == leap_week_quarter):
                tmp_weeks = 6
            else:
                tmp_weeks = 5
            for weeks in range(1,tmp_weeks+1):
                # 7 days
                for days in range(1,8):
                    tmp_datestr = dt.datetime.strftime(tmp_datetime,'%d/%m/%Y')
                    tmp_weekday = dt.datetime.strftime(tmp_datetime,'%A')
                    tmp_monthstr = str(month_of_year) if month_of_year >=  10 else '0' + str(month_of_year)
                    tmp_yyyy_mm = dt.datetime.strftime(tmp_datetime,'%Y') + '-' + tmp_monthstr
                    tmp_quarter = 'Q' + str(quarters) + ' ' + dt.datetime.strftime(tmp_datetime,'%Y')
                    output.append([tmp_datestr,tmp_weekday, week_of_year, tmp_yyyy_mm, tmp_quarter])
                    tmp_datetime = tmp_datetime + dt.timedelta(days=1)
                week_of_year += 1
            month_of_year += 1

    df = pd.DataFrame(output,columns = ['date','day','week_no','yyyy-mm','quarter'])

    return df


my445calender = create_445_calender('06/04/2020', 6, 5, 4)
Related