[{"data":1,"prerenderedAt":2359},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fbuild-a-4-4-5-fiscal-calendar-with-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fbuild-a-4-4-5-fiscal-calendar-with-pandas":2328,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fbuild-a-4-4-5-fiscal-calendar-with-pandas":2335,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fbuild-a-4-4-5-fiscal-calendar-with-pandas":2339,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fbuild-a-4-4-5-fiscal-calendar-with-pandas":2340},{"id":4,"title":5,"body":6,"dateModified":2296,"datePublished":2296,"description":2297,"extension":2298,"faq":2299,"keyword":2308,"kind":2309,"meta":2310,"navigation":99,"path":2318,"seo":2319,"slug":2322,"stem":2323,"topics":2324,"type":2326,"__hash__":2327},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fbuild-a-4-4-5-fiscal-calendar-with-pandas\u002Findex.md","Build a 4-4-5 Fiscal Calendar with pandas",{"type":7,"value":8,"toc":2284},"minimark",[9,24,29,58,64,68,71,457,460,572,576,579,785,791,795,798,1091,1098,1312,1316,1319,1719,1722,1805,1809,1812,2166,2169,2173,2212,2216,2219,2223,2229,2235,2241,2247,2251,2280],[10,11,12,13,17,18,23],"p",{},"Retailers, restaurants and many manufacturers do not report by calendar month. A calendar month has 28 to 31 days and four or five weekends, so comparing March with April — or this March with last March — mixes in differences that have nothing to do with performance. A ",[14,15,16],"em",{},"4-4-5 calendar"," fixes that: each quarter has three periods of four, four and five whole weeks, every period starts on the same weekday, and so every period contains the same number of Saturdays as its counterpart last year. The price is a little complexity: the fiscal year starts on a weekday near a fixed date rather than on 1 January, and every five or six years the year has 53 weeks instead of 52. This page builds such a calendar as a date table with pandas — one row per date with its fiscal year, quarter, period and week — maps sales onto it, and compares periods like for like. It belongs to ",[19,20,22],"a",{"href":21},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002F","Working with Dates and Times in Excel Data",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[37,38,39],"code",{"__ignoreMap":35},[40,41,44,48,52,55],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sMTad","pip",[40,49,51],{"class":50},"srMev"," install",[40,53,54],{"class":50}," pandas",[40,56,57],{"class":50}," openpyxl\n",[10,59,60,63],{},[37,61,62],{},"daily_sales.xlsx"," holds daily store sales from January 2023 to September 2026, with Saturdays and Sundays busier than weekdays — the pattern that makes calendar-month comparisons unfair. The calendar below follows the common retail convention: the fiscal year starts on the Sunday nearest 1 February and is named after the calendar year in which it starts.",[25,65,67],{"id":66},"step-1-when-does-each-fiscal-year-start","Step 1 — When does each fiscal year start?",[10,69,70],{},"Everything follows from the start dates. \"The Sunday nearest 1 February\" is the Sunday within three days either side of it:",[30,72,76],{"className":73,"code":74,"language":75,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ndef fiscal_year_start(year):\n    feb1 = pd.Timestamp(year=year, month=2, day=1)\n    days_to_sunday = (6 - feb1.dayofweek) % 7          # dayofweek: Monday=0 … Sunday=6\n    offset = days_to_sunday if days_to_sunday \u003C= 3 else days_to_sunday - 7\n    return feb1 + pd.Timedelta(days=offset)\n\nstarts = {y: fiscal_year_start(y) for y in range(2023, 2028)}\nfor y in range(2023, 2027):\n    length = (starts[y + 1] - starts[y]).days \u002F\u002F 7\n    print(f\"FY{y}: {starts[y]:%a %d %b %Y} → {starts[y + 1] - pd.Timedelta(days=1):%a %d %b %Y}  ({length} weeks)\")\n","python",[37,77,78,94,101,114,158,188,221,244,249,286,309,338],{"__ignoreMap":35},[40,79,80,84,88,91],{"class":42,"line":43},[40,81,83],{"class":82},"s-kum","import",[40,85,87],{"class":86},"skGVy"," pandas ",[40,89,90],{"class":82},"as",[40,92,93],{"class":86}," pd\n",[40,95,97],{"class":42,"line":96},2,[40,98,100],{"emptyLinePlaceholder":99},true,"\n",[40,102,104,107,111],{"class":42,"line":103},3,[40,105,106],{"class":82},"def",[40,108,110],{"class":109},"s_Opv"," fiscal_year_start",[40,112,113],{"class":86},"(year):\n",[40,115,117,120,123,126,130,132,135,138,140,144,147,150,152,155],{"class":42,"line":116},4,[40,118,119],{"class":86},"    feb1 ",[40,121,122],{"class":82},"=",[40,124,125],{"class":86}," pd.Timestamp(",[40,127,129],{"class":128},"sa561","year",[40,131,122],{"class":82},[40,133,134],{"class":86},"year, ",[40,136,137],{"class":128},"month",[40,139,122],{"class":82},[40,141,143],{"class":142},"sP0c6","2",[40,145,146],{"class":86},", ",[40,148,149],{"class":128},"day",[40,151,122],{"class":82},[40,153,154],{"class":142},"1",[40,156,157],{"class":86},")\n",[40,159,161,164,166,169,172,175,178,181,184],{"class":42,"line":160},5,[40,162,163],{"class":86},"    days_to_sunday ",[40,165,122],{"class":82},[40,167,168],{"class":86}," (",[40,170,171],{"class":142},"6",[40,173,174],{"class":82}," -",[40,176,177],{"class":86}," feb1.dayofweek) ",[40,179,180],{"class":82},"%",[40,182,183],{"class":142}," 7",[40,185,187],{"class":186},"s-wDw","          # dayofweek: Monday=0 … Sunday=6\n",[40,189,191,194,196,199,202,204,207,210,213,215,218],{"class":42,"line":190},6,[40,192,193],{"class":86},"    offset ",[40,195,122],{"class":82},[40,197,198],{"class":86}," days_to_sunday ",[40,200,201],{"class":82},"if",[40,203,198],{"class":86},[40,205,206],{"class":82},"\u003C=",[40,208,209],{"class":142}," 3",[40,211,212],{"class":82}," else",[40,214,198],{"class":86},[40,216,217],{"class":82},"-",[40,219,220],{"class":142}," 7\n",[40,222,224,227,230,233,236,239,241],{"class":42,"line":223},7,[40,225,226],{"class":82},"    return",[40,228,229],{"class":86}," feb1 ",[40,231,232],{"class":82},"+",[40,234,235],{"class":86}," pd.Timedelta(",[40,237,238],{"class":128},"days",[40,240,122],{"class":82},[40,242,243],{"class":86},"offset)\n",[40,245,247],{"class":42,"line":246},8,[40,248,100],{"emptyLinePlaceholder":99},[40,250,252,255,257,260,263,266,269,272,275,278,280,283],{"class":42,"line":251},9,[40,253,254],{"class":86},"starts ",[40,256,122],{"class":82},[40,258,259],{"class":86}," {y: fiscal_year_start(y) ",[40,261,262],{"class":82},"for",[40,264,265],{"class":86}," y ",[40,267,268],{"class":82},"in",[40,270,271],{"class":142}," range",[40,273,274],{"class":86},"(",[40,276,277],{"class":142},"2023",[40,279,146],{"class":86},[40,281,282],{"class":142},"2028",[40,284,285],{"class":86},")}\n",[40,287,289,291,293,295,297,299,301,303,306],{"class":42,"line":288},10,[40,290,262],{"class":82},[40,292,265],{"class":86},[40,294,268],{"class":82},[40,296,271],{"class":142},[40,298,274],{"class":86},[40,300,277],{"class":142},[40,302,146],{"class":86},[40,304,305],{"class":142},"2027",[40,307,308],{"class":86},"):\n",[40,310,312,315,317,320,322,325,328,330,333,336],{"class":42,"line":311},11,[40,313,314],{"class":86},"    length ",[40,316,122],{"class":82},[40,318,319],{"class":86}," (starts[y ",[40,321,232],{"class":82},[40,323,324],{"class":142}," 1",[40,326,327],{"class":86},"] ",[40,329,217],{"class":82},[40,331,332],{"class":86}," starts[y]).days ",[40,334,335],{"class":82},"\u002F\u002F",[40,337,220],{"class":142},[40,339,341,344,346,349,352,356,359,362,365,367,370,373,375,378,380,383,385,388,390,393,395,398,400,403,405,407,409,411,413,415,417,419,422,424,426,428,430,432,434,436,438,440,442,445,447,450,452,455],{"class":42,"line":340},12,[40,342,343],{"class":142},"    print",[40,345,274],{"class":86},[40,347,348],{"class":82},"f",[40,350,351],{"class":50},"\"FY",[40,353,355],{"class":354},"sSjpA","{",[40,357,358],{"class":86},"y",[40,360,361],{"class":354},"}",[40,363,364],{"class":50},": ",[40,366,355],{"class":354},[40,368,369],{"class":86},"starts[y]",[40,371,372],{"class":82},":",[40,374,180],{"class":86},[40,376,377],{"class":82},"a ",[40,379,180],{"class":86},[40,381,382],{"class":82},"d ",[40,384,180],{"class":86},[40,386,387],{"class":82},"b ",[40,389,180],{"class":86},[40,391,392],{"class":82},"Y",[40,394,361],{"class":354},[40,396,397],{"class":50}," → ",[40,399,355],{"class":354},[40,401,402],{"class":86},"starts[y ",[40,404,232],{"class":82},[40,406,324],{"class":142},[40,408,327],{"class":86},[40,410,217],{"class":82},[40,412,235],{"class":86},[40,414,238],{"class":128},[40,416,122],{"class":82},[40,418,154],{"class":142},[40,420,421],{"class":86},")",[40,423,372],{"class":82},[40,425,180],{"class":86},[40,427,377],{"class":82},[40,429,180],{"class":86},[40,431,382],{"class":82},[40,433,180],{"class":86},[40,435,387],{"class":82},[40,437,180],{"class":86},[40,439,392],{"class":82},[40,441,361],{"class":354},[40,443,444],{"class":50},"  (",[40,446,355],{"class":354},[40,448,449],{"class":86},"length",[40,451,361],{"class":354},[40,453,454],{"class":50}," weeks)\"",[40,456,157],{"class":86},[10,458,459],{},"FY2023 starts on Sunday 29 January 2023 and runs to Saturday 3 February 2024 — 53 weeks, because the next start, the Sunday nearest 1 February 2024, falls on 4 February. FY2024 and FY2025 have 52 weeks each. The 53-week years are not an error but the price of every year starting on a Sunday: 52 weeks are 364 days, so the start drifts a day earlier each year (two after a leap day) until a 53rd week pulls it back. Other businesses anchor on a different weekday or date — the Saturday nearest 31 December, the last Saturday of September — and only this function changes.",[461,462,470,471,470,475,470,479,470,487,470,494,470,504,470,511,470,516,470,520,470,524,470,530,470,536,470,540,470,542,470,544,470,547,470,551,470,554,470,556,470,558,470,561,470,565,470,568,470,570],"svg",{"viewBox":463,"role":464,"ariaLabelledBy":465,"xmlns":468,"style":469},"4 0 752 211","img",[466,467],"f445-years-t","f445-years-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[472,473,474],"title",{"id":466},"Fiscal years that start on the Sunday nearest 1 February",[476,477,478],"desc",{"id":467},"FY2023 starts on 29 January 2023 and has 53 weeks, ending on 3 February 2024. FY2024 starts on 4 February 2024 and FY2025 on 2 February 2025, each with 52 weeks. The start date drifts and a 53rd week every few years brings it back towards 1 February.",[480,481],"rect",{"x":482,"y":483,"width":484,"height":485,"fill":486},"4","0","752","211","#ffffff",[42,488],{"x1":489,"y1":490,"x2":491,"y2":490,"stroke":492,"style":493},"94.0","105.5","666.0","var(--muted,#5b6780)","stroke-width:2.5px",[480,495],{"x":496,"y":497,"width":498,"height":499,"rx":500,"fill":501,"stroke":502,"style":503},"20.0","16.0","172.0","57.5","12","#fdefd8","var(--gold,#b4740a)","stroke-width:2px",[505,506,510],"text",{"x":507,"y":508,"style":509},"106.0","40.5","font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","29 Jan 2023",[505,512,515],{"x":507,"y":513,"style":514},"59.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","FY2023 · 53 weeks",[42,517],{"x1":507,"y1":518,"x2":507,"y2":490,"stroke":492,"style":519},"73.5","stroke-width:1.5px",[521,522],"circle",{"cx":507,"cy":490,"r":523,"fill":502,"stroke":486,"style":503},"7.0",[480,525],{"x":526,"y":527,"width":498,"height":499,"rx":500,"fill":528,"stroke":529,"style":503},"202.7","137.5","#f0f4ff","var(--brand,#5b5cf0)",[505,531,535],{"x":532,"y":533,"style":534},"288.7","162.0","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","4 Feb 2024",[505,537,539],{"x":532,"y":538,"style":514},"180.5","FY2024 · 52 weeks",[42,541],{"x1":532,"y1":490,"x2":532,"y2":527,"stroke":492,"style":519},[521,543],{"cx":532,"cy":490,"r":523,"fill":529,"stroke":486,"style":503},[480,545],{"x":546,"y":497,"width":498,"height":499,"rx":500,"fill":528,"stroke":529,"style":503},"385.3",[505,548,550],{"x":549,"y":508,"style":534},"471.3","2 Feb 2025",[505,552,553],{"x":549,"y":513,"style":514},"FY2025 · 52 weeks",[42,555],{"x1":549,"y1":518,"x2":549,"y2":490,"stroke":492,"style":519},[521,557],{"cx":549,"cy":490,"r":523,"fill":529,"stroke":486,"style":503},[480,559],{"x":560,"y":527,"width":498,"height":499,"rx":500,"fill":528,"stroke":529,"style":503},"568.0",[505,562,564],{"x":563,"y":533,"style":534},"654.0","1 Feb 2026",[505,566,567],{"x":563,"y":538,"style":514},"FY2026 · 52 weeks",[42,569],{"x1":563,"y1":490,"x2":563,"y2":527,"stroke":492,"style":519},[521,571],{"cx":563,"cy":490,"r":523,"fill":529,"stroke":486,"style":503},[25,573,575],{"id":574},"step-2-periods-of-4-4-and-5-weeks","Step 2 — Periods of 4, 4 and 5 weeks",[10,577,578],{},"Within a year, weeks are numbered from 1, and the 4-4-5 pattern assigns them to periods: weeks 1–4 are period 1, 5–8 period 2, 9–13 period 3, and so on through four quarters. A 53rd week is added to the last period:",[30,580,582],{"className":73,"code":581,"language":75,"meta":35,"style":35},"PATTERN = [4, 4, 5] * 4                                 # weeks per period, twelve periods\nperiod_of_week = {}\nw = 1\nfor p, n in enumerate(PATTERN, start=1):\n    for _ in range(n):\n        period_of_week[w] = p\n        w += 1\nperiod_of_week[53] = 12                                  # extra week joins the final period\n\nprint({p: sum(1 for wk, pp in period_of_week.items() if pp == p) for p in (1, 2, 3, 12)})\n",[37,583,584,617,627,637,664,679,689,699,717,721],{"__ignoreMap":35},[40,585,586,589,592,595,597,599,601,603,606,608,611,614],{"class":42,"line":43},[40,587,588],{"class":142},"PATTERN",[40,590,591],{"class":82}," =",[40,593,594],{"class":86}," [",[40,596,482],{"class":142},[40,598,146],{"class":86},[40,600,482],{"class":142},[40,602,146],{"class":86},[40,604,605],{"class":142},"5",[40,607,327],{"class":86},[40,609,610],{"class":82},"*",[40,612,613],{"class":142}," 4",[40,615,616],{"class":186},"                                 # weeks per period, twelve periods\n",[40,618,619,622,624],{"class":42,"line":96},[40,620,621],{"class":86},"period_of_week ",[40,623,122],{"class":82},[40,625,626],{"class":86}," {}\n",[40,628,629,632,634],{"class":42,"line":103},[40,630,631],{"class":86},"w ",[40,633,122],{"class":82},[40,635,636],{"class":142}," 1\n",[40,638,639,641,644,646,649,651,653,655,658,660,662],{"class":42,"line":116},[40,640,262],{"class":82},[40,642,643],{"class":86}," p, n ",[40,645,268],{"class":82},[40,647,648],{"class":142}," enumerate",[40,650,274],{"class":86},[40,652,588],{"class":142},[40,654,146],{"class":86},[40,656,657],{"class":128},"start",[40,659,122],{"class":82},[40,661,154],{"class":142},[40,663,308],{"class":86},[40,665,666,669,672,674,676],{"class":42,"line":160},[40,667,668],{"class":82},"    for",[40,670,671],{"class":86}," _ ",[40,673,268],{"class":82},[40,675,271],{"class":142},[40,677,678],{"class":86},"(n):\n",[40,680,681,684,686],{"class":42,"line":190},[40,682,683],{"class":86},"        period_of_week[w] ",[40,685,122],{"class":82},[40,687,688],{"class":86}," p\n",[40,690,691,694,697],{"class":42,"line":223},[40,692,693],{"class":86},"        w ",[40,695,696],{"class":82},"+=",[40,698,636],{"class":142},[40,700,701,704,707,709,711,714],{"class":42,"line":246},[40,702,703],{"class":86},"period_of_week[",[40,705,706],{"class":142},"53",[40,708,327],{"class":86},[40,710,122],{"class":82},[40,712,713],{"class":142}," 12",[40,715,716],{"class":186},"                                  # extra week joins the final period\n",[40,718,719],{"class":42,"line":251},[40,720,100],{"emptyLinePlaceholder":99},[40,722,723,726,729,732,734,736,739,742,744,747,749,752,755,758,760,763,765,767,769,771,773,775,778,780,782],{"class":42,"line":288},[40,724,725],{"class":142},"print",[40,727,728],{"class":86},"({p: ",[40,730,731],{"class":142},"sum",[40,733,274],{"class":86},[40,735,154],{"class":142},[40,737,738],{"class":82}," for",[40,740,741],{"class":86}," wk, pp ",[40,743,268],{"class":82},[40,745,746],{"class":86}," period_of_week.items() ",[40,748,201],{"class":82},[40,750,751],{"class":86}," pp ",[40,753,754],{"class":82},"==",[40,756,757],{"class":86}," p) ",[40,759,262],{"class":82},[40,761,762],{"class":86}," p ",[40,764,268],{"class":82},[40,766,168],{"class":86},[40,768,154],{"class":142},[40,770,146],{"class":86},[40,772,143],{"class":142},[40,774,146],{"class":86},[40,776,777],{"class":142},"3",[40,779,146],{"class":86},[40,781,500],{"class":142},[40,783,784],{"class":86},")})\n",[10,786,787,788,790],{},"Periods 1 and 2 have four weeks, period 3 five, and the pattern repeats each quarter, so every quarter has exactly 13 weeks. In a 53-week year, period 12 gets six weeks — the most common convention, though some businesses add the extra week to the first period of the next year or keep it as a separate \"week 53\" outside the periods. Whichever is chosen, write it down: it decides how the 53-week year compares with the years either side. A 4-5-4 or 5-4-4 pattern is just a different ",[37,789,588],{}," list.",[25,792,794],{"id":793},"step-3-the-date-table","Step 3 — The date table",[10,796,797],{},"With the year starts and the week-to-period map, every date gets its fiscal labels:",[30,799,801],{"className":73,"code":800,"language":75,"meta":35,"style":35},"def fiscal_labels(d):\n    year = d.year if d >= starts[d.year] else d.year - 1\n    week = (d - starts[year]).days \u002F\u002F 7 + 1\n    period = period_of_week[week]\n    return pd.Series({\"Fiscal year\": year, \"Fiscal week\": week, \"Fiscal period\": period,\n                      \"Fiscal quarter\": (period - 1) \u002F\u002F 3 + 1, \"Weekday\": d.strftime(\"%a\")})\n\ndates = pd.DataFrame({\"Date\": pd.date_range(\"2023-01-29\", \"2026-09-30\", freq=\"D\")})\ndates = dates.join(dates[\"Date\"].apply(fiscal_labels))\ndates[\"Period start\"] = dates.groupby([\"Fiscal year\", \"Fiscal period\"])[\"Date\"].transform(\"min\")\nprint(dates.iloc[[0, 27, 28, 62, 63]].to_string(index=False))\nprint(dates.groupby(\"Fiscal year\")[\"Fiscal week\"].max().to_dict())\n",[37,802,803,813,843,867,877,902,943,947,983,997,1031,1074],{"__ignoreMap":35},[40,804,805,807,810],{"class":42,"line":43},[40,806,106],{"class":82},[40,808,809],{"class":109}," fiscal_labels",[40,811,812],{"class":86},"(d):\n",[40,814,815,818,820,823,825,828,831,834,837,839,841],{"class":42,"line":96},[40,816,817],{"class":86},"    year ",[40,819,122],{"class":82},[40,821,822],{"class":86}," d.year ",[40,824,201],{"class":82},[40,826,827],{"class":86}," d ",[40,829,830],{"class":82},">=",[40,832,833],{"class":86}," starts[d.year] ",[40,835,836],{"class":82},"else",[40,838,822],{"class":86},[40,840,217],{"class":82},[40,842,636],{"class":142},[40,844,845,848,850,853,855,858,860,862,865],{"class":42,"line":103},[40,846,847],{"class":86},"    week ",[40,849,122],{"class":82},[40,851,852],{"class":86}," (d ",[40,854,217],{"class":82},[40,856,857],{"class":86}," starts[year]).days ",[40,859,335],{"class":82},[40,861,183],{"class":142},[40,863,864],{"class":82}," +",[40,866,636],{"class":142},[40,868,869,872,874],{"class":42,"line":116},[40,870,871],{"class":86},"    period ",[40,873,122],{"class":82},[40,875,876],{"class":86}," period_of_week[week]\n",[40,878,879,881,884,887,890,893,896,899],{"class":42,"line":160},[40,880,226],{"class":82},[40,882,883],{"class":86}," pd.Series({",[40,885,886],{"class":50},"\"Fiscal year\"",[40,888,889],{"class":86},": year, ",[40,891,892],{"class":50},"\"Fiscal week\"",[40,894,895],{"class":86},": week, ",[40,897,898],{"class":50},"\"Fiscal period\"",[40,900,901],{"class":86},": period,\n",[40,903,904,907,910,912,914,917,919,921,923,925,927,930,933,936,939,941],{"class":42,"line":190},[40,905,906],{"class":50},"                      \"Fiscal quarter\"",[40,908,909],{"class":86},": (period ",[40,911,217],{"class":82},[40,913,324],{"class":142},[40,915,916],{"class":86},") ",[40,918,335],{"class":82},[40,920,209],{"class":142},[40,922,864],{"class":82},[40,924,324],{"class":142},[40,926,146],{"class":86},[40,928,929],{"class":50},"\"Weekday\"",[40,931,932],{"class":86},": d.strftime(",[40,934,935],{"class":50},"\"",[40,937,938],{"class":354},"%a",[40,940,935],{"class":50},[40,942,784],{"class":86},[40,944,945],{"class":42,"line":223},[40,946,100],{"emptyLinePlaceholder":99},[40,948,949,952,954,957,960,963,966,968,971,973,976,978,981],{"class":42,"line":246},[40,950,951],{"class":86},"dates ",[40,953,122],{"class":82},[40,955,956],{"class":86}," pd.DataFrame({",[40,958,959],{"class":50},"\"Date\"",[40,961,962],{"class":86},": pd.date_range(",[40,964,965],{"class":50},"\"2023-01-29\"",[40,967,146],{"class":86},[40,969,970],{"class":50},"\"2026-09-30\"",[40,972,146],{"class":86},[40,974,975],{"class":128},"freq",[40,977,122],{"class":82},[40,979,980],{"class":50},"\"D\"",[40,982,784],{"class":86},[40,984,985,987,989,992,994],{"class":42,"line":251},[40,986,951],{"class":86},[40,988,122],{"class":82},[40,990,991],{"class":86}," dates.join(dates[",[40,993,959],{"class":50},[40,995,996],{"class":86},"].apply(fiscal_labels))\n",[40,998,999,1002,1005,1007,1009,1012,1014,1016,1018,1021,1023,1026,1029],{"class":42,"line":288},[40,1000,1001],{"class":86},"dates[",[40,1003,1004],{"class":50},"\"Period start\"",[40,1006,327],{"class":86},[40,1008,122],{"class":82},[40,1010,1011],{"class":86}," dates.groupby([",[40,1013,886],{"class":50},[40,1015,146],{"class":86},[40,1017,898],{"class":50},[40,1019,1020],{"class":86},"])[",[40,1022,959],{"class":50},[40,1024,1025],{"class":86},"].transform(",[40,1027,1028],{"class":50},"\"min\"",[40,1030,157],{"class":86},[40,1032,1033,1035,1038,1040,1042,1045,1047,1050,1052,1055,1057,1060,1063,1066,1068,1071],{"class":42,"line":311},[40,1034,725],{"class":142},[40,1036,1037],{"class":86},"(dates.iloc[[",[40,1039,483],{"class":142},[40,1041,146],{"class":86},[40,1043,1044],{"class":142},"27",[40,1046,146],{"class":86},[40,1048,1049],{"class":142},"28",[40,1051,146],{"class":86},[40,1053,1054],{"class":142},"62",[40,1056,146],{"class":86},[40,1058,1059],{"class":142},"63",[40,1061,1062],{"class":86},"]].to_string(",[40,1064,1065],{"class":128},"index",[40,1067,122],{"class":82},[40,1069,1070],{"class":142},"False",[40,1072,1073],{"class":86},"))\n",[40,1075,1076,1078,1081,1083,1086,1088],{"class":42,"line":340},[40,1077,725],{"class":142},[40,1079,1080],{"class":86},"(dates.groupby(",[40,1082,886],{"class":50},[40,1084,1085],{"class":86},")[",[40,1087,892],{"class":50},[40,1089,1090],{"class":86},"].max().to_dict())\n",[10,1092,1093,1094,23],{},"Each date's fiscal year is the one whose start it has passed; its week is the number of whole weeks since that start, plus one; period and quarter follow from the week. The sample rows show 29 January 2023 as week 1 of period 1, the move to period 2 four weeks later, and the move to period 3 after eight weeks. Every period starts on a Sunday, and the maximum week per year confirms the 53-week FY2023. A table like this is usually built once and reused everywhere — joined to sales, used in pivots, written to a \"Calendar\" sheet for Excel users — exactly like the ordinary date table in ",[19,1095,1097],{"href":1096},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fbuild-a-date-table-for-excel-reports-with-pandas\u002F","Build a Date Table for Excel Reports with pandas",[461,1099,470,1105,470,1108,470,1111,470,1116,470,1124,470,1128,470,1134,470,1138,470,1142,470,1146,470,1150,470,1154,470,1158,470,1162,470,1166,470,1170,470,1173,470,1175,470,1179,470,1181,470,1184,470,1186,470,1189,470,1191,470,1194,470,1196,470,1199,470,1202,470,1205,470,1207,470,1211,470,1213,470,1215,470,1217,470,1219,470,1221,470,1223,470,1225,470,1227,470,1230,470,1233,470,1235,470,1238,470,1240,470,1242,470,1244,470,1246,470,1248,470,1250,470,1252,470,1254,470,1257,470,1260,470,1262,470,1265,470,1267,470,1269,470,1271,470,1273,470,1277,470,1280,470,1282,470,1284,470,1287,470,1290,470,1292,470,1295,470,1297,470,1299,470,1301,470,1303,470,1305,470,1307,470,1309],{"viewBox":1100,"role":464,"ariaLabelledBy":1101,"xmlns":468,"style":1104},"173 0 413 196",[1102,1103],"f445-table-t","f445-table-d","width:100%;max-width:413px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[472,1106,1107],{"id":1102},"Fiscal labels for every date",[476,1109,1110],{"id":1103},"Each date in the date table carries its fiscal year, quarter, period and week. 29 January 2023 is week 1 of period 1. 26 February 2023 starts period 2 in week 5, and 26 March 2023 starts the five-week period 3 in week 9.",[480,1112],{"x":1113,"y":483,"width":1114,"height":1115,"fill":486},"173","413","196",[480,1117],{"x":1118,"y":497,"width":1119,"height":1120,"rx":483,"fill":1121,"stroke":1122,"style":1123},"189.9","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[480,1125],{"x":1126,"y":497,"width":1127,"height":1120,"rx":483,"fill":1121,"stroke":1122,"style":1123},"221.9","116.5",[505,1129,1133],{"x":1130,"y":1131,"style":1132},"280.1","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[480,1135],{"x":1136,"y":497,"width":1137,"height":1120,"rx":483,"fill":1121,"stroke":1122,"style":1123},"338.4","52.4",[505,1139,1141],{"x":1140,"y":1131,"style":1132},"364.6","B",[480,1143],{"x":1144,"y":497,"width":1145,"height":1120,"rx":483,"fill":1121,"stroke":1122,"style":1123},"390.8","65.4",[505,1147,1149],{"x":1148,"y":1131,"style":1132},"423.5","C",[480,1151],{"x":1152,"y":497,"width":1153,"height":1120,"rx":483,"fill":1121,"stroke":1122,"style":1123},"456.3","58.9",[505,1155,1157],{"x":1156,"y":1131,"style":1132},"485.7","D",[480,1159],{"x":1160,"y":497,"width":1161,"height":1120,"rx":483,"fill":1121,"stroke":1122,"style":1123},"515.1","55.0",[505,1163,1165],{"x":1164,"y":1131,"style":1132},"542.6","E",[480,1167],{"x":1118,"y":1168,"width":1119,"height":1169,"rx":483,"fill":1121,"stroke":1122,"style":1123},"40.0","28.0",[505,1171,154],{"x":1172,"y":513,"style":1132},"205.9",[480,1174],{"x":1126,"y":1168,"width":1127,"height":1169,"rx":483,"fill":528,"stroke":529,"style":519},[505,1176,1178],{"x":1130,"y":513,"style":1177},"font-size:10.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Date",[480,1180],{"x":1136,"y":1168,"width":1137,"height":1169,"rx":483,"fill":528,"stroke":529,"style":519},[505,1182,1183],{"x":1140,"y":513,"style":1177},"FY",[480,1185],{"x":1144,"y":1168,"width":1145,"height":1169,"rx":483,"fill":528,"stroke":529,"style":519},[505,1187,1188],{"x":1148,"y":513,"style":1177},"Quarter",[480,1190],{"x":1152,"y":1168,"width":1153,"height":1169,"rx":483,"fill":528,"stroke":529,"style":519},[505,1192,1193],{"x":1156,"y":513,"style":1177},"Period",[480,1195],{"x":1160,"y":1168,"width":1161,"height":1169,"rx":483,"fill":528,"stroke":529,"style":519},[505,1197,1198],{"x":1164,"y":513,"style":1177},"Week",[480,1200],{"x":1118,"y":1201,"width":1119,"height":1169,"rx":483,"fill":1121,"stroke":1122,"style":1123},"68.0",[505,1203,143],{"x":1172,"y":1204,"style":1132},"87.0",[480,1206],{"x":1126,"y":1201,"width":1127,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1208,1210],{"x":1130,"y":1204,"style":1209},"font-size:10.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Sun 29 Jan 2023",[480,1212],{"x":1136,"y":1201,"width":1137,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1214,277],{"x":1140,"y":1204,"style":1209},[480,1216],{"x":1144,"y":1201,"width":1145,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1218,154],{"x":1148,"y":1204,"style":1209},[480,1220],{"x":1152,"y":1201,"width":1153,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1222,154],{"x":1156,"y":1204,"style":1209},[480,1224],{"x":1160,"y":1201,"width":1161,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1226,154],{"x":1164,"y":1204,"style":1209},[480,1228],{"x":1118,"y":1229,"width":1119,"height":1169,"rx":483,"fill":1121,"stroke":1122,"style":1123},"96.0",[505,1231,777],{"x":1172,"y":1232,"style":1132},"115.0",[480,1234],{"x":1126,"y":1229,"width":1127,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1236,1237],{"x":1130,"y":1232,"style":1209},"Sat 25 Feb 2023",[480,1239],{"x":1136,"y":1229,"width":1137,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1241,277],{"x":1140,"y":1232,"style":1209},[480,1243],{"x":1144,"y":1229,"width":1145,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1245,154],{"x":1148,"y":1232,"style":1209},[480,1247],{"x":1152,"y":1229,"width":1153,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1249,154],{"x":1156,"y":1232,"style":1209},[480,1251],{"x":1160,"y":1229,"width":1161,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1253,482],{"x":1164,"y":1232,"style":1209},[480,1255],{"x":1118,"y":1256,"width":1119,"height":1169,"rx":483,"fill":1121,"stroke":1122,"style":1123},"124.0",[505,1258,482],{"x":1172,"y":1259,"style":1132},"143.0",[480,1261],{"x":1126,"y":1256,"width":1127,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1263,1264],{"x":1130,"y":1259,"style":1209},"Sun 26 Feb 2023",[480,1266],{"x":1136,"y":1256,"width":1137,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1268,277],{"x":1140,"y":1259,"style":1209},[480,1270],{"x":1144,"y":1256,"width":1145,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1272,154],{"x":1148,"y":1259,"style":1209},[480,1274],{"x":1152,"y":1256,"width":1153,"height":1169,"rx":483,"fill":1275,"stroke":1276,"style":519},"#d9f4f1","var(--teal,#0f9488)",[505,1278,143],{"x":1156,"y":1259,"style":1279},"font-size:10.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[480,1281],{"x":1160,"y":1256,"width":1161,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1283,605],{"x":1164,"y":1259,"style":1209},[480,1285],{"x":1118,"y":1286,"width":1119,"height":1169,"rx":483,"fill":1121,"stroke":1122,"style":1123},"152.0",[505,1288,605],{"x":1172,"y":1289,"style":1132},"171.0",[480,1291],{"x":1126,"y":1286,"width":1127,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1293,1294],{"x":1130,"y":1289,"style":1209},"Sun 26 Mar 2023",[480,1296],{"x":1136,"y":1286,"width":1137,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1298,277],{"x":1140,"y":1289,"style":1209},[480,1300],{"x":1144,"y":1286,"width":1145,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1302,154],{"x":1148,"y":1289,"style":1209},[480,1304],{"x":1152,"y":1286,"width":1153,"height":1169,"rx":483,"fill":1275,"stroke":1276,"style":519},[505,1306,777],{"x":1156,"y":1289,"style":1279},[480,1308],{"x":1160,"y":1286,"width":1161,"height":1169,"rx":483,"fill":486,"stroke":1122,"style":1123},[505,1310,1311],{"x":1164,"y":1289,"style":1209},"9",[25,1313,1315],{"id":1314},"step-4-map-sales-and-compare-like-for-like","Step 4 — Map sales and compare like for like",[10,1317,1318],{},"Join the sales to the date table and compare each period with the same period last year:",[30,1320,1322],{"className":73,"code":1321,"language":75,"meta":35,"style":35},"sales = pd.read_excel(\"daily_sales.xlsx\").merge(dates, on=\"Date\", how=\"left\")\nby_period = sales.groupby([\"Fiscal year\", \"Fiscal period\"]).agg(Sales=(\"Sales\", \"sum\"), Days=(\"Date\", \"size\"),\n                                                               Saturdays=(\"Weekday\", lambda s: (s == \"Sat\").sum()))\ncmp = by_period.unstack(\"Fiscal year\")\nfy24, fy25 = cmp[\"Sales\"][2024], cmp[\"Sales\"][2025]\ntable = pd.DataFrame({\"FY2024\": fy24.round(0), \"FY2025\": fy25.round(0), \"Change %\": ((fy25 \u002F fy24 - 1) * 100).round(1),\n                      \"Saturdays 24\u002F25\": cmp[\"Saturdays\"][2024].astype(int).astype(str) + \"\u002F\" + cmp[\"Saturdays\"][2025].astype(int).astype(str)})\nprint(table.head(6).to_string())\n\ncal = sales.assign(Month=sales[\"Date\"].dt.to_period(\"M\")).groupby(\"Month\")[\"Weekday\"].apply(lambda s: (s == \"Sat\").sum())\nprint(\"Saturdays per calendar month in 2025:\", {str(k): int(v) for k, v in cal.loc[\"2025-01\":\"2025-06\"].items()})\n",[37,1323,1324,1359,1413,1440,1454,1492,1551,1610,1622,1626,1676],{"__ignoreMap":35},[40,1325,1326,1329,1331,1334,1337,1340,1343,1345,1347,1349,1352,1354,1357],{"class":42,"line":43},[40,1327,1328],{"class":86},"sales ",[40,1330,122],{"class":82},[40,1332,1333],{"class":86}," pd.read_excel(",[40,1335,1336],{"class":50},"\"daily_sales.xlsx\"",[40,1338,1339],{"class":86},").merge(dates, ",[40,1341,1342],{"class":128},"on",[40,1344,122],{"class":82},[40,1346,959],{"class":50},[40,1348,146],{"class":86},[40,1350,1351],{"class":128},"how",[40,1353,122],{"class":82},[40,1355,1356],{"class":50},"\"left\"",[40,1358,157],{"class":86},[40,1360,1361,1364,1366,1369,1371,1373,1375,1378,1381,1383,1385,1388,1390,1393,1396,1399,1401,1403,1405,1407,1410],{"class":42,"line":96},[40,1362,1363],{"class":86},"by_period ",[40,1365,122],{"class":82},[40,1367,1368],{"class":86}," sales.groupby([",[40,1370,886],{"class":50},[40,1372,146],{"class":86},[40,1374,898],{"class":50},[40,1376,1377],{"class":86},"]).agg(",[40,1379,1380],{"class":128},"Sales",[40,1382,122],{"class":82},[40,1384,274],{"class":86},[40,1386,1387],{"class":50},"\"Sales\"",[40,1389,146],{"class":86},[40,1391,1392],{"class":50},"\"sum\"",[40,1394,1395],{"class":86},"), ",[40,1397,1398],{"class":128},"Days",[40,1400,122],{"class":82},[40,1402,274],{"class":86},[40,1404,959],{"class":50},[40,1406,146],{"class":86},[40,1408,1409],{"class":50},"\"size\"",[40,1411,1412],{"class":86},"),\n",[40,1414,1415,1418,1420,1422,1424,1426,1429,1432,1434,1437],{"class":42,"line":103},[40,1416,1417],{"class":128},"                                                               Saturdays",[40,1419,122],{"class":82},[40,1421,274],{"class":86},[40,1423,929],{"class":50},[40,1425,146],{"class":86},[40,1427,1428],{"class":82},"lambda",[40,1430,1431],{"class":86}," s: (s ",[40,1433,754],{"class":82},[40,1435,1436],{"class":50}," \"Sat\"",[40,1438,1439],{"class":86},").sum()))\n",[40,1441,1442,1445,1447,1450,1452],{"class":42,"line":116},[40,1443,1444],{"class":128},"cmp",[40,1446,591],{"class":82},[40,1448,1449],{"class":86}," by_period.unstack(",[40,1451,886],{"class":50},[40,1453,157],{"class":86},[40,1455,1456,1459,1461,1464,1467,1469,1472,1475,1478,1480,1482,1484,1486,1489],{"class":42,"line":160},[40,1457,1458],{"class":86},"fy24, fy25 ",[40,1460,122],{"class":82},[40,1462,1463],{"class":128}," cmp",[40,1465,1466],{"class":86},"[",[40,1468,1387],{"class":50},[40,1470,1471],{"class":86},"][",[40,1473,1474],{"class":142},"2024",[40,1476,1477],{"class":86},"], ",[40,1479,1444],{"class":128},[40,1481,1466],{"class":86},[40,1483,1387],{"class":50},[40,1485,1471],{"class":86},[40,1487,1488],{"class":142},"2025",[40,1490,1491],{"class":86},"]\n",[40,1493,1494,1497,1499,1501,1504,1507,1509,1511,1514,1517,1519,1521,1524,1527,1530,1533,1535,1537,1539,1541,1544,1547,1549],{"class":42,"line":190},[40,1495,1496],{"class":86},"table ",[40,1498,122],{"class":82},[40,1500,956],{"class":86},[40,1502,1503],{"class":50},"\"FY2024\"",[40,1505,1506],{"class":86},": fy24.round(",[40,1508,483],{"class":142},[40,1510,1395],{"class":86},[40,1512,1513],{"class":50},"\"FY2025\"",[40,1515,1516],{"class":86},": fy25.round(",[40,1518,483],{"class":142},[40,1520,1395],{"class":86},[40,1522,1523],{"class":50},"\"Change %\"",[40,1525,1526],{"class":86},": ((fy25 ",[40,1528,1529],{"class":82},"\u002F",[40,1531,1532],{"class":86}," fy24 ",[40,1534,217],{"class":82},[40,1536,324],{"class":142},[40,1538,916],{"class":86},[40,1540,610],{"class":82},[40,1542,1543],{"class":142}," 100",[40,1545,1546],{"class":86},").round(",[40,1548,154],{"class":142},[40,1550,1412],{"class":86},[40,1552,1553,1556,1558,1560,1562,1565,1567,1569,1572,1575,1578,1581,1583,1585,1588,1590,1592,1594,1596,1598,1600,1602,1604,1606,1608],{"class":42,"line":223},[40,1554,1555],{"class":50},"                      \"Saturdays 24\u002F25\"",[40,1557,364],{"class":86},[40,1559,1444],{"class":128},[40,1561,1466],{"class":86},[40,1563,1564],{"class":50},"\"Saturdays\"",[40,1566,1471],{"class":86},[40,1568,1474],{"class":142},[40,1570,1571],{"class":86},"].astype(",[40,1573,1574],{"class":142},"int",[40,1576,1577],{"class":86},").astype(",[40,1579,1580],{"class":142},"str",[40,1582,916],{"class":86},[40,1584,232],{"class":82},[40,1586,1587],{"class":50}," \"\u002F\"",[40,1589,864],{"class":82},[40,1591,1463],{"class":128},[40,1593,1466],{"class":86},[40,1595,1564],{"class":50},[40,1597,1471],{"class":86},[40,1599,1488],{"class":142},[40,1601,1571],{"class":86},[40,1603,1574],{"class":142},[40,1605,1577],{"class":86},[40,1607,1580],{"class":142},[40,1609,784],{"class":86},[40,1611,1612,1614,1617,1619],{"class":42,"line":246},[40,1613,725],{"class":142},[40,1615,1616],{"class":86},"(table.head(",[40,1618,171],{"class":142},[40,1620,1621],{"class":86},").to_string())\n",[40,1623,1624],{"class":42,"line":251},[40,1625,100],{"emptyLinePlaceholder":99},[40,1627,1628,1631,1633,1636,1639,1641,1644,1646,1649,1652,1655,1658,1660,1662,1665,1667,1669,1671,1673],{"class":42,"line":288},[40,1629,1630],{"class":86},"cal ",[40,1632,122],{"class":82},[40,1634,1635],{"class":86}," sales.assign(",[40,1637,1638],{"class":128},"Month",[40,1640,122],{"class":82},[40,1642,1643],{"class":86},"sales[",[40,1645,959],{"class":50},[40,1647,1648],{"class":86},"].dt.to_period(",[40,1650,1651],{"class":50},"\"M\"",[40,1653,1654],{"class":86},")).groupby(",[40,1656,1657],{"class":50},"\"Month\"",[40,1659,1085],{"class":86},[40,1661,929],{"class":50},[40,1663,1664],{"class":86},"].apply(",[40,1666,1428],{"class":82},[40,1668,1431],{"class":86},[40,1670,754],{"class":82},[40,1672,1436],{"class":50},[40,1674,1675],{"class":86},").sum())\n",[40,1677,1678,1680,1682,1685,1688,1690,1693,1695,1698,1700,1703,1705,1708,1711,1713,1716],{"class":42,"line":311},[40,1679,725],{"class":142},[40,1681,274],{"class":86},[40,1683,1684],{"class":50},"\"Saturdays per calendar month in 2025:\"",[40,1686,1687],{"class":86},", {",[40,1689,1580],{"class":142},[40,1691,1692],{"class":86},"(k): ",[40,1694,1574],{"class":142},[40,1696,1697],{"class":86},"(v) ",[40,1699,262],{"class":82},[40,1701,1702],{"class":86}," k, v ",[40,1704,268],{"class":82},[40,1706,1707],{"class":86}," cal.loc[",[40,1709,1710],{"class":50},"\"2025-01\"",[40,1712,372],{"class":86},[40,1714,1715],{"class":50},"\"2025-06\"",[40,1717,1718],{"class":86},"].items()})\n",[10,1720,1721],{},"Every fiscal period compares with a period of exactly the same length and the same number of Saturdays — four in each four-week period, five in each five-week period, both years. Calendar months cannot do that: in 2025, some months have four Saturdays and others five, so a busy-weekend business sees month-on-month swings of several percent from the calendar alone. With the 4-4-5 table, the remaining differences are about trading. The one exception is the 53-week year: its final period has an extra week, so compare it on average weekly sales, or exclude the extra week, rather than on the raw total.",[461,1723,470,1728,470,1731,470,1734,470,1737,470,1742,470,1753,470,1759,470,1764,470,1769,470,1773,470,1777,470,1780,470,1787,470,1792,470,1796,470,1799,470,1802],{"viewBox":1724,"role":464,"ariaLabelledBy":1725,"xmlns":468,"style":469},"4 0 752 207",[1726,1727],"f445-vs-t","f445-vs-d",[472,1729,1730],{"id":1726},"Calendar months versus 4-4-5 periods",[476,1732,1733],{"id":1727},"Calendar months have 28 to 31 days and four or five Saturdays, which changes from year to year, so comparisons mix in calendar effects. 4-4-5 periods have whole weeks, start on the same weekday and contain the same number of Saturdays as the same period last year.",[480,1735],{"x":482,"y":483,"width":484,"height":1736,"fill":486},"207",[480,1738],{"x":496,"y":497,"width":1739,"height":1740,"rx":1741,"fill":501,"stroke":502,"style":503},"351.0","175.0","14",[1743,1744,1746],"g",{"transform":1745},"translate(182.5,29.0) scale(1.083)",[1747,1748],"path",{"d":1749,"fill":1750,"stroke":1751,"style":1752},"M4 5h16v15H4z M4 10h16 M8 3v4 M16 3v4","none","var(--gold-ink,#7a4e06)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[505,1754,1758],{"x":1755,"y":1756,"style":1757},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","calendar months",[42,1760],{"x1":1761,"y1":1762,"x2":1763,"y2":1762,"stroke":502,"style":1123},"36.0","91.0","355.0",[505,1765,1768],{"x":1755,"y":1766,"style":1767},"111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","28–31 days",[505,1770,1772],{"x":1755,"y":1771,"style":1767},"137.0","4 or 5 Saturdays, varies by year",[505,1774,1776],{"x":1755,"y":1775,"style":1767},"163.0","calendar effects in every comparison",[480,1778],{"x":1779,"y":497,"width":1739,"height":1740,"rx":1741,"fill":1275,"stroke":1276,"style":503},"389.0",[1743,1781,1783],{"transform":1782},"translate(551.5,29.0) scale(1.083)",[1747,1784],{"d":1785,"fill":1750,"stroke":1786,"style":1752},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[505,1788,1791],{"x":1789,"y":1756,"style":1790},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","4-4-5 periods",[42,1793],{"x1":1794,"y1":1762,"x2":1795,"y2":1762,"stroke":1276,"style":1123},"405.0","724.0",[505,1797,1798],{"x":1789,"y":1766,"style":1767},"4 or 5 whole weeks",[505,1800,1801],{"x":1789,"y":1771,"style":1767},"always start on Sunday",[505,1803,1804],{"x":1789,"y":1775,"style":1767},"same Saturdays as last year",[25,1806,1808],{"id":1807},"step-5-write-the-calendar-for-excel-users","Step 5 — Write the calendar for Excel users",[10,1810,1811],{},"Write the date table as its own sheet, so Excel users can look up any date and build pivots on fiscal labels:",[30,1813,1815],{"className":73,"code":1814,"language":75,"meta":35,"style":35},"from openpyxl.styles import Font\n\nwith pd.ExcelWriter(\"fiscal_calendar.xlsx\", engine=\"openpyxl\") as xw:\n    dates.to_excel(xw, sheet_name=\"Calendar\", index=False)\n    periods = dates.groupby([\"Fiscal year\", \"Fiscal period\"]).agg(Start=(\"Date\", \"min\"), End=(\"Date\", \"max\"), Weeks=(\"Fiscal week\", \"nunique\")).reset_index()\n    periods.to_excel(xw, sheet_name=\"Periods\", index=False)\n    for ws in xw.book.worksheets:\n        for c in ws[1]:\n            c.font = Font(bold=True)\n        ws.freeze_panes = \"A2\"\n        for col in \"ABCDEFGH\":\n            ws.column_dimensions[col].width = 14\n    for name, ws in xw.sheets.items():\n        for row in ws.iter_rows(min_row=2):\n            for c in row:\n                if hasattr(c.value, \"year\"):\n                    c.number_format = \"ddd dd mmm yyyy\"\nprint(periods[periods[\"Fiscal year\"] == 2023].tail(2).to_string(index=False))\n",[37,1816,1817,1830,1834,1862,1885,1950,1972,1984,2002,2022,2032,2047,2057,2070,2092,2105,2122,2133],{"__ignoreMap":35},[40,1818,1819,1822,1825,1827],{"class":42,"line":43},[40,1820,1821],{"class":82},"from",[40,1823,1824],{"class":86}," openpyxl.styles ",[40,1826,83],{"class":82},[40,1828,1829],{"class":86}," Font\n",[40,1831,1832],{"class":42,"line":96},[40,1833,100],{"emptyLinePlaceholder":99},[40,1835,1836,1839,1842,1845,1847,1850,1852,1855,1857,1859],{"class":42,"line":103},[40,1837,1838],{"class":82},"with",[40,1840,1841],{"class":86}," pd.ExcelWriter(",[40,1843,1844],{"class":50},"\"fiscal_calendar.xlsx\"",[40,1846,146],{"class":86},[40,1848,1849],{"class":128},"engine",[40,1851,122],{"class":82},[40,1853,1854],{"class":50},"\"openpyxl\"",[40,1856,916],{"class":86},[40,1858,90],{"class":82},[40,1860,1861],{"class":86}," xw:\n",[40,1863,1864,1867,1870,1872,1875,1877,1879,1881,1883],{"class":42,"line":116},[40,1865,1866],{"class":86},"    dates.to_excel(xw, ",[40,1868,1869],{"class":128},"sheet_name",[40,1871,122],{"class":82},[40,1873,1874],{"class":50},"\"Calendar\"",[40,1876,146],{"class":86},[40,1878,1065],{"class":128},[40,1880,122],{"class":82},[40,1882,1070],{"class":142},[40,1884,157],{"class":86},[40,1886,1887,1890,1892,1894,1896,1898,1900,1902,1905,1907,1909,1911,1913,1915,1917,1920,1922,1924,1926,1928,1931,1933,1936,1938,1940,1942,1944,1947],{"class":42,"line":160},[40,1888,1889],{"class":86},"    periods ",[40,1891,122],{"class":82},[40,1893,1011],{"class":86},[40,1895,886],{"class":50},[40,1897,146],{"class":86},[40,1899,898],{"class":50},[40,1901,1377],{"class":86},[40,1903,1904],{"class":128},"Start",[40,1906,122],{"class":82},[40,1908,274],{"class":86},[40,1910,959],{"class":50},[40,1912,146],{"class":86},[40,1914,1028],{"class":50},[40,1916,1395],{"class":86},[40,1918,1919],{"class":128},"End",[40,1921,122],{"class":82},[40,1923,274],{"class":86},[40,1925,959],{"class":50},[40,1927,146],{"class":86},[40,1929,1930],{"class":50},"\"max\"",[40,1932,1395],{"class":86},[40,1934,1935],{"class":128},"Weeks",[40,1937,122],{"class":82},[40,1939,274],{"class":86},[40,1941,892],{"class":50},[40,1943,146],{"class":86},[40,1945,1946],{"class":50},"\"nunique\"",[40,1948,1949],{"class":86},")).reset_index()\n",[40,1951,1952,1955,1957,1959,1962,1964,1966,1968,1970],{"class":42,"line":190},[40,1953,1954],{"class":86},"    periods.to_excel(xw, ",[40,1956,1869],{"class":128},[40,1958,122],{"class":82},[40,1960,1961],{"class":50},"\"Periods\"",[40,1963,146],{"class":86},[40,1965,1065],{"class":128},[40,1967,122],{"class":82},[40,1969,1070],{"class":142},[40,1971,157],{"class":86},[40,1973,1974,1976,1979,1981],{"class":42,"line":223},[40,1975,668],{"class":82},[40,1977,1978],{"class":86}," ws ",[40,1980,268],{"class":82},[40,1982,1983],{"class":86}," xw.book.worksheets:\n",[40,1985,1986,1989,1992,1994,1997,1999],{"class":42,"line":246},[40,1987,1988],{"class":82},"        for",[40,1990,1991],{"class":86}," c ",[40,1993,268],{"class":82},[40,1995,1996],{"class":86}," ws[",[40,1998,154],{"class":142},[40,2000,2001],{"class":86},"]:\n",[40,2003,2004,2007,2009,2012,2015,2017,2020],{"class":42,"line":251},[40,2005,2006],{"class":86},"            c.font ",[40,2008,122],{"class":82},[40,2010,2011],{"class":86}," Font(",[40,2013,2014],{"class":128},"bold",[40,2016,122],{"class":82},[40,2018,2019],{"class":142},"True",[40,2021,157],{"class":86},[40,2023,2024,2027,2029],{"class":42,"line":288},[40,2025,2026],{"class":86},"        ws.freeze_panes ",[40,2028,122],{"class":82},[40,2030,2031],{"class":50}," \"A2\"\n",[40,2033,2034,2036,2039,2041,2044],{"class":42,"line":311},[40,2035,1988],{"class":82},[40,2037,2038],{"class":86}," col ",[40,2040,268],{"class":82},[40,2042,2043],{"class":50}," \"ABCDEFGH\"",[40,2045,2046],{"class":86},":\n",[40,2048,2049,2052,2054],{"class":42,"line":340},[40,2050,2051],{"class":86},"            ws.column_dimensions[col].width ",[40,2053,122],{"class":82},[40,2055,2056],{"class":142}," 14\n",[40,2058,2060,2062,2065,2067],{"class":42,"line":2059},13,[40,2061,668],{"class":82},[40,2063,2064],{"class":86}," name, ws ",[40,2066,268],{"class":82},[40,2068,2069],{"class":86}," xw.sheets.items():\n",[40,2071,2073,2075,2078,2080,2083,2086,2088,2090],{"class":42,"line":2072},14,[40,2074,1988],{"class":82},[40,2076,2077],{"class":86}," row ",[40,2079,268],{"class":82},[40,2081,2082],{"class":86}," ws.iter_rows(",[40,2084,2085],{"class":128},"min_row",[40,2087,122],{"class":82},[40,2089,143],{"class":142},[40,2091,308],{"class":86},[40,2093,2095,2098,2100,2102],{"class":42,"line":2094},15,[40,2096,2097],{"class":82},"            for",[40,2099,1991],{"class":86},[40,2101,268],{"class":82},[40,2103,2104],{"class":86}," row:\n",[40,2106,2108,2111,2114,2117,2120],{"class":42,"line":2107},16,[40,2109,2110],{"class":82},"                if",[40,2112,2113],{"class":142}," hasattr",[40,2115,2116],{"class":86},"(c.value, ",[40,2118,2119],{"class":50},"\"year\"",[40,2121,308],{"class":86},[40,2123,2125,2128,2130],{"class":42,"line":2124},17,[40,2126,2127],{"class":86},"                    c.number_format ",[40,2129,122],{"class":82},[40,2131,2132],{"class":50}," \"ddd dd mmm yyyy\"\n",[40,2134,2136,2138,2141,2143,2145,2147,2150,2153,2155,2158,2160,2162,2164],{"class":42,"line":2135},18,[40,2137,725],{"class":142},[40,2139,2140],{"class":86},"(periods[periods[",[40,2142,886],{"class":50},[40,2144,327],{"class":86},[40,2146,754],{"class":82},[40,2148,2149],{"class":142}," 2023",[40,2151,2152],{"class":86},"].tail(",[40,2154,143],{"class":142},[40,2156,2157],{"class":86},").to_string(",[40,2159,1065],{"class":128},[40,2161,122],{"class":82},[40,2163,1070],{"class":142},[40,2165,1073],{"class":86},[10,2167,2168],{},"The Calendar sheet has one row per date with its fiscal labels; the Periods sheet lists every period with its start and end date and number of weeks — the reference finance teams print and pin up. The last periods of FY2023 show period 12 with six weeks, the 53-week year's extra week. Dates are formatted with the weekday so the Sunday starts are visible at a glance.",[25,2170,2172],{"id":2171},"common-pitfalls","Common pitfalls",[2174,2175,2176,2184,2194,2200,2206],"ul",{},[2177,2178,2179,2183],"li",{},[2180,2181,2182],"strong",{},"Assuming every fiscal year has 52 weeks."," Some have 53; decide where the extra week goes.",[2177,2185,2186,2189,2190,2193],{},[2180,2187,2188],{},"Naming the year inconsistently."," Some businesses name a fiscal year after the year it ",[14,2191,2192],{},"ends","; agree a convention.",[2177,2195,2196,2199],{},[2180,2197,2198],{},"Comparing a 53-week year's totals directly."," Compare weekly averages or exclude the extra week.",[2177,2201,2202,2205],{},[2180,2203,2204],{},"Building fiscal labels inside every report."," Build one date table and join to it.",[2177,2207,2208,2211],{},[2180,2209,2210],{},"Hard-coding period dates."," Derive them from the year-start rule so future years are automatic.",[25,2213,2215],{"id":2214},"conclusion","Conclusion",[10,2217,2218],{},"A 4-4-5 fiscal calendar is a date table built from two rules: when each fiscal year starts — here the Sunday nearest 1 February — and how weeks group into periods — four, four and five per quarter, with a 53rd week added to the last period when the year needs one. Label every date with its fiscal year, quarter, period and week, join transactions to that table, and periods can be compared like for like, each with the same number of weeks and weekends as its counterpart. Write the calendar and a period list to Excel so everyone uses the same dates.",[25,2220,2222],{"id":2221},"frequently-asked-questions","Frequently asked questions",[10,2224,2225,2228],{},[2180,2226,2227],{},"What is a 4-4-5 calendar?","\nA fiscal calendar in which each quarter has three periods of four, four and five whole weeks, so every period starts on the same weekday and contains a fixed number of weeks.",[10,2230,2231,2234],{},[2180,2232,2233],{},"Why do some fiscal years have 53 weeks?","\n52 weeks are 364 days, so a year that always starts on the same weekday drifts by a day or two each year. Every five or six years a 53rd week is added to keep the start near its anchor date.",[10,2236,2237,2240],{},[2180,2238,2239],{},"How do I find the Sunday nearest 1 February in pandas?","\nCompute how many days separate 1 February from the next Sunday; if that is three or fewer, move forward, otherwise move back to the previous Sunday.",[10,2242,2243,2246],{},[2180,2244,2245],{},"How do I assign fiscal periods to dates in Python?","\nFind the fiscal year whose start the date has passed, compute the week number as whole weeks since that start plus one, and map the week to its period with the 4-4-5 pattern.",[25,2248,2250],{"id":2249},"related","Related",[2174,2252,2253,2259,2266,2273],{},[2177,2254,2255,2256,2258],{},"Up one level: ",[19,2257,22],{"href":21}," — dates in Excel data.",[2177,2260,2261,2265],{},[19,2262,2264],{"href":2263},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-data-by-fiscal-year-and-iso-week\u002F","Group Excel Data by Fiscal Year and ISO Week"," — simpler fiscal years.",[2177,2267,2268,2272],{},[19,2269,2271],{"href":2270},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcompare-two-periods-side-by-side-in-a-pivot\u002F","Compare Two Periods Side by Side in a Pivot"," — like-for-like comparisons.",[2177,2274,2275,2279],{},[19,2276,2278],{"href":2277},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-year-week-codes-to-dates-with-pandas\u002F","Convert Year-Week Codes to Dates with pandas"," — week codes back to dates.",[2281,2282,2283],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":35,"searchDepth":96,"depth":96,"links":2285},[2286,2287,2288,2289,2290,2291,2292,2293,2294,2295],{"id":27,"depth":96,"text":28},{"id":66,"depth":96,"text":67},{"id":574,"depth":96,"text":575},{"id":793,"depth":96,"text":794},{"id":1314,"depth":96,"text":1315},{"id":1807,"depth":96,"text":1808},{"id":2171,"depth":96,"text":2172},{"id":2214,"depth":96,"text":2215},{"id":2221,"depth":96,"text":2222},{"id":2249,"depth":96,"text":2250},"2026-10-02","Create a retail-style 4-4-5 fiscal calendar as a date table with pandas: fiscal years that start on the Sunday nearest 1 February, periods of four, four and five weeks per quarter, the occasional 53-week year and where its extra week goes, fiscal year, quarter, period and week for every date, mapping transactions onto it, and comparing periods that all have whole weeks.","md",[2300,2302,2304,2306],{"q":2227,"a":2301},"A fiscal calendar in which each quarter has three periods of four, four and five whole weeks, so every period starts on the same weekday and contains a fixed number of weeks.",{"q":2233,"a":2303},"52 weeks are 364 days, so a year that always starts on the same weekday drifts by a day or two each year. Every five or six years a 53rd week is added to keep the start near its anchor date.",{"q":2239,"a":2305},"Compute how many days separate 1 February from the next Sunday; if that is three or fewer, move forward, otherwise move back to the previous Sunday.",{"q":2245,"a":2307},"Find the fiscal year whose start the date has passed, compute the week number as whole weeks since that start plus one, and map the week to its period with the 4-4-5 pattern.","build a 4-4-5 fiscal calendar with pandas",null,{"breadcrumb":2311},[2312,2314,2317],{"name":2313,"item":1529},"Home",{"name":2315,"item":2316},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":22,"item":21},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fbuild-a-4-4-5-fiscal-calendar-with-pandas",{"title":2320,"description":2321},"Build a 4-4-5 Fiscal Calendar Date Table with pandas","Generate a 4-4-5 retail fiscal calendar in pandas: Sunday-nearest-1-February year starts, 4\u002F4\u002F5-week periods, 53-week years, fiscal labels for every date, joins to sales data and like-for-like period comparisons.","build-a-4-4-5-fiscal-calendar-with-pandas","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fbuild-a-4-4-5-fiscal-calendar-with-pandas\u002Findex",[2325],"dates-and-times","how-to","Ijbd7I_Ngh1eLlqCMK5IbazgbMichhlFlwQGHOCayLA",[2329,2332],{"title":22,"path":2330,"stem":2331,"children":-1},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Findex",{"title":1097,"path":2333,"stem":2334,"children":-1},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fbuild-a-date-table-for-excel-reports-with-pandas","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fbuild-a-date-table-for-excel-reports-with-pandas\u002Findex",[2336,2338],{"path":2337,"title":2315},"\u002Fadvanced-data-transformation-and-cleaning",{"path":2330,"title":22},[],[2341,2344,2347,2350,2353,2356],{"path":2342,"title":2343,"kind":2326},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-overdue-dates-with-openpyxl","Highlight Overdue Dates in Excel with openpyxl",{"path":2345,"title":2346,"kind":2326},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-weekends-and-holidays-in-a-date-column","Highlight Weekends and Holidays in a Date Column",{"path":2348,"title":2349,"kind":2326},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fkeep-the-latest-record-per-key-with-pandas","Keep the Latest Record per Key When Deduplicating with pandas",{"path":2351,"title":2352,"kind":2326},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcalculate-moving-averages-with-pandas-rolling","Calculate Moving Averages with pandas rolling",{"path":2354,"title":2355,"kind":2326},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Feomonth-and-edate-equivalents-in-pandas","EOMONTH and EDATE Equivalents in pandas",{"path":2357,"title":2358,"kind":2326},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fyearfrac-and-days360-equivalents-in-python","YEARFRAC and DAYS360 Equivalents in Python",1790972101020]