[{"data":1,"prerenderedAt":1878},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-the-1904-date-system-in-excel-files":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-the-1904-date-system-in-excel-files":1844,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-the-1904-date-system-in-excel-files":1853,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-the-1904-date-system-in-excel-files":1858,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-the-1904-date-system-in-excel-files":1859},{"id":4,"title":5,"body":6,"dateModified":1809,"datePublished":1809,"description":1810,"extension":1811,"faq":1812,"keyword":1821,"kind":1822,"meta":1823,"navigation":235,"path":1832,"seo":1833,"slug":1836,"stem":1837,"topics":1838,"type":1842,"__hash__":1843},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-the-1904-date-system-in-excel-files\u002Findex.md","Handle the 1904 Date System in Excel Files with Python",{"type":7,"value":8,"toc":1794},"minimark",[9,19,131,136,164,178,182,193,365,372,376,379,468,471,475,478,587,590,758,762,765,968,971,975,981,1209,1220,1369,1373,1376,1536,1539,1543,1546,1642,1645,1649,1705,1709,1719,1723,1729,1733,1739,1745,1751,1757,1761,1790],[10,11,12,13,18],"p",{},"Excel stores a date as a number of days since an origin. Almost every workbook uses the 1900 system, in which 46082 is 1 March 2026. A minority — historically, workbooks created on early Macs, and any workbook where someone ticked \"Use 1904 date system\" — count from 1 January 1904 instead, and there the same date is 44620. As long as a cell is formatted as a date, Excel and Python libraries convert it correctly. When serial numbers travel as plain numbers — an unformatted column, a CSV export, values pasted between workbooks — they arrive exactly four years and one day wrong. This page, part of ",[14,15,17],"a",{"href":16},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002F","Working with Dates and Times in Excel Data",", shows how to detect the system and convert correctly.",[20,21,29,30,29,34,29,38,29,46,29,54,29,64,29,71,29,76,29,80,29,84,29,90,29,96,29,100,29,104,29,106,29,108,29,113,29,119,29,123,29,127,29,129],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"4 0 752 245","img",[25,26],"epoch-line-t","epoch-line-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  ",[31,32,33],"title",{"id":25},"One date, two serial numbers",[35,36,37],"desc",{"id":26},"A timeline showing the two origins. The 1900 system counts from 30 December 1899, the 1904 system from 1 January 1904, 1,462 days later. The date 1 March 2026 is serial 46082 in the 1900 system and serial 44620 in the 1904 system.",[39,40],"rect",{"x":41,"y":42,"width":43,"height":44,"fill":45},"4","0","752","245","#ffffff",[47,48],"line",{"x1":49,"y1":50,"x2":51,"y2":50,"stroke":52,"style":53},"96.0","122.5","664.0","var(--muted,#5b6780)","stroke-width:2.5px",[39,55],{"x":56,"y":57,"width":58,"height":59,"rx":60,"fill":61,"stroke":62,"style":63},"20.0","16.0","176.0","74.5","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[65,66,70],"text",{"x":67,"y":68,"style":69},"108.0","49.0","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","1899-12-30",[65,72,75],{"x":67,"y":73,"style":74},"67.5","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","1900 system origin (day 0)",[47,77],{"x1":67,"y1":78,"x2":67,"y2":50,"stroke":52,"style":79},"90.5","stroke-width:1.5px",[81,82],"circle",{"cx":67,"cy":50,"r":83,"fill":62,"stroke":45,"style":63},"7.0",[39,85],{"x":86,"y":87,"width":58,"height":59,"rx":60,"fill":88,"stroke":89,"style":63},"292.0","154.5","#fdefd8","var(--gold,#b4740a)",[65,91,95],{"x":92,"y":93,"style":94},"380.0","179.0","font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","1904-01-01",[65,97,99],{"x":92,"y":98,"style":74},"197.5","1904 system origin, +1,462",[65,101,103],{"x":92,"y":102,"style":74},"214.5","days",[47,105],{"x1":92,"y1":50,"x2":92,"y2":87,"stroke":52,"style":79},[81,107],{"cx":92,"cy":50,"r":83,"fill":89,"stroke":45,"style":63},[39,109],{"x":110,"y":57,"width":58,"height":59,"rx":60,"fill":111,"stroke":112,"style":63},"564.0","#d9f4f1","var(--teal,#0f9488)",[65,114,118],{"x":115,"y":116,"style":117},"652.0","40.5","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","2026-03-01",[65,120,122],{"x":115,"y":121,"style":74},"59.0","46082 (1900) = 44620",[65,124,126],{"x":115,"y":125,"style":74},"76.0","(1904)",[47,128],{"x1":115,"y1":78,"x2":115,"y2":50,"stroke":52,"style":79},[81,130],{"cx":115,"cy":50,"r":83,"fill":112,"stroke":45,"style":63},[132,133,135],"h2",{"id":134},"prerequisites","Prerequisites",[137,138,143],"pre",{"className":139,"code":140,"language":141,"meta":142,"style":142},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pandas\n","bash","",[144,145,146],"code",{"__ignoreMap":142},[147,148,150,154,158,161],"span",{"class":47,"line":149},1,[147,151,153],{"class":152},"sMTad","pip",[147,155,157],{"class":156},"srMev"," install",[147,159,160],{"class":156}," openpyxl",[147,162,163],{"class":156}," pandas\n",[10,165,166,169,170,173,174,177],{},[144,167,168],{},"windows.xlsx"," and ",[144,171,172],{},"mac1904.xlsx"," each contain one order shipped on 1 March 2026, stored as a date-formatted cell, and a ",[144,175,176],{},"due_serial"," column holding the same date as a plain, unformatted serial number — 46082 in the 1900 workbook, 44620 in the 1904 one. That second column is the kind of thing an export or a formula produces.",[132,179,181],{"id":180},"step-1-detect-the-date-system","Step 1 — Detect the date system",[10,183,184,185,188,189,192],{},"openpyxl reads the workbook's ",[144,186,187],{},"date1904"," flag and exposes it as ",[144,190,191],{},"wb.epoch",":",[137,194,198],{"className":195,"code":196,"language":197,"meta":142,"style":142},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import load_workbook\nfrom openpyxl.utils.datetime import CALENDAR_MAC_1904\n\ndef date_system(path):\n    wb = load_workbook(path, read_only=True)\n    try:\n        return \"1904\" if wb.epoch == CALENDAR_MAC_1904 else \"1900\"\n    finally:\n        wb.close()\n\nfor name in (\"windows.xlsx\", \"mac1904.xlsx\"):\n    print(name, date_system(name))\n","python",[144,199,200,216,230,237,250,274,283,310,318,324,329,356],{"__ignoreMap":142},[147,201,202,206,210,213],{"class":47,"line":149},[147,203,205],{"class":204},"s-kum","from",[147,207,209],{"class":208},"skGVy"," openpyxl ",[147,211,212],{"class":204},"import",[147,214,215],{"class":208}," load_workbook\n",[147,217,219,221,224,226],{"class":47,"line":218},2,[147,220,205],{"class":204},[147,222,223],{"class":208}," openpyxl.utils.datetime ",[147,225,212],{"class":204},[147,227,229],{"class":228},"sP0c6"," CALENDAR_MAC_1904\n",[147,231,233],{"class":47,"line":232},3,[147,234,236],{"emptyLinePlaceholder":235},true,"\n",[147,238,240,243,247],{"class":47,"line":239},4,[147,241,242],{"class":204},"def",[147,244,246],{"class":245},"s_Opv"," date_system",[147,248,249],{"class":208},"(path):\n",[147,251,253,256,259,262,266,268,271],{"class":47,"line":252},5,[147,254,255],{"class":208},"    wb ",[147,257,258],{"class":204},"=",[147,260,261],{"class":208}," load_workbook(path, ",[147,263,265],{"class":264},"sa561","read_only",[147,267,258],{"class":204},[147,269,270],{"class":228},"True",[147,272,273],{"class":208},")\n",[147,275,277,280],{"class":47,"line":276},6,[147,278,279],{"class":204},"    try",[147,281,282],{"class":208},":\n",[147,284,286,289,292,295,298,301,304,307],{"class":47,"line":285},7,[147,287,288],{"class":204},"        return",[147,290,291],{"class":156}," \"1904\"",[147,293,294],{"class":204}," if",[147,296,297],{"class":208}," wb.epoch ",[147,299,300],{"class":204},"==",[147,302,303],{"class":228}," CALENDAR_MAC_1904",[147,305,306],{"class":204}," else",[147,308,309],{"class":156}," \"1900\"\n",[147,311,313,316],{"class":47,"line":312},8,[147,314,315],{"class":204},"    finally",[147,317,282],{"class":208},[147,319,321],{"class":47,"line":320},9,[147,322,323],{"class":208},"        wb.close()\n",[147,325,327],{"class":47,"line":326},10,[147,328,236],{"emptyLinePlaceholder":235},[147,330,332,335,338,341,344,347,350,353],{"class":47,"line":331},11,[147,333,334],{"class":204},"for",[147,336,337],{"class":208}," name ",[147,339,340],{"class":204},"in",[147,342,343],{"class":208}," (",[147,345,346],{"class":156},"\"windows.xlsx\"",[147,348,349],{"class":208},", ",[147,351,352],{"class":156},"\"mac1904.xlsx\"",[147,354,355],{"class":208},"):\n",[147,357,359,362],{"class":47,"line":358},12,[147,360,361],{"class":228},"    print",[147,363,364],{"class":208},"(name, date_system(name))\n",[10,366,367,368,371],{},"In the file itself the setting is one attribute on the workbook part, ",[144,369,370],{},"\u003CworkbookPr date1904=\"1\"\u002F>",", so a zip-level check works too when openpyxl is not available. Checking is cheap enough to do for every file a job reads, and far cheaper than finding the problem in a finished report.",[132,373,375],{"id":374},"step-2-see-that-date-formatted-cells-are-already-right","Step 2 — See that date-formatted cells are already right",[10,377,378],{},"For cells formatted as dates, the libraries apply the right origin automatically:",[137,380,382],{"className":195,"code":381,"language":197,"meta":142,"style":142},"import pandas as pd\n\nfor name in (\"windows.xlsx\", \"mac1904.xlsx\"):\n    print(name, pd.read_excel(name).iloc[0][\"shipped\"], \"| calamine:\",\n          pd.read_excel(name, engine=\"calamine\").iloc[0][\"shipped\"])\n",[144,383,384,397,401,419,443],{"__ignoreMap":142},[147,385,386,388,391,394],{"class":47,"line":149},[147,387,212],{"class":204},[147,389,390],{"class":208}," pandas ",[147,392,393],{"class":204},"as",[147,395,396],{"class":208}," pd\n",[147,398,399],{"class":47,"line":218},[147,400,236],{"emptyLinePlaceholder":235},[147,402,403,405,407,409,411,413,415,417],{"class":47,"line":232},[147,404,334],{"class":204},[147,406,337],{"class":208},[147,408,340],{"class":204},[147,410,343],{"class":208},[147,412,346],{"class":156},[147,414,349],{"class":208},[147,416,352],{"class":156},[147,418,355],{"class":208},[147,420,421,423,426,428,431,434,437,440],{"class":47,"line":239},[147,422,361],{"class":228},[147,424,425],{"class":208},"(name, pd.read_excel(name).iloc[",[147,427,42],{"class":228},[147,429,430],{"class":208},"][",[147,432,433],{"class":156},"\"shipped\"",[147,435,436],{"class":208},"], ",[147,438,439],{"class":156},"\"| calamine:\"",[147,441,442],{"class":208},",\n",[147,444,445,448,451,453,456,459,461,463,465],{"class":47,"line":252},[147,446,447],{"class":208},"          pd.read_excel(name, ",[147,449,450],{"class":264},"engine",[147,452,258],{"class":204},[147,454,455],{"class":156},"\"calamine\"",[147,457,458],{"class":208},").iloc[",[147,460,42],{"class":228},[147,462,430],{"class":208},[147,464,433],{"class":156},[147,466,467],{"class":208},"])\n",[10,469,470],{},"Both files give 1 March 2026 with both the openpyxl and the calamine engines. If all the dates you care about are formatted cells, a 1904 workbook needs no special handling at all. The rest of this page is about the numbers that are not formatted.",[132,472,474],{"id":473},"step-3-convert-raw-serial-numbers-with-the-right-origin","Step 3 — Convert raw serial numbers with the right origin",[10,476,477],{},"An unformatted serial is just a number to every reader, and converting it requires knowing which origin it counts from. With the wrong one, the date is off by 1,462 days:",[137,479,481],{"className":195,"code":480,"language":197,"meta":142,"style":142},"mac = pd.read_excel(\"mac1904.xlsx\")\nserial = mac.loc[0, \"due_serial\"]\nprint(serial)\nprint(\"as 1900:\", pd.to_datetime(serial, unit=\"D\", origin=\"1899-12-30\").date())\nprint(\"as 1904:\", pd.to_datetime(serial, unit=\"D\", origin=\"1904-01-01\").date())\n",[144,482,483,497,517,525,559],{"__ignoreMap":142},[147,484,485,488,490,493,495],{"class":47,"line":149},[147,486,487],{"class":208},"mac ",[147,489,258],{"class":204},[147,491,492],{"class":208}," pd.read_excel(",[147,494,352],{"class":156},[147,496,273],{"class":208},[147,498,499,502,504,507,509,511,514],{"class":47,"line":218},[147,500,501],{"class":208},"serial ",[147,503,258],{"class":204},[147,505,506],{"class":208}," mac.loc[",[147,508,42],{"class":228},[147,510,349],{"class":208},[147,512,513],{"class":156},"\"due_serial\"",[147,515,516],{"class":208},"]\n",[147,518,519,522],{"class":47,"line":232},[147,520,521],{"class":228},"print",[147,523,524],{"class":208},"(serial)\n",[147,526,527,529,532,535,538,541,543,546,548,551,553,556],{"class":47,"line":239},[147,528,521],{"class":228},[147,530,531],{"class":208},"(",[147,533,534],{"class":156},"\"as 1900:\"",[147,536,537],{"class":208},", pd.to_datetime(serial, ",[147,539,540],{"class":264},"unit",[147,542,258],{"class":204},[147,544,545],{"class":156},"\"D\"",[147,547,349],{"class":208},[147,549,550],{"class":264},"origin",[147,552,258],{"class":204},[147,554,555],{"class":156},"\"1899-12-30\"",[147,557,558],{"class":208},").date())\n",[147,560,561,563,565,568,570,572,574,576,578,580,582,585],{"class":47,"line":252},[147,562,521],{"class":228},[147,564,531],{"class":208},[147,566,567],{"class":156},"\"as 1904:\"",[147,569,537],{"class":208},[147,571,540],{"class":264},[147,573,258],{"class":204},[147,575,545],{"class":156},[147,577,349],{"class":208},[147,579,550],{"class":264},[147,581,258],{"class":204},[147,583,584],{"class":156},"\"1904-01-01\"",[147,586,558],{"class":208},[10,588,589],{},"Read as a 1900 serial, 44620 is 28 February 2022 — four years and one day early. With the 1904 origin it is 1 March 2026, matching the formatted column. The 1900 system's origin is written as 30 December 1899, not 1 January 1900, because Excel's 1900 system counts a nonexistent 29 February 1900; using the 30th makes every date from March 1900 onwards come out right.",[20,591,29,597,29,600,29,603,29,608,29,617,29,621,29,627,29,631,29,636,29,638,29,641,29,644,29,648,29,650,29,654,29,657,29,661,29,663,29,665,29,668,29,672,29,675,29,679,29,683,29,685,29,688,29,690,29,692,29,694,29,697,29,699,29,701,29,703,29,705,29,709,29,713,29,715,29,717,29,719,29,721,29,723,29,726,29,731,29,735,29,740,29,745,29,753],{"viewBox":592,"role":23,"ariaLabelledBy":593,"xmlns":27,"style":596},"62 0 636 164",[594,595],"epoch-wrong-t","epoch-wrong-d","width:100%;max-width:636px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,598,599],{"id":594},"The same serial converted with each origin",[35,601,602],{"id":595},"Left: the serial 44620 from a 1904-system workbook. Right: converted with the 1900 origin it becomes 2022-02-28, four years and one day early; converted with the 1904 origin it becomes 2026-03-01, the correct date.",[39,604],{"x":605,"y":42,"width":606,"height":607,"fill":45},"62","636","164",[39,609],{"x":610,"y":611,"width":612,"height":613,"rx":42,"fill":614,"stroke":615,"style":616},"78.3","40.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[39,618],{"x":619,"y":611,"width":620,"height":613,"rx":42,"fill":614,"stroke":615,"style":616},"110.3","87.1",[65,622,626],{"x":623,"y":624,"style":625},"153.9","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[39,628],{"x":610,"y":629,"width":612,"height":630,"rx":42,"fill":614,"stroke":615,"style":616},"64.0","28.0",[65,632,635],{"x":633,"y":634,"style":625},"94.3","83.0","1",[39,637],{"x":619,"y":629,"width":620,"height":630,"rx":42,"fill":45,"stroke":615,"style":616},[65,639,176],{"x":623,"y":634,"style":640},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[39,642],{"x":610,"y":643,"width":612,"height":630,"rx":42,"fill":614,"stroke":615,"style":616},"92.0",[65,645,647],{"x":633,"y":646,"style":625},"111.0","2",[39,649],{"x":619,"y":643,"width":620,"height":630,"rx":42,"fill":45,"stroke":615,"style":616},[65,651,653],{"x":623,"y":646,"style":652},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","44620",[39,655],{"x":610,"y":656,"width":612,"height":630,"rx":42,"fill":614,"stroke":615,"style":616},"120.0",[65,658,660],{"x":633,"y":659,"style":625},"139.0","3",[39,662],{"x":619,"y":656,"width":620,"height":630,"rx":42,"fill":45,"stroke":615,"style":616},[65,664,653],{"x":623,"y":659,"style":652},[39,666],{"x":667,"y":611,"width":612,"height":613,"rx":42,"fill":614,"stroke":615,"style":616},"453.3",[39,669],{"x":670,"y":611,"width":671,"height":613,"rx":42,"fill":614,"stroke":615,"style":616},"485.3","95.8",[65,673,626],{"x":674,"y":624,"style":625},"533.1",[39,676],{"x":677,"y":611,"width":678,"height":613,"rx":42,"fill":614,"stroke":615,"style":616},"581.0","100.7",[65,680,682],{"x":681,"y":624,"style":625},"631.4","B",[39,684],{"x":667,"y":629,"width":612,"height":630,"rx":42,"fill":614,"stroke":615,"style":616},[65,686,635],{"x":687,"y":634,"style":625},"469.3",[39,689],{"x":670,"y":629,"width":671,"height":630,"rx":42,"fill":45,"stroke":615,"style":616},[65,691,550],{"x":674,"y":634,"style":640},[39,693],{"x":677,"y":629,"width":678,"height":630,"rx":42,"fill":45,"stroke":615,"style":616},[65,695,696],{"x":681,"y":634,"style":640},"date",[39,698],{"x":667,"y":643,"width":612,"height":630,"rx":42,"fill":614,"stroke":615,"style":616},[65,700,647],{"x":687,"y":646,"style":625},[39,702],{"x":670,"y":643,"width":671,"height":630,"rx":42,"fill":45,"stroke":615,"style":616},[65,704,70],{"x":674,"y":646,"style":652},[39,706],{"x":677,"y":643,"width":678,"height":630,"rx":42,"fill":707,"stroke":708,"style":79},"#fee8f2","var(--accent,#f43f8f)",[65,710,712],{"x":681,"y":646,"style":711},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","2022-02-28",[39,714],{"x":667,"y":656,"width":612,"height":630,"rx":42,"fill":614,"stroke":615,"style":616},[65,716,660],{"x":687,"y":659,"style":625},[39,718],{"x":670,"y":656,"width":671,"height":630,"rx":42,"fill":45,"stroke":615,"style":616},[65,720,95],{"x":674,"y":659,"style":652},[39,722],{"x":677,"y":656,"width":678,"height":630,"rx":42,"fill":111,"stroke":112,"style":79},[65,724,118],{"x":681,"y":659,"style":725},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[65,727,176],{"x":728,"y":729,"style":730},"137.9","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle",[65,732,734],{"x":733,"y":729,"style":730},"567.5","converted",[47,736],{"x1":737,"y1":738,"x2":739,"y2":738,"stroke":62,"style":63},"207.4","94.0","439.3",[741,742],"polygon",{"points":743,"fill":744},"443.3,94.0 434.3,99.0 434.3,89.0","#5b5cf0",[39,746],{"x":747,"y":748,"width":749,"height":750,"rx":751,"fill":45,"stroke":615,"style":752},"239.0","61.0","172.7","22.0","11","stroke-width:1.2px",[65,754,757],{"x":755,"y":125,"style":756},"325.3","font-size:11.5px;font-weight:400;fill:var(--brand-strong,#4338ca);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","to_datetime(origin=…)",[132,759,761],{"id":760},"step-4-convert-serial-columns-using-each-files-own-system","Step 4 — Convert serial columns using each file's own system",[10,763,764],{},"When a job reads many files, pick the origin per file from Step 1 rather than assuming. A small helper keeps the rule in one place:",[137,766,768],{"className":195,"code":767,"language":197,"meta":142,"style":142},"ORIGINS = {\"1900\": \"1899-12-30\", \"1904\": \"1904-01-01\"}\n\ndef read_with_serial_dates(path, serial_columns):\n    frame = pd.read_excel(path)\n    origin = ORIGINS[date_system(path)]\n    for col in serial_columns:\n        frame[col] = pd.to_datetime(frame[col], unit=\"D\", origin=origin)\n    frame[\"source\"] = path\n    return frame\n\ncombined = pd.concat([read_with_serial_dates(p, [\"due_serial\"]) for p in (\"windows.xlsx\", \"mac1904.xlsx\")])\nprint(combined[[\"source\", \"shipped\", \"due_serial\"]].to_string(index=False))\n",[144,769,770,801,805,815,825,838,851,876,892,900,904,937],{"__ignoreMap":142},[147,771,772,775,778,781,784,787,789,791,794,796,798],{"class":47,"line":149},[147,773,774],{"class":228},"ORIGINS",[147,776,777],{"class":204}," =",[147,779,780],{"class":208}," {",[147,782,783],{"class":156},"\"1900\"",[147,785,786],{"class":208},": ",[147,788,555],{"class":156},[147,790,349],{"class":208},[147,792,793],{"class":156},"\"1904\"",[147,795,786],{"class":208},[147,797,584],{"class":156},[147,799,800],{"class":208},"}\n",[147,802,803],{"class":47,"line":218},[147,804,236],{"emptyLinePlaceholder":235},[147,806,807,809,812],{"class":47,"line":232},[147,808,242],{"class":204},[147,810,811],{"class":245}," read_with_serial_dates",[147,813,814],{"class":208},"(path, serial_columns):\n",[147,816,817,820,822],{"class":47,"line":239},[147,818,819],{"class":208},"    frame ",[147,821,258],{"class":204},[147,823,824],{"class":208}," pd.read_excel(path)\n",[147,826,827,830,832,835],{"class":47,"line":252},[147,828,829],{"class":208},"    origin ",[147,831,258],{"class":204},[147,833,834],{"class":228}," ORIGINS",[147,836,837],{"class":208},"[date_system(path)]\n",[147,839,840,843,846,848],{"class":47,"line":276},[147,841,842],{"class":204},"    for",[147,844,845],{"class":208}," col ",[147,847,340],{"class":204},[147,849,850],{"class":208}," serial_columns:\n",[147,852,853,856,858,861,863,865,867,869,871,873],{"class":47,"line":285},[147,854,855],{"class":208},"        frame[col] ",[147,857,258],{"class":204},[147,859,860],{"class":208}," pd.to_datetime(frame[col], ",[147,862,540],{"class":264},[147,864,258],{"class":204},[147,866,545],{"class":156},[147,868,349],{"class":208},[147,870,550],{"class":264},[147,872,258],{"class":204},[147,874,875],{"class":208},"origin)\n",[147,877,878,881,884,887,889],{"class":47,"line":312},[147,879,880],{"class":208},"    frame[",[147,882,883],{"class":156},"\"source\"",[147,885,886],{"class":208},"] ",[147,888,258],{"class":204},[147,890,891],{"class":208}," path\n",[147,893,894,897],{"class":47,"line":320},[147,895,896],{"class":204},"    return",[147,898,899],{"class":208}," frame\n",[147,901,902],{"class":47,"line":326},[147,903,236],{"emptyLinePlaceholder":235},[147,905,906,909,911,914,916,919,921,924,926,928,930,932,934],{"class":47,"line":331},[147,907,908],{"class":208},"combined ",[147,910,258],{"class":204},[147,912,913],{"class":208}," pd.concat([read_with_serial_dates(p, [",[147,915,513],{"class":156},[147,917,918],{"class":208},"]) ",[147,920,334],{"class":204},[147,922,923],{"class":208}," p ",[147,925,340],{"class":204},[147,927,343],{"class":208},[147,929,346],{"class":156},[147,931,349],{"class":208},[147,933,352],{"class":156},[147,935,936],{"class":208},")])\n",[147,938,939,941,944,946,948,950,952,954,957,960,962,965],{"class":47,"line":358},[147,940,521],{"class":228},[147,942,943],{"class":208},"(combined[[",[147,945,883],{"class":156},[147,947,349],{"class":208},[147,949,433],{"class":156},[147,951,349],{"class":208},[147,953,513],{"class":156},[147,955,956],{"class":208},"]].to_string(",[147,958,959],{"class":264},"index",[147,961,258],{"class":204},[147,963,964],{"class":228},"False",[147,966,967],{"class":208},"))\n",[10,969,970],{},"Both rows now show 1 March 2026 in both columns, although the underlying serials were 46082 and 44620. Without the per-file origin, concatenating the two would silently mix dates four years apart — the kind of error that survives until someone notices a shipment \"due\" before it was ordered.",[132,972,974],{"id":973},"step-5-write-a-workbook-in-either-system","Step 5 — Write a workbook in either system",[10,976,977,978,980],{},"openpyxl writes the 1900 system by default. Set ",[144,979,191],{}," before writing to produce a 1904 workbook, for example to match a template that downstream users expect:",[137,982,984],{"className":195,"code":983,"language":197,"meta":142,"style":142},"import datetime as dt\nfrom openpyxl import Workbook\n\nwb = Workbook()\nwb.epoch = CALENDAR_MAC_1904\nws = wb.active\nws[\"A1\"] = dt.datetime(2026, 3, 1)\nws[\"A1\"].number_format = \"yyyy-mm-dd\"\nwb.save(\"written_1904.xlsx\")\n\nimport re, zipfile\nwith zipfile.ZipFile(\"written_1904.xlsx\") as z:\n    print(re.findall(r\"\u003CworkbookPr[^>]*>\", z.read(\"xl\u002Fworkbook.xml\").decode()))\n    print(re.findall(r'\u003Cc r=\"A1\"[^>]*>\u003Cv>(\\d+)\u003C\u002Fv>', z.read(\"xl\u002Fworksheets\u002Fsheet1.xml\").decode()))\n",[144,985,986,998,1009,1013,1023,1032,1042,1070,1084,1094,1098,1105,1123,1165],{"__ignoreMap":142},[147,987,988,990,993,995],{"class":47,"line":149},[147,989,212],{"class":204},[147,991,992],{"class":208}," datetime ",[147,994,393],{"class":204},[147,996,997],{"class":208}," dt\n",[147,999,1000,1002,1004,1006],{"class":47,"line":218},[147,1001,205],{"class":204},[147,1003,209],{"class":208},[147,1005,212],{"class":204},[147,1007,1008],{"class":208}," Workbook\n",[147,1010,1011],{"class":47,"line":232},[147,1012,236],{"emptyLinePlaceholder":235},[147,1014,1015,1018,1020],{"class":47,"line":239},[147,1016,1017],{"class":208},"wb ",[147,1019,258],{"class":204},[147,1021,1022],{"class":208}," Workbook()\n",[147,1024,1025,1028,1030],{"class":47,"line":252},[147,1026,1027],{"class":208},"wb.epoch ",[147,1029,258],{"class":204},[147,1031,229],{"class":228},[147,1033,1034,1037,1039],{"class":47,"line":276},[147,1035,1036],{"class":208},"ws ",[147,1038,258],{"class":204},[147,1040,1041],{"class":208}," wb.active\n",[147,1043,1044,1047,1050,1052,1054,1057,1060,1062,1064,1066,1068],{"class":47,"line":285},[147,1045,1046],{"class":208},"ws[",[147,1048,1049],{"class":156},"\"A1\"",[147,1051,886],{"class":208},[147,1053,258],{"class":204},[147,1055,1056],{"class":208}," dt.datetime(",[147,1058,1059],{"class":228},"2026",[147,1061,349],{"class":208},[147,1063,660],{"class":228},[147,1065,349],{"class":208},[147,1067,635],{"class":228},[147,1069,273],{"class":208},[147,1071,1072,1074,1076,1079,1081],{"class":47,"line":312},[147,1073,1046],{"class":208},[147,1075,1049],{"class":156},[147,1077,1078],{"class":208},"].number_format ",[147,1080,258],{"class":204},[147,1082,1083],{"class":156}," \"yyyy-mm-dd\"\n",[147,1085,1086,1089,1092],{"class":47,"line":320},[147,1087,1088],{"class":208},"wb.save(",[147,1090,1091],{"class":156},"\"written_1904.xlsx\"",[147,1093,273],{"class":208},[147,1095,1096],{"class":47,"line":326},[147,1097,236],{"emptyLinePlaceholder":235},[147,1099,1100,1102],{"class":47,"line":331},[147,1101,212],{"class":204},[147,1103,1104],{"class":208}," re, zipfile\n",[147,1106,1107,1110,1113,1115,1118,1120],{"class":47,"line":358},[147,1108,1109],{"class":204},"with",[147,1111,1112],{"class":208}," zipfile.ZipFile(",[147,1114,1091],{"class":156},[147,1116,1117],{"class":208},") ",[147,1119,393],{"class":204},[147,1121,1122],{"class":208}," z:\n",[147,1124,1126,1128,1131,1134,1137,1140,1143,1147,1150,1153,1156,1159,1162],{"class":47,"line":1125},13,[147,1127,361],{"class":228},[147,1129,1130],{"class":208},"(re.findall(",[147,1132,1133],{"class":204},"r",[147,1135,1136],{"class":156},"\"\u003CworkbookPr",[147,1138,1139],{"class":228},"[",[147,1141,1142],{"class":204},"^",[147,1144,1146],{"class":1145},"sSjpA",">",[147,1148,1149],{"class":228},"]",[147,1151,1152],{"class":204},"*",[147,1154,1155],{"class":156},">\"",[147,1157,1158],{"class":208},", z.read(",[147,1160,1161],{"class":156},"\"xl\u002Fworkbook.xml\"",[147,1163,1164],{"class":208},").decode()))\n",[147,1166,1168,1170,1172,1174,1177,1179,1181,1183,1185,1187,1190,1193,1196,1199,1202,1204,1207],{"class":47,"line":1167},14,[147,1169,361],{"class":228},[147,1171,1130],{"class":208},[147,1173,1133],{"class":204},[147,1175,1176],{"class":156},"'\u003Cc r=\"A1\"",[147,1178,1139],{"class":228},[147,1180,1142],{"class":204},[147,1182,1146],{"class":1145},[147,1184,1149],{"class":228},[147,1186,1152],{"class":204},[147,1188,1189],{"class":156},">\u003Cv>",[147,1191,1192],{"class":228},"(\\d",[147,1194,1195],{"class":204},"+",[147,1197,1198],{"class":228},")",[147,1200,1201],{"class":156},"\u003C\u002Fv>'",[147,1203,1158],{"class":208},[147,1205,1206],{"class":156},"\"xl\u002Fworksheets\u002Fsheet1.xml\"",[147,1208,1164],{"class":208},[10,1210,1211,1212,1215,1216,1219],{},"The file carries ",[144,1213,1214],{},"date1904=\"1\""," and stores the date as 44620. Assign Python ",[144,1217,1218],{},"datetime"," objects rather than serial numbers when writing — openpyxl converts them with the workbook's epoch, so they are correct whichever system you chose. Writing precomputed serials is where mistakes creep in.",[20,1221,29,1226,29,1229,29,1232,29,1235,29,1238,29,1242,29,1248,29,1251,29,1255,29,1259,29,1264,29,1266,29,1276,29,1278,29,1285,29,1288,29,1292,29,1294,29,1299,29,1301,29,1306,29,1309,29,1313,29,1315,29,1320,29,1322,29,1327,29,1330,29,1334,29,1336,29,1341,29,1343,29,1348,29,1351,29,1355,29,1357,29,1362,29,1364],{"viewBox":1222,"role":23,"ariaLabelledBy":1223,"xmlns":27,"style":28},"4 0 752 242",[1224,1225],"epoch-where-t","epoch-where-d",[31,1227,1228],{"id":1224},"Where the 1904 system causes trouble and where it does not",[35,1230,1231],{"id":1225},"A table of situations. Date-formatted cells read with openpyxl or pandas are correct in both systems. Python datetimes written with openpyxl are converted with the workbook epoch and are correct. Raw serial numbers read as numbers, serials exported to CSV, and values pasted between workbooks with different systems must be converted with the source origin, or they are off by 1,462 days.",[39,1233],{"x":41,"y":42,"width":43,"height":1234,"fill":45},"242",[39,1236],{"x":56,"y":57,"width":1237,"height":611,"rx":42,"fill":45,"stroke":615,"style":616},"270.6",[39,1239],{"x":1240,"y":57,"width":1241,"height":611,"rx":42,"fill":61,"stroke":62,"style":752},"290.6","224.7",[65,1243,1247],{"x":1244,"y":1245,"style":1246},"402.9","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Automatically correct",[39,1249],{"x":1250,"y":57,"width":1241,"height":611,"rx":42,"fill":61,"stroke":62,"style":752},"515.3",[65,1252,1254],{"x":1253,"y":1245,"style":1246},"627.6","Needs the source origin",[39,1256],{"x":56,"y":1257,"width":1237,"height":1258,"rx":42,"fill":614,"stroke":615,"style":616},"56.0","34.0",[65,1260,1263],{"x":612,"y":1261,"style":1262},"78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","Date-formatted cells, read",[39,1265],{"x":1240,"y":1257,"width":1241,"height":1258,"rx":42,"fill":45,"stroke":615,"style":616},[1267,1268,1270],"g",{"transform":1269},"translate(393.9,64.0) scale(0.750)",[1271,1272],"path",{"d":1273,"fill":1274,"stroke":112,"style":1275},"M5 12.5l4.5 4.5L19 7","none","stroke-width:2.40px;stroke-linecap:round;stroke-linejoin:round",[39,1277],{"x":1250,"y":1257,"width":1241,"height":1258,"rx":42,"fill":45,"stroke":615,"style":616},[1267,1279,1281],{"transform":1280},"translate(619.6,65.0) scale(0.667)",[1271,1282],{"d":1283,"fill":1274,"stroke":708,"style":1284},"M6 6l12 12 M18 6L6 18","stroke-width:2.70px;stroke-linecap:round;stroke-linejoin:round",[39,1286],{"x":56,"y":1287,"width":1237,"height":1258,"rx":42,"fill":614,"stroke":615,"style":616},"90.0",[65,1289,1291],{"x":612,"y":1290,"style":1262},"112.0","datetime objects, written by openpyxl",[39,1293],{"x":1240,"y":1287,"width":1241,"height":1258,"rx":42,"fill":45,"stroke":615,"style":616},[1267,1295,1297],{"transform":1296},"translate(393.9,98.0) scale(0.750)",[1271,1298],{"d":1273,"fill":1274,"stroke":112,"style":1275},[39,1300],{"x":1250,"y":1287,"width":1241,"height":1258,"rx":42,"fill":45,"stroke":615,"style":616},[1267,1302,1304],{"transform":1303},"translate(619.6,99.0) scale(0.667)",[1271,1305],{"d":1283,"fill":1274,"stroke":708,"style":1284},[39,1307],{"x":56,"y":1308,"width":1237,"height":1258,"rx":42,"fill":614,"stroke":615,"style":616},"124.0",[65,1310,1312],{"x":612,"y":1311,"style":1262},"146.0","Raw serial numbers read as numbers",[39,1314],{"x":1240,"y":1308,"width":1241,"height":1258,"rx":42,"fill":45,"stroke":615,"style":616},[1267,1316,1318],{"transform":1317},"translate(394.9,133.0) scale(0.667)",[1271,1319],{"d":1283,"fill":1274,"stroke":708,"style":1284},[39,1321],{"x":1250,"y":1308,"width":1241,"height":1258,"rx":42,"fill":45,"stroke":615,"style":616},[1267,1323,1325],{"transform":1324},"translate(618.6,132.0) scale(0.750)",[1271,1326],{"d":1273,"fill":1274,"stroke":112,"style":1275},[39,1328],{"x":56,"y":1329,"width":1237,"height":1258,"rx":42,"fill":614,"stroke":615,"style":616},"158.0",[65,1331,1333],{"x":612,"y":1332,"style":1262},"180.0","Serials exported to CSV",[39,1335],{"x":1240,"y":1329,"width":1241,"height":1258,"rx":42,"fill":45,"stroke":615,"style":616},[1267,1337,1339],{"transform":1338},"translate(394.9,167.0) scale(0.667)",[1271,1340],{"d":1283,"fill":1274,"stroke":708,"style":1284},[39,1342],{"x":1250,"y":1329,"width":1241,"height":1258,"rx":42,"fill":45,"stroke":615,"style":616},[1267,1344,1346],{"transform":1345},"translate(618.6,166.0) scale(0.750)",[1271,1347],{"d":1273,"fill":1274,"stroke":112,"style":1275},[39,1349],{"x":56,"y":1350,"width":1237,"height":1258,"rx":42,"fill":614,"stroke":615,"style":616},"192.0",[65,1352,1354],{"x":612,"y":1353,"style":1262},"214.0","Values pasted between workbooks",[39,1356],{"x":1240,"y":1350,"width":1241,"height":1258,"rx":42,"fill":45,"stroke":615,"style":616},[1267,1358,1360],{"transform":1359},"translate(394.9,201.0) scale(0.667)",[1271,1361],{"d":1283,"fill":1274,"stroke":708,"style":1284},[39,1363],{"x":1250,"y":1350,"width":1241,"height":1258,"rx":42,"fill":45,"stroke":615,"style":616},[1267,1365,1367],{"transform":1366},"translate(618.6,200.0) scale(0.750)",[1271,1368],{"d":1273,"fill":1274,"stroke":112,"style":1275},[132,1370,1372],{"id":1371},"convert-a-1904-workbook-to-the-1900-system","Convert a 1904 workbook to the 1900 system",[10,1374,1375],{},"When a 1904 workbook should join the rest of an organisation's files on the 1900 system, openpyxl can convert it: date cells are loaded as Python datetimes, and saving with the other epoch re-encodes them. Plain serial columns are just numbers to openpyxl, so they must be shifted by hand:",[137,1377,1379],{"className":195,"code":1378,"language":197,"meta":142,"style":142},"from openpyxl.utils.datetime import CALENDAR_WINDOWS_1900\n\nwb = load_workbook(\"mac1904.xlsx\")\nwb.epoch = CALENDAR_WINDOWS_1900\nws = wb.active\nfor (cell,) in ws.iter_rows(min_row=2, min_col=3, max_col=3):   # the unformatted serial column\n    if isinstance(cell.value, (int, float)):\n        cell.value += 1462\nwb.save(\"converted_1900.xlsx\")\n\nprint(date_system(\"converted_1900.xlsx\"), pd.read_excel(\"converted_1900.xlsx\").iloc[0].to_dict())\n",[144,1380,1381,1392,1396,1409,1417,1425,1469,1491,1502,1511,1515],{"__ignoreMap":142},[147,1382,1383,1385,1387,1389],{"class":47,"line":149},[147,1384,205],{"class":204},[147,1386,223],{"class":208},[147,1388,212],{"class":204},[147,1390,1391],{"class":228}," CALENDAR_WINDOWS_1900\n",[147,1393,1394],{"class":47,"line":218},[147,1395,236],{"emptyLinePlaceholder":235},[147,1397,1398,1400,1402,1405,1407],{"class":47,"line":232},[147,1399,1017],{"class":208},[147,1401,258],{"class":204},[147,1403,1404],{"class":208}," load_workbook(",[147,1406,352],{"class":156},[147,1408,273],{"class":208},[147,1410,1411,1413,1415],{"class":47,"line":239},[147,1412,1027],{"class":208},[147,1414,258],{"class":204},[147,1416,1391],{"class":228},[147,1418,1419,1421,1423],{"class":47,"line":252},[147,1420,1036],{"class":208},[147,1422,258],{"class":204},[147,1424,1041],{"class":208},[147,1426,1427,1429,1432,1434,1437,1440,1442,1444,1446,1449,1451,1453,1455,1458,1460,1462,1465],{"class":47,"line":276},[147,1428,334],{"class":204},[147,1430,1431],{"class":208}," (cell,) ",[147,1433,340],{"class":204},[147,1435,1436],{"class":208}," ws.iter_rows(",[147,1438,1439],{"class":264},"min_row",[147,1441,258],{"class":204},[147,1443,647],{"class":228},[147,1445,349],{"class":208},[147,1447,1448],{"class":264},"min_col",[147,1450,258],{"class":204},[147,1452,660],{"class":228},[147,1454,349],{"class":208},[147,1456,1457],{"class":264},"max_col",[147,1459,258],{"class":204},[147,1461,660],{"class":228},[147,1463,1464],{"class":208},"):   ",[147,1466,1468],{"class":1467},"s-wDw","# the unformatted serial column\n",[147,1470,1471,1474,1477,1480,1483,1485,1488],{"class":47,"line":285},[147,1472,1473],{"class":204},"    if",[147,1475,1476],{"class":228}," isinstance",[147,1478,1479],{"class":208},"(cell.value, (",[147,1481,1482],{"class":228},"int",[147,1484,349],{"class":208},[147,1486,1487],{"class":228},"float",[147,1489,1490],{"class":208},")):\n",[147,1492,1493,1496,1499],{"class":47,"line":312},[147,1494,1495],{"class":208},"        cell.value ",[147,1497,1498],{"class":204},"+=",[147,1500,1501],{"class":228}," 1462\n",[147,1503,1504,1506,1509],{"class":47,"line":320},[147,1505,1088],{"class":208},[147,1507,1508],{"class":156},"\"converted_1900.xlsx\"",[147,1510,273],{"class":208},[147,1512,1513],{"class":47,"line":326},[147,1514,236],{"emptyLinePlaceholder":235},[147,1516,1517,1519,1522,1524,1527,1529,1531,1533],{"class":47,"line":331},[147,1518,521],{"class":228},[147,1520,1521],{"class":208},"(date_system(",[147,1523,1508],{"class":156},[147,1525,1526],{"class":208},"), pd.read_excel(",[147,1528,1508],{"class":156},[147,1530,458],{"class":208},[147,1532,42],{"class":228},[147,1534,1535],{"class":208},"].to_dict())\n",[10,1537,1538],{},"The shipped date stays 1 March 2026 without any code touching it, and the serial becomes 46082, the 1900-system number for the same day. Know which columns hold serials before converting — shifting a quantity column by 1,462 would be an interesting way to inflate stock — and convert a copy, keeping the original for comparison.",[132,1540,1542],{"id":1541},"step-6-check-a-folder-for-mixed-systems","Step 6 — Check a folder for mixed systems",[10,1544,1545],{},"Before consolidating a folder of workbooks, find out whether any use the 1904 system. It is rare enough that nobody expects it, which is exactly why it is worth checking:",[137,1547,1549],{"className":195,"code":1548,"language":197,"meta":142,"style":142},"from pathlib import Path\n\nsystems = {p.name: date_system(p) for p in sorted(Path(\".\").glob(\"*.xlsx\"))}\nprint(systems)\nprint(\"1904 files:\", [n for n, s in systems.items() if s == \"1904\"])\n",[144,1550,1551,1563,1567,1601,1608],{"__ignoreMap":142},[147,1552,1553,1555,1558,1560],{"class":47,"line":149},[147,1554,205],{"class":204},[147,1556,1557],{"class":208}," pathlib ",[147,1559,212],{"class":204},[147,1561,1562],{"class":208}," Path\n",[147,1564,1565],{"class":47,"line":218},[147,1566,236],{"emptyLinePlaceholder":235},[147,1568,1569,1572,1574,1577,1579,1581,1583,1586,1589,1592,1595,1598],{"class":47,"line":232},[147,1570,1571],{"class":208},"systems ",[147,1573,258],{"class":204},[147,1575,1576],{"class":208}," {p.name: date_system(p) ",[147,1578,334],{"class":204},[147,1580,923],{"class":208},[147,1582,340],{"class":204},[147,1584,1585],{"class":228}," sorted",[147,1587,1588],{"class":208},"(Path(",[147,1590,1591],{"class":156},"\".\"",[147,1593,1594],{"class":208},").glob(",[147,1596,1597],{"class":156},"\"*.xlsx\"",[147,1599,1600],{"class":208},"))}\n",[147,1602,1603,1605],{"class":47,"line":239},[147,1604,521],{"class":228},[147,1606,1607],{"class":208},"(systems)\n",[147,1609,1610,1612,1614,1617,1620,1622,1625,1627,1630,1633,1636,1638,1640],{"class":47,"line":252},[147,1611,521],{"class":228},[147,1613,531],{"class":208},[147,1615,1616],{"class":156},"\"1904 files:\"",[147,1618,1619],{"class":208},", [n ",[147,1621,334],{"class":204},[147,1623,1624],{"class":208}," n, s ",[147,1626,340],{"class":204},[147,1628,1629],{"class":208}," systems.items() ",[147,1631,1632],{"class":204},"if",[147,1634,1635],{"class":208}," s ",[147,1637,300],{"class":204},[147,1639,291],{"class":156},[147,1641,467],{"class":208},[10,1643,1644],{},"Log the result in the job. If a 1904 file appears where none has before, it usually means someone rebuilt a workbook from an old template — a detail worth knowing before their dates are merged into a report.",[132,1646,1648],{"id":1647},"common-pitfalls","Common pitfalls",[1650,1651,1652,1663,1673,1679,1693,1699],"ul",{},[1653,1654,1655,1659,1660,1662],"li",{},[1656,1657,1658],"strong",{},"Assuming every file is 1900-based."," Check ",[144,1661,191],{}," for files you did not create.",[1653,1664,1665,1668,1669,1672],{},[1656,1666,1667],{},"Using 1900-01-01 as the 1900 origin."," It gives dates two days off; use 1899-12-30 with ",[144,1670,1671],{},"unit=\"D\"",".",[1653,1674,1675,1678],{},[1656,1676,1677],{},"Writing serial numbers instead of datetimes."," Let openpyxl convert with the workbook's epoch.",[1653,1680,1681,1684,1685,1688,1689,1692],{},[1656,1682,1683],{},"Serial numbers typed into formulas."," A condition such as ",[144,1686,1687],{},"=B2>45000"," means a different date in each system. Write ",[144,1690,1691],{},"DATE(2026,3,1)"," in formulas instead, which each workbook evaluates in its own system.",[1653,1694,1695,1698],{},[1656,1696,1697],{},"Exporting serials to CSV."," Once a serial leaves the workbook, nothing records which origin it used; convert to ISO dates before exporting.",[1653,1700,1701,1704],{},[1656,1702,1703],{},"Pasting between workbooks."," Excel itself shifts pasted date values when the two workbooks use different systems; convert through Python datetimes instead.",[132,1706,1708],{"id":1707},"performance-notes","Performance notes",[10,1710,1711,1714,1715,1718],{},[144,1712,1713],{},"date_system"," opens each workbook in read-only mode and reads only the workbook part, so checking hundreds of files takes a second or two. Serial conversion with ",[144,1716,1717],{},"to_datetime(unit=\"D\")"," is vectorised and effectively free.",[132,1720,1722],{"id":1721},"conclusion","Conclusion",[10,1724,1725,1726,1728],{},"A 1904-system workbook stores the same date as a serial number 1,462 lower. Date-formatted cells are converted correctly by openpyxl and pandas either way; raw serials are not. Detect the system per file with ",[144,1727,191],{},", convert unformatted serials with origin 1899-12-30 or 1904-01-01 to match the source, keep the origin per file when combining several, and write dates as Python datetimes so the workbook's own epoch is applied.",[132,1730,1732],{"id":1731},"frequently-asked-questions","Frequently asked questions",[10,1734,1735,1738],{},[1656,1736,1737],{},"What is the 1904 date system?","\nAn alternative way for a workbook to store dates, counting days from 1 January 1904 instead of from the end of 1899. It came from early Macintosh versions of Excel and is still a per-workbook setting today. The same serial number is 1,462 days later in a 1904 workbook.",[10,1740,1741,1744],{},[1656,1742,1743],{},"Do pandas and openpyxl handle 1904 workbooks correctly?","\nYes, for cells formatted as dates. openpyxl reads the workbook's date1904 flag and converts date cells with the right origin, and pandas inherits that. Problems appear only with raw serial numbers that are not formatted as dates.",[10,1746,1747,1750],{},[1656,1748,1749],{},"How do I tell which system a file uses?","\nLoad it with openpyxl and check wb.epoch: 1899-12-30 means the 1900 system, 1904-01-01 means the 1904 system. In the file itself, the workbook part has a date1904=\"1\" attribute when the 1904 system is on.",[10,1752,1753,1756],{},[1656,1754,1755],{},"Why are my dates exactly four years and one day off?","\nSerial numbers from one system were read with the other system's origin. The two origins are 1,462 days apart. Convert serials with the origin of the workbook they came from.",[132,1758,1760],{"id":1759},"related","Related",[1650,1762,1763,1769,1776,1783],{},[1653,1764,1765,1766,1768],{},"Up one level: ",[14,1767,17],{"href":16}," — dates from import to report.",[1653,1770,1771,1775],{},[14,1772,1774],{"href":1773},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Ffix-excel-serial-numbers-showing-instead-of-dates\u002F","Fix Excel Serial Numbers Showing Instead of Dates"," — serials in the 1900 system.",[1653,1777,1778,1782],{},[14,1779,1781],{"href":1780},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fhow-openpyxl-stores-python-values-in-cells\u002F","How openpyxl Stores Python Values: Types, Dates and None"," — how dates become numbers.",[1653,1784,1785,1789],{},[14,1786,1788],{"href":1787},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fcombine-multiple-excel-files-into-one-python\u002F","Combine Multiple Excel Files into One in Python"," — the folder loop where mixed systems meet.",[1791,1792,1793],"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 .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":142,"searchDepth":218,"depth":218,"links":1795},[1796,1797,1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,1808],{"id":134,"depth":218,"text":135},{"id":180,"depth":218,"text":181},{"id":374,"depth":218,"text":375},{"id":473,"depth":218,"text":474},{"id":760,"depth":218,"text":761},{"id":973,"depth":218,"text":974},{"id":1371,"depth":218,"text":1372},{"id":1541,"depth":218,"text":1542},{"id":1647,"depth":218,"text":1648},{"id":1707,"depth":218,"text":1708},{"id":1721,"depth":218,"text":1722},{"id":1731,"depth":218,"text":1732},{"id":1759,"depth":218,"text":1760},"2026-09-25","Some workbooks count dates from 1 January 1904 instead of 1900, so the same serial number means a date four years and one day later. Detect which system a file uses with openpyxl, convert raw serial numbers with the right origin, combine files that use different systems, and write workbooks in either system.","md",[1813,1815,1817,1819],{"q":1737,"a":1814},"An alternative way for a workbook to store dates, counting days from 1 January 1904 instead of from the end of 1899. It came from early Macintosh versions of Excel and is still a per-workbook setting today. The same serial number is 1,462 days later in a 1904 workbook.",{"q":1743,"a":1816},"Yes, for cells formatted as dates. openpyxl reads the workbook's date1904 flag and converts date cells with the right origin, and pandas inherits that. Problems appear only with raw serial numbers that are not formatted as dates.",{"q":1749,"a":1818},"Load it with openpyxl and check wb.epoch: 1899-12-30 means the 1900 system, 1904-01-01 means the 1904 system. In the file itself, the workbook part has a date1904=\"1\" attribute when the 1904 system is on.",{"q":1755,"a":1820},"Serial numbers from one system were read with the other system's origin. The two origins are 1,462 days apart. Convert serials with the origin of the workbook they came from.","handle the 1904 date system in excel files",null,{"breadcrumb":1824},[1825,1828,1831],{"name":1826,"item":1827},"Home","\u002F",{"name":1829,"item":1830},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":17,"item":16},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-the-1904-date-system-in-excel-files",{"title":1834,"description":1835},"Excel 1904 Date System in Python (Dates Off by 4 Years and 1 Day)","Detect and handle Excel's 1904 date system with Python: openpyxl wb.epoch, date1904 in workbook.xml, converting serial numbers with origin 1904-01-01, the 1,462-day offset, and mixing files from both systems.","handle-the-1904-date-system-in-excel-files","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-the-1904-date-system-in-excel-files\u002Findex",[1839,1840,1841],"dates-and-times","reading-excel","openpyxl","how-to","rZGUcXmjJuPygglCxoq4x7n2E3DEbLSm2eYUcdolz8M",[1845,1849],{"title":1846,"path":1847,"stem":1848,"children":-1},"Handle Daylight Saving Time in Excel Timestamps","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-daylight-saving-time-in-excel-timestamps","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-daylight-saving-time-in-excel-timestamps\u002Findex",{"title":1850,"path":1851,"stem":1852,"children":-1},"Handle Timezones in Excel Timestamps with Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-timezones-in-excel-timestamps-with-python","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-timezones-in-excel-timestamps-with-python\u002Findex",[1854,1856],{"path":1855,"title":1829},"\u002Fadvanced-data-transformation-and-cleaning",{"path":1857,"title":17},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data",[],[1860,1863,1866,1869,1872,1875],{"path":1861,"title":1862,"kind":1842},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-overdue-dates-with-openpyxl","Highlight Overdue Dates in Excel with openpyxl",{"path":1864,"title":1865,"kind":1842},"\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":1867,"title":1868,"kind":1842},"\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":1870,"title":1871,"kind":1842},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcalculate-moving-averages-with-pandas-rolling","Calculate Moving Averages with pandas rolling",{"path":1873,"title":1874,"kind":1842},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Feomonth-and-edate-equivalents-in-pandas","EOMONTH and EDATE Equivalents in pandas",{"path":1876,"title":1877,"kind":1842},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fyearfrac-and-days360-equivalents-in-python","YEARFRAC and DAYS360 Equivalents in Python",1790972106468]