[{"data":1,"prerenderedAt":2330},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-year-week-codes-to-dates-with-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-year-week-codes-to-dates-with-pandas":2296,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-year-week-codes-to-dates-with-pandas":2305,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-year-week-codes-to-dates-with-pandas":2310,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-year-week-codes-to-dates-with-pandas":2311},{"id":4,"title":5,"body":6,"dateModified":2263,"datePublished":2263,"description":2264,"extension":2265,"faq":2266,"keyword":2275,"kind":2276,"meta":2277,"navigation":139,"path":2286,"seo":2287,"slug":2290,"stem":2291,"topics":2292,"type":2294,"__hash__":2295},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-year-week-codes-to-dates-with-pandas\u002Findex.md","Convert Year-Week Codes to Dates with pandas",{"type":7,"value":8,"toc":2251},"minimark",[9,33,38,66,92,96,103,513,551,555,570,686,718,858,862,865,1122,1139,1266,1270,1279,1752,1782,1865,1869,1875,2089,2114,2118,2164,2168,2186,2190,2196,2202,2208,2214,2218,2247],[10,11,12,13,17,18,21,22,26,27,32],"p",{},"Planning tools, ERP systems and production schedules love week numbers. A capacity plan exported to Excel has columns headed ",[14,15,16],"code",{},"2026-W14",", a forecast file has a \"Week\" column containing ",[14,19,20],{},"202614",", and a supplier's delivery schedule says \"wk 14\u002F2026\". To join these to daily sales or to show them on a calendar, each code must become a date — usually the Monday that starts the week. The catch is that \"week 14\" is not one thing. The ISO standard starts weeks on Monday and assigns each week to the year that contains its Thursday, so 29 December can belong to week 1 of the ",[23,24,25],"em",{},"next"," year; American conventions start weeks on Sunday and count from the week containing 1 January. Some years have 53 ISO weeks, most have 52. This page normalises the different code formats, converts them with ISO rules in pandas, handles week 53 and Sunday-based weeks, checks against Excel's week functions, and converts dates back into week codes. It belongs to ",[28,29,31],"a",{"href":30},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002F","Working with Dates and Times in Excel Data",".",[34,35,37],"h2",{"id":36},"prerequisites","Prerequisites",[39,40,45],"pre",{"className":41,"code":42,"language":43,"meta":44,"style":44},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,46,47],{"__ignoreMap":44},[48,49,52,56,60,63],"span",{"class":50,"line":51},"line",1,[48,53,55],{"class":54},"sMTad","pip",[48,57,59],{"class":58},"srMev"," install",[48,61,62],{"class":58}," pandas",[48,64,65],{"class":58}," openpyxl\n",[10,67,68,71,72,75,76,79,80,83,84,87,88,91],{},[14,69,70],{},"plan.xlsx"," holds a production plan with a week code and planned units per row. The codes arrive in six different styles, as they do when several people and systems feed one sheet, and two of them are suspicious: ",[14,73,74],{},"2025-W53"," (2025 has no week 53) and ",[14,77,78],{},"2026-W53"," (which does exist). LibreOffice (",[14,81,82],{},"soffice",") recalculates Excel's ",[14,85,86],{},"ISOWEEKNUM"," and ",[14,89,90],{},"WEEKNUM"," in Step 4 for comparison.",[34,93,95],{"id":94},"step-1-normalise-the-codes-to-one-format","Step 1 — Normalise the codes to one format",[10,97,98,99,102],{},"Before converting, reduce every style to ",[14,100,101],{},"YYYY-Www",". A regular expression that finds a four-digit year and a one- or two-digit week, in either order, handles all the common styles:",[39,104,108],{"className":105,"code":106,"language":107,"meta":44,"style":44},"language-python shiki shiki-themes github-light github-dark-high-contrast","import re\nimport pandas as pd\n\nplan = pd.read_excel(\"plan.xlsx\", dtype={\"Week\": str})\n\ndef normalise_week(code):\n    s = str(code).upper().replace(\"WK\", \"W\")\n    m = re.search(r\"(\\d{4})\\D*W?(\\d{1,2})\\b\", s) or re.search(r\"W?(\\d{1,2})\\D+(\\d{4})\", s)\n    if not m:\n        return None\n    a, b = m.groups()\n    year, week = (a, b) if len(a) == 4 else (b, a)\n    if len(s.strip()) == 6 and s.isdigit():               # 202615 → year 2026, week 15\n        year, week = s[:4], s[4:]\n    return f\"{int(year)}-W{int(week):02d}\"\n\nplan[\"ISO code\"] = plan[\"Week\"].map(normalise_week)\nprint(plan[[\"Week\", \"ISO code\"]].to_string(index=False))\n","python",[14,109,110,120,134,141,181,186,199,224,305,317,326,337,369,394,416,457,462,484],{"__ignoreMap":44},[48,111,112,116],{"class":50,"line":51},[48,113,115],{"class":114},"s-kum","import",[48,117,119],{"class":118},"skGVy"," re\n",[48,121,123,125,128,131],{"class":50,"line":122},2,[48,124,115],{"class":114},[48,126,127],{"class":118}," pandas ",[48,129,130],{"class":114},"as",[48,132,133],{"class":118}," pd\n",[48,135,137],{"class":50,"line":136},3,[48,138,140],{"emptyLinePlaceholder":139},true,"\n",[48,142,144,147,150,153,156,159,163,165,168,171,174,178],{"class":50,"line":143},4,[48,145,146],{"class":118},"plan ",[48,148,149],{"class":114},"=",[48,151,152],{"class":118}," pd.read_excel(",[48,154,155],{"class":58},"\"plan.xlsx\"",[48,157,158],{"class":118},", ",[48,160,162],{"class":161},"sa561","dtype",[48,164,149],{"class":114},[48,166,167],{"class":118},"{",[48,169,170],{"class":58},"\"Week\"",[48,172,173],{"class":118},": ",[48,175,177],{"class":176},"sP0c6","str",[48,179,180],{"class":118},"})\n",[48,182,184],{"class":50,"line":183},5,[48,185,140],{"emptyLinePlaceholder":139},[48,187,189,192,196],{"class":50,"line":188},6,[48,190,191],{"class":114},"def",[48,193,195],{"class":194},"s_Opv"," normalise_week",[48,197,198],{"class":118},"(code):\n",[48,200,202,205,207,210,213,216,218,221],{"class":50,"line":201},7,[48,203,204],{"class":118},"    s ",[48,206,149],{"class":114},[48,208,209],{"class":176}," str",[48,211,212],{"class":118},"(code).upper().replace(",[48,214,215],{"class":58},"\"WK\"",[48,217,158],{"class":118},[48,219,220],{"class":58},"\"W\"",[48,222,223],{"class":118},")\n",[48,225,227,230,232,235,238,241,244,247,250,253,256,259,261,264,267,269,272,275,277,279,282,284,286,288,290,293,295,297,300,302],{"class":50,"line":226},8,[48,228,229],{"class":118},"    m ",[48,231,149],{"class":114},[48,233,234],{"class":118}," re.search(",[48,236,237],{"class":114},"r",[48,239,240],{"class":58},"\"",[48,242,243],{"class":176},"(\\d",[48,245,246],{"class":114},"{4}",[48,248,249],{"class":176},")\\D",[48,251,252],{"class":114},"*",[48,254,255],{"class":58},"W",[48,257,258],{"class":114},"?",[48,260,243],{"class":176},[48,262,263],{"class":114},"{1,2}",[48,265,266],{"class":176},")\\b",[48,268,240],{"class":58},[48,270,271],{"class":118},", s) ",[48,273,274],{"class":114},"or",[48,276,234],{"class":118},[48,278,237],{"class":114},[48,280,281],{"class":58},"\"W",[48,283,258],{"class":114},[48,285,243],{"class":176},[48,287,263],{"class":114},[48,289,249],{"class":176},[48,291,292],{"class":114},"+",[48,294,243],{"class":176},[48,296,246],{"class":114},[48,298,299],{"class":176},")",[48,301,240],{"class":58},[48,303,304],{"class":118},", s)\n",[48,306,308,311,314],{"class":50,"line":307},9,[48,309,310],{"class":114},"    if",[48,312,313],{"class":114}," not",[48,315,316],{"class":118}," m:\n",[48,318,320,323],{"class":50,"line":319},10,[48,321,322],{"class":114},"        return",[48,324,325],{"class":176}," None\n",[48,327,329,332,334],{"class":50,"line":328},11,[48,330,331],{"class":118},"    a, b ",[48,333,149],{"class":114},[48,335,336],{"class":118}," m.groups()\n",[48,338,340,343,345,348,351,354,357,360,363,366],{"class":50,"line":339},12,[48,341,342],{"class":118},"    year, week ",[48,344,149],{"class":114},[48,346,347],{"class":118}," (a, b) ",[48,349,350],{"class":114},"if",[48,352,353],{"class":176}," len",[48,355,356],{"class":118},"(a) ",[48,358,359],{"class":114},"==",[48,361,362],{"class":176}," 4",[48,364,365],{"class":114}," else",[48,367,368],{"class":118}," (b, a)\n",[48,370,372,374,376,379,381,384,387,390],{"class":50,"line":371},13,[48,373,310],{"class":114},[48,375,353],{"class":176},[48,377,378],{"class":118},"(s.strip()) ",[48,380,359],{"class":114},[48,382,383],{"class":176}," 6",[48,385,386],{"class":114}," and",[48,388,389],{"class":118}," s.isdigit():               ",[48,391,393],{"class":392},"s-wDw","# 202615 → year 2026, week 15\n",[48,395,397,400,402,405,408,411,413],{"class":50,"line":396},14,[48,398,399],{"class":118},"        year, week ",[48,401,149],{"class":114},[48,403,404],{"class":118}," s[:",[48,406,407],{"class":176},"4",[48,409,410],{"class":118},"], s[",[48,412,407],{"class":176},[48,414,415],{"class":118},":]\n",[48,417,419,422,425,427,430,433,436,439,442,444,446,449,452,454],{"class":50,"line":418},15,[48,420,421],{"class":114},"    return",[48,423,424],{"class":114}," f",[48,426,240],{"class":58},[48,428,167],{"class":429},"sSjpA",[48,431,432],{"class":176},"int",[48,434,435],{"class":118},"(year)",[48,437,438],{"class":429},"}",[48,440,441],{"class":58},"-W",[48,443,167],{"class":429},[48,445,432],{"class":176},[48,447,448],{"class":118},"(week)",[48,450,451],{"class":114},":02d",[48,453,438],{"class":429},[48,455,456],{"class":58},"\"\n",[48,458,460],{"class":50,"line":459},16,[48,461,140],{"emptyLinePlaceholder":139},[48,463,465,468,471,474,476,479,481],{"class":50,"line":464},17,[48,466,467],{"class":118},"plan[",[48,469,470],{"class":58},"\"ISO code\"",[48,472,473],{"class":118},"] ",[48,475,149],{"class":114},[48,477,478],{"class":118}," plan[",[48,480,170],{"class":58},[48,482,483],{"class":118},"].map(normalise_week)\n",[48,485,487,490,493,495,497,499,502,505,507,510],{"class":50,"line":486},18,[48,488,489],{"class":176},"print",[48,491,492],{"class":118},"(plan[[",[48,494,170],{"class":58},[48,496,158],{"class":118},[48,498,470],{"class":58},[48,500,501],{"class":118},"]].to_string(",[48,503,504],{"class":161},"index",[48,506,149],{"class":114},[48,508,509],{"class":176},"False",[48,511,512],{"class":118},"))\n",[10,514,515,158,517,158,520,158,523,158,526,87,529,532,533,535,536,539,540,542,543,546,547,550],{},[14,516,16],{},[14,518,519],{},"202615",[14,521,522],{},"wk 16\u002F2026",[14,524,525],{},"2026W17",[14,527,528],{},"W2 2027",[14,530,531],{},"2026 - 18"," all become ",[14,534,101],{},". Reading the column with ",[14,537,538],{},"dtype=str"," matters: a code like ",[14,541,519],{}," would otherwise arrive as a number, and one like ",[14,544,545],{},"2026-W01"," could be mangled by date guessing. Codes the pattern cannot interpret come back as ",[14,548,549],{},"None"," and should be reported rather than guessed. This normalised form is the ISO 8601 week notation, which is also what Excel users see in many planning add-ins.",[34,552,554],{"id":553},"step-2-iso-week-codes-to-dates","Step 2 — ISO week codes to dates",[10,556,557,558,561,562,565,566,569],{},"Python's ",[14,559,560],{},"%G"," (ISO year), ",[14,563,564],{},"%V"," (ISO week) and ",[14,567,568],{},"%u"," (ISO weekday, Monday = 1) parse ISO weeks correctly. Add a weekday to the code and parse:",[39,571,573],{"className":105,"code":572,"language":107,"meta":44,"style":44},"plan[\"Week start\"] = pd.to_datetime(plan[\"ISO code\"] + \"-1\", format=\"%G-W%V-%u\", errors=\"coerce\")\nplan[\"Week end\"] = plan[\"Week start\"] + pd.Timedelta(days=6)\nprint(plan[[\"ISO code\", \"Week start\", \"Week end\"]].to_string(index=False))\n",[14,574,575,628,660],{"__ignoreMap":44},[48,576,577,579,582,584,586,589,591,593,595,598,600,603,605,607,609,612,614,616,618,621,623,626],{"class":50,"line":51},[48,578,467],{"class":118},[48,580,581],{"class":58},"\"Week start\"",[48,583,473],{"class":118},[48,585,149],{"class":114},[48,587,588],{"class":118}," pd.to_datetime(plan[",[48,590,470],{"class":58},[48,592,473],{"class":118},[48,594,292],{"class":114},[48,596,597],{"class":58}," \"-1\"",[48,599,158],{"class":118},[48,601,602],{"class":161},"format",[48,604,149],{"class":114},[48,606,240],{"class":58},[48,608,560],{"class":429},[48,610,611],{"class":58},"-W%V-",[48,613,568],{"class":429},[48,615,240],{"class":58},[48,617,158],{"class":118},[48,619,620],{"class":161},"errors",[48,622,149],{"class":114},[48,624,625],{"class":58},"\"coerce\"",[48,627,223],{"class":118},[48,629,630,632,635,637,639,641,643,645,647,650,653,655,658],{"class":50,"line":122},[48,631,467],{"class":118},[48,633,634],{"class":58},"\"Week end\"",[48,636,473],{"class":118},[48,638,149],{"class":114},[48,640,478],{"class":118},[48,642,581],{"class":58},[48,644,473],{"class":118},[48,646,292],{"class":114},[48,648,649],{"class":118}," pd.Timedelta(",[48,651,652],{"class":161},"days",[48,654,149],{"class":114},[48,656,657],{"class":176},"6",[48,659,223],{"class":118},[48,661,662,664,666,668,670,672,674,676,678,680,682,684],{"class":50,"line":136},[48,663,489],{"class":176},[48,665,492],{"class":118},[48,667,470],{"class":58},[48,669,158],{"class":118},[48,671,581],{"class":58},[48,673,158],{"class":118},[48,675,634],{"class":58},[48,677,501],{"class":118},[48,679,504],{"class":161},[48,681,149],{"class":114},[48,683,509],{"class":176},[48,685,512],{"class":118},[10,687,688,690,691,694,695,699,700,702,703,706,707,709,710,713,714,717],{},[14,689,16],{}," starts on Monday 30 March 2026 and ends on Sunday 5 April. ",[14,692,693],{},"2025-W01"," starts on Monday 30 December ",[696,697,698],"strong",{},"2024"," — ISO week 1 is the week containing the first Thursday of the year (equivalently, the week containing 4 January), so it can begin in the previous December. That is the most common surprise with week codes, and the reason the format uses ",[14,701,560],{}," (ISO year) rather than ",[14,704,705],{},"%Y"," (calendar year): with ",[14,708,705],{}," the result would be wrong around every new year. ",[14,711,712],{},"errors=\"coerce\""," turns codes that cannot exist into ",[14,715,716],{},"NaT"," instead of raising, which the next step uses.",[719,720,728,729,728,733,728,737,728,744,728,751,728,761,728,768,728,773,728,777,728,781,728,787,728,793,728,796,728,800,728,802,728,804,728,809,728,814,728,817,728,819,728,821,728,826,728,832,728,836,728,840,728,842,728,844,728,847,728,851,728,854,728,856],"svg",{"viewBox":721,"role":722,"ariaLabelledBy":723,"xmlns":726,"style":727},"4 0 752 245","img",[724,725],"wk-iso1-t","wk-iso1-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  ",[730,731,732],"title",{"id":724},"Where ISO week 1 of 2025 begins",[734,735,736],"desc",{"id":725},"ISO week 1 of 2025 is the week containing Thursday 2 January 2025, so it starts on Monday 30 December 2024. The last days of December 2024 therefore belong to week 1 of ISO year 2025, not to the last week of 2024.",[738,739],"rect",{"x":407,"y":740,"width":741,"height":742,"fill":743},"0","752","245","#ffffff",[50,745],{"x1":746,"y1":747,"x2":748,"y2":747,"stroke":749,"style":750},"76.0","122.5","684.0","var(--muted,#5b6780)","stroke-width:2.5px",[738,752],{"x":753,"y":754,"width":755,"height":756,"rx":757,"fill":758,"stroke":759,"style":760},"20.0","16.0","136.0","74.5","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[762,763,767],"text",{"x":764,"y":765,"style":766},"88.0","49.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Mon 23 Dec 2024",[762,769,772],{"x":764,"y":770,"style":771},"67.5","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","2024-W52",[50,774],{"x1":764,"y1":775,"x2":764,"y2":747,"stroke":749,"style":776},"90.5","stroke-width:1.5px",[778,779],"circle",{"cx":764,"cy":747,"r":780,"fill":749,"stroke":743,"style":760},"7.0",[738,782],{"x":783,"y":784,"width":755,"height":756,"rx":757,"fill":785,"stroke":786,"style":760},"166.0","154.5","#fdefd8","var(--gold,#b4740a)",[762,788,792],{"x":789,"y":790,"style":791},"234.0","179.2","font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Mon 30 Dec",[762,794,698],{"x":789,"y":795,"style":791},"195.8",[762,797,799],{"x":789,"y":798,"style":771},"214.2","2025-W01 starts",[50,801],{"x1":789,"y1":747,"x2":789,"y2":784,"stroke":749,"style":776},[778,803],{"cx":789,"cy":747,"r":780,"fill":786,"stroke":743,"style":760},[738,805],{"x":806,"y":754,"width":755,"height":756,"rx":757,"fill":807,"stroke":808,"style":760},"312.0","#f0f4ff","var(--brand,#5b5cf0)",[762,810,813],{"x":811,"y":765,"style":812},"380.0","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Wed 1 Jan 2025",[762,815,816],{"x":811,"y":770,"style":771},"still 2025-W01",[50,818],{"x1":811,"y1":775,"x2":811,"y2":747,"stroke":749,"style":776},[778,820],{"cx":811,"cy":747,"r":780,"fill":808,"stroke":743,"style":760},[738,822],{"x":823,"y":784,"width":755,"height":756,"rx":757,"fill":824,"stroke":825,"style":760},"458.0","#d9f4f1","var(--teal,#0f9488)",[762,827,831],{"x":828,"y":829,"style":830},"526.0","179.0","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Thu 2 Jan 2025",[762,833,835],{"x":828,"y":834,"style":771},"197.5","the Thursday that",[762,837,839],{"x":828,"y":838,"style":771},"214.5","decides",[50,841],{"x1":828,"y1":747,"x2":828,"y2":784,"stroke":749,"style":776},[778,843],{"cx":828,"cy":747,"r":780,"fill":825,"stroke":743,"style":760},[738,845],{"x":846,"y":754,"width":755,"height":756,"rx":757,"fill":758,"stroke":759,"style":760},"604.0",[762,848,850],{"x":849,"y":765,"style":766},"672.0","Mon 6 Jan 2025",[762,852,853],{"x":849,"y":770,"style":771},"2025-W02",[50,855],{"x1":849,"y1":775,"x2":849,"y2":747,"stroke":749,"style":776},[778,857],{"cx":849,"cy":747,"r":780,"fill":749,"stroke":743,"style":760},[34,859,861],{"id":860},"step-3-week-53-and-catching-codes-that-cannot-exist","Step 3 — Week 53, and catching codes that cannot exist",[10,863,864],{},"A year has 53 ISO weeks when 1 January falls on a Thursday, or on a Wednesday in a leap year; otherwise 52. Python can tell you how many weeks any year has — 28 December is always in the last week:",[39,866,868],{"className":105,"code":867,"language":107,"meta":44,"style":44},"from datetime import date\n\ndef iso_weeks_in(year):\n    return date(year, 12, 28).isocalendar().week\n\nprint({y: iso_weeks_in(y) for y in range(2024, 2030)})\nplan[\"Year\"] = plan[\"ISO code\"].str[:4].astype(int)\nplan[\"Weeknum\"] = plan[\"ISO code\"].str[-2:].astype(int)\nplan[\"Valid\"] = plan[\"Weeknum\"].between(1, 52) | (plan[\"Weeknum\"] == plan[\"Year\"].map(iso_weeks_in))\nprint(plan.loc[~plan[\"Valid\"], [\"Week\", \"ISO code\"]].to_string(index=False))\nprint(\"2026-W53 starts:\", plan.loc[plan[\"ISO code\"] == \"2026-W53\", \"Week start\"].iloc[0].date())\n",[14,869,870,883,887,897,914,918,950,977,1008,1056,1089],{"__ignoreMap":44},[48,871,872,875,878,880],{"class":50,"line":51},[48,873,874],{"class":114},"from",[48,876,877],{"class":118}," datetime ",[48,879,115],{"class":114},[48,881,882],{"class":118}," date\n",[48,884,885],{"class":50,"line":122},[48,886,140],{"emptyLinePlaceholder":139},[48,888,889,891,894],{"class":50,"line":136},[48,890,191],{"class":114},[48,892,893],{"class":194}," iso_weeks_in",[48,895,896],{"class":118},"(year):\n",[48,898,899,901,904,906,908,911],{"class":50,"line":143},[48,900,421],{"class":114},[48,902,903],{"class":118}," date(year, ",[48,905,757],{"class":176},[48,907,158],{"class":118},[48,909,910],{"class":176},"28",[48,912,913],{"class":118},").isocalendar().week\n",[48,915,916],{"class":50,"line":183},[48,917,140],{"emptyLinePlaceholder":139},[48,919,920,922,925,928,931,934,937,940,942,944,947],{"class":50,"line":188},[48,921,489],{"class":176},[48,923,924],{"class":118},"({y: iso_weeks_in(y) ",[48,926,927],{"class":114},"for",[48,929,930],{"class":118}," y ",[48,932,933],{"class":114},"in",[48,935,936],{"class":176}," range",[48,938,939],{"class":118},"(",[48,941,698],{"class":176},[48,943,158],{"class":118},[48,945,946],{"class":176},"2030",[48,948,949],{"class":118},")})\n",[48,951,952,954,957,959,961,963,965,968,970,973,975],{"class":50,"line":201},[48,953,467],{"class":118},[48,955,956],{"class":58},"\"Year\"",[48,958,473],{"class":118},[48,960,149],{"class":114},[48,962,478],{"class":118},[48,964,470],{"class":58},[48,966,967],{"class":118},"].str[:",[48,969,407],{"class":176},[48,971,972],{"class":118},"].astype(",[48,974,432],{"class":176},[48,976,223],{"class":118},[48,978,979,981,984,986,988,990,992,995,998,1001,1004,1006],{"class":50,"line":226},[48,980,467],{"class":118},[48,982,983],{"class":58},"\"Weeknum\"",[48,985,473],{"class":118},[48,987,149],{"class":114},[48,989,478],{"class":118},[48,991,470],{"class":58},[48,993,994],{"class":118},"].str[",[48,996,997],{"class":114},"-",[48,999,1000],{"class":176},"2",[48,1002,1003],{"class":118},":].astype(",[48,1005,432],{"class":176},[48,1007,223],{"class":118},[48,1009,1010,1012,1015,1017,1019,1021,1023,1026,1029,1031,1034,1037,1040,1043,1045,1047,1049,1051,1053],{"class":50,"line":307},[48,1011,467],{"class":118},[48,1013,1014],{"class":58},"\"Valid\"",[48,1016,473],{"class":118},[48,1018,149],{"class":114},[48,1020,478],{"class":118},[48,1022,983],{"class":58},[48,1024,1025],{"class":118},"].between(",[48,1027,1028],{"class":176},"1",[48,1030,158],{"class":118},[48,1032,1033],{"class":176},"52",[48,1035,1036],{"class":118},") ",[48,1038,1039],{"class":114},"|",[48,1041,1042],{"class":118}," (plan[",[48,1044,983],{"class":58},[48,1046,473],{"class":118},[48,1048,359],{"class":114},[48,1050,478],{"class":118},[48,1052,956],{"class":58},[48,1054,1055],{"class":118},"].map(iso_weeks_in))\n",[48,1057,1058,1060,1063,1066,1068,1070,1073,1075,1077,1079,1081,1083,1085,1087],{"class":50,"line":319},[48,1059,489],{"class":176},[48,1061,1062],{"class":118},"(plan.loc[",[48,1064,1065],{"class":114},"~",[48,1067,467],{"class":118},[48,1069,1014],{"class":58},[48,1071,1072],{"class":118},"], [",[48,1074,170],{"class":58},[48,1076,158],{"class":118},[48,1078,470],{"class":58},[48,1080,501],{"class":118},[48,1082,504],{"class":161},[48,1084,149],{"class":114},[48,1086,509],{"class":176},[48,1088,512],{"class":118},[48,1090,1091,1093,1095,1098,1101,1103,1105,1107,1110,1112,1114,1117,1119],{"class":50,"line":328},[48,1092,489],{"class":176},[48,1094,939],{"class":118},[48,1096,1097],{"class":58},"\"2026-W53 starts:\"",[48,1099,1100],{"class":118},", plan.loc[plan[",[48,1102,470],{"class":58},[48,1104,473],{"class":118},[48,1106,359],{"class":114},[48,1108,1109],{"class":58}," \"2026-W53\"",[48,1111,158],{"class":118},[48,1113,581],{"class":58},[48,1115,1116],{"class":118},"].iloc[",[48,1118,740],{"class":176},[48,1120,1121],{"class":118},"].date())\n",[10,1123,1124,1125,1127,1128,1130,1131,87,1133,1135,1136,1138],{},"Of the years shown, only 2026 has 53 ISO weeks — 1 January 2026 is a Thursday — so ",[14,1126,78],{}," is a real week starting Monday 28 December 2026, while ",[14,1129,74],{}," cannot exist. Here the parser rejected ",[14,1132,74],{},[14,1134,712],{}," turned it into ",[14,1137,716],{},", but an explicit check like this one is still worth having: it states the rule, catches the problem before any join, and produces a list someone can act on rather than a silent blank. Report invalid codes back to whoever produced the plan; the likely fix is a typo.",[719,1140,728,1145,728,1148,728,1151,728,1154,728,1158,728,1169,728,1175,728,1179,728,1184,728,1189,728,1192,728,1199,728,1204,728,1207,728,1211,728,1214,728,1217,728,1224,728,1230,728,1234,728,1238,728,1242,728,1245,728,1248,728,1255,728,1260,728,1263],{"viewBox":1141,"role":722,"ariaLabelledBy":1142,"xmlns":726,"style":727},"4 0 752 142",[1143,1144],"wk-flow-t","wk-flow-d",[730,1146,1147],{"id":1143},"From messy week codes to dates",[734,1149,1150],{"id":1144},"Read the codes as text, normalise every style to YYYY-Www with a regular expression, parse with %G-W%V-%u to get each week's Monday, validate week numbers against the number of ISO weeks in the year, and add six days for the Sunday.",[738,1152],{"x":407,"y":740,"width":741,"height":1153,"fill":743},"142",[738,1155],{"x":753,"y":754,"width":1156,"height":1157,"rx":757,"fill":758,"stroke":759,"style":760},"153.0","110.5",[1159,1160,1162],"g",{"transform":1161},"translate(84.5,37.8) scale(1.000)",[1163,1164],"path",{"d":1165,"fill":1166,"stroke":1167,"style":1168},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[762,1170,1174],{"x":1171,"y":1172,"style":1173},"96.5","84.2","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Read as text",[762,1176,538],{"x":1171,"y":1177,"style":1178},"103.8","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[50,1180],{"x1":1181,"y1":1182,"x2":1183,"y2":1182,"stroke":808,"style":760},"178.0","71.2","200.0",[1185,1186],"polygon",{"points":1187,"fill":1188},"204.0,71.2 195.0,76.2 195.0,66.2","#5b5cf0",[738,1190],{"x":1191,"y":754,"width":1156,"height":1157,"rx":757,"fill":807,"stroke":808,"style":760},"209.0",[1159,1193,1195],{"transform":1194},"translate(273.5,37.8) scale(1.000)",[1163,1196],{"d":1197,"fill":1166,"stroke":1198,"style":1168},"M20 11a8 8 0 1 0-2.3 5.7 M20 4v7h-7","var(--brand-strong,#4338ca)",[762,1200,1203],{"x":1201,"y":1172,"style":1202},"285.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Normalise",[762,1205,1206],{"x":1201,"y":1177,"style":1178},"→ 2026-W14",[50,1208],{"x1":1209,"y1":1182,"x2":1210,"y2":1182,"stroke":808,"style":760},"367.0","389.0",[1185,1212],{"points":1213,"fill":1188},"393.0,71.2 384.0,76.2 384.0,66.2",[738,1215],{"x":1216,"y":754,"width":1156,"height":1157,"rx":757,"fill":824,"stroke":825,"style":760},"398.0",[1159,1218,1220],{"transform":1219},"translate(462.5,29.0) scale(1.000)",[1163,1221],{"d":1222,"fill":1166,"stroke":1223,"style":1168},"M4 5h16v15H4z M4 10h16 M8 3v4 M16 3v4","var(--teal-ink,#0b6157)",[762,1225,1229],{"x":1226,"y":1227,"style":1228},"474.5","75.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Parse",[762,1231,1233],{"x":1226,"y":1232,"style":1178},"95.0","%G-W%V-%u →",[762,1235,1237],{"x":1226,"y":1236,"style":1178},"112.5","Monday",[50,1239],{"x1":1240,"y1":1182,"x2":1241,"y2":1182,"stroke":808,"style":760},"556.0","578.0",[1185,1243],{"points":1244,"fill":1188},"582.0,71.2 573.0,76.2 573.0,66.2",[738,1246],{"x":1247,"y":754,"width":1156,"height":1157,"rx":757,"fill":785,"stroke":786,"style":760},"587.0",[1159,1249,1251],{"transform":1250},"translate(651.5,29.0) scale(1.000)",[1163,1252],{"d":1253,"fill":1166,"stroke":1254,"style":1168},"M5 12.5l4.5 4.5L19 7","var(--gold-ink,#7a4e06)",[762,1256,1259],{"x":1257,"y":1227,"style":1258},"663.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Validate",[762,1261,1262],{"x":1257,"y":1232,"style":1178},"week 53 only when it",[762,1264,1265],{"x":1257,"y":1236,"style":1178},"exists",[34,1267,1269],{"id":1268},"step-4-check-against-excel-and-sunday-based-weeks","Step 4 — Check against Excel, and Sunday-based weeks",[10,1271,1272,1273,1275,1276,1278],{},"Excel has two week functions: ",[14,1274,86],{},", which follows ISO 8601, and ",[14,1277,90],{},", which by default starts weeks on Sunday and counts week 1 as the week containing 1 January. Compare both with Python for dates around a new year:",[39,1280,1282],{"className":105,"code":1281,"language":107,"meta":44,"style":44},"import subprocess\nfrom openpyxl import Workbook, load_workbook\n\ntest_dates = pd.to_datetime([\"2024-12-29\", \"2024-12-30\", \"2025-01-01\", \"2025-01-05\", \"2025-01-06\", \"2026-12-31\", \"2027-01-03\"])\nwb = Workbook()\nws = wb.active\nfor i, d in enumerate(test_dates, start=1):\n    ws.cell(i, 1, d.to_pydatetime())\n    ws.cell(i, 2, f\"=_xlfn.ISOWEEKNUM(A{i})\")\n    ws.cell(i, 3, f\"=WEEKNUM(A{i})\")\nwb.save(\"weeks.xlsx\")\nsubprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"xlsx\", \"--outdir\", \"calc\", \"weeks.xlsx\"], check=True, capture_output=True)\nc = load_workbook(\"calc\u002Fweeks.xlsx\", data_only=True).active\n\nfor i, d in enumerate(test_dates, start=1):\n    iso = d.isocalendar()\n    us_week = int(d.strftime(\"%U\")) + (0 if date(d.year, 1, 1).weekday() == 6 else 1)\n    print(f\"{d:%a %d %b %Y}  ISO {iso.year}-W{iso.week:02d} (ISOWEEKNUM {c.cell(i, 2).value})  \"\n          f\"US-style week {us_week} (LibreOffice WEEKNUM {c.cell(i, 3).value})\")\n",[14,1283,1284,1291,1303,1307,1353,1363,1373,1398,1408,1434,1458,1468,1527,1552,1556,1576,1586,1638,1719],{"__ignoreMap":44},[48,1285,1286,1288],{"class":50,"line":51},[48,1287,115],{"class":114},[48,1289,1290],{"class":118}," subprocess\n",[48,1292,1293,1295,1298,1300],{"class":50,"line":122},[48,1294,874],{"class":114},[48,1296,1297],{"class":118}," openpyxl ",[48,1299,115],{"class":114},[48,1301,1302],{"class":118}," Workbook, load_workbook\n",[48,1304,1305],{"class":50,"line":136},[48,1306,140],{"emptyLinePlaceholder":139},[48,1308,1309,1312,1314,1317,1320,1322,1325,1327,1330,1332,1335,1337,1340,1342,1345,1347,1350],{"class":50,"line":143},[48,1310,1311],{"class":118},"test_dates ",[48,1313,149],{"class":114},[48,1315,1316],{"class":118}," pd.to_datetime([",[48,1318,1319],{"class":58},"\"2024-12-29\"",[48,1321,158],{"class":118},[48,1323,1324],{"class":58},"\"2024-12-30\"",[48,1326,158],{"class":118},[48,1328,1329],{"class":58},"\"2025-01-01\"",[48,1331,158],{"class":118},[48,1333,1334],{"class":58},"\"2025-01-05\"",[48,1336,158],{"class":118},[48,1338,1339],{"class":58},"\"2025-01-06\"",[48,1341,158],{"class":118},[48,1343,1344],{"class":58},"\"2026-12-31\"",[48,1346,158],{"class":118},[48,1348,1349],{"class":58},"\"2027-01-03\"",[48,1351,1352],{"class":118},"])\n",[48,1354,1355,1358,1360],{"class":50,"line":183},[48,1356,1357],{"class":118},"wb ",[48,1359,149],{"class":114},[48,1361,1362],{"class":118}," Workbook()\n",[48,1364,1365,1368,1370],{"class":50,"line":188},[48,1366,1367],{"class":118},"ws ",[48,1369,149],{"class":114},[48,1371,1372],{"class":118}," wb.active\n",[48,1374,1375,1377,1380,1382,1385,1388,1391,1393,1395],{"class":50,"line":201},[48,1376,927],{"class":114},[48,1378,1379],{"class":118}," i, d ",[48,1381,933],{"class":114},[48,1383,1384],{"class":176}," enumerate",[48,1386,1387],{"class":118},"(test_dates, ",[48,1389,1390],{"class":161},"start",[48,1392,149],{"class":114},[48,1394,1028],{"class":176},[48,1396,1397],{"class":118},"):\n",[48,1399,1400,1403,1405],{"class":50,"line":226},[48,1401,1402],{"class":118},"    ws.cell(i, ",[48,1404,1028],{"class":176},[48,1406,1407],{"class":118},", d.to_pydatetime())\n",[48,1409,1410,1412,1414,1416,1419,1422,1424,1427,1429,1432],{"class":50,"line":307},[48,1411,1402],{"class":118},[48,1413,1000],{"class":176},[48,1415,158],{"class":118},[48,1417,1418],{"class":114},"f",[48,1420,1421],{"class":58},"\"=_xlfn.ISOWEEKNUM(A",[48,1423,167],{"class":429},[48,1425,1426],{"class":118},"i",[48,1428,438],{"class":429},[48,1430,1431],{"class":58},")\"",[48,1433,223],{"class":118},[48,1435,1436,1438,1441,1443,1445,1448,1450,1452,1454,1456],{"class":50,"line":319},[48,1437,1402],{"class":118},[48,1439,1440],{"class":176},"3",[48,1442,158],{"class":118},[48,1444,1418],{"class":114},[48,1446,1447],{"class":58},"\"=WEEKNUM(A",[48,1449,167],{"class":429},[48,1451,1426],{"class":118},[48,1453,438],{"class":429},[48,1455,1431],{"class":58},[48,1457,223],{"class":118},[48,1459,1460,1463,1466],{"class":50,"line":328},[48,1461,1462],{"class":118},"wb.save(",[48,1464,1465],{"class":58},"\"weeks.xlsx\"",[48,1467,223],{"class":118},[48,1469,1470,1473,1476,1478,1481,1483,1486,1488,1491,1493,1496,1498,1501,1503,1505,1508,1511,1513,1516,1518,1521,1523,1525],{"class":50,"line":339},[48,1471,1472],{"class":118},"subprocess.run([",[48,1474,1475],{"class":58},"\"soffice\"",[48,1477,158],{"class":118},[48,1479,1480],{"class":58},"\"--headless\"",[48,1482,158],{"class":118},[48,1484,1485],{"class":58},"\"--convert-to\"",[48,1487,158],{"class":118},[48,1489,1490],{"class":58},"\"xlsx\"",[48,1492,158],{"class":118},[48,1494,1495],{"class":58},"\"--outdir\"",[48,1497,158],{"class":118},[48,1499,1500],{"class":58},"\"calc\"",[48,1502,158],{"class":118},[48,1504,1465],{"class":58},[48,1506,1507],{"class":118},"], ",[48,1509,1510],{"class":161},"check",[48,1512,149],{"class":114},[48,1514,1515],{"class":176},"True",[48,1517,158],{"class":118},[48,1519,1520],{"class":161},"capture_output",[48,1522,149],{"class":114},[48,1524,1515],{"class":176},[48,1526,223],{"class":118},[48,1528,1529,1532,1534,1537,1540,1542,1545,1547,1549],{"class":50,"line":371},[48,1530,1531],{"class":118},"c ",[48,1533,149],{"class":114},[48,1535,1536],{"class":118}," load_workbook(",[48,1538,1539],{"class":58},"\"calc\u002Fweeks.xlsx\"",[48,1541,158],{"class":118},[48,1543,1544],{"class":161},"data_only",[48,1546,149],{"class":114},[48,1548,1515],{"class":176},[48,1550,1551],{"class":118},").active\n",[48,1553,1554],{"class":50,"line":396},[48,1555,140],{"emptyLinePlaceholder":139},[48,1557,1558,1560,1562,1564,1566,1568,1570,1572,1574],{"class":50,"line":418},[48,1559,927],{"class":114},[48,1561,1379],{"class":118},[48,1563,933],{"class":114},[48,1565,1384],{"class":176},[48,1567,1387],{"class":118},[48,1569,1390],{"class":161},[48,1571,149],{"class":114},[48,1573,1028],{"class":176},[48,1575,1397],{"class":118},[48,1577,1578,1581,1583],{"class":50,"line":459},[48,1579,1580],{"class":118},"    iso ",[48,1582,149],{"class":114},[48,1584,1585],{"class":118}," d.isocalendar()\n",[48,1587,1588,1591,1593,1596,1599,1602,1605,1607,1610,1612,1615,1618,1620,1622,1624,1627,1629,1631,1633,1636],{"class":50,"line":464},[48,1589,1590],{"class":118},"    us_week ",[48,1592,149],{"class":114},[48,1594,1595],{"class":176}," int",[48,1597,1598],{"class":118},"(d.strftime(",[48,1600,1601],{"class":58},"\"%U\"",[48,1603,1604],{"class":118},")) ",[48,1606,292],{"class":114},[48,1608,1609],{"class":118}," (",[48,1611,740],{"class":176},[48,1613,1614],{"class":114}," if",[48,1616,1617],{"class":118}," date(d.year, ",[48,1619,1028],{"class":176},[48,1621,158],{"class":118},[48,1623,1028],{"class":176},[48,1625,1626],{"class":118},").weekday() ",[48,1628,359],{"class":114},[48,1630,383],{"class":176},[48,1632,365],{"class":114},[48,1634,1635],{"class":176}," 1",[48,1637,223],{"class":118},[48,1639,1640,1643,1645,1647,1649,1651,1654,1657,1660,1663,1665,1668,1670,1673,1675,1678,1680,1683,1685,1688,1690,1692,1694,1697,1699,1701,1704,1706,1709,1711,1714,1716],{"class":50,"line":486},[48,1641,1642],{"class":176},"    print",[48,1644,939],{"class":118},[48,1646,1418],{"class":114},[48,1648,240],{"class":58},[48,1650,167],{"class":429},[48,1652,1653],{"class":118},"d",[48,1655,1656],{"class":114},":",[48,1658,1659],{"class":118},"%",[48,1661,1662],{"class":114},"a ",[48,1664,1659],{"class":118},[48,1666,1667],{"class":114},"d ",[48,1669,1659],{"class":118},[48,1671,1672],{"class":114},"b ",[48,1674,1659],{"class":118},[48,1676,1677],{"class":114},"Y",[48,1679,438],{"class":429},[48,1681,1682],{"class":58},"  ISO ",[48,1684,167],{"class":429},[48,1686,1687],{"class":118},"iso.year",[48,1689,438],{"class":429},[48,1691,441],{"class":58},[48,1693,167],{"class":429},[48,1695,1696],{"class":118},"iso.week",[48,1698,451],{"class":114},[48,1700,438],{"class":429},[48,1702,1703],{"class":58}," (ISOWEEKNUM ",[48,1705,167],{"class":429},[48,1707,1708],{"class":118},"c.cell(i, ",[48,1710,1000],{"class":176},[48,1712,1713],{"class":118},").value",[48,1715,438],{"class":429},[48,1717,1718],{"class":58},")  \"\n",[48,1720,1722,1725,1728,1730,1733,1735,1738,1740,1742,1744,1746,1748,1750],{"class":50,"line":1721},19,[48,1723,1724],{"class":114},"          f",[48,1726,1727],{"class":58},"\"US-style week ",[48,1729,167],{"class":429},[48,1731,1732],{"class":118},"us_week",[48,1734,438],{"class":429},[48,1736,1737],{"class":58}," (LibreOffice WEEKNUM ",[48,1739,167],{"class":429},[48,1741,1708],{"class":118},[48,1743,1440],{"class":176},[48,1745,1713],{"class":118},[48,1747,438],{"class":429},[48,1749,1431],{"class":58},[48,1751,223],{"class":118},[10,1753,557,1754,1757,1758,1760,1761,1763,1764,1767,1768,1771,1772,1774,1775,1778,1779,1781],{},[14,1755,1756],{},"isocalendar()"," agrees with ",[14,1759,86],{}," on every date, including 29 December 2024 (ISO week 52 of 2024) and 30 December 2024 (week 1 of 2025). ",[14,1762,86],{}," is another function stored with the ",[14,1765,1766],{},"_xlfn."," prefix; without it the spreadsheet shows ",[14,1769,1770],{},"#NAME?",". The US-style column follows Excel's documented ",[14,1773,90],{}," rule — weeks start on Sunday and week 1 is the week containing 1 January, numbered within the date's own calendar year — computed with Python's ",[14,1776,1777],{},"%U"," plus one unless the year starts on a Sunday, so 30 December 2024 is week 53 and 3 January 2027 week 2. LibreOffice's ",[14,1780,90],{},", however, returned week 1 for the last days of December 2024 and 2026 in this test, numbering them as part of the next year's first week. Spreadsheet programs evidently do not agree on Sunday-based week numbers at the year boundary, which is one more reason to prefer ISO weeks, and to ask which program and convention produced a file's week numbers before converting them. If it came from a European ERP or planning system it is almost always ISO.",[719,1783,728,1788,728,1791,728,1794,728,1797,728,1802,728,1808,728,1814,728,1820,728,1825,728,1829,728,1833,728,1837,728,1839,728,1844,728,1849,728,1853,728,1856,728,1859,728,1862],{"viewBox":1784,"role":722,"ariaLabelledBy":1785,"xmlns":726,"style":727},"4 0 752 233",[1786,1787],"wk-systems-t","wk-systems-d",[730,1789,1790],{"id":1786},"ISO weeks versus US-style weeks",[734,1792,1793],{"id":1787},"ISO weeks start on Monday, week 1 contains the first Thursday of the year, a week belongs to the ISO year of its Thursday, and some years have week 53. Excel's documented default WEEKNUM starts weeks on Sunday and week 1 always contains 1 January, so the two numbers differ around the new year, and spreadsheet programs differ at the boundary.",[738,1795],{"x":407,"y":740,"width":741,"height":1796,"fill":743},"233",[738,1798],{"x":753,"y":754,"width":1799,"height":1800,"rx":1801,"fill":824,"stroke":825,"style":760},"351.0","201.0","14",[1159,1803,1805],{"transform":1804},"translate(182.5,29.0) scale(1.083)",[1163,1806],{"d":1222,"fill":1166,"stroke":1223,"style":1807},"stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[762,1809,1813],{"x":1810,"y":1811,"style":1812},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","ISO 8601 (ISOWEEKNUM, %G-%V)",[50,1815],{"x1":1816,"y1":1817,"x2":1818,"y2":1817,"stroke":825,"style":1819},"36.0","91.0","355.0","stroke-width:1px",[762,1821,1824],{"x":1810,"y":1822,"style":1823},"111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","weeks start on Monday",[762,1826,1828],{"x":1810,"y":1827,"style":1823},"137.0","week 1 holds the first Thursday",[762,1830,1832],{"x":1810,"y":1831,"style":1823},"163.0","29 Dec can be week 1 of next year",[762,1834,1836],{"x":1810,"y":1835,"style":1823},"189.0","52 or 53 weeks",[738,1838],{"x":1210,"y":754,"width":1799,"height":1800,"rx":1801,"fill":785,"stroke":786,"style":760},[1159,1840,1842],{"transform":1841},"translate(551.5,29.0) scale(1.083)",[1163,1843],{"d":1222,"fill":1166,"stroke":1254,"style":1807},[762,1845,1848],{"x":1846,"y":1811,"style":1847},"564.5","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","US style (WEEKNUM, %U)",[50,1850],{"x1":1851,"y1":1817,"x2":1852,"y2":1817,"stroke":786,"style":1819},"405.0","724.0",[762,1854,1855],{"x":1846,"y":1822,"style":1823},"weeks start on Sunday",[762,1857,1858],{"x":1846,"y":1827,"style":1823},"week 1 holds 1 January",[762,1860,1861],{"x":1846,"y":1831,"style":1823},"partial first and last weeks",[762,1863,1864],{"x":1846,"y":1835,"style":1823},"up to 54 week numbers",[34,1866,1868],{"id":1867},"step-5-from-dates-back-to-week-codes-and-joining","Step 5 — From dates back to week codes, and joining",[10,1870,1871,1872,1874],{},"Going the other way — labelling daily data with week codes so it can be joined to the plan — uses ",[14,1873,1756],{}," on a date column:",[39,1876,1878],{"className":105,"code":1877,"language":107,"meta":44,"style":44},"daily = pd.DataFrame({\"Date\": pd.date_range(\"2026-03-30\", \"2026-05-10\", freq=\"D\")})\niso = daily[\"Date\"].dt.isocalendar()\ndaily[\"ISO code\"] = iso[\"year\"].astype(str) + \"-W\" + iso[\"week\"].astype(str).str.zfill(2)\ndaily[\"Units made\"] = 180\nactual = daily.groupby(\"ISO code\")[\"Units made\"].sum()\njoined = plan.set_index(\"ISO code\")[[\"Week start\", \"Planned units\"]].join(actual, how=\"inner\")\nprint(joined.to_string())\nplan.drop(columns=[\"Year\", \"Weeknum\"]).to_excel(\"plan_with_dates.xlsx\", index=False)\n",[14,1879,1880,1916,1931,1978,1992,2012,2047,2054],{"__ignoreMap":44},[48,1881,1882,1885,1887,1890,1893,1896,1899,1901,1904,1906,1909,1911,1914],{"class":50,"line":51},[48,1883,1884],{"class":118},"daily ",[48,1886,149],{"class":114},[48,1888,1889],{"class":118}," pd.DataFrame({",[48,1891,1892],{"class":58},"\"Date\"",[48,1894,1895],{"class":118},": pd.date_range(",[48,1897,1898],{"class":58},"\"2026-03-30\"",[48,1900,158],{"class":118},[48,1902,1903],{"class":58},"\"2026-05-10\"",[48,1905,158],{"class":118},[48,1907,1908],{"class":161},"freq",[48,1910,149],{"class":114},[48,1912,1913],{"class":58},"\"D\"",[48,1915,949],{"class":118},[48,1917,1918,1921,1923,1926,1928],{"class":50,"line":122},[48,1919,1920],{"class":118},"iso ",[48,1922,149],{"class":114},[48,1924,1925],{"class":118}," daily[",[48,1927,1892],{"class":58},[48,1929,1930],{"class":118},"].dt.isocalendar()\n",[48,1932,1933,1936,1938,1940,1942,1945,1948,1950,1952,1954,1956,1959,1962,1964,1967,1969,1971,1974,1976],{"class":50,"line":136},[48,1934,1935],{"class":118},"daily[",[48,1937,470],{"class":58},[48,1939,473],{"class":118},[48,1941,149],{"class":114},[48,1943,1944],{"class":118}," iso[",[48,1946,1947],{"class":58},"\"year\"",[48,1949,972],{"class":118},[48,1951,177],{"class":176},[48,1953,1036],{"class":118},[48,1955,292],{"class":114},[48,1957,1958],{"class":58}," \"-W\"",[48,1960,1961],{"class":114}," +",[48,1963,1944],{"class":118},[48,1965,1966],{"class":58},"\"week\"",[48,1968,972],{"class":118},[48,1970,177],{"class":176},[48,1972,1973],{"class":118},").str.zfill(",[48,1975,1000],{"class":176},[48,1977,223],{"class":118},[48,1979,1980,1982,1985,1987,1989],{"class":50,"line":143},[48,1981,1935],{"class":118},[48,1983,1984],{"class":58},"\"Units made\"",[48,1986,473],{"class":118},[48,1988,149],{"class":114},[48,1990,1991],{"class":176}," 180\n",[48,1993,1994,1997,1999,2002,2004,2007,2009],{"class":50,"line":183},[48,1995,1996],{"class":118},"actual ",[48,1998,149],{"class":114},[48,2000,2001],{"class":118}," daily.groupby(",[48,2003,470],{"class":58},[48,2005,2006],{"class":118},")[",[48,2008,1984],{"class":58},[48,2010,2011],{"class":118},"].sum()\n",[48,2013,2014,2017,2019,2022,2024,2027,2029,2031,2034,2037,2040,2042,2045],{"class":50,"line":188},[48,2015,2016],{"class":118},"joined ",[48,2018,149],{"class":114},[48,2020,2021],{"class":118}," plan.set_index(",[48,2023,470],{"class":58},[48,2025,2026],{"class":118},")[[",[48,2028,581],{"class":58},[48,2030,158],{"class":118},[48,2032,2033],{"class":58},"\"Planned units\"",[48,2035,2036],{"class":118},"]].join(actual, ",[48,2038,2039],{"class":161},"how",[48,2041,149],{"class":114},[48,2043,2044],{"class":58},"\"inner\"",[48,2046,223],{"class":118},[48,2048,2049,2051],{"class":50,"line":201},[48,2050,489],{"class":176},[48,2052,2053],{"class":118},"(joined.to_string())\n",[48,2055,2056,2059,2062,2064,2067,2069,2071,2073,2076,2079,2081,2083,2085,2087],{"class":50,"line":226},[48,2057,2058],{"class":118},"plan.drop(",[48,2060,2061],{"class":161},"columns",[48,2063,149],{"class":114},[48,2065,2066],{"class":118},"[",[48,2068,956],{"class":58},[48,2070,158],{"class":118},[48,2072,983],{"class":58},[48,2074,2075],{"class":118},"]).to_excel(",[48,2077,2078],{"class":58},"\"plan_with_dates.xlsx\"",[48,2080,158],{"class":118},[48,2082,504],{"class":161},[48,2084,149],{"class":114},[48,2086,509],{"class":176},[48,2088,223],{"class":118},[10,2090,2091,2094,2095,2097,2098,2101,2102,2105,2106,2109,2110,32],{},[14,2092,2093],{},"Series.dt.isocalendar()"," returns ISO year, week and day for every date; combined into the same ",[14,2096,101],{}," form, the codes join directly to the plan. Using the ISO ",[23,2099,2100],{},"year"," from ",[14,2103,2104],{},"isocalendar"," — not ",[14,2107,2108],{},"Date.dt.year"," — is what keeps 30 December 2024 in week 2025-W01 on both sides of the join. The plan with real week start and end dates is written back to Excel, ready for a calendar view or a chart with a proper date axis. Fiscal weeks that follow a company calendar instead of ISO are covered in ",[28,2111,2113],{"href":2112},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fbuild-a-4-4-5-fiscal-calendar-with-pandas\u002F","Build a 4-4-5 Fiscal Calendar with pandas",[34,2115,2117],{"id":2116},"common-pitfalls","Common pitfalls",[2119,2120,2121,2133,2143,2149,2155],"ul",{},[2122,2123,2124,2132],"li",{},[696,2125,2126,2128,2129,2131],{},[14,2127,705],{}," instead of ",[14,2130,560],{}," for ISO weeks."," Dates around new year land in the wrong year.",[2122,2134,2135,2138,2139,2142],{},[696,2136,2137],{},"Week codes read as numbers."," Read the column as text so codes like ",[14,2140,2141],{},"202601"," survive.",[2122,2144,2145,2148],{},[696,2146,2147],{},"Trusting the parser with week 53."," Check validity explicitly; some Python versions roll over silently.",[2122,2150,2151,2154],{},[696,2152,2153],{},"Mixing ISO and US week numbers."," They differ around every new year; confirm the convention.",[2122,2156,2157,2160,2161,2163],{},[696,2158,2159],{},"Calendar year in the join key."," Use the ISO year from ",[14,2162,1756],{}," when building codes from dates.",[34,2165,2167],{"id":2166},"conclusion","Conclusion",[10,2169,2170,2171,2173,2174,2177,2178,87,2180,2182,2183,2185],{},"To convert week codes to dates, first normalise every style to ",[14,2172,101],{},", then parse with ",[14,2175,2176],{},"pd.to_datetime(code + \"-1\", format=\"%G-W%V-%u\")"," to get each ISO week's Monday, adding six days for its Sunday. Remember that ISO week 1 can start in the previous December and that only some years have a week 53 — validate codes rather than trusting the parser — and check whether a file uses ISO weeks or US-style Sunday weeks, which Excel's ",[14,2179,86],{},[14,2181,90],{}," distinguish. To join daily data to week-based plans, build codes from dates with ",[14,2184,1756],{},", using the ISO year.",[34,2187,2189],{"id":2188},"frequently-asked-questions","Frequently asked questions",[10,2191,2192,2195],{},[696,2193,2194],{},"How do I convert 2026-W14 to a date in pandas?","\nAppend a weekday and parse with ISO directives: pd.to_datetime(\"2026-W14\" + \"-1\", format=\"%G-W%V-%u\") returns Monday 30 March 2026.",[10,2197,2198,2201],{},[696,2199,2200],{},"Why does week 1 of 2025 start in December 2024?","\nISO week 1 is the week containing the first Thursday of the year. In 2025 that is 2 January, so the week starts on Monday 30 December 2024.",[10,2203,2204,2207],{},[696,2205,2206],{},"Which years have 53 ISO weeks?","\nYears in which 1 January falls on a Thursday, or on a Wednesday in a leap year. date(year, 12, 28).isocalendar().week gives the number of weeks in any year.",[10,2209,2210,2213],{},[696,2211,2212],{},"What is the difference between Excel's WEEKNUM and ISOWEEKNUM?","\nISOWEEKNUM follows ISO 8601 with Monday-start weeks. WEEKNUM by default starts weeks on Sunday and counts the week containing 1 January as week 1; spreadsheet programs differ in how they number the last days of December.",[34,2215,2217],{"id":2216},"related","Related",[2119,2219,2220,2226,2233,2240],{},[2122,2221,2222,2223,2225],{},"Up one level: ",[28,2224,31],{"href":30}," — dates in Excel data.",[2122,2227,2228,2232],{},[28,2229,2231],{"href":2230},"\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"," — grouping by week.",[2122,2234,2235,2239],{},[28,2236,2238],{"href":2237},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-text-dates-like-20240131-with-pandas\u002F","Convert Text Dates like 20240131 with pandas"," — other compact date codes.",[2122,2241,2242,2246],{},[28,2243,2245],{"href":2244},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fbuild-a-date-table-for-excel-reports-with-pandas\u002F","Build a Date Table for Excel Reports with pandas"," — weeks alongside months and quarters.",[2248,2249,2250],"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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":44,"searchDepth":122,"depth":122,"links":2252},[2253,2254,2255,2256,2257,2258,2259,2260,2261,2262],{"id":36,"depth":122,"text":37},{"id":94,"depth":122,"text":95},{"id":553,"depth":122,"text":554},{"id":860,"depth":122,"text":861},{"id":1268,"depth":122,"text":1269},{"id":1867,"depth":122,"text":1868},{"id":2116,"depth":122,"text":2117},{"id":2166,"depth":122,"text":2167},{"id":2188,"depth":122,"text":2189},{"id":2216,"depth":122,"text":2217},"2026-10-02","Turn week codes such as 2026-W14, 202614 or \"wk 14\u002F2026\" from planning and ERP exports into real dates with pandas: ISO weeks that start on Monday and belong to the ISO year, parsing with %G-%V-%u, the Monday and Sunday of each week, week 53 and which years have it, US-style Sunday weeks, Excel's ISOWEEKNUM and WEEKNUM, and going from dates back to week codes.","md",[2267,2269,2271,2273],{"q":2194,"a":2268},"Append a weekday and parse with ISO directives: pd.to_datetime(\"2026-W14\" + \"-1\", format=\"%G-W%V-%u\") returns Monday 30 March 2026.",{"q":2200,"a":2270},"ISO week 1 is the week containing the first Thursday of the year. In 2025 that is 2 January, so the week starts on Monday 30 December 2024.",{"q":2206,"a":2272},"Years in which 1 January falls on a Thursday, or on a Wednesday in a leap year. date(year, 12, 28).isocalendar().week gives the number of weeks in any year.",{"q":2212,"a":2274},"ISOWEEKNUM follows ISO 8601 with Monday-start weeks. WEEKNUM by default starts weeks on Sunday and counts the week containing 1 January as week 1; spreadsheet programs differ in how they number the last days of December.","convert year-week codes to dates with pandas",null,{"breadcrumb":2278},[2279,2282,2285],{"name":2280,"item":2281},"Home","\u002F",{"name":2283,"item":2284},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":31,"item":30},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-year-week-codes-to-dates-with-pandas",{"title":2288,"description":2289},"Convert Year-Week Codes like 2026-W14 to Dates in pandas","Parse ISO week codes from Excel with pandas: normalise 2026-W14, 202614 and wk 14\u002F2026, use %G-%V-%u, get week start and end dates, handle week 53, compare with US weeks and Excel ISOWEEKNUM and WEEKNUM.","convert-year-week-codes-to-dates-with-pandas","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-year-week-codes-to-dates-with-pandas\u002Findex",[2293],"dates-and-times","how-to","LXkK3SzpBeLGAisXzMwRUyao3acBncXgGMcWrpqgAkQ",[2297,2301],{"title":2298,"path":2299,"stem":2300,"children":-1},"Convert Unix Timestamps to Excel Dates with pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-unix-timestamps-to-excel-dates-with-pandas","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-unix-timestamps-to-excel-dates-with-pandas\u002Findex",{"title":2302,"path":2303,"stem":2304,"children":-1},"Create a Shift Calendar with Rotating Patterns","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcreate-a-shift-calendar-with-rotating-patterns","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcreate-a-shift-calendar-with-rotating-patterns\u002Findex",[2306,2308],{"path":2307,"title":2283},"\u002Fadvanced-data-transformation-and-cleaning",{"path":2309,"title":31},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data",[],[2312,2315,2318,2321,2324,2327],{"path":2313,"title":2314,"kind":2294},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-overdue-dates-with-openpyxl","Highlight Overdue Dates in Excel with openpyxl",{"path":2316,"title":2317,"kind":2294},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-weekends-and-holidays-in-a-date-column","Highlight Weekends and Holidays in a Date Column",{"path":2319,"title":2320,"kind":2294},"\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":2322,"title":2323,"kind":2294},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcalculate-moving-averages-with-pandas-rolling","Calculate Moving Averages with pandas rolling",{"path":2325,"title":2326,"kind":2294},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Feomonth-and-edate-equivalents-in-pandas","EOMONTH and EDATE Equivalents in pandas",{"path":2328,"title":2329,"kind":2294},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fyearfrac-and-days360-equivalents-in-python","YEARFRAC and DAYS360 Equivalents in Python",1790972105339]