[{"data":1,"prerenderedAt":1937},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fflag-public-holidays-in-excel-data-with-python":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fflag-public-holidays-in-excel-data-with-python":1904,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fflag-public-holidays-in-excel-data-with-python":1913,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fflag-public-holidays-in-excel-data-with-python":1918,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fflag-public-holidays-in-excel-data-with-python":1919},{"id":4,"title":5,"body":6,"dateModified":1871,"datePublished":1871,"description":1872,"extension":1873,"faq":1874,"keyword":1883,"kind":1884,"meta":1885,"navigation":124,"path":1894,"seo":1895,"slug":1898,"stem":1899,"topics":1900,"type":1902,"__hash__":1903},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fflag-public-holidays-in-excel-data-with-python\u002Findex.md","Flag Public Holidays in Excel Data with Python",{"type":7,"value":8,"toc":1859},"minimark",[9,24,29,63,76,80,88,421,427,559,563,566,798,805,809,812,1007,1014,1018,1021,1299,1309,1463,1467,1470,1610,1617,1741,1745,1780,1784,1794,1798,1804,1810,1816,1822,1826,1855],[10,11,12,13,17,18,23],"p",{},"Daily sales, delivery times, call volumes and staffing figures all behave differently on public holidays, and a report that ignores them misleads in predictable ways: an average daily order count dragged down by Christmas, an SLA breach counted against a day the warehouse was legally closed, a forecast that expects a normal Monday on Easter Monday. Knowing which dates are holidays sounds trivial until the data covers several countries, holidays move every year (Easter), some apply only in certain regions (Bavaria has holidays Berlin does not), and a holiday that falls on a Saturday is often moved to the following Monday. The ",[14,15,16],"code",{},"holidays"," package knows all of this for more than a hundred countries. This page uses it with pandas to flag holiday rows per country and region, add company closure days, count working days between dates, and compare averages with and without holidays. 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 holidays pandas openpyxl numpy\n","bash","",[14,37,38],{"__ignoreMap":35},[39,40,43,47,51,54,57,60],"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}," holidays",[39,55,56],{"class":49}," pandas",[39,58,59],{"class":49}," openpyxl",[39,61,62],{"class":49}," numpy\n",[10,64,65,68,69,72,73,75],{},[14,66,67],{},"shipments.xlsx"," holds daily shipments for five distribution centres in England, Scotland, Bavaria, Berlin and France from mid-December 2025 to mid-June 2026 — a period with Christmas, New Year, Easter, May holidays and several regional differences. ",[14,70,71],{},"deliveries.xlsx"," holds four delivery requests with requested and delivered dates, for counting working days. The examples were run with ",[14,74,16],{}," 0.105.",[25,77,79],{"id":78},"step-1-holidays-for-each-country-and-region-in-the-data","Step 1 — Holidays for each country and region in the data",[10,81,82,83,87],{},"Build one holiday calendar per country-and-region combination that appears in the data. Regions are called ",[84,85,86],"em",{},"subdivisions"," in the package:",[30,89,93],{"className":90,"code":91,"language":92,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","import holidays\nimport pandas as pd\n\nship = pd.read_excel(\"shipments.xlsx\")\nyears = sorted(ship[\"Date\"].dt.year.unique())\nplaces = ship[[\"Country\", \"Region\"]].drop_duplicates()\n\ncalendars = {}\nfor country, region in places.itertuples(index=False):\n    sub = region if isinstance(region, str) else None\n    calendars[(country, sub)] = holidays.country_holidays(country, subdiv=sub, years=years)\n\nfor key in [(\"GB\", \"ENG\"), (\"GB\", \"SCT\"), (\"DE\", \"BY\"), (\"DE\", \"BE\")]:\n    hols = calendars[key]\n    print(key, len(hols), \"holidays, e.g.\", sorted({n for d, n in hols.items() if d.month in (1, 3)})[:3])\n","python",[14,94,95,105,119,126,144,165,188,193,204,231,263,290,295,348,359],{"__ignoreMap":35},[39,96,97,101],{"class":41,"line":42},[39,98,100],{"class":99},"s-kum","import",[39,102,104],{"class":103},"skGVy"," holidays\n",[39,106,108,110,113,116],{"class":41,"line":107},2,[39,109,100],{"class":99},[39,111,112],{"class":103}," pandas ",[39,114,115],{"class":99},"as",[39,117,118],{"class":103}," pd\n",[39,120,122],{"class":41,"line":121},3,[39,123,125],{"emptyLinePlaceholder":124},true,"\n",[39,127,129,132,135,138,141],{"class":41,"line":128},4,[39,130,131],{"class":103},"ship ",[39,133,134],{"class":99},"=",[39,136,137],{"class":103}," pd.read_excel(",[39,139,140],{"class":49},"\"shipments.xlsx\"",[39,142,143],{"class":103},")\n",[39,145,147,150,152,156,159,162],{"class":41,"line":146},5,[39,148,149],{"class":103},"years ",[39,151,134],{"class":99},[39,153,155],{"class":154},"sP0c6"," sorted",[39,157,158],{"class":103},"(ship[",[39,160,161],{"class":49},"\"Date\"",[39,163,164],{"class":103},"].dt.year.unique())\n",[39,166,168,171,173,176,179,182,185],{"class":41,"line":167},6,[39,169,170],{"class":103},"places ",[39,172,134],{"class":99},[39,174,175],{"class":103}," ship[[",[39,177,178],{"class":49},"\"Country\"",[39,180,181],{"class":103},", ",[39,183,184],{"class":49},"\"Region\"",[39,186,187],{"class":103},"]].drop_duplicates()\n",[39,189,191],{"class":41,"line":190},7,[39,192,125],{"emptyLinePlaceholder":124},[39,194,196,199,201],{"class":41,"line":195},8,[39,197,198],{"class":103},"calendars ",[39,200,134],{"class":99},[39,202,203],{"class":103}," {}\n",[39,205,207,210,213,216,219,223,225,228],{"class":41,"line":206},9,[39,208,209],{"class":99},"for",[39,211,212],{"class":103}," country, region ",[39,214,215],{"class":99},"in",[39,217,218],{"class":103}," places.itertuples(",[39,220,222],{"class":221},"sa561","index",[39,224,134],{"class":99},[39,226,227],{"class":154},"False",[39,229,230],{"class":103},"):\n",[39,232,234,237,239,242,245,248,251,254,257,260],{"class":41,"line":233},10,[39,235,236],{"class":103},"    sub ",[39,238,134],{"class":99},[39,240,241],{"class":103}," region ",[39,243,244],{"class":99},"if",[39,246,247],{"class":154}," isinstance",[39,249,250],{"class":103},"(region, ",[39,252,253],{"class":154},"str",[39,255,256],{"class":103},") ",[39,258,259],{"class":99},"else",[39,261,262],{"class":154}," None\n",[39,264,266,269,271,274,277,279,282,285,287],{"class":41,"line":265},11,[39,267,268],{"class":103},"    calendars[(country, sub)] ",[39,270,134],{"class":99},[39,272,273],{"class":103}," holidays.country_holidays(country, ",[39,275,276],{"class":221},"subdiv",[39,278,134],{"class":99},[39,280,281],{"class":103},"sub, ",[39,283,284],{"class":221},"years",[39,286,134],{"class":99},[39,288,289],{"class":103},"years)\n",[39,291,293],{"class":41,"line":292},12,[39,294,125],{"emptyLinePlaceholder":124},[39,296,298,300,303,305,308,311,313,316,319,321,323,326,328,331,333,336,338,340,342,345],{"class":41,"line":297},13,[39,299,209],{"class":99},[39,301,302],{"class":103}," key ",[39,304,215],{"class":99},[39,306,307],{"class":103}," [(",[39,309,310],{"class":49},"\"GB\"",[39,312,181],{"class":103},[39,314,315],{"class":49},"\"ENG\"",[39,317,318],{"class":103},"), (",[39,320,310],{"class":49},[39,322,181],{"class":103},[39,324,325],{"class":49},"\"SCT\"",[39,327,318],{"class":103},[39,329,330],{"class":49},"\"DE\"",[39,332,181],{"class":103},[39,334,335],{"class":49},"\"BY\"",[39,337,318],{"class":103},[39,339,330],{"class":49},[39,341,181],{"class":103},[39,343,344],{"class":49},"\"BE\"",[39,346,347],{"class":103},")]:\n",[39,349,351,354,356],{"class":41,"line":350},14,[39,352,353],{"class":103},"    hols ",[39,355,134],{"class":99},[39,357,358],{"class":103}," calendars[key]\n",[39,360,362,365,368,371,374,377,379,382,385,387,390,392,395,397,400,402,405,408,410,413,416,418],{"class":41,"line":361},15,[39,363,364],{"class":154},"    print",[39,366,367],{"class":103},"(key, ",[39,369,370],{"class":154},"len",[39,372,373],{"class":103},"(hols), ",[39,375,376],{"class":49},"\"holidays, e.g.\"",[39,378,181],{"class":103},[39,380,381],{"class":154},"sorted",[39,383,384],{"class":103},"({n ",[39,386,209],{"class":99},[39,388,389],{"class":103}," d, n ",[39,391,215],{"class":99},[39,393,394],{"class":103}," hols.items() ",[39,396,244],{"class":99},[39,398,399],{"class":103}," d.month ",[39,401,215],{"class":99},[39,403,404],{"class":103}," (",[39,406,407],{"class":154},"1",[39,409,181],{"class":103},[39,411,412],{"class":154},"3",[39,414,415],{"class":103},")})[:",[39,417,412],{"class":154},[39,419,420],{"class":103},"])\n",[10,422,423,426],{},[14,424,425],{},"holidays.country_holidays(country, subdiv=..., years=...)"," returns a dictionary-like object of dates and names for that place and those years. England and Scotland differ — Scotland has 2 January and St Andrew's Day, England has Easter Monday but Scotland does not — and Bavaria has Epiphany and Corpus Christi, which Berlin does not, while Berlin has International Women's Day on 8 March. Building calendars from the combinations actually present in the data means a new site in a new country only needs its country and region codes filled in. Keep the package up to date: governments announce one-off holidays, and the version used here already lists a one-off Scottish holiday on 15 June 2026 that an older version would miss — worth reviewing whenever the package is upgraded.",[428,429,437,438,437,442,437,446,437,454,437,462,437,469,437,476,437,479,437,483,437,488,437,494,437,496,437,501,437,503,437,506,437,509,437,513,437,515,437,519,437,521,437,523,437,526,437,530,437,532,437,536,437,538,437,541,437,544,437,548,437,550,437,554,437,556],"svg",{"viewBox":430,"role":431,"ariaLabelledBy":432,"xmlns":435,"style":436},"4 0 752 208","img",[433,434],"ph-regions-t","ph-regions-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  ",[439,440,441],"title",{"id":433},"Same country, different holidays",[443,444,445],"desc",{"id":434},"England has Easter Monday, Scotland does not but has 2 January. Bavaria has Epiphany on 6 January and Corpus Christi, Berlin has International Women's Day on 8 March. A holiday flag must use the region, not just the country.",[447,448],"rect",{"x":449,"y":450,"width":451,"height":452,"fill":453},"4","0","752","208","#ffffff",[447,455],{"x":456,"y":457,"width":458,"height":459,"rx":450,"fill":453,"stroke":460,"style":461},"20.0","16.0","149.5","40.0","var(--line,#cdd5e6)","stroke-width:1px",[447,463],{"x":464,"y":457,"width":465,"height":459,"rx":450,"fill":466,"stroke":467,"style":468},"169.5","285.2","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[470,471,475],"text",{"x":472,"y":473,"style":474},"312.2","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","only here",[447,477],{"x":478,"y":457,"width":465,"height":459,"rx":450,"fill":466,"stroke":467,"style":468},"454.8",[470,480,482],{"x":481,"y":473,"style":474},"597.4","not here",[447,484],{"x":456,"y":485,"width":458,"height":486,"rx":450,"fill":487,"stroke":460,"style":461},"56.0","34.0","#e7ebef",[470,489,493],{"x":490,"y":491,"style":492},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","England (GB-ENG)",[447,495],{"x":464,"y":485,"width":465,"height":486,"rx":450,"fill":453,"stroke":460,"style":461},[470,497,500],{"x":472,"y":498,"style":499},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Easter Monday",[447,502],{"x":478,"y":485,"width":465,"height":486,"rx":450,"fill":453,"stroke":460,"style":461},[470,504,505],{"x":481,"y":498,"style":499},"2 January",[447,507],{"x":456,"y":508,"width":458,"height":486,"rx":450,"fill":487,"stroke":460,"style":461},"90.0",[470,510,512],{"x":490,"y":511,"style":492},"112.0","Scotland (GB-SCT)",[447,514],{"x":464,"y":508,"width":465,"height":486,"rx":450,"fill":453,"stroke":460,"style":461},[470,516,518],{"x":472,"y":517,"style":499},"111.0","2 January, St Andrew's Day",[447,520],{"x":478,"y":508,"width":465,"height":486,"rx":450,"fill":453,"stroke":460,"style":461},[470,522,500],{"x":481,"y":517,"style":499},[447,524],{"x":456,"y":525,"width":458,"height":486,"rx":450,"fill":487,"stroke":460,"style":461},"124.0",[470,527,529],{"x":490,"y":528,"style":492},"146.0","Bavaria (DE-BY)",[447,531],{"x":464,"y":525,"width":465,"height":486,"rx":450,"fill":453,"stroke":460,"style":461},[470,533,535],{"x":472,"y":534,"style":499},"145.0","Epiphany, Corpus Christi",[447,537],{"x":478,"y":525,"width":465,"height":486,"rx":450,"fill":453,"stroke":460,"style":461},[470,539,540],{"x":481,"y":534,"style":499},"Women's Day",[447,542],{"x":456,"y":543,"width":458,"height":486,"rx":450,"fill":487,"stroke":460,"style":461},"158.0",[470,545,547],{"x":490,"y":546,"style":492},"180.0","Berlin (DE-BE)",[447,549],{"x":464,"y":543,"width":465,"height":486,"rx":450,"fill":453,"stroke":460,"style":461},[470,551,553],{"x":472,"y":552,"style":499},"179.0","Women's Day (8 March)",[447,555],{"x":478,"y":543,"width":465,"height":486,"rx":450,"fill":453,"stroke":460,"style":461},[470,557,558],{"x":481,"y":552,"style":499},"Epiphany",[25,560,562],{"id":561},"step-2-flag-each-row-with-its-holiday","Step 2 — Flag each row with its holiday",[10,564,565],{},"With the calendars built, each row looks up its own date in its own place's calendar:",[30,567,569],{"className":90,"code":568,"language":92,"meta":35,"style":35},"def holiday_name(row):\n    sub = row[\"Region\"] if isinstance(row[\"Region\"], str) else None\n    return calendars[(row[\"Country\"], sub)].get(row[\"Date\"].date())\n\nship[\"Holiday\"] = ship.apply(holiday_name, axis=1)\nship[\"Is holiday\"] = ship[\"Holiday\"].notna()\nship[\"Is weekend\"] = ship[\"Date\"].dt.dayofweek >= 5\nprint(ship.loc[ship[\"Is holiday\"], [\"Date\", \"Site\", \"Holiday\"]].drop_duplicates([\"Date\", \"Holiday\"]).head(10).to_string(index=False))\nprint(ship[ship[\"Date\"] == \"2026-01-06\"][[\"Site\", \"Is holiday\", \"Holiday\"]].to_string(index=False))\n",[14,570,571,583,617,635,639,663,682,706,757],{"__ignoreMap":35},[39,572,573,576,580],{"class":41,"line":42},[39,574,575],{"class":99},"def",[39,577,579],{"class":578},"s_Opv"," holiday_name",[39,581,582],{"class":103},"(row):\n",[39,584,585,587,589,592,594,597,599,601,604,606,609,611,613,615],{"class":41,"line":107},[39,586,236],{"class":103},[39,588,134],{"class":99},[39,590,591],{"class":103}," row[",[39,593,184],{"class":49},[39,595,596],{"class":103},"] ",[39,598,244],{"class":99},[39,600,247],{"class":154},[39,602,603],{"class":103},"(row[",[39,605,184],{"class":49},[39,607,608],{"class":103},"], ",[39,610,253],{"class":154},[39,612,256],{"class":103},[39,614,259],{"class":99},[39,616,262],{"class":154},[39,618,619,622,625,627,630,632],{"class":41,"line":121},[39,620,621],{"class":99},"    return",[39,623,624],{"class":103}," calendars[(row[",[39,626,178],{"class":49},[39,628,629],{"class":103},"], sub)].get(row[",[39,631,161],{"class":49},[39,633,634],{"class":103},"].date())\n",[39,636,637],{"class":41,"line":128},[39,638,125],{"emptyLinePlaceholder":124},[39,640,641,644,647,649,651,654,657,659,661],{"class":41,"line":146},[39,642,643],{"class":103},"ship[",[39,645,646],{"class":49},"\"Holiday\"",[39,648,596],{"class":103},[39,650,134],{"class":99},[39,652,653],{"class":103}," ship.apply(holiday_name, ",[39,655,656],{"class":221},"axis",[39,658,134],{"class":99},[39,660,407],{"class":154},[39,662,143],{"class":103},[39,664,665,667,670,672,674,677,679],{"class":41,"line":167},[39,666,643],{"class":103},[39,668,669],{"class":49},"\"Is holiday\"",[39,671,596],{"class":103},[39,673,134],{"class":99},[39,675,676],{"class":103}," ship[",[39,678,646],{"class":49},[39,680,681],{"class":103},"].notna()\n",[39,683,684,686,689,691,693,695,697,700,703],{"class":41,"line":190},[39,685,643],{"class":103},[39,687,688],{"class":49},"\"Is weekend\"",[39,690,596],{"class":103},[39,692,134],{"class":99},[39,694,676],{"class":103},[39,696,161],{"class":49},[39,698,699],{"class":103},"].dt.dayofweek ",[39,701,702],{"class":99},">=",[39,704,705],{"class":154}," 5\n",[39,707,708,711,714,716,719,721,723,726,728,730,733,735,737,739,742,745,748,750,752,754],{"class":41,"line":195},[39,709,710],{"class":154},"print",[39,712,713],{"class":103},"(ship.loc[ship[",[39,715,669],{"class":49},[39,717,718],{"class":103},"], [",[39,720,161],{"class":49},[39,722,181],{"class":103},[39,724,725],{"class":49},"\"Site\"",[39,727,181],{"class":103},[39,729,646],{"class":49},[39,731,732],{"class":103},"]].drop_duplicates([",[39,734,161],{"class":49},[39,736,181],{"class":103},[39,738,646],{"class":49},[39,740,741],{"class":103},"]).head(",[39,743,744],{"class":154},"10",[39,746,747],{"class":103},").to_string(",[39,749,222],{"class":221},[39,751,134],{"class":99},[39,753,227],{"class":154},[39,755,756],{"class":103},"))\n",[39,758,759,761,764,766,768,771,774,777,779,781,783,785,787,790,792,794,796],{"class":41,"line":206},[39,760,710],{"class":154},[39,762,763],{"class":103},"(ship[ship[",[39,765,161],{"class":49},[39,767,596],{"class":103},[39,769,770],{"class":99},"==",[39,772,773],{"class":49}," \"2026-01-06\"",[39,775,776],{"class":103},"][[",[39,778,725],{"class":49},[39,780,181],{"class":103},[39,782,669],{"class":49},[39,784,181],{"class":103},[39,786,646],{"class":49},[39,788,789],{"class":103},"]].to_string(",[39,791,222],{"class":221},[39,793,134],{"class":99},[39,795,227],{"class":154},[39,797,756],{"class":103},[10,799,800,801,804],{},"Each row now carries the holiday's name, or nothing, plus boolean flags for holidays and weekends. Checking a single date shows the regional effect: 6 January is a holiday in Munich and nowhere else in the data. The name is worth keeping alongside the flag — \"why is this day low?\" is answered by \"Corpus Christi\" far better than by ",[14,802,803],{},"True",". For large tables, apply the lookup per unique (date, country, region) combination and merge the result back, rather than row by row; the logic is the same.",[25,806,808],{"id":807},"step-3-substitute-days-and-company-closures","Step 3 — Substitute days and company closures",[10,810,811],{},"When a holiday falls on a weekend, many countries give a substitute weekday — Boxing Day on a Saturday becomes a holiday on Monday 28 December. The package includes these \"observed\" days where the law provides them; company-specific closures can be added to a calendar by hand:",[30,813,815],{"className":90,"code":814,"language":92,"meta":35,"style":35},"eng = calendars[(\"GB\", \"ENG\")]\nprint({d.isoformat(): n for d, n in sorted(eng.items()) if d.month == 12})\n\ncompany = holidays.country_holidays(\"GB\", subdiv=\"ENG\", years=years)\ncompany.update({pd.Timestamp(\"2025-12-24\").date(): \"Company closure\", pd.Timestamp(\"2025-12-31\").date(): \"Company closure\"})\nleeds = ship[\"Site\"] == \"Leeds DC\"\nship.loc[leeds, \"Closed\"] = ship.loc[leeds, \"Date\"].dt.date.map(lambda d: company.get(d))\nprint(ship.loc[leeds & ship[\"Closed\"].notna(), [\"Date\", \"Closed\"]].head(6).to_string(index=False))\n",[14,816,817,836,866,870,898,924,942,968],{"__ignoreMap":35},[39,818,819,822,824,827,829,831,833],{"class":41,"line":42},[39,820,821],{"class":103},"eng ",[39,823,134],{"class":99},[39,825,826],{"class":103}," calendars[(",[39,828,310],{"class":49},[39,830,181],{"class":103},[39,832,315],{"class":49},[39,834,835],{"class":103},")]\n",[39,837,838,840,843,845,847,849,851,854,856,858,860,863],{"class":41,"line":107},[39,839,710],{"class":154},[39,841,842],{"class":103},"({d.isoformat(): n ",[39,844,209],{"class":99},[39,846,389],{"class":103},[39,848,215],{"class":99},[39,850,155],{"class":154},[39,852,853],{"class":103},"(eng.items()) ",[39,855,244],{"class":99},[39,857,399],{"class":103},[39,859,770],{"class":99},[39,861,862],{"class":154}," 12",[39,864,865],{"class":103},"})\n",[39,867,868],{"class":41,"line":121},[39,869,125],{"emptyLinePlaceholder":124},[39,871,872,875,877,880,882,884,886,888,890,892,894,896],{"class":41,"line":128},[39,873,874],{"class":103},"company ",[39,876,134],{"class":99},[39,878,879],{"class":103}," holidays.country_holidays(",[39,881,310],{"class":49},[39,883,181],{"class":103},[39,885,276],{"class":221},[39,887,134],{"class":99},[39,889,315],{"class":49},[39,891,181],{"class":103},[39,893,284],{"class":221},[39,895,134],{"class":99},[39,897,289],{"class":103},[39,899,900,903,906,909,912,915,918,920,922],{"class":41,"line":146},[39,901,902],{"class":103},"company.update({pd.Timestamp(",[39,904,905],{"class":49},"\"2025-12-24\"",[39,907,908],{"class":103},").date(): ",[39,910,911],{"class":49},"\"Company closure\"",[39,913,914],{"class":103},", pd.Timestamp(",[39,916,917],{"class":49},"\"2025-12-31\"",[39,919,908],{"class":103},[39,921,911],{"class":49},[39,923,865],{"class":103},[39,925,926,929,931,933,935,937,939],{"class":41,"line":167},[39,927,928],{"class":103},"leeds ",[39,930,134],{"class":99},[39,932,676],{"class":103},[39,934,725],{"class":49},[39,936,596],{"class":103},[39,938,770],{"class":99},[39,940,941],{"class":49}," \"Leeds DC\"\n",[39,943,944,947,950,952,954,957,959,962,965],{"class":41,"line":190},[39,945,946],{"class":103},"ship.loc[leeds, ",[39,948,949],{"class":49},"\"Closed\"",[39,951,596],{"class":103},[39,953,134],{"class":99},[39,955,956],{"class":103}," ship.loc[leeds, ",[39,958,161],{"class":49},[39,960,961],{"class":103},"].dt.date.map(",[39,963,964],{"class":99},"lambda",[39,966,967],{"class":103}," d: company.get(d))\n",[39,969,970,972,975,978,980,982,985,987,989,991,994,997,999,1001,1003,1005],{"class":41,"line":195},[39,971,710],{"class":154},[39,973,974],{"class":103},"(ship.loc[leeds ",[39,976,977],{"class":99},"&",[39,979,676],{"class":103},[39,981,949],{"class":49},[39,983,984],{"class":103},"].notna(), [",[39,986,161],{"class":49},[39,988,181],{"class":103},[39,990,949],{"class":49},[39,992,993],{"class":103},"]].head(",[39,995,996],{"class":154},"6",[39,998,747],{"class":103},[39,1000,222],{"class":221},[39,1002,134],{"class":99},[39,1004,227],{"class":154},[39,1006,756],{"class":103},[10,1008,1009,1010,1013],{},"England's December entries show the effect: in 2025 Christmas Day and Boxing Day fall on a Thursday and Friday, while in 2026 Boxing Day is a Saturday and the package adds \"Boxing Day (observed)\" on Monday 28 December automatically. ",[14,1011,1012],{},"update"," adds company closure days — Christmas Eve and New Year's Eve here — to a copy of the public calendar, so the Leeds site's \"closed\" flag combines both. Keeping company days in a separate, explicit list (or a sheet maintained by HR) makes them easy to review each year; mixing them invisibly into the public calendar would make it hard to tell later why a date was excluded.",[25,1015,1017],{"id":1016},"step-4-working-days-between-two-dates-skipping-holidays","Step 4 — Working days between two dates, skipping holidays",[10,1019,1020],{},"Delivery times, SLAs and payment terms are usually counted in working days. NumPy's business-day functions accept a holiday list:",[30,1022,1024],{"className":90,"code":1023,"language":92,"meta":35,"style":35},"import numpy as np\n\ndeliv = pd.read_excel(\"deliveries.xlsx\").merge(ship[[\"Site\", \"Country\", \"Region\"]].drop_duplicates(), on=\"Site\")\n\ndef working_days(row):\n    sub = row[\"Region\"] if isinstance(row[\"Region\"], str) else None\n    hols = holidays.country_holidays(row[\"Country\"], subdiv=sub, years=[2026])\n    hol_dates = np.array(list(hols.keys()), dtype=\"datetime64[D]\")\n    return int(np.busday_count(row[\"Requested\"].date(), row[\"Delivered\"].date(), holidays=hol_dates))\n\ndeliv[\"Calendar days\"] = (deliv[\"Delivered\"] - deliv[\"Requested\"]).dt.days\ndeliv[\"Working days\"] = deliv.apply(working_days, axis=1)\nprint(deliv[[\"Site\", \"Requested\", \"Delivered\", \"Calendar days\", \"Working days\"]].to_string(index=False))\n",[14,1025,1026,1038,1042,1079,1083,1092,1122,1153,1179,1208,1212,1242,1264],{"__ignoreMap":35},[39,1027,1028,1030,1033,1035],{"class":41,"line":42},[39,1029,100],{"class":99},[39,1031,1032],{"class":103}," numpy ",[39,1034,115],{"class":99},[39,1036,1037],{"class":103}," np\n",[39,1039,1040],{"class":41,"line":107},[39,1041,125],{"emptyLinePlaceholder":124},[39,1043,1044,1047,1049,1051,1054,1057,1059,1061,1063,1065,1067,1070,1073,1075,1077],{"class":41,"line":121},[39,1045,1046],{"class":103},"deliv ",[39,1048,134],{"class":99},[39,1050,137],{"class":103},[39,1052,1053],{"class":49},"\"deliveries.xlsx\"",[39,1055,1056],{"class":103},").merge(ship[[",[39,1058,725],{"class":49},[39,1060,181],{"class":103},[39,1062,178],{"class":49},[39,1064,181],{"class":103},[39,1066,184],{"class":49},[39,1068,1069],{"class":103},"]].drop_duplicates(), ",[39,1071,1072],{"class":221},"on",[39,1074,134],{"class":99},[39,1076,725],{"class":49},[39,1078,143],{"class":103},[39,1080,1081],{"class":41,"line":128},[39,1082,125],{"emptyLinePlaceholder":124},[39,1084,1085,1087,1090],{"class":41,"line":146},[39,1086,575],{"class":99},[39,1088,1089],{"class":578}," working_days",[39,1091,582],{"class":103},[39,1093,1094,1096,1098,1100,1102,1104,1106,1108,1110,1112,1114,1116,1118,1120],{"class":41,"line":167},[39,1095,236],{"class":103},[39,1097,134],{"class":99},[39,1099,591],{"class":103},[39,1101,184],{"class":49},[39,1103,596],{"class":103},[39,1105,244],{"class":99},[39,1107,247],{"class":154},[39,1109,603],{"class":103},[39,1111,184],{"class":49},[39,1113,608],{"class":103},[39,1115,253],{"class":154},[39,1117,256],{"class":103},[39,1119,259],{"class":99},[39,1121,262],{"class":154},[39,1123,1124,1126,1128,1131,1133,1135,1137,1139,1141,1143,1145,1148,1151],{"class":41,"line":190},[39,1125,353],{"class":103},[39,1127,134],{"class":99},[39,1129,1130],{"class":103}," holidays.country_holidays(row[",[39,1132,178],{"class":49},[39,1134,608],{"class":103},[39,1136,276],{"class":221},[39,1138,134],{"class":99},[39,1140,281],{"class":103},[39,1142,284],{"class":221},[39,1144,134],{"class":99},[39,1146,1147],{"class":103},"[",[39,1149,1150],{"class":154},"2026",[39,1152,420],{"class":103},[39,1154,1155,1158,1160,1163,1166,1169,1172,1174,1177],{"class":41,"line":195},[39,1156,1157],{"class":103},"    hol_dates ",[39,1159,134],{"class":99},[39,1161,1162],{"class":103}," np.array(",[39,1164,1165],{"class":154},"list",[39,1167,1168],{"class":103},"(hols.keys()), ",[39,1170,1171],{"class":221},"dtype",[39,1173,134],{"class":99},[39,1175,1176],{"class":49},"\"datetime64[D]\"",[39,1178,143],{"class":103},[39,1180,1181,1183,1186,1189,1192,1195,1198,1201,1203,1205],{"class":41,"line":206},[39,1182,621],{"class":99},[39,1184,1185],{"class":154}," int",[39,1187,1188],{"class":103},"(np.busday_count(row[",[39,1190,1191],{"class":49},"\"Requested\"",[39,1193,1194],{"class":103},"].date(), row[",[39,1196,1197],{"class":49},"\"Delivered\"",[39,1199,1200],{"class":103},"].date(), ",[39,1202,16],{"class":221},[39,1204,134],{"class":99},[39,1206,1207],{"class":103},"hol_dates))\n",[39,1209,1210],{"class":41,"line":233},[39,1211,125],{"emptyLinePlaceholder":124},[39,1213,1214,1217,1220,1222,1224,1227,1229,1231,1234,1237,1239],{"class":41,"line":265},[39,1215,1216],{"class":103},"deliv[",[39,1218,1219],{"class":49},"\"Calendar days\"",[39,1221,596],{"class":103},[39,1223,134],{"class":99},[39,1225,1226],{"class":103}," (deliv[",[39,1228,1197],{"class":49},[39,1230,596],{"class":103},[39,1232,1233],{"class":99},"-",[39,1235,1236],{"class":103}," deliv[",[39,1238,1191],{"class":49},[39,1240,1241],{"class":103},"]).dt.days\n",[39,1243,1244,1246,1249,1251,1253,1256,1258,1260,1262],{"class":41,"line":292},[39,1245,1216],{"class":103},[39,1247,1248],{"class":49},"\"Working days\"",[39,1250,596],{"class":103},[39,1252,134],{"class":99},[39,1254,1255],{"class":103}," deliv.apply(working_days, ",[39,1257,656],{"class":221},[39,1259,134],{"class":99},[39,1261,407],{"class":154},[39,1263,143],{"class":103},[39,1265,1266,1268,1271,1273,1275,1277,1279,1281,1283,1285,1287,1289,1291,1293,1295,1297],{"class":41,"line":297},[39,1267,710],{"class":154},[39,1269,1270],{"class":103},"(deliv[[",[39,1272,725],{"class":49},[39,1274,181],{"class":103},[39,1276,1191],{"class":49},[39,1278,181],{"class":103},[39,1280,1197],{"class":49},[39,1282,181],{"class":103},[39,1284,1219],{"class":49},[39,1286,181],{"class":103},[39,1288,1248],{"class":49},[39,1290,789],{"class":103},[39,1292,222],{"class":221},[39,1294,134],{"class":99},[39,1296,227],{"class":154},[39,1298,756],{"class":103},[10,1300,1301,1304,1305,23],{},[14,1302,1303],{},"np.busday_count(start, end, holidays=...)"," counts weekdays from the start date up to (not including) the end date, skipping the listed holidays. The Leeds request from 31 March to 8 April spans Good Friday and Easter Monday, so eight calendar days are only four working days; the Munich order over the same period loses Good Friday and Easter Monday too, while the Berlin delivery in late May loses Whit Monday. The Leeds request from 23 to 30 December 2026 shows the substitute day at work: seven calendar days, but only three working days once Christmas Day and the observed Boxing Day are skipped. A delivery promised \"within five working days\" can be checked correctly only this way. Business days without holidays are covered in ",[19,1306,1308],{"href":1307},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-business-days-between-excel-dates-with-pandas\u002F","Calculate Business Days Between Excel Dates with pandas",[428,1310,437,1316,437,1319,437,1322,437,1327,437,1331,437,1335,437,1341,437,1345,437,1349,437,1353,437,1357,437,1359,437,1363,437,1365,437,1368,437,1371,437,1374,437,1376,437,1380,437,1382,437,1385,437,1389,437,1394,437,1398,437,1402,437,1405,437,1409,437,1411,437,1414,437,1417,437,1421,437,1426,437,1429,437,1435,437,1439,437,1445,437,1450,437,1457],{"viewBox":1311,"role":431,"ariaLabelledBy":1312,"xmlns":435,"style":1315},"45 0 669 140",[1313,1314],"ph-busday-t","ph-busday-d","width:100%;max-width:669px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[439,1317,1318],{"id":1313},"Calendar days versus working days around Easter",[443,1320,1321],{"id":1314},"A Leeds delivery requested on Tuesday 31 March and delivered on Wednesday 8 April 2026 took eight calendar days. Skipping the weekend, Good Friday on 3 April and Easter Monday on 6 April leaves four working days.",[447,1323],{"x":1324,"y":450,"width":1325,"height":1326,"fill":453},"45","669","140",[447,1328],{"x":1329,"y":459,"width":490,"height":1330,"rx":450,"fill":487,"stroke":460,"style":461},"61.6","24.0",[447,1332],{"x":1333,"y":459,"width":1334,"height":1330,"rx":450,"fill":487,"stroke":460,"style":461},"93.6","83.1",[470,1336,1340],{"x":1337,"y":1338,"style":1339},"135.1","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[447,1342],{"x":1343,"y":459,"width":1344,"height":1330,"rx":450,"fill":487,"stroke":460,"style":461},"176.6","81.0",[470,1346,1348],{"x":1347,"y":1338,"style":1339},"217.2","B",[447,1350],{"x":1329,"y":1351,"width":490,"height":1352,"rx":450,"fill":487,"stroke":460,"style":461},"64.0","28.0",[470,1354,407],{"x":1355,"y":1356,"style":1339},"77.6","83.0",[447,1358],{"x":1333,"y":1351,"width":1334,"height":1352,"rx":450,"fill":453,"stroke":460,"style":461},[470,1360,1362],{"x":1337,"y":1356,"style":1361},"font-size:10.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Requested",[447,1364],{"x":1343,"y":1351,"width":1344,"height":1352,"rx":450,"fill":453,"stroke":460,"style":461},[470,1366,1367],{"x":1347,"y":1356,"style":1361},"Delivered",[447,1369],{"x":1329,"y":1370,"width":490,"height":1352,"rx":450,"fill":487,"stroke":460,"style":461},"92.0",[470,1372,1373],{"x":1355,"y":517,"style":1339},"2",[447,1375],{"x":1333,"y":1370,"width":1334,"height":1352,"rx":450,"fill":453,"stroke":460,"style":461},[470,1377,1379],{"x":1337,"y":517,"style":1378},"font-size:10.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Tue 31 Mar",[447,1381],{"x":1343,"y":1370,"width":1344,"height":1352,"rx":450,"fill":453,"stroke":460,"style":461},[470,1383,1384],{"x":1347,"y":517,"style":1378},"Wed 8 Apr",[447,1386],{"x":1387,"y":459,"width":1388,"height":1352,"rx":450,"fill":453,"stroke":460,"style":461},"577.1","71.2",[470,1390,1393],{"x":1391,"y":1392,"style":1361},"612.7","59.0","measure",[447,1395],{"x":1396,"y":459,"width":1397,"height":1352,"rx":450,"fill":453,"stroke":460,"style":461},"648.3","50.2",[470,1399,1401],{"x":1400,"y":1392,"style":1361},"673.3","days",[447,1403],{"x":1387,"y":1404,"width":1388,"height":1352,"rx":450,"fill":453,"stroke":460,"style":461},"68.0",[470,1406,1408],{"x":1391,"y":1407,"style":1378},"87.0","calendar",[447,1410],{"x":1396,"y":1404,"width":1397,"height":1352,"rx":450,"fill":453,"stroke":460,"style":461},[470,1412,1413],{"x":1400,"y":1407,"style":1378},"8",[447,1415],{"x":1387,"y":1416,"width":1388,"height":1352,"rx":450,"fill":453,"stroke":460,"style":461},"96.0",[470,1418,1420],{"x":1391,"y":1419,"style":1378},"115.0","working",[447,1422],{"x":1396,"y":1416,"width":1397,"height":1352,"rx":450,"fill":1423,"stroke":1424,"style":1425},"#d9f4f1","var(--teal,#0f9488)","stroke-width:1.5px",[470,1427,449],{"x":1400,"y":1419,"style":1428},"font-size:10.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[470,1430,1434],{"x":1431,"y":1432,"style":1433},"159.6","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","dates",[470,1436,1438],{"x":1437,"y":1432,"style":1433},"637.8","count",[41,1440],{"x1":1441,"y1":1442,"x2":1443,"y2":1442,"stroke":467,"style":1444},"267.7","80.0","563.1","stroke-width:2px",[1446,1447],"polygon",{"points":1448,"fill":1449},"567.1,80.0 558.1,85.0 558.1,75.0","#5b5cf0",[447,1451],{"x":1452,"y":1453,"width":1454,"height":1455,"rx":1456,"fill":453,"stroke":460,"style":468},"305.6","47.0","223.5","22.0","11",[470,1458,1462],{"x":1459,"y":1460,"style":1461},"417.4","62.0","font-size:11.5px;font-weight:400;fill:var(--brand-strong,#4338ca);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","np.busday_count(…, holidays)",[25,1464,1466],{"id":1465},"step-5-holidays-and-daily-averages","Step 5 — Holidays and daily averages",[10,1468,1469],{},"The most common reporting error is averaging across holidays. Compare a site's average weekday shipments with and without holidays:",[30,1471,1473],{"className":90,"code":1472,"language":92,"meta":35,"style":35},"weekdays = ship[~ship[\"Is weekend\"]]\nsummary = weekdays.groupby(\"Site\").agg(\n    all_weekdays=(\"Shipments\", \"mean\"),\n    working_days_only=(\"Shipments\", lambda s: s[~weekdays.loc[s.index, \"Is holiday\"]].mean()),\n    holiday_weekdays=(\"Is holiday\", \"sum\"),\n).round(1)\nprint(summary.to_string())\nship.to_excel(\"shipments_flagged.xlsx\", index=False)\n",[14,1474,1475,1494,1509,1530,1558,1576,1585,1592],{"__ignoreMap":35},[39,1476,1477,1480,1482,1484,1487,1489,1491],{"class":41,"line":42},[39,1478,1479],{"class":103},"weekdays ",[39,1481,134],{"class":99},[39,1483,676],{"class":103},[39,1485,1486],{"class":99},"~",[39,1488,643],{"class":103},[39,1490,688],{"class":49},[39,1492,1493],{"class":103},"]]\n",[39,1495,1496,1499,1501,1504,1506],{"class":41,"line":107},[39,1497,1498],{"class":103},"summary ",[39,1500,134],{"class":99},[39,1502,1503],{"class":103}," weekdays.groupby(",[39,1505,725],{"class":49},[39,1507,1508],{"class":103},").agg(\n",[39,1510,1511,1514,1516,1519,1522,1524,1527],{"class":41,"line":121},[39,1512,1513],{"class":221},"    all_weekdays",[39,1515,134],{"class":99},[39,1517,1518],{"class":103},"(",[39,1520,1521],{"class":49},"\"Shipments\"",[39,1523,181],{"class":103},[39,1525,1526],{"class":49},"\"mean\"",[39,1528,1529],{"class":103},"),\n",[39,1531,1532,1535,1537,1539,1541,1543,1545,1548,1550,1553,1555],{"class":41,"line":128},[39,1533,1534],{"class":221},"    working_days_only",[39,1536,134],{"class":99},[39,1538,1518],{"class":103},[39,1540,1521],{"class":49},[39,1542,181],{"class":103},[39,1544,964],{"class":99},[39,1546,1547],{"class":103}," s: s[",[39,1549,1486],{"class":99},[39,1551,1552],{"class":103},"weekdays.loc[s.index, ",[39,1554,669],{"class":49},[39,1556,1557],{"class":103},"]].mean()),\n",[39,1559,1560,1563,1565,1567,1569,1571,1574],{"class":41,"line":146},[39,1561,1562],{"class":221},"    holiday_weekdays",[39,1564,134],{"class":99},[39,1566,1518],{"class":103},[39,1568,669],{"class":49},[39,1570,181],{"class":103},[39,1572,1573],{"class":49},"\"sum\"",[39,1575,1529],{"class":103},[39,1577,1578,1581,1583],{"class":41,"line":167},[39,1579,1580],{"class":103},").round(",[39,1582,407],{"class":154},[39,1584,143],{"class":103},[39,1586,1587,1589],{"class":41,"line":190},[39,1588,710],{"class":154},[39,1590,1591],{"class":103},"(summary.to_string())\n",[39,1593,1594,1597,1600,1602,1604,1606,1608],{"class":41,"line":195},[39,1595,1596],{"class":103},"ship.to_excel(",[39,1598,1599],{"class":49},"\"shipments_flagged.xlsx\"",[39,1601,181],{"class":103},[39,1603,222],{"class":221},[39,1605,134],{"class":99},[39,1607,227],{"class":154},[39,1609,143],{"class":103},[10,1611,1612,1613,1616],{},"In this synthetic data, holidays were not simulated as quiet days, so the averages barely differ — which is itself the check: real data would show a clear gap, and the ",[14,1614,1615],{},"holiday_weekdays"," column shows how many weekday holidays each site had in the period, from Munich and Berlin with more regional holidays to France. With real shipments that drop on holidays, the \"working days only\" average is the one to use for capacity planning and forecasting, and the holiday rows deserve their own line in the report. The flagged table is written back to Excel, ready for pivots that slice by holiday.",[428,1618,437,1623,437,1626,437,1629,437,1632,437,1637,437,1648,437,1654,437,1658,437,1662,437,1665,437,1668,437,1675,437,1680,437,1683,437,1687,437,1690,437,1695,437,1702,437,1707,437,1710,437,1714,437,1717,437,1720,437,1727,437,1733,437,1737],{"viewBox":1619,"role":431,"ariaLabelledBy":1620,"xmlns":435,"style":436},"4 0 752 142",[1621,1622],"ph-flow-t","ph-flow-d",[439,1624,1625],{"id":1621},"Making a dataset holiday-aware",[443,1627,1628],{"id":1622},"Build one holiday calendar per country and region present, flag each row with its holiday name, add company closure days from an explicit list, count working days between dates with those holidays, and calculate averages on working days only.",[447,1630],{"x":449,"y":450,"width":451,"height":1631,"fill":453},"142",[447,1633],{"x":456,"y":457,"width":1634,"height":1635,"rx":1636,"fill":487,"stroke":460,"style":1444},"153.0","110.5","12",[1638,1639,1641],"g",{"transform":1640},"translate(84.5,37.8) scale(1.000)",[1642,1643],"path",{"d":1644,"fill":1645,"stroke":1646,"style":1647},"M4 5h16v15H4z M4 10h16 M8 3v4 M16 3v4","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[470,1649,1653],{"x":1650,"y":1651,"style":1652},"96.5","84.2","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Calendars",[470,1655,1657],{"x":1650,"y":1656,"style":499},"103.8","per country + region",[41,1659],{"x1":1660,"y1":1388,"x2":1661,"y2":1388,"stroke":467,"style":1444},"178.0","200.0",[1446,1663],{"points":1664,"fill":1449},"204.0,71.2 195.0,76.2 195.0,66.2",[447,1666],{"x":1667,"y":457,"width":1634,"height":1635,"rx":1636,"fill":466,"stroke":467,"style":1444},"209.0",[1638,1669,1671],{"transform":1670},"translate(273.5,37.8) scale(1.000)",[1642,1672],{"d":1673,"fill":1645,"stroke":1674,"style":1647},"M3 5h18l-7 8v6l-4 2v-8z","var(--brand-strong,#4338ca)",[470,1676,1679],{"x":1677,"y":1651,"style":1678},"285.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Flag rows",[470,1681,1682],{"x":1677,"y":1656,"style":499},"holiday name per date",[41,1684],{"x1":1685,"y1":1388,"x2":1686,"y2":1388,"stroke":467,"style":1444},"367.0","389.0",[1446,1688],{"points":1689,"fill":1449},"393.0,71.2 384.0,76.2 384.0,66.2",[447,1691],{"x":1692,"y":457,"width":1634,"height":1635,"rx":1636,"fill":1693,"stroke":1694,"style":1444},"398.0","#fdefd8","var(--gold,#b4740a)",[1638,1696,1698],{"transform":1697},"translate(462.5,37.8) scale(1.000)",[1642,1699],{"d":1700,"fill":1645,"stroke":1701,"style":1647},"M9 11a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z M2.5 20c0-3.6 2.9-6 6.5-6s6.5 2.4 6.5 6 M16 4.5a3.3 3.3 0 0 1 0 6.3 M18 14.3c2.2.7 3.5 2.8 3.5 5.7","var(--gold-ink,#7a4e06)",[470,1703,1706],{"x":1704,"y":1651,"style":1705},"474.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Company days",[470,1708,1709],{"x":1704,"y":1656,"style":499},"explicit extra list",[41,1711],{"x1":1712,"y1":1388,"x2":1713,"y2":1388,"stroke":467,"style":1444},"556.0","578.0",[1446,1715],{"points":1716,"fill":1449},"582.0,71.2 573.0,76.2 573.0,66.2",[447,1718],{"x":1719,"y":457,"width":1634,"height":1635,"rx":1636,"fill":1423,"stroke":1424,"style":1444},"587.0",[1638,1721,1723],{"transform":1722},"translate(651.5,29.0) scale(1.000)",[1642,1724],{"d":1725,"fill":1645,"stroke":1726,"style":1647},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[470,1728,1732],{"x":1729,"y":1730,"style":1731},"663.5","75.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Working days",[470,1734,1736],{"x":1729,"y":1735,"style":499},"95.0","busday_count,",[470,1738,1740],{"x":1729,"y":1739,"style":499},"112.5","averages",[25,1742,1744],{"id":1743},"common-pitfalls","Common pitfalls",[1746,1747,1748,1756,1762,1768,1774],"ul",{},[1749,1750,1751,1755],"li",{},[1752,1753,1754],"strong",{},"One national calendar for all sites."," Regional holidays differ; use subdivisions.",[1749,1757,1758,1761],{},[1752,1759,1760],{},"Hard-coded holiday dates."," Easter and others move every year; generate them.",[1749,1763,1764,1767],{},[1752,1765,1766],{},"Forgetting substitute days."," A weekend holiday often moves to Monday; the package handles it.",[1749,1769,1770,1773],{},[1752,1771,1772],{},"Company closures mixed in silently."," Keep them as a separate, reviewable list.",[1749,1775,1776,1779],{},[1752,1777,1778],{},"Averages that include holidays."," Report working-day averages separately.",[25,1781,1783],{"id":1782},"conclusion","Conclusion",[10,1785,1786,1787,1789,1790,1793],{},"To make Excel data holiday-aware, build a calendar with the ",[14,1788,16],{}," package for each country and region in the data, look up every row's date in its own calendar to add a holiday flag and name, and add company closure days from an explicit list. Use the same holiday lists with ",[14,1791,1792],{},"np.busday_count"," to count working days for delivery times and SLAs, and calculate daily averages on working days only, with holiday rows reported separately.",[25,1795,1797],{"id":1796},"frequently-asked-questions","Frequently asked questions",[10,1799,1800,1803],{},[1752,1801,1802],{},"How do I add public holidays to a pandas DataFrame?","\nCreate a calendar with the country_holidays function of the holidays package, passing the country, the region as subdiv and the years, then map each date to the calendar's get method, which returns the holiday name or None.",[10,1805,1806,1809],{},[1752,1807,1808],{},"How do I handle regional holidays such as German states?","\nPass the region code as subdiv, for example \"BY\" for Bavaria, and build a separate calendar for each country and region combination in the data.",[10,1811,1812,1815],{},[1752,1813,1814],{},"Does the holidays package include substitute days?","\nYes, where the country's rules provide them, such as a Monday holiday when Christmas falls on a weekend in England.",[10,1817,1818,1821],{},[1752,1819,1820],{},"How do I count working days excluding holidays?","\nUse numpy.busday_count with a holidays argument containing the holiday dates for the relevant country and region.",[25,1823,1825],{"id":1824},"related","Related",[1746,1827,1828,1834,1841,1848],{},[1749,1829,1830,1831,1833],{},"Up one level: ",[19,1832,22],{"href":21}," — dates in Excel data.",[1749,1835,1836,1840],{},[19,1837,1839],{"href":1838},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-weekends-and-holidays-in-a-date-column\u002F","Highlight Weekends and Holidays in a Date Column"," — shading the same days in Excel.",[1749,1842,1843,1847],{},[19,1844,1846],{"href":1845},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-working-hours-between-two-timestamps\u002F","Calculate Working Hours Between Two Timestamps"," — holidays at hour level.",[1749,1849,1850,1854],{},[19,1851,1853],{"href":1852},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-support-ticket-sla-report\u002F","Build a Support Ticket SLA Report"," — working-time SLAs in practice.",[1856,1857,1858],"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 .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}",{"title":35,"searchDepth":107,"depth":107,"links":1860},[1861,1862,1863,1864,1865,1866,1867,1868,1869,1870],{"id":27,"depth":107,"text":28},{"id":78,"depth":107,"text":79},{"id":561,"depth":107,"text":562},{"id":807,"depth":107,"text":808},{"id":1016,"depth":107,"text":1017},{"id":1465,"depth":107,"text":1466},{"id":1743,"depth":107,"text":1744},{"id":1782,"depth":107,"text":1783},{"id":1796,"depth":107,"text":1797},{"id":1824,"depth":107,"text":1825},"2026-10-02","Mark which rows of an Excel dataset fall on public holidays, country by country and region by region, with the holidays package and pandas: holiday names per row, substitute days when a holiday lands on a weekend, regional holidays such as German states, company shutdown days, working days between two dates that skip holidays, and the effect of holidays on daily averages.","md",[1875,1877,1879,1881],{"q":1802,"a":1876},"Create a calendar with the country_holidays function of the holidays package, passing the country, the region as subdiv and the years, then map each date to the calendar's get method, which returns the holiday name or None.",{"q":1808,"a":1878},"Pass the region code as subdiv, for example \"BY\" for Bavaria, and build a separate calendar for each country and region combination in the data.",{"q":1814,"a":1880},"Yes, where the country's rules provide them, such as a Monday holiday when Christmas falls on a weekend in England.",{"q":1820,"a":1882},"Use numpy.busday_count with a holidays argument containing the holiday dates for the relevant country and region.","flag public holidays in excel data with python",null,{"breadcrumb":1886},[1887,1890,1893],{"name":1888,"item":1889},"Home","\u002F",{"name":1891,"item":1892},"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\u002Fflag-public-holidays-in-excel-data-with-python",{"title":1896,"description":1897},"Flag Public Holidays in Excel Data with Python and pandas","Add public holiday flags and names to Excel data with the holidays package: per country and region, observed substitute days, company closures, business days excluding holidays and holiday-aware daily averages.","flag-public-holidays-in-excel-data-with-python","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fflag-public-holidays-in-excel-data-with-python\u002Findex",[1901],"dates-and-times","how-to","-xzI2RLuh1yADx4KHIO6zgA5xrtDVp5JlOt0NXKIVys",[1905,1909],{"title":1906,"path":1907,"stem":1908,"children":-1},"Fix Mixed Day-First and Month-First Dates in Excel Data","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Ffix-mixed-day-first-and-month-first-dates-in-excel-data","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Ffix-mixed-day-first-and-month-first-dates-in-excel-data\u002Findex",{"title":1910,"path":1911,"stem":1912,"children":-1},"Group Excel Data by Fiscal Year and ISO Week with pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-data-by-fiscal-year-and-iso-week","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-data-by-fiscal-year-and-iso-week\u002Findex",[1914,1916],{"path":1915,"title":1891},"\u002Fadvanced-data-transformation-and-cleaning",{"path":1917,"title":22},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data",[],[1920,1923,1925,1928,1931,1934],{"path":1921,"title":1922,"kind":1902},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-overdue-dates-with-openpyxl","Highlight Overdue Dates in Excel with openpyxl",{"path":1924,"title":1839,"kind":1902},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-weekends-and-holidays-in-a-date-column",{"path":1926,"title":1927,"kind":1902},"\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":1929,"title":1930,"kind":1902},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcalculate-moving-averages-with-pandas-rolling","Calculate Moving Averages with pandas rolling",{"path":1932,"title":1933,"kind":1902},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Feomonth-and-edate-equivalents-in-pandas","EOMONTH and EDATE Equivalents in pandas",{"path":1935,"title":1936,"kind":1902},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fyearfrac-and-days360-equivalents-in-python","YEARFRAC and DAYS360 Equivalents in Python",1790972106362]