[{"data":1,"prerenderedAt":2510},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fbuild-a-calendar-sheet-in-excel-with-python":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fbuild-a-calendar-sheet-in-excel-with-python":2474,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fbuild-a-calendar-sheet-in-excel-with-python":2483,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fbuild-a-calendar-sheet-in-excel-with-python":2488,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fbuild-a-calendar-sheet-in-excel-with-python":2489},{"id":4,"title":5,"body":6,"dateModified":2440,"datePublished":2440,"description":2441,"extension":2442,"faq":2443,"keyword":2452,"kind":2453,"meta":2454,"navigation":276,"path":2463,"seo":2464,"slug":2467,"stem":2468,"topics":2469,"type":2472,"__hash__":2473},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fbuild-a-calendar-sheet-in-excel-with-python\u002Findex.md","Build a Calendar Sheet in Excel with Python",{"type":7,"value":8,"toc":2428},"minimark",[9,24,29,57,63,244,248,254,346,357,361,364,1341,1348,1508,1512,1515,1766,1774,1778,1781,1956,1959,1963,1966,2118,2130,2180,2183,2292,2296,2349,2353,2360,2364,2370,2376,2382,2392,2396,2425],[10,11,12,13,17,18,23],"p",{},"Team rotas, release plans, school terms, maintenance windows and holiday planners all end up as the same artefact: a calendar in a spreadsheet, one grid per month, with some days coloured and some text in the squares. Building one by hand means fiddling with which weekday the 1st falls on, twelve times, every year. Python's ",[14,15,16],"code",{},"calendar"," module already knows the layout of every month, so a script can produce the grids, shade the weekends, mark holidays, drop events from a list into the right squares and set the page up for printing. Next year's calendar is then a change of one number. This page is part of ",[19,20,22],"a",{"href":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002F","Styling Excel Cells with openpyxl",".",[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 openpyxl pandas\n","bash","",[14,37,38],{"__ignoreMap":35},[39,40,43,47,51,54],"span",{"class":41,"line":42},"line",1,[39,44,46],{"class":45},"sMTad","pip",[39,48,50],{"class":49},"srMev"," install",[39,52,53],{"class":49}," openpyxl",[39,55,56],{"class":49}," pandas\n",[10,58,59,62],{},[14,60,61],{},"events.xlsx"," lists team events with a date and a description; two fall on the same day and one spans two days. The calendar covers October to December 2026, with 25 and 26 December as public holidays.",[64,65,73,74,73,78,73,82,73,90,73,100,73,111,73,118,73,123,73,129,73,134,73,138,73,145,73,151,73,155,73,159,73,163,73,166,73,169,73,175,73,179,73,182,73,185,73,189,73,192,73,197,73,204,73,209,73,212,73,214,73,218,73,221,73,226,73,233,73,238,73,241],"svg",{"viewBox":66,"role":67,"ariaLabelledBy":68,"xmlns":71,"style":72},"4 0 752 142","img",[69,70],"cal-flow-t","cal-flow-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  ",[75,76,77],"title",{"id":69},"From a year and an event list to a printable calendar",[79,80,81],"desc",{"id":70},"The calendar module gives each month as weeks of day numbers, the grid is written with weekday headers and ISO week numbers, weekends and holidays are shaded, events are placed into their day cells, and the page is set up to print one month per page.",[83,84],"rect",{"x":85,"y":86,"width":87,"height":88,"fill":89},"4","0","752","142","#ffffff",[83,91],{"x":92,"y":93,"width":94,"height":95,"rx":96,"fill":97,"stroke":98,"style":99},"20.0","16.0","115.2","110.5","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[101,102,104],"g",{"transform":103},"translate(65.6,37.8) scale(1.000)",[105,106],"path",{"d":107,"fill":108,"stroke":109,"style":110},"M4 5h16v15H4z M4 10h16 M8 3v4 M16 3v4","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[112,113,117],"text",{"x":114,"y":115,"style":116},"77.6","84.2","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Month layout",[112,119,122],{"x":114,"y":120,"style":121},"103.8","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","monthcalendar()",[41,124],{"x1":125,"y1":126,"x2":127,"y2":126,"stroke":128,"style":99},"140.2","71.2","162.2","var(--brand,#5b5cf0)",[130,131],"polygon",{"points":132,"fill":133},"166.2,71.2 157.2,76.2 157.2,66.2","#5b5cf0",[83,135],{"x":136,"y":93,"width":94,"height":95,"rx":96,"fill":137,"stroke":128,"style":99},"171.2","#f0f4ff",[101,139,141],{"transform":140},"translate(216.8,29.0) scale(1.000)",[105,142],{"d":143,"fill":108,"stroke":144,"style":110},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","var(--brand-strong,#4338ca)",[112,146,150],{"x":147,"y":148,"style":149},"228.8","75.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Write grid",[112,152,154],{"x":147,"y":153,"style":121},"95.0","headers and",[112,156,158],{"x":147,"y":157,"style":121},"112.5","week numbers",[41,160],{"x1":161,"y1":126,"x2":162,"y2":126,"stroke":128,"style":99},"291.4","313.4",[130,164],{"points":165,"fill":133},"317.4,71.2 308.4,76.2 308.4,66.2",[83,167],{"x":168,"y":93,"width":94,"height":95,"rx":96,"fill":137,"stroke":128,"style":99},"322.4",[101,170,172],{"transform":171},"translate(368.0,29.0) scale(1.000)",[105,173],{"d":174,"fill":108,"stroke":144,"style":110},"M3 5h18l-7 8v6l-4 2v-8z",[112,176,178],{"x":177,"y":148,"style":149},"380.0","Shade days",[112,180,181],{"x":177,"y":153,"style":121},"weekends,",[112,183,184],{"x":177,"y":157,"style":121},"holidays",[41,186],{"x1":187,"y1":126,"x2":188,"y2":126,"stroke":128,"style":99},"442.6","464.6",[130,190],{"points":191,"fill":133},"468.6,71.2 459.6,76.2 459.6,66.2",[83,193],{"x":194,"y":93,"width":94,"height":95,"rx":96,"fill":195,"stroke":196,"style":99},"473.6","#d9f4f1","var(--teal,#0f9488)",[101,198,200],{"transform":199},"translate(519.2,29.0) scale(1.000)",[105,201],{"d":202,"fill":108,"stroke":203,"style":110},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[112,205,208],{"x":206,"y":148,"style":207},"531.2","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Place events",[112,210,211],{"x":206,"y":153,"style":121},"from",[112,213,61],{"x":206,"y":157,"style":121},[41,215],{"x1":216,"y1":126,"x2":217,"y2":126,"stroke":128,"style":99},"593.8","615.8",[130,219],{"points":220,"fill":133},"619.8,71.2 610.8,76.2 610.8,66.2",[83,222],{"x":223,"y":93,"width":94,"height":95,"rx":96,"fill":224,"stroke":225,"style":99},"624.8","#fdefd8","var(--gold,#b4740a)",[101,227,229],{"transform":228},"translate(670.4,29.0) scale(1.000)",[105,230],{"d":231,"fill":108,"stroke":232,"style":110},"M6 3h9l4 4v14H6z M15 3v4h4 M9 12h6 M9 15.5h6 M9 19h4","var(--gold-ink,#7a4e06)",[112,234,237],{"x":235,"y":148,"style":236},"682.4","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Page setup",[112,239,240],{"x":235,"y":153,"style":121},"one month per",[112,242,243],{"x":235,"y":157,"style":121},"page",[25,245,247],{"id":246},"step-1-get-the-month-layout-from-the-calendar-module","Step 1 — Get the month layout from the calendar module",[10,249,250,253],{},[14,251,252],{},"calendar.monthcalendar(year, month)"," returns the month as a list of weeks, each a list of seven day numbers, with zeros for days that belong to the neighbouring months. That is exactly the grid a calendar needs:",[30,255,259],{"className":256,"code":257,"language":258,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","import calendar\n\ncalendar.setfirstweekday(calendar.MONDAY)\nfor week in calendar.monthcalendar(2026, 10):\n    print(week)\nprint(calendar.month_name[10], calendar.day_abbr[0])\n","python",[14,260,261,271,278,291,318,327],{"__ignoreMap":35},[39,262,263,267],{"class":41,"line":42},[39,264,266],{"class":265},"s-kum","import",[39,268,270],{"class":269},"skGVy"," calendar\n",[39,272,274],{"class":41,"line":273},2,[39,275,277],{"emptyLinePlaceholder":276},true,"\n",[39,279,281,284,288],{"class":41,"line":280},3,[39,282,283],{"class":269},"calendar.setfirstweekday(calendar.",[39,285,287],{"class":286},"sP0c6","MONDAY",[39,289,290],{"class":269},")\n",[39,292,294,297,300,303,306,309,312,315],{"class":41,"line":293},4,[39,295,296],{"class":265},"for",[39,298,299],{"class":269}," week ",[39,301,302],{"class":265},"in",[39,304,305],{"class":269}," calendar.monthcalendar(",[39,307,308],{"class":286},"2026",[39,310,311],{"class":269},", ",[39,313,314],{"class":286},"10",[39,316,317],{"class":269},"):\n",[39,319,321,324],{"class":41,"line":320},5,[39,322,323],{"class":286},"    print",[39,325,326],{"class":269},"(week)\n",[39,328,330,333,336,338,341,343],{"class":41,"line":329},6,[39,331,332],{"class":286},"print",[39,334,335],{"class":269},"(calendar.month_name[",[39,337,314],{"class":286},[39,339,340],{"class":269},"], calendar.day_abbr[",[39,342,86],{"class":286},[39,344,345],{"class":269},"])\n",[10,347,348,349,352,353,356],{},"October 2026 starts on a Thursday, so the first week is ",[14,350,351],{},"[0, 0, 0, 1, 2, 3, 4]",". ",[14,354,355],{},"setfirstweekday"," decides whether weeks start on Monday (the ISO and European convention) or Sunday (common in the US); every other part of the code follows from it.",[25,358,360],{"id":359},"step-2-write-one-months-grid","Step 2 — Write one month's grid",[10,362,363],{},"Write a title, the weekday names, and the day numbers, with each day cell tall enough to hold a few lines of text:",[30,365,367],{"className":256,"code":366,"language":258,"meta":35,"style":35},"from datetime import date\nfrom openpyxl import Workbook\nfrom openpyxl.styles import Alignment, Border, Font, PatternFill, Side\n\nthin = Side(style=\"thin\", color=\"CBD5E1\")\nBOX = Border(left=thin, right=thin, top=thin, bottom=thin)\nHEAD = PatternFill(\"solid\", fgColor=\"4338CA\")\nWEEKEND = PatternFill(\"solid\", fgColor=\"EEF2FF\")\nHOLIDAY = PatternFill(\"solid\", fgColor=\"FDEFD8\")\nFONT = \"Calibri\"\n\ndef write_month(ws, year, month, top, holidays=frozenset()):\n    ws.cell(top, 2, f\"{calendar.month_name[month]} {year}\").font = Font(name=FONT, bold=True, size=16)\n    ws.cell(top + 1, 1, \"Wk\").font = Font(name=FONT, bold=True, color=\"64748B\")\n    for i, name in enumerate(calendar.day_abbr):\n        c = ws.cell(top + 1, i + 2, name)\n        c.font, c.fill, c.alignment = Font(name=FONT, bold=True, color=\"FFFFFF\"), HEAD, Alignment(horizontal=\"center\")\n    cells = {}\n    for w, week in enumerate(calendar.monthcalendar(year, month)):\n        r = top + 2 + w\n        ws.row_dimensions[r].height = 58\n        first_day = next(d for d in week if d)\n        wk = ws.cell(r, 1, date(year, month, first_day).isocalendar().week)\n        wk.font, wk.alignment = Font(name=FONT, color=\"64748B\"), Alignment(vertical=\"top\")\n        for i, d in enumerate(week):\n            c = ws.cell(r, i + 2)\n            c.border = BOX\n            c.alignment = Alignment(vertical=\"top\", wrap_text=True)\n            if not d:\n                continue\n            day = date(year, month, d)\n            c.value = str(d)\n            c.font = Font(name=FONT, bold=True)\n            if day in holidays:\n                c.fill = HOLIDAY\n            elif i >= 5:\n                c.fill = WEEKEND\n            cells[day] = c\n    return cells, top + 2 + len(calendar.monthcalendar(year, month))\n\nwb = Workbook()\nws = wb.active\nws.title = \"Q4 2026\"\nws.column_dimensions[\"A\"].width = 5\nfor col in \"BCDEFGH\":\n    ws.column_dimensions[col].width = 18\nholidays = {date(2026, 12, 25), date(2026, 12, 26)}\nday_cells, next_row = write_month(ws, 2026, 10, 1, holidays)\nprint(len(day_cells), \"days; next free row\", next_row)\n",[14,368,369,381,393,405,409,441,482,508,531,554,565,570,591,665,718,735,761,812,823,838,859,870,899,915,952,968,985,996,1024,1036,1042,1053,1067,1093,1106,1117,1135,1145,1156,1177,1182,1193,1204,1215,1232,1247,1258,1297,1321],{"__ignoreMap":35},[39,370,371,373,376,378],{"class":41,"line":42},[39,372,211],{"class":265},[39,374,375],{"class":269}," datetime ",[39,377,266],{"class":265},[39,379,380],{"class":269}," date\n",[39,382,383,385,388,390],{"class":41,"line":273},[39,384,211],{"class":265},[39,386,387],{"class":269}," openpyxl ",[39,389,266],{"class":265},[39,391,392],{"class":269}," Workbook\n",[39,394,395,397,400,402],{"class":41,"line":280},[39,396,211],{"class":265},[39,398,399],{"class":269}," openpyxl.styles ",[39,401,266],{"class":265},[39,403,404],{"class":269}," Alignment, Border, Font, PatternFill, Side\n",[39,406,407],{"class":41,"line":293},[39,408,277],{"emptyLinePlaceholder":276},[39,410,411,414,417,420,424,426,429,431,434,436,439],{"class":41,"line":320},[39,412,413],{"class":269},"thin ",[39,415,416],{"class":265},"=",[39,418,419],{"class":269}," Side(",[39,421,423],{"class":422},"sa561","style",[39,425,416],{"class":265},[39,427,428],{"class":49},"\"thin\"",[39,430,311],{"class":269},[39,432,433],{"class":422},"color",[39,435,416],{"class":265},[39,437,438],{"class":49},"\"CBD5E1\"",[39,440,290],{"class":269},[39,442,443,446,449,452,455,457,460,463,465,467,470,472,474,477,479],{"class":41,"line":329},[39,444,445],{"class":286},"BOX",[39,447,448],{"class":265}," =",[39,450,451],{"class":269}," Border(",[39,453,454],{"class":422},"left",[39,456,416],{"class":265},[39,458,459],{"class":269},"thin, ",[39,461,462],{"class":422},"right",[39,464,416],{"class":265},[39,466,459],{"class":269},[39,468,469],{"class":422},"top",[39,471,416],{"class":265},[39,473,459],{"class":269},[39,475,476],{"class":422},"bottom",[39,478,416],{"class":265},[39,480,481],{"class":269},"thin)\n",[39,483,485,488,490,493,496,498,501,503,506],{"class":41,"line":484},7,[39,486,487],{"class":286},"HEAD",[39,489,448],{"class":265},[39,491,492],{"class":269}," PatternFill(",[39,494,495],{"class":49},"\"solid\"",[39,497,311],{"class":269},[39,499,500],{"class":422},"fgColor",[39,502,416],{"class":265},[39,504,505],{"class":49},"\"4338CA\"",[39,507,290],{"class":269},[39,509,511,514,516,518,520,522,524,526,529],{"class":41,"line":510},8,[39,512,513],{"class":286},"WEEKEND",[39,515,448],{"class":265},[39,517,492],{"class":269},[39,519,495],{"class":49},[39,521,311],{"class":269},[39,523,500],{"class":422},[39,525,416],{"class":265},[39,527,528],{"class":49},"\"EEF2FF\"",[39,530,290],{"class":269},[39,532,534,537,539,541,543,545,547,549,552],{"class":41,"line":533},9,[39,535,536],{"class":286},"HOLIDAY",[39,538,448],{"class":265},[39,540,492],{"class":269},[39,542,495],{"class":49},[39,544,311],{"class":269},[39,546,500],{"class":422},[39,548,416],{"class":265},[39,550,551],{"class":49},"\"FDEFD8\"",[39,553,290],{"class":269},[39,555,557,560,562],{"class":41,"line":556},10,[39,558,559],{"class":286},"FONT",[39,561,448],{"class":265},[39,563,564],{"class":49}," \"Calibri\"\n",[39,566,568],{"class":41,"line":567},11,[39,569,277],{"emptyLinePlaceholder":276},[39,571,573,576,580,583,585,588],{"class":41,"line":572},12,[39,574,575],{"class":265},"def",[39,577,579],{"class":578},"s_Opv"," write_month",[39,581,582],{"class":269},"(ws, year, month, top, holidays",[39,584,416],{"class":265},[39,586,587],{"class":286},"frozenset",[39,589,590],{"class":269},"()):\n",[39,592,594,597,600,602,605,608,612,615,618,621,624,626,628,631,633,636,639,641,643,645,648,650,653,655,658,660,663],{"class":41,"line":593},13,[39,595,596],{"class":269},"    ws.cell(top, ",[39,598,599],{"class":286},"2",[39,601,311],{"class":269},[39,603,604],{"class":265},"f",[39,606,607],{"class":49},"\"",[39,609,611],{"class":610},"sSjpA","{",[39,613,614],{"class":269},"calendar.month_name[month]",[39,616,617],{"class":610},"}",[39,619,620],{"class":610}," {",[39,622,623],{"class":269},"year",[39,625,617],{"class":610},[39,627,607],{"class":49},[39,629,630],{"class":269},").font ",[39,632,416],{"class":265},[39,634,635],{"class":269}," Font(",[39,637,638],{"class":422},"name",[39,640,416],{"class":265},[39,642,559],{"class":286},[39,644,311],{"class":269},[39,646,647],{"class":422},"bold",[39,649,416],{"class":265},[39,651,652],{"class":286},"True",[39,654,311],{"class":269},[39,656,657],{"class":422},"size",[39,659,416],{"class":265},[39,661,662],{"class":286},"16",[39,664,290],{"class":269},[39,666,668,671,674,677,679,682,684,687,689,691,693,695,697,699,701,703,705,707,709,711,713,716],{"class":41,"line":667},14,[39,669,670],{"class":269},"    ws.cell(top ",[39,672,673],{"class":265},"+",[39,675,676],{"class":286}," 1",[39,678,311],{"class":269},[39,680,681],{"class":286},"1",[39,683,311],{"class":269},[39,685,686],{"class":49},"\"Wk\"",[39,688,630],{"class":269},[39,690,416],{"class":265},[39,692,635],{"class":269},[39,694,638],{"class":422},[39,696,416],{"class":265},[39,698,559],{"class":286},[39,700,311],{"class":269},[39,702,647],{"class":422},[39,704,416],{"class":265},[39,706,652],{"class":286},[39,708,311],{"class":269},[39,710,433],{"class":422},[39,712,416],{"class":265},[39,714,715],{"class":49},"\"64748B\"",[39,717,290],{"class":269},[39,719,721,724,727,729,732],{"class":41,"line":720},15,[39,722,723],{"class":265},"    for",[39,725,726],{"class":269}," i, name ",[39,728,302],{"class":265},[39,730,731],{"class":286}," enumerate",[39,733,734],{"class":269},"(calendar.day_abbr):\n",[39,736,738,741,743,746,748,750,753,755,758],{"class":41,"line":737},16,[39,739,740],{"class":269},"        c ",[39,742,416],{"class":265},[39,744,745],{"class":269}," ws.cell(top ",[39,747,673],{"class":265},[39,749,676],{"class":286},[39,751,752],{"class":269},", i ",[39,754,673],{"class":265},[39,756,757],{"class":286}," 2",[39,759,760],{"class":269},", name)\n",[39,762,764,767,769,771,773,775,777,779,781,783,785,787,789,791,794,797,799,802,805,807,810],{"class":41,"line":763},17,[39,765,766],{"class":269},"        c.font, c.fill, c.alignment ",[39,768,416],{"class":265},[39,770,635],{"class":269},[39,772,638],{"class":422},[39,774,416],{"class":265},[39,776,559],{"class":286},[39,778,311],{"class":269},[39,780,647],{"class":422},[39,782,416],{"class":265},[39,784,652],{"class":286},[39,786,311],{"class":269},[39,788,433],{"class":422},[39,790,416],{"class":265},[39,792,793],{"class":49},"\"FFFFFF\"",[39,795,796],{"class":269},"), ",[39,798,487],{"class":286},[39,800,801],{"class":269},", Alignment(",[39,803,804],{"class":422},"horizontal",[39,806,416],{"class":265},[39,808,809],{"class":49},"\"center\"",[39,811,290],{"class":269},[39,813,815,818,820],{"class":41,"line":814},18,[39,816,817],{"class":269},"    cells ",[39,819,416],{"class":265},[39,821,822],{"class":269}," {}\n",[39,824,826,828,831,833,835],{"class":41,"line":825},19,[39,827,723],{"class":265},[39,829,830],{"class":269}," w, week ",[39,832,302],{"class":265},[39,834,731],{"class":286},[39,836,837],{"class":269},"(calendar.monthcalendar(year, month)):\n",[39,839,841,844,846,849,851,853,856],{"class":41,"line":840},20,[39,842,843],{"class":269},"        r ",[39,845,416],{"class":265},[39,847,848],{"class":269}," top ",[39,850,673],{"class":265},[39,852,757],{"class":286},[39,854,855],{"class":265}," +",[39,857,858],{"class":269}," w\n",[39,860,862,865,867],{"class":41,"line":861},21,[39,863,864],{"class":269},"        ws.row_dimensions[r].height ",[39,866,416],{"class":265},[39,868,869],{"class":286}," 58\n",[39,871,873,876,878,881,884,886,889,891,893,896],{"class":41,"line":872},22,[39,874,875],{"class":269},"        first_day ",[39,877,416],{"class":265},[39,879,880],{"class":286}," next",[39,882,883],{"class":269},"(d ",[39,885,296],{"class":265},[39,887,888],{"class":269}," d ",[39,890,302],{"class":265},[39,892,299],{"class":269},[39,894,895],{"class":265},"if",[39,897,898],{"class":269}," d)\n",[39,900,902,905,907,910,912],{"class":41,"line":901},23,[39,903,904],{"class":269},"        wk ",[39,906,416],{"class":265},[39,908,909],{"class":269}," ws.cell(r, ",[39,911,681],{"class":286},[39,913,914],{"class":269},", date(year, month, first_day).isocalendar().week)\n",[39,916,918,921,923,925,927,929,931,933,935,937,939,942,945,947,950],{"class":41,"line":917},24,[39,919,920],{"class":269},"        wk.font, wk.alignment ",[39,922,416],{"class":265},[39,924,635],{"class":269},[39,926,638],{"class":422},[39,928,416],{"class":265},[39,930,559],{"class":286},[39,932,311],{"class":269},[39,934,433],{"class":422},[39,936,416],{"class":265},[39,938,715],{"class":49},[39,940,941],{"class":269},"), Alignment(",[39,943,944],{"class":422},"vertical",[39,946,416],{"class":265},[39,948,949],{"class":49},"\"top\"",[39,951,290],{"class":269},[39,953,955,958,961,963,965],{"class":41,"line":954},25,[39,956,957],{"class":265},"        for",[39,959,960],{"class":269}," i, d ",[39,962,302],{"class":265},[39,964,731],{"class":286},[39,966,967],{"class":269},"(week):\n",[39,969,971,974,976,979,981,983],{"class":41,"line":970},26,[39,972,973],{"class":269},"            c ",[39,975,416],{"class":265},[39,977,978],{"class":269}," ws.cell(r, i ",[39,980,673],{"class":265},[39,982,757],{"class":286},[39,984,290],{"class":269},[39,986,988,991,993],{"class":41,"line":987},27,[39,989,990],{"class":269},"            c.border ",[39,992,416],{"class":265},[39,994,995],{"class":286}," BOX\n",[39,997,999,1002,1004,1007,1009,1011,1013,1015,1018,1020,1022],{"class":41,"line":998},28,[39,1000,1001],{"class":269},"            c.alignment ",[39,1003,416],{"class":265},[39,1005,1006],{"class":269}," Alignment(",[39,1008,944],{"class":422},[39,1010,416],{"class":265},[39,1012,949],{"class":49},[39,1014,311],{"class":269},[39,1016,1017],{"class":422},"wrap_text",[39,1019,416],{"class":265},[39,1021,652],{"class":286},[39,1023,290],{"class":269},[39,1025,1027,1030,1033],{"class":41,"line":1026},29,[39,1028,1029],{"class":265},"            if",[39,1031,1032],{"class":265}," not",[39,1034,1035],{"class":269}," d:\n",[39,1037,1039],{"class":41,"line":1038},30,[39,1040,1041],{"class":265},"                continue\n",[39,1043,1045,1048,1050],{"class":41,"line":1044},31,[39,1046,1047],{"class":269},"            day ",[39,1049,416],{"class":265},[39,1051,1052],{"class":269}," date(year, month, d)\n",[39,1054,1056,1059,1061,1064],{"class":41,"line":1055},32,[39,1057,1058],{"class":269},"            c.value ",[39,1060,416],{"class":265},[39,1062,1063],{"class":286}," str",[39,1065,1066],{"class":269},"(d)\n",[39,1068,1070,1073,1075,1077,1079,1081,1083,1085,1087,1089,1091],{"class":41,"line":1069},33,[39,1071,1072],{"class":269},"            c.font ",[39,1074,416],{"class":265},[39,1076,635],{"class":269},[39,1078,638],{"class":422},[39,1080,416],{"class":265},[39,1082,559],{"class":286},[39,1084,311],{"class":269},[39,1086,647],{"class":422},[39,1088,416],{"class":265},[39,1090,652],{"class":286},[39,1092,290],{"class":269},[39,1094,1096,1098,1101,1103],{"class":41,"line":1095},34,[39,1097,1029],{"class":265},[39,1099,1100],{"class":269}," day ",[39,1102,302],{"class":265},[39,1104,1105],{"class":269}," holidays:\n",[39,1107,1109,1112,1114],{"class":41,"line":1108},35,[39,1110,1111],{"class":269},"                c.fill ",[39,1113,416],{"class":265},[39,1115,1116],{"class":286}," HOLIDAY\n",[39,1118,1120,1123,1126,1129,1132],{"class":41,"line":1119},36,[39,1121,1122],{"class":265},"            elif",[39,1124,1125],{"class":269}," i ",[39,1127,1128],{"class":265},">=",[39,1130,1131],{"class":286}," 5",[39,1133,1134],{"class":269},":\n",[39,1136,1138,1140,1142],{"class":41,"line":1137},37,[39,1139,1111],{"class":269},[39,1141,416],{"class":265},[39,1143,1144],{"class":286}," WEEKEND\n",[39,1146,1148,1151,1153],{"class":41,"line":1147},38,[39,1149,1150],{"class":269},"            cells[day] ",[39,1152,416],{"class":265},[39,1154,1155],{"class":269}," c\n",[39,1157,1159,1162,1165,1167,1169,1171,1174],{"class":41,"line":1158},39,[39,1160,1161],{"class":265},"    return",[39,1163,1164],{"class":269}," cells, top ",[39,1166,673],{"class":265},[39,1168,757],{"class":286},[39,1170,855],{"class":265},[39,1172,1173],{"class":286}," len",[39,1175,1176],{"class":269},"(calendar.monthcalendar(year, month))\n",[39,1178,1180],{"class":41,"line":1179},40,[39,1181,277],{"emptyLinePlaceholder":276},[39,1183,1185,1188,1190],{"class":41,"line":1184},41,[39,1186,1187],{"class":269},"wb ",[39,1189,416],{"class":265},[39,1191,1192],{"class":269}," Workbook()\n",[39,1194,1196,1199,1201],{"class":41,"line":1195},42,[39,1197,1198],{"class":269},"ws ",[39,1200,416],{"class":265},[39,1202,1203],{"class":269}," wb.active\n",[39,1205,1207,1210,1212],{"class":41,"line":1206},43,[39,1208,1209],{"class":269},"ws.title ",[39,1211,416],{"class":265},[39,1213,1214],{"class":49}," \"Q4 2026\"\n",[39,1216,1218,1221,1224,1227,1229],{"class":41,"line":1217},44,[39,1219,1220],{"class":269},"ws.column_dimensions[",[39,1222,1223],{"class":49},"\"A\"",[39,1225,1226],{"class":269},"].width ",[39,1228,416],{"class":265},[39,1230,1231],{"class":286}," 5\n",[39,1233,1235,1237,1240,1242,1245],{"class":41,"line":1234},45,[39,1236,296],{"class":265},[39,1238,1239],{"class":269}," col ",[39,1241,302],{"class":265},[39,1243,1244],{"class":49}," \"BCDEFGH\"",[39,1246,1134],{"class":269},[39,1248,1250,1253,1255],{"class":41,"line":1249},46,[39,1251,1252],{"class":269},"    ws.column_dimensions[col].width ",[39,1254,416],{"class":265},[39,1256,1257],{"class":286}," 18\n",[39,1259,1261,1264,1266,1269,1271,1273,1275,1277,1280,1283,1285,1287,1289,1291,1294],{"class":41,"line":1260},47,[39,1262,1263],{"class":269},"holidays ",[39,1265,416],{"class":265},[39,1267,1268],{"class":269}," {date(",[39,1270,308],{"class":286},[39,1272,311],{"class":269},[39,1274,96],{"class":286},[39,1276,311],{"class":269},[39,1278,1279],{"class":286},"25",[39,1281,1282],{"class":269},"), date(",[39,1284,308],{"class":286},[39,1286,311],{"class":269},[39,1288,96],{"class":286},[39,1290,311],{"class":269},[39,1292,1293],{"class":286},"26",[39,1295,1296],{"class":269},")}\n",[39,1298,1300,1303,1305,1308,1310,1312,1314,1316,1318],{"class":41,"line":1299},48,[39,1301,1302],{"class":269},"day_cells, next_row ",[39,1304,416],{"class":265},[39,1306,1307],{"class":269}," write_month(ws, ",[39,1309,308],{"class":286},[39,1311,311],{"class":269},[39,1313,314],{"class":286},[39,1315,311],{"class":269},[39,1317,681],{"class":286},[39,1319,1320],{"class":269},", holidays)\n",[39,1322,1324,1326,1329,1332,1335,1338],{"class":41,"line":1323},49,[39,1325,332],{"class":286},[39,1327,1328],{"class":269},"(",[39,1330,1331],{"class":286},"len",[39,1333,1334],{"class":269},"(day_cells), ",[39,1336,1337],{"class":49},"\"days; next free row\"",[39,1339,1340],{"class":269},", next_row)\n",[10,1342,1343,1344,1347],{},"Each day number is written as text so that events can be appended to the same cell in the next step. The ISO week number for each row comes from the first real day in that week — ",[14,1345,1346],{},"date.isocalendar().week"," — and sits in a narrow first column, which is how most European planners read week numbers. The function returns the cell for every date, which makes placing events a dictionary lookup, and the next free row, so months can be stacked on one sheet.",[64,1349,73,1355,73,1358,73,1361,73,1366,73,1372,73,1378,73,1382,73,1386,73,1390,73,1394,73,1398,73,1402,73,1406,73,1410,73,1414,73,1418,73,1421,73,1425,73,1429,73,1433,73,1436,73,1441,73,1443,73,1445,73,1447,73,1449,73,1451,73,1453,73,1457,73,1459,73,1463,73,1465,73,1467,73,1470,73,1474,73,1476,73,1479,73,1481,73,1484,73,1486,73,1489,73,1491,73,1494,73,1496,73,1499,73,1501,73,1503,73,1505],{"viewBox":1350,"role":67,"ariaLabelledBy":1351,"xmlns":71,"style":1354},"111 0 537 116",[1352,1353],"cal-grid-t","cal-grid-d","width:100%;max-width:537px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[75,1356,1357],{"id":1352},"The first two weeks of October 2026",[79,1359,1360],{"id":1353},"A calendar grid with a week number column and weekday headers from Monday to Sunday. October starts on Thursday the 1st; Saturday and Sunday are shaded, and Friday the 2nd holds two events.",[83,1362],{"x":1363,"y":86,"width":1364,"height":1365,"fill":89},"111","537","116",[83,1367],{"x":1368,"y":93,"width":1369,"height":1370,"rx":86,"fill":137,"stroke":128,"style":1371},"127.6","44.0","28.0","stroke-width:1.5px",[112,1373,1377],{"x":1374,"y":1375,"style":1376},"149.6","35.0","font-size:11px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Wk",[83,1379],{"x":1380,"y":93,"width":1381,"height":1370,"rx":86,"fill":137,"stroke":128,"style":1371},"171.6","49.0",[112,1383,1385],{"x":1384,"y":1375,"style":1376},"196.1","Mon",[83,1387],{"x":1388,"y":93,"width":1389,"height":1370,"rx":86,"fill":137,"stroke":128,"style":1371},"220.6","45.8",[112,1391,1393],{"x":1392,"y":1375,"style":1376},"243.5","Tue",[83,1395],{"x":1396,"y":93,"width":1397,"height":1370,"rx":86,"fill":137,"stroke":128,"style":1371},"266.4","50.1",[112,1399,1401],{"x":1400,"y":1375,"style":1376},"291.5","Wed",[83,1403],{"x":1404,"y":93,"width":1405,"height":1370,"rx":86,"fill":137,"stroke":128,"style":1371},"316.6","46.1",[112,1407,1409],{"x":1408,"y":1375,"style":1376},"339.6","Thu",[83,1411],{"x":1412,"y":93,"width":1413,"height":1370,"rx":86,"fill":137,"stroke":128,"style":1371},"362.7","179.8",[112,1415,1417],{"x":1416,"y":1375,"style":1376},"452.6","Fri",[83,1419],{"x":1420,"y":93,"width":1369,"height":1370,"rx":86,"fill":137,"stroke":128,"style":1371},"542.5",[112,1422,1424],{"x":1423,"y":1375,"style":1376},"564.5","Sat",[83,1426],{"x":1427,"y":93,"width":1428,"height":1370,"rx":86,"fill":137,"stroke":128,"style":1371},"586.5","46.0",[112,1430,1432],{"x":1431,"y":1375,"style":1376},"609.4","Sun",[83,1434],{"x":1368,"y":1369,"width":1369,"height":1370,"rx":86,"fill":89,"stroke":98,"style":1435},"stroke-width:1px",[112,1437,1440],{"x":1374,"y":1438,"style":1439},"63.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","40",[83,1442],{"x":1380,"y":1369,"width":1381,"height":1370,"rx":86,"fill":89,"stroke":98,"style":1435},[83,1444],{"x":1388,"y":1369,"width":1389,"height":1370,"rx":86,"fill":89,"stroke":98,"style":1435},[83,1446],{"x":1396,"y":1369,"width":1397,"height":1370,"rx":86,"fill":89,"stroke":98,"style":1435},[83,1448],{"x":1404,"y":1369,"width":1405,"height":1370,"rx":86,"fill":89,"stroke":98,"style":1435},[112,1450,681],{"x":1408,"y":1438,"style":1439},[83,1452],{"x":1412,"y":1369,"width":1413,"height":1370,"rx":86,"fill":195,"stroke":196,"style":1371},[112,1454,1456],{"x":1416,"y":1438,"style":1455},"font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","2 · Release 4.2 · Board pack",[83,1458],{"x":1420,"y":1369,"width":1369,"height":1370,"rx":86,"fill":97,"stroke":98,"style":1371},[112,1460,1462],{"x":1423,"y":1438,"style":1461},"font-size:11px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","3",[83,1464],{"x":1427,"y":1369,"width":1428,"height":1370,"rx":86,"fill":97,"stroke":98,"style":1371},[112,1466,85],{"x":1431,"y":1438,"style":1461},[83,1468],{"x":1368,"y":1469,"width":1369,"height":1370,"rx":86,"fill":89,"stroke":98,"style":1435},"72.0",[112,1471,1473],{"x":1374,"y":1472,"style":1439},"91.0","41",[83,1475],{"x":1380,"y":1469,"width":1381,"height":1370,"rx":86,"fill":89,"stroke":98,"style":1435},[112,1477,1478],{"x":1384,"y":1472,"style":1439},"5",[83,1480],{"x":1388,"y":1469,"width":1389,"height":1370,"rx":86,"fill":89,"stroke":98,"style":1435},[112,1482,1483],{"x":1392,"y":1472,"style":1439},"6",[83,1485],{"x":1396,"y":1469,"width":1397,"height":1370,"rx":86,"fill":89,"stroke":98,"style":1435},[112,1487,1488],{"x":1400,"y":1472,"style":1439},"7",[83,1490],{"x":1404,"y":1469,"width":1405,"height":1370,"rx":86,"fill":89,"stroke":98,"style":1435},[112,1492,1493],{"x":1408,"y":1472,"style":1439},"8",[83,1495],{"x":1412,"y":1469,"width":1413,"height":1370,"rx":86,"fill":89,"stroke":98,"style":1435},[112,1497,1498],{"x":1416,"y":1472,"style":1439},"9",[83,1500],{"x":1420,"y":1469,"width":1369,"height":1370,"rx":86,"fill":97,"stroke":98,"style":1371},[112,1502,314],{"x":1423,"y":1472,"style":1461},[83,1504],{"x":1427,"y":1469,"width":1428,"height":1370,"rx":86,"fill":97,"stroke":98,"style":1371},[112,1506,1507],{"x":1431,"y":1472,"style":1461},"11",[25,1509,1511],{"id":1510},"step-3-place-the-events","Step 3 — Place the events",[10,1513,1514],{},"Read the event list, group events by date, and append them to the day cells under the day number, one per line:",[30,1516,1518],{"className":256,"code":1517,"language":258,"meta":35,"style":35},"import pandas as pd\n\nevents = pd.read_excel(\"events.xlsx\", parse_dates=[\"Date\"])\nby_day = events.groupby(events[\"Date\"].dt.date)[\"Event\"].apply(list)\n\ndef place_events(cells, by_day):\n    placed = 0\n    for day, items in by_day.items():\n        if day in cells:\n            cell = cells[day]\n            cell.value = cell.value + \"\\n\" + \"\\n\".join(f\"• {e}\" for e in items)\n            cell.font = Font(name=FONT, size=9)\n            placed += len(items)\n    return placed\n\nprint(place_events(day_cells, by_day), \"events placed in October\")\n",[14,1519,1520,1533,1537,1565,1591,1595,1605,1615,1627,1639,1649,1705,1730,1743,1750,1754],{"__ignoreMap":35},[39,1521,1522,1524,1527,1530],{"class":41,"line":42},[39,1523,266],{"class":265},[39,1525,1526],{"class":269}," pandas ",[39,1528,1529],{"class":265},"as",[39,1531,1532],{"class":269}," pd\n",[39,1534,1535],{"class":41,"line":273},[39,1536,277],{"emptyLinePlaceholder":276},[39,1538,1539,1542,1544,1547,1550,1552,1555,1557,1560,1563],{"class":41,"line":280},[39,1540,1541],{"class":269},"events ",[39,1543,416],{"class":265},[39,1545,1546],{"class":269}," pd.read_excel(",[39,1548,1549],{"class":49},"\"events.xlsx\"",[39,1551,311],{"class":269},[39,1553,1554],{"class":422},"parse_dates",[39,1556,416],{"class":265},[39,1558,1559],{"class":269},"[",[39,1561,1562],{"class":49},"\"Date\"",[39,1564,345],{"class":269},[39,1566,1567,1570,1572,1575,1577,1580,1583,1586,1589],{"class":41,"line":293},[39,1568,1569],{"class":269},"by_day ",[39,1571,416],{"class":265},[39,1573,1574],{"class":269}," events.groupby(events[",[39,1576,1562],{"class":49},[39,1578,1579],{"class":269},"].dt.date)[",[39,1581,1582],{"class":49},"\"Event\"",[39,1584,1585],{"class":269},"].apply(",[39,1587,1588],{"class":286},"list",[39,1590,290],{"class":269},[39,1592,1593],{"class":41,"line":320},[39,1594,277],{"emptyLinePlaceholder":276},[39,1596,1597,1599,1602],{"class":41,"line":329},[39,1598,575],{"class":265},[39,1600,1601],{"class":578}," place_events",[39,1603,1604],{"class":269},"(cells, by_day):\n",[39,1606,1607,1610,1612],{"class":41,"line":484},[39,1608,1609],{"class":269},"    placed ",[39,1611,416],{"class":265},[39,1613,1614],{"class":286}," 0\n",[39,1616,1617,1619,1622,1624],{"class":41,"line":510},[39,1618,723],{"class":265},[39,1620,1621],{"class":269}," day, items ",[39,1623,302],{"class":265},[39,1625,1626],{"class":269}," by_day.items():\n",[39,1628,1629,1632,1634,1636],{"class":41,"line":533},[39,1630,1631],{"class":265},"        if",[39,1633,1100],{"class":269},[39,1635,302],{"class":265},[39,1637,1638],{"class":269}," cells:\n",[39,1640,1641,1644,1646],{"class":41,"line":556},[39,1642,1643],{"class":269},"            cell ",[39,1645,416],{"class":265},[39,1647,1648],{"class":269}," cells[day]\n",[39,1650,1651,1654,1656,1659,1661,1664,1667,1669,1671,1673,1675,1677,1680,1682,1685,1687,1690,1692,1694,1697,1700,1702],{"class":41,"line":567},[39,1652,1653],{"class":269},"            cell.value ",[39,1655,416],{"class":265},[39,1657,1658],{"class":269}," cell.value ",[39,1660,673],{"class":265},[39,1662,1663],{"class":49}," \"",[39,1665,1666],{"class":610},"\\n",[39,1668,607],{"class":49},[39,1670,855],{"class":265},[39,1672,1663],{"class":49},[39,1674,1666],{"class":610},[39,1676,607],{"class":49},[39,1678,1679],{"class":269},".join(",[39,1681,604],{"class":265},[39,1683,1684],{"class":49},"\"• ",[39,1686,611],{"class":610},[39,1688,1689],{"class":269},"e",[39,1691,617],{"class":610},[39,1693,607],{"class":49},[39,1695,1696],{"class":265}," for",[39,1698,1699],{"class":269}," e ",[39,1701,302],{"class":265},[39,1703,1704],{"class":269}," items)\n",[39,1706,1707,1710,1712,1714,1716,1718,1720,1722,1724,1726,1728],{"class":41,"line":572},[39,1708,1709],{"class":269},"            cell.font ",[39,1711,416],{"class":265},[39,1713,635],{"class":269},[39,1715,638],{"class":422},[39,1717,416],{"class":265},[39,1719,559],{"class":286},[39,1721,311],{"class":269},[39,1723,657],{"class":422},[39,1725,416],{"class":265},[39,1727,1498],{"class":286},[39,1729,290],{"class":269},[39,1731,1732,1735,1738,1740],{"class":41,"line":593},[39,1733,1734],{"class":269},"            placed ",[39,1736,1737],{"class":265},"+=",[39,1739,1173],{"class":286},[39,1741,1742],{"class":269},"(items)\n",[39,1744,1745,1747],{"class":41,"line":667},[39,1746,1161],{"class":265},[39,1748,1749],{"class":269}," placed\n",[39,1751,1752],{"class":41,"line":720},[39,1753,277],{"emptyLinePlaceholder":276},[39,1755,1756,1758,1761,1764],{"class":41,"line":737},[39,1757,332],{"class":286},[39,1759,1760],{"class":269},"(place_events(day_cells, by_day), ",[39,1762,1763],{"class":49},"\"events placed in October\"",[39,1765,290],{"class":269},[10,1767,1768,1769,1773],{},"Two events on 2 October appear as two bullet lines in one cell. The font of the whole cell drops to 9 points once it holds events, because a cell has a single font — the day number and the events cannot be styled differently without rich text, which ",[19,1770,1772],{"href":1771},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fwrite-rich-text-in-a-cell-with-openpyxl\u002F","Write Rich Text in a Cell with openpyxl"," covers if the day number should stay bold. Wrapped text and a fixed row height keep the grid regular; more than three or four events on one day will not fit, which is usually a sign the day needs its own list rather than a bigger cell. Events outside the months on the sheet are ignored here — the next step adds the other months.",[25,1775,1777],{"id":1776},"step-4-a-quarter-on-one-sheet-or-a-sheet-per-month","Step 4 — A quarter on one sheet, or a sheet per month",[10,1779,1780],{},"Stack the months with a gap between them, or give each its own tab. One sheet suits printing a quarter; tabs suit a year that people navigate:",[30,1782,1784],{"className":256,"code":1783,"language":258,"meta":35,"style":35},"wb = Workbook()\nws = wb.active\nws.title = \"Q4 2026\"\nws.column_dimensions[\"A\"].width = 5\nfor col in \"BCDEFGH\":\n    ws.column_dimensions[col].width = 18\n\nrow, all_cells, breaks = 1, {}, []\nfor month in (10, 11, 12):\n    cells, row = write_month(ws, 2026, month, row, holidays)\n    all_cells.update(cells)\n    breaks.append(row)\n    row += 1\nprint(place_events(all_cells, by_day), \"events placed\")\nfor d in sorted(holidays):\n    all_cells[d].value += \"\\nPublic holiday\"\n",[14,1785,1786,1794,1802,1810,1822,1834,1842,1846,1858,1882,1896,1901,1906,1916,1928,1942],{"__ignoreMap":35},[39,1787,1788,1790,1792],{"class":41,"line":42},[39,1789,1187],{"class":269},[39,1791,416],{"class":265},[39,1793,1192],{"class":269},[39,1795,1796,1798,1800],{"class":41,"line":273},[39,1797,1198],{"class":269},[39,1799,416],{"class":265},[39,1801,1203],{"class":269},[39,1803,1804,1806,1808],{"class":41,"line":280},[39,1805,1209],{"class":269},[39,1807,416],{"class":265},[39,1809,1214],{"class":49},[39,1811,1812,1814,1816,1818,1820],{"class":41,"line":293},[39,1813,1220],{"class":269},[39,1815,1223],{"class":49},[39,1817,1226],{"class":269},[39,1819,416],{"class":265},[39,1821,1231],{"class":286},[39,1823,1824,1826,1828,1830,1832],{"class":41,"line":320},[39,1825,296],{"class":265},[39,1827,1239],{"class":269},[39,1829,302],{"class":265},[39,1831,1244],{"class":49},[39,1833,1134],{"class":269},[39,1835,1836,1838,1840],{"class":41,"line":329},[39,1837,1252],{"class":269},[39,1839,416],{"class":265},[39,1841,1257],{"class":286},[39,1843,1844],{"class":41,"line":484},[39,1845,277],{"emptyLinePlaceholder":276},[39,1847,1848,1851,1853,1855],{"class":41,"line":510},[39,1849,1850],{"class":269},"row, all_cells, breaks ",[39,1852,416],{"class":265},[39,1854,676],{"class":286},[39,1856,1857],{"class":269},", {}, []\n",[39,1859,1860,1862,1865,1867,1870,1872,1874,1876,1878,1880],{"class":41,"line":533},[39,1861,296],{"class":265},[39,1863,1864],{"class":269}," month ",[39,1866,302],{"class":265},[39,1868,1869],{"class":269}," (",[39,1871,314],{"class":286},[39,1873,311],{"class":269},[39,1875,1507],{"class":286},[39,1877,311],{"class":269},[39,1879,96],{"class":286},[39,1881,317],{"class":269},[39,1883,1884,1887,1889,1891,1893],{"class":41,"line":556},[39,1885,1886],{"class":269},"    cells, row ",[39,1888,416],{"class":265},[39,1890,1307],{"class":269},[39,1892,308],{"class":286},[39,1894,1895],{"class":269},", month, row, holidays)\n",[39,1897,1898],{"class":41,"line":567},[39,1899,1900],{"class":269},"    all_cells.update(cells)\n",[39,1902,1903],{"class":41,"line":572},[39,1904,1905],{"class":269},"    breaks.append(row)\n",[39,1907,1908,1911,1913],{"class":41,"line":593},[39,1909,1910],{"class":269},"    row ",[39,1912,1737],{"class":265},[39,1914,1915],{"class":286}," 1\n",[39,1917,1918,1920,1923,1926],{"class":41,"line":667},[39,1919,332],{"class":286},[39,1921,1922],{"class":269},"(place_events(all_cells, by_day), ",[39,1924,1925],{"class":49},"\"events placed\"",[39,1927,290],{"class":269},[39,1929,1930,1932,1934,1936,1939],{"class":41,"line":720},[39,1931,296],{"class":265},[39,1933,888],{"class":269},[39,1935,302],{"class":265},[39,1937,1938],{"class":286}," sorted",[39,1940,1941],{"class":269},"(holidays):\n",[39,1943,1944,1947,1949,1951,1953],{"class":41,"line":737},[39,1945,1946],{"class":269},"    all_cells[d].value ",[39,1948,1737],{"class":265},[39,1950,1663],{"class":49},[39,1952,1666],{"class":610},[39,1954,1955],{"class":49},"Public holiday\"\n",[10,1957,1958],{},"The offsite on 18 and 19 November lands in both days, because the event list has a row for each. Holidays get a label as well as the colour, so the calendar still makes sense printed in black and white.",[25,1960,1962],{"id":1961},"step-5-set-the-page-up-for-printing","Step 5 — Set the page up for printing",[10,1964,1965],{},"A calendar is usually printed. Landscape, fitted to the page width, one month per page, with the gridlines off because the day cells have their own borders:",[30,1967,1969],{"className":256,"code":1968,"language":258,"meta":35,"style":35},"from openpyxl.worksheet.pagebreak import Break\n\nws.page_setup.orientation = \"landscape\"\nws.page_setup.paperSize = ws.PAPERSIZE_A4\nws.sheet_properties.pageSetUpPr.fitToPage = True\nws.page_setup.fitToWidth = 1\nws.page_setup.fitToHeight = 0\nfor r in breaks[:-1]:\n    ws.row_breaks.append(Break(id=r))\nws.sheet_view.showGridLines = False\nws.print_options.horizontalCentered = True\nwb.save(\"calendar_q4_2026.xlsx\")\nprint(\"row breaks after rows:\", breaks[:-1])\n",[14,1970,1971,1983,1987,1997,2010,2020,2029,2038,2058,2071,2081,2090,2100],{"__ignoreMap":35},[39,1972,1973,1975,1978,1980],{"class":41,"line":42},[39,1974,211],{"class":265},[39,1976,1977],{"class":269}," openpyxl.worksheet.pagebreak ",[39,1979,266],{"class":265},[39,1981,1982],{"class":269}," Break\n",[39,1984,1985],{"class":41,"line":273},[39,1986,277],{"emptyLinePlaceholder":276},[39,1988,1989,1992,1994],{"class":41,"line":280},[39,1990,1991],{"class":269},"ws.page_setup.orientation ",[39,1993,416],{"class":265},[39,1995,1996],{"class":49}," \"landscape\"\n",[39,1998,1999,2002,2004,2007],{"class":41,"line":293},[39,2000,2001],{"class":269},"ws.page_setup.paperSize ",[39,2003,416],{"class":265},[39,2005,2006],{"class":269}," ws.",[39,2008,2009],{"class":286},"PAPERSIZE_A4\n",[39,2011,2012,2015,2017],{"class":41,"line":320},[39,2013,2014],{"class":269},"ws.sheet_properties.pageSetUpPr.fitToPage ",[39,2016,416],{"class":265},[39,2018,2019],{"class":286}," True\n",[39,2021,2022,2025,2027],{"class":41,"line":329},[39,2023,2024],{"class":269},"ws.page_setup.fitToWidth ",[39,2026,416],{"class":265},[39,2028,1915],{"class":286},[39,2030,2031,2034,2036],{"class":41,"line":484},[39,2032,2033],{"class":269},"ws.page_setup.fitToHeight ",[39,2035,416],{"class":265},[39,2037,1614],{"class":286},[39,2039,2040,2042,2045,2047,2050,2053,2055],{"class":41,"line":510},[39,2041,296],{"class":265},[39,2043,2044],{"class":269}," r ",[39,2046,302],{"class":265},[39,2048,2049],{"class":269}," breaks[:",[39,2051,2052],{"class":265},"-",[39,2054,681],{"class":286},[39,2056,2057],{"class":269},"]:\n",[39,2059,2060,2063,2066,2068],{"class":41,"line":533},[39,2061,2062],{"class":269},"    ws.row_breaks.append(Break(",[39,2064,2065],{"class":422},"id",[39,2067,416],{"class":265},[39,2069,2070],{"class":269},"r))\n",[39,2072,2073,2076,2078],{"class":41,"line":556},[39,2074,2075],{"class":269},"ws.sheet_view.showGridLines ",[39,2077,416],{"class":265},[39,2079,2080],{"class":286}," False\n",[39,2082,2083,2086,2088],{"class":41,"line":567},[39,2084,2085],{"class":269},"ws.print_options.horizontalCentered ",[39,2087,416],{"class":265},[39,2089,2019],{"class":286},[39,2091,2092,2095,2098],{"class":41,"line":572},[39,2093,2094],{"class":269},"wb.save(",[39,2096,2097],{"class":49},"\"calendar_q4_2026.xlsx\"",[39,2099,290],{"class":269},[39,2101,2102,2104,2106,2109,2112,2114,2116],{"class":41,"line":593},[39,2103,332],{"class":286},[39,2105,1328],{"class":269},[39,2107,2108],{"class":49},"\"row breaks after rows:\"",[39,2110,2111],{"class":269},", breaks[:",[39,2113,2052],{"class":265},[39,2115,681],{"class":286},[39,2117,345],{"class":269},[10,2119,2120,2121,2124,2125,2129],{},"A manual row break after each month puts every month on its own page; ",[14,2122,2123],{},"fitToHeight = 0"," lets the page count follow the breaks instead of squeezing the quarter onto one page. ",[19,2126,2128],{"href":2127},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fset-print-area-and-page-setup-with-openpyxl\u002F","Set the Print Area and Page Setup with openpyxl"," covers the options in detail. To check the result without Excel, convert the file to PDF with LibreOffice and look at the pages:",[30,2131,2133],{"className":32,"code":2132,"language":34,"meta":35,"style":35},"soffice -env:UserInstallation=file:\u002F\u002F$PWD\u002Flo_profile --headless --convert-to pdf calendar_q4_2026.xlsx > \u002Fdev\u002Fnull 2>&1\npython -c \"from pypdf import PdfReader; print(len(PdfReader('calendar_q4_2026.pdf').pages), 'pages')\"\n",[14,2134,2135,2170],{"__ignoreMap":35},[39,2136,2137,2140,2143,2146,2149,2152,2155,2158,2161,2164,2167],{"class":41,"line":42},[39,2138,2139],{"class":45},"soffice",[39,2141,2142],{"class":286}," -env:UserInstallation=file:\u002F\u002F",[39,2144,2145],{"class":269},"$PWD",[39,2147,2148],{"class":286},"\u002Flo_profile",[39,2150,2151],{"class":286}," --headless",[39,2153,2154],{"class":286}," --convert-to",[39,2156,2157],{"class":49}," pdf",[39,2159,2160],{"class":49}," calendar_q4_2026.xlsx",[39,2162,2163],{"class":265}," >",[39,2165,2166],{"class":49}," \u002Fdev\u002Fnull",[39,2168,2169],{"class":265}," 2>&1\n",[39,2171,2172,2174,2177],{"class":41,"line":273},[39,2173,258],{"class":45},[39,2175,2176],{"class":286}," -c",[39,2178,2179],{"class":49}," \"from pypdf import PdfReader; print(len(PdfReader('calendar_q4_2026.pdf').pages), 'pages')\"\n",[10,2181,2182],{},"Three pages, one per month, is what the breaks promised.",[64,2184,73,2189,73,2192,73,2195,73,2198,73,2203,73,2210,73,2216,73,2220,73,2226,73,2232,73,2236,73,2241,73,2245,73,2251,73,2254,73,2259,73,2263,73,2267,73,2271,73,2276,73,2279,73,2284,73,2287],{"viewBox":2185,"role":67,"ariaLabelledBy":2186,"xmlns":71,"style":72},"4 0 752 207",[2187,2188],"cal-layout-t","cal-layout-d",[75,2190,2191],{"id":2187},"One sheet per quarter or one tab per month",[79,2193,2194],{"id":2188},"Two panels comparing layouts. Months stacked on one sheet print a quarter in order and are easy to scroll; one tab per month suits a full year and keeps each month on its own screen.",[83,2196],{"x":85,"y":86,"width":87,"height":2197,"fill":89},"207",[83,2199],{"x":92,"y":93,"width":2200,"height":2201,"rx":2202,"fill":137,"stroke":128,"style":99},"351.0","175.0","14",[101,2204,2206],{"transform":2205},"translate(182.5,29.0) scale(1.083)",[105,2207],{"d":2208,"fill":108,"stroke":144,"style":2209},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[112,2211,2215],{"x":2212,"y":2213,"style":2214},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Stacked on one sheet",[41,2217],{"x1":2218,"y1":1472,"x2":2219,"y2":1472,"stroke":128,"style":1435},"36.0","355.0",[112,2221,2225],{"x":2222,"y":2223,"style":2224},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","prints a quarter in order",[101,2227,2229],{"transform":2228},"translate(38.5,99.5) scale(0.625)",[105,2230],{"d":202,"fill":108,"stroke":196,"style":2231},"stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[112,2233,2235],{"x":2222,"y":2234,"style":2224},"137.0","one scroll to see three months",[101,2237,2239],{"transform":2238},"translate(38.5,125.5) scale(0.625)",[105,2240],{"d":202,"fill":108,"stroke":196,"style":2231},[112,2242,2244],{"x":2222,"y":2243,"style":2224},"163.0","needs page breaks per month",[101,2246,2248],{"transform":2247},"translate(38.5,151.5) scale(0.625)",[105,2249],{"d":2250,"fill":108,"stroke":225,"style":2231},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4",[83,2252],{"x":2253,"y":93,"width":2200,"height":2201,"rx":2202,"fill":195,"stroke":196,"style":99},"389.0",[101,2255,2257],{"transform":2256},"translate(551.5,29.0) scale(1.083)",[105,2258],{"d":143,"fill":108,"stroke":203,"style":2209},[112,2260,2262],{"x":1423,"y":2213,"style":2261},"font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","One tab per month",[41,2264],{"x1":2265,"y1":1472,"x2":2266,"y2":1472,"stroke":196,"style":1435},"405.0","724.0",[112,2268,2270],{"x":2269,"y":2223,"style":2224},"429.0","a year stays navigable",[101,2272,2274],{"transform":2273},"translate(407.5,99.5) scale(0.625)",[105,2275],{"d":202,"fill":108,"stroke":196,"style":2231},[112,2277,2278],{"x":2269,"y":2234,"style":2224},"each month fills the screen",[101,2280,2282],{"transform":2281},"translate(407.5,125.5) scale(0.625)",[105,2283],{"d":202,"fill":108,"stroke":196,"style":2231},[112,2285,2286],{"x":2269,"y":2243,"style":2224},"twelve tabs to print",[101,2288,2290],{"transform":2289},"translate(407.5,151.5) scale(0.625)",[105,2291],{"d":2250,"fill":108,"stroke":225,"style":2231},[25,2293,2295],{"id":2294},"common-pitfalls","Common pitfalls",[2297,2298,2299,2311,2317,2327,2333,2343],"ul",{},[2300,2301,2302,2306,2307,2310],"li",{},[2303,2304,2305],"strong",{},"Week start."," Decide Monday or Sunday first with ",[14,2308,2309],{},"calendar.setfirstweekday","; mixing conventions puts weekend shading on the wrong columns.",[2300,2312,2313,2316],{},[2303,2314,2315],{},"Week numbers."," ISO weeks start on Monday and week 1 contains the first Thursday of the year; with Sunday-first calendars, use a different rule and say which.",[2300,2318,2319,2322,2323,2326],{},[2303,2320,2321],{},"Unnamed fonts."," A ",[14,2324,2325],{},"Font()"," without a name is shown in the application's default font, which LibreOffice renders as a serif face; name the font on every style you create.",[2300,2328,2329,2332],{},[2303,2330,2331],{},"Merged cells for titles."," They complicate printing and later edits; a bold title in one cell is enough.",[2300,2334,2335,2338,2339,2342],{},[2303,2336,2337],{},"Events as dates with times."," Group by ",[14,2340,2341],{},".dt.date",", or an event at 09:00 does not match the date key.",[2300,2344,2345,2348],{},[2303,2346,2347],{},"Colour-only meaning."," Add a text label for holidays so printed copies still carry the information.",[25,2350,2352],{"id":2351},"conclusion","Conclusion",[10,2354,2355,2356,2359],{},"Let ",[14,2357,2358],{},"calendar.monthcalendar"," lay out each month, write the grid with weekday headers and ISO week numbers, shade weekends and holidays, and keep a dictionary from each date to its cell so events from a list can be appended in one loop. Stack months on one sheet with a page break after each, or give each month a tab, and set landscape, fit-to-width printing with gridlines off. Changing the year or the event file produces next year's calendar.",[25,2361,2363],{"id":2362},"frequently-asked-questions","Frequently asked questions",[10,2365,2366,2369],{},[2303,2367,2368],{},"How do I create a calendar in Excel with Python?","\nUse calendar.monthcalendar(year, month) to get each month as weeks of day numbers, write the numbers into a grid of cells with openpyxl, and style weekends and holidays. Setting the first weekday with calendar.setfirstweekday controls whether weeks start on Monday or Sunday.",[10,2371,2372,2375],{},[2303,2373,2374],{},"How do I add events to the calendar cells?","\nKeep a dictionary from each date to the cell written for it, group your events by date, and append each date's events to its cell's text with line breaks. Turn on wrap_text so they display on separate lines.",[10,2377,2378,2381],{},[2303,2379,2380],{},"How do I show week numbers?","\nWrite date(year, month, day).isocalendar().week for the first real day of each week in a column next to the grid. ISO week numbers assume Monday-first weeks.",[10,2383,2384,2387,2388,2391],{},[2303,2385,2386],{},"How do I print one month per page?","\nAdd a manual row break after each month with ",[14,2389,2390],{},"ws.row_breaks.append(Break(id=row))",", set the page to landscape and fit to one page wide with fitToHeight set to 0.",[25,2393,2395],{"id":2394},"related","Related",[2297,2397,2398,2404,2411,2418],{},[2300,2399,2400,2401,2403],{},"Up one level: ",[19,2402,22],{"href":21}," — fonts, fills, borders and layout.",[2300,2405,2406,2410],{},[19,2407,2409],{"href":2408},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fwrap-text-and-align-cells-with-openpyxl\u002F","Wrap Text and Align Cells with openpyxl"," — the cell settings the day squares use.",[2300,2412,2413,2417],{},[19,2414,2416],{"href":2415},"\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"," — the ISO week rules in detail.",[2300,2419,2420,2424],{},[19,2421,2423],{"href":2422},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-gantt-chart-in-excel-with-python\u002F","Create a Gantt Chart in Excel with Python"," — another way to show work over time.",[423,2426,2427],{},"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 .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":35,"searchDepth":273,"depth":273,"links":2429},[2430,2431,2432,2433,2434,2435,2436,2437,2438,2439],{"id":27,"depth":273,"text":28},{"id":246,"depth":273,"text":247},{"id":359,"depth":273,"text":360},{"id":1510,"depth":273,"text":1511},{"id":1776,"depth":273,"text":1777},{"id":1961,"depth":273,"text":1962},{"id":2294,"depth":273,"text":2295},{"id":2351,"depth":273,"text":2352},{"id":2362,"depth":273,"text":2363},{"id":2394,"depth":273,"text":2395},"2026-09-29","Generate a printable month-grid calendar in a workbook: the grid from Python's calendar module, weekends shaded, public holidays marked, events from a spreadsheet list written into their day cells, ISO week numbers down the side, twelve months on one sheet or one per tab, and a layout that prints on one page.","md",[2444,2446,2448,2450],{"q":2368,"a":2445},"Use calendar.monthcalendar(year, month) to get each month as weeks of day numbers, write the numbers into a grid of cells with openpyxl, and style weekends and holidays. Setting the first weekday with calendar.setfirstweekday controls whether weeks start on Monday or Sunday.",{"q":2374,"a":2447},"Keep a dictionary from each date to the cell written for it, group your events by date, and append each date's events to its cell's text with line breaks. Turn on wrap_text so they display on separate lines.",{"q":2380,"a":2449},"Write date(year, month, day).isocalendar().week for the first real day of each week in a column next to the grid. ISO week numbers assume Monday-first weeks.",{"q":2386,"a":2451},"Add a manual row break after each month with ws.row_breaks.append(Break(id=row)), set the page to landscape and fit to one page wide with fitToHeight set to 0.","build a calendar sheet in excel with python",null,{"breadcrumb":2455},[2456,2459,2462],{"name":2457,"item":2458},"Home","\u002F",{"name":2460,"item":2461},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":22,"item":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fbuild-a-calendar-sheet-in-excel-with-python",{"title":2465,"description":2466},"Create a Calendar in Excel with Python and openpyxl","Build an Excel calendar with Python: monthly grids with calendar.monthcalendar, weekend shading, holidays, events placed from a list, ISO week numbers, wrapped text in day cells, and print setup for one month per page.","build-a-calendar-sheet-in-excel-with-python","formatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fbuild-a-calendar-sheet-in-excel-with-python\u002Findex",[2470,2471],"report-layouts","cell-styling","how-to","HQYI-tnSppR8jRo4WGfaqHNaWLVT3kiAiSFUNnDmyYw",[2475,2479],{"title":2476,"path":2477,"stem":2478,"children":-1},"Apply Gradient and Pattern Fills with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-gradient-and-pattern-fills-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-gradient-and-pattern-fills-with-openpyxl\u002Findex",{"title":2480,"path":2481,"stem":2482,"children":-1},"Change the Default Font of an Excel Workbook with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fchange-the-default-font-of-an-excel-workbook-with-python","formatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fchange-the-default-font-of-an-excel-workbook-with-python\u002Findex",[2484,2486],{"path":2485,"title":2460},"\u002Fformatting-and-charting-excel-reports-with-python",{"path":2487,"title":22},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl",[],[2490,2494,2497,2500,2503,2506],{"path":2491,"title":2492,"kind":2493},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports","Building Finance and Operations Reports in Excel with Python","guide",{"path":2495,"title":2496,"kind":2472},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fallocate-shared-costs-across-departments","Allocate Shared Costs Across Departments with Python",{"path":2498,"title":2499,"kind":2472},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-bank-reconciliation-report","Build a Bank Reconciliation Report with Python and Excel",{"path":2501,"title":2502,"kind":2472},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-bill-of-materials-cost-rollup","Build a Bill of Materials Cost Rollup",{"path":2504,"title":2505,"kind":2472},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-break-even-analysis-in-excel-with-python","Build a Break-Even Analysis in Excel with Python",{"path":2507,"title":2508,"kind":2509},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-budget-vs-actual-variance-report","Build a Budget vs Actual Variance Report in Excel with Python","compare",1790972117234]