[{"data":1,"prerenderedAt":2486},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fparse-excel-dates-into-python-datetimes-with-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fparse-excel-dates-into-python-datetimes-with-pandas":2477},{"id":4,"title":5,"body":6,"dateModified":2450,"datePublished":2450,"description":2451,"extension":2452,"faq":2453,"meta":2468,"navigation":257,"path":2469,"seo":2470,"slug":2473,"stem":2474,"type":2475,"__hash__":2476},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fparse-excel-dates-into-python-datetimes-with-pandas\u002Findex.md","Parse Excel Dates into Python datetimes with pandas",{"type":7,"value":8,"toc":2437},"minimark",[9,28,193,198,226,229,422,426,436,500,520,524,530,617,620,758,779,783,789,893,900,906,947,951,957,1058,1265,1268,1387,1394,1398,1404,1407,1598,1608,1692,1696,1699,1798,1820,1962,1966,1969,2161,2164,2174,2180,2250,2266,2270,2292,2296,2328,2344,2360,2380,2389,2393,2433],[10,11,12,13,17,18,21,22,27],"p",{},"A date column that arrives as text is the most common blocker between an Excel export and a working report. You cannot filter on it, group it into months, or write it back with a date format until it is real ",[14,15,16],"code",{},"datetime64"," data. pandas has one function for the job — ",[14,19,20],{},"pd.to_datetime"," — and getting good results from it is a matter of knowing four arguments and one habit: never let a failed parse pass unnoticed. This guide walks the parsing path end to end. It is the practical companion to ",[23,24,26],"a",{"href":25},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002F","Working with Dates and Times in Excel Data",".",[29,30,39,40,39,44,39,48,39,55,39,65,39,72,39,90,39,106,39,115,39,121,39,126,39,130,39,135,39,138,39,143,39,146,39,149,39,153,39,158,39,163,39,166,39,169,39,173,39,178,39,183,39,186,39,189],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"ariaLabelledBy":34,"xmlns":37,"style":38},"0 0 800 232","img","Decision path for parsing an Excel date column: typed date cells need only parse_dates, text with one known layout takes an explicit format, mixed layouts take format equals mixed, and numeric serials take unit D with the 1899-12-30 origin.",[35,36],"pdec-t","pdec-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:800px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[41,42,43],"title",{"id":35},"Choosing a parsing strategy from what the column contains",[45,46,47],"desc",{"id":36},"Four branches from the question of what the column holds. Already-typed date cells need only parse_dates on read. Text in one known layout should use an explicit format string, which is both fastest and strictest. Text in several layouts needs format set to mixed. Bare numbers are Excel serials and need unit D with origin 1899-12-30.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","800","232","#ffffff",[49,56],{"x":57,"y":58,"width":59,"height":60,"rx":61,"fill":62,"stroke":63,"style":64},"286","14","228","44","11","#ebebfd","var(--brand,#5b5cf0)","stroke-width:2px",[66,67,71],"text",{"x":68,"y":69,"style":70},"400","42","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","what is in the column?",[73,74,76,77,76,81,76,84,76,87,39],"g",{"stroke":63,"style":64,"fill":75},"none","\n    ",[78,79],"path",{"d":80},"M400 58 V 82 H 106 V 106",[78,82],{"d":83},"M400 58 V 82 H 302 V 106",[78,85],{"d":86},"M400 58 V 82 H 498 V 106",[78,88],{"d":89},"M400 58 V 82 H 694 V 106",[73,91,76,93,76,97,76,100,76,103,39],{"fill":92},"#5b5cf0",[94,95],"polygon",{"points":96},"106,114 99,102 113,102",[94,98],{"points":99},"302,114 295,102 309,102",[94,101],{"points":102},"498,114 491,102 505,102",[94,104],{"points":105},"694,114 687,102 701,102",[49,107],{"x":108,"y":109,"width":110,"height":111,"rx":112,"fill":113,"stroke":114,"style":64},"16","114","180","102","12","#d9f4f1","var(--teal,#0f9488)",[66,116,120],{"x":117,"y":118,"style":119},"106","138","font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","typed date cells",[66,122,125],{"x":117,"y":123,"style":124},"164","font-size:10.5px;fill:var(--text,#172033);text-anchor:middle","Excel already knows",[66,127,129],{"x":117,"y":128,"style":124},"182","they are dates",[66,131,134],{"x":117,"y":132,"style":133},"204","font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","parse_dates=[...]",[49,136],{"x":137,"y":109,"width":110,"height":111,"rx":112,"fill":62,"stroke":63,"style":64},"212",[66,139,142],{"x":140,"y":118,"style":141},"302","font-size:11.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","text, one layout",[66,144,145],{"x":140,"y":123,"style":124},"fastest and strictest",[66,147,148],{"x":140,"y":128,"style":124},"nothing inferred",[66,150,152],{"x":140,"y":132,"style":151},"font-size:11px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","format=\"%d\u002F%m\u002F%Y\"",[49,154],{"x":155,"y":109,"width":110,"height":111,"rx":112,"fill":156,"stroke":157,"style":64},"408","#fdefd8","var(--gold,#b4740a)",[66,159,162],{"x":160,"y":118,"style":161},"498","font-size:11.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","text, many layouts",[66,164,165],{"x":160,"y":123,"style":124},"per-value inference",[66,167,168],{"x":160,"y":128,"style":124},"slower, forgiving",[66,170,172],{"x":160,"y":132,"style":171},"font-size:11px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","format=\"mixed\"",[49,174],{"x":175,"y":109,"width":110,"height":111,"rx":112,"fill":176,"stroke":177,"style":64},"604","#fee8f2","var(--accent,#f43f8f)",[66,179,182],{"x":180,"y":118,"style":181},"694","font-size:11.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","bare numbers",[66,184,185],{"x":180,"y":123,"style":124},"Excel day serials",[66,187,188],{"x":180,"y":128,"style":124},"need the epoch",[66,190,192],{"x":180,"y":132,"style":191},"font-size:11px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","unit=\"D\", origin=",[194,195,197],"h2",{"id":196},"prerequisites","Prerequisites",[199,200,205],"pre",{"className":201,"code":202,"language":203,"meta":204,"style":204},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,206,207],{"__ignoreMap":204},[208,209,212,216,220,223],"span",{"class":210,"line":211},"line",1,[208,213,215],{"class":214},"sMTad","pip",[208,217,219],{"class":218},"srMev"," install",[208,221,222],{"class":218}," pandas",[208,224,225],{"class":218}," openpyxl\n",[10,227,228],{},"A sample workbook with a deliberately messy date column, so every example below has something real to chew on:",[199,230,234],{"className":231,"code":232,"language":233,"meta":204,"style":204},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nmessy = pd.DataFrame({\n    \"order\": [1001, 1002, 1003, 1004, 1005, 1006],\n    \"invoice_date\": [\"2026-08-15\", \"15\u002F08\u002F2026\", \"Aug 16, 2026\",\n                     \"\", \"pending\", \"2026-08-18\"],\n    \"amount\": [159.92, 247.50, 137.44, 88.00, 412.10, 96.35],\n})\nmessy.to_excel(\"orders.xlsx\", index=False)\n","python",[14,235,236,252,259,271,313,337,355,393,399],{"__ignoreMap":204},[208,237,238,242,246,249],{"class":210,"line":211},[208,239,241],{"class":240},"s-kum","import",[208,243,245],{"class":244},"skGVy"," pandas ",[208,247,248],{"class":240},"as",[208,250,251],{"class":244}," pd\n",[208,253,255],{"class":210,"line":254},2,[208,256,258],{"emptyLinePlaceholder":257},true,"\n",[208,260,262,265,268],{"class":210,"line":261},3,[208,263,264],{"class":244},"messy ",[208,266,267],{"class":240},"=",[208,269,270],{"class":244}," pd.DataFrame({\n",[208,272,274,277,280,284,287,290,292,295,297,300,302,305,307,310],{"class":210,"line":273},4,[208,275,276],{"class":218},"    \"order\"",[208,278,279],{"class":244},": [",[208,281,283],{"class":282},"sP0c6","1001",[208,285,286],{"class":244},", ",[208,288,289],{"class":282},"1002",[208,291,286],{"class":244},[208,293,294],{"class":282},"1003",[208,296,286],{"class":244},[208,298,299],{"class":282},"1004",[208,301,286],{"class":244},[208,303,304],{"class":282},"1005",[208,306,286],{"class":244},[208,308,309],{"class":282},"1006",[208,311,312],{"class":244},"],\n",[208,314,316,319,321,324,326,329,331,334],{"class":210,"line":315},5,[208,317,318],{"class":218},"    \"invoice_date\"",[208,320,279],{"class":244},[208,322,323],{"class":218},"\"2026-08-15\"",[208,325,286],{"class":244},[208,327,328],{"class":218},"\"15\u002F08\u002F2026\"",[208,330,286],{"class":244},[208,332,333],{"class":218},"\"Aug 16, 2026\"",[208,335,336],{"class":244},",\n",[208,338,340,343,345,348,350,353],{"class":210,"line":339},6,[208,341,342],{"class":218},"                     \"\"",[208,344,286],{"class":244},[208,346,347],{"class":218},"\"pending\"",[208,349,286],{"class":244},[208,351,352],{"class":218},"\"2026-08-18\"",[208,354,312],{"class":244},[208,356,358,361,363,366,368,371,373,376,378,381,383,386,388,391],{"class":210,"line":357},7,[208,359,360],{"class":218},"    \"amount\"",[208,362,279],{"class":244},[208,364,365],{"class":282},"159.92",[208,367,286],{"class":244},[208,369,370],{"class":282},"247.50",[208,372,286],{"class":244},[208,374,375],{"class":282},"137.44",[208,377,286],{"class":244},[208,379,380],{"class":282},"88.00",[208,382,286],{"class":244},[208,384,385],{"class":282},"412.10",[208,387,286],{"class":244},[208,389,390],{"class":282},"96.35",[208,392,312],{"class":244},[208,394,396],{"class":210,"line":395},8,[208,397,398],{"class":244},"})\n",[208,400,402,405,408,410,414,416,419],{"class":210,"line":401},9,[208,403,404],{"class":244},"messy.to_excel(",[208,406,407],{"class":218},"\"orders.xlsx\"",[208,409,286],{"class":244},[208,411,413],{"class":412},"sa561","index",[208,415,267],{"class":240},[208,417,418],{"class":282},"False",[208,420,421],{"class":244},")\n",[194,423,425],{"id":424},"step-1-let-read_excel-do-it-when-the-cells-are-already-dates","Step 1 — Let read_excel do it when the cells are already dates",[10,427,428,429,431,432,435],{},"If Excel typed the cells as dates, pandas returns ",[14,430,16],{}," without being asked. ",[14,433,434],{},"parse_dates"," is then just insurance that the dtype is what you expect:",[199,437,439],{"className":231,"code":438,"language":233,"meta":204,"style":204},"import pandas as pd\n\ndf = pd.read_excel(\"clean_orders.xlsx\", parse_dates=[\"invoice_date\"])\nprint(df[\"invoice_date\"].dtype)     # datetime64[ns]\n",[14,440,441,451,455,483],{"__ignoreMap":204},[208,442,443,445,447,449],{"class":210,"line":211},[208,444,241],{"class":240},[208,446,245],{"class":244},[208,448,248],{"class":240},[208,450,251],{"class":244},[208,452,453],{"class":210,"line":254},[208,454,258],{"emptyLinePlaceholder":257},[208,456,457,460,462,465,468,470,472,474,477,480],{"class":210,"line":261},[208,458,459],{"class":244},"df ",[208,461,267],{"class":240},[208,463,464],{"class":244}," pd.read_excel(",[208,466,467],{"class":218},"\"clean_orders.xlsx\"",[208,469,286],{"class":244},[208,471,434],{"class":412},[208,473,267],{"class":240},[208,475,476],{"class":244},"[",[208,478,479],{"class":218},"\"invoice_date\"",[208,481,482],{"class":244},"])\n",[208,484,485,488,491,493,496],{"class":210,"line":273},[208,486,487],{"class":282},"print",[208,489,490],{"class":244},"(df[",[208,492,479],{"class":218},[208,494,495],{"class":244},"].dtype)     ",[208,497,499],{"class":498},"s-wDw","# datetime64[ns]\n",[10,501,502,503,505,506,286,509,512,513,516,517,27],{},"That is the whole story for well-formed files. ",[14,504,434],{}," deliberately offers no ",[14,507,508],{},"format",[14,510,511],{},"dayfirst"," or ",[14,514,515],{},"errors"," argument, so the moment the values are text you move to ",[14,518,519],{},"to_datetime",[194,521,523],{"id":522},"step-2-parse-text-with-an-explicit-format","Step 2 — Parse text with an explicit format",[10,525,526,527,529],{},"When you know the layout, say so. An explicit ",[14,528,508],{}," takes a fast vectorised path and — more importantly — refuses to guess:",[199,531,533],{"className":231,"code":532,"language":233,"meta":204,"style":204},"import pandas as pd\n\ndf = pd.read_excel(\"orders.xlsx\")\n\ndf[\"invoice_date\"] = pd.to_datetime(\n    df[\"invoice_date\"], format=\"%d\u002F%m\u002F%Y\", errors=\"coerce\"\n)\n",[14,534,535,545,549,561,565,580,613],{"__ignoreMap":204},[208,536,537,539,541,543],{"class":210,"line":211},[208,538,241],{"class":240},[208,540,245],{"class":244},[208,542,248],{"class":240},[208,544,251],{"class":244},[208,546,547],{"class":210,"line":254},[208,548,258],{"emptyLinePlaceholder":257},[208,550,551,553,555,557,559],{"class":210,"line":261},[208,552,459],{"class":244},[208,554,267],{"class":240},[208,556,464],{"class":244},[208,558,407],{"class":218},[208,560,421],{"class":244},[208,562,563],{"class":210,"line":273},[208,564,258],{"emptyLinePlaceholder":257},[208,566,567,570,572,575,577],{"class":210,"line":315},[208,568,569],{"class":244},"df[",[208,571,479],{"class":218},[208,573,574],{"class":244},"] ",[208,576,267],{"class":240},[208,578,579],{"class":244}," pd.to_datetime(\n",[208,581,582,585,587,590,592,594,597,601,604,606,608,610],{"class":210,"line":339},[208,583,584],{"class":244},"    df[",[208,586,479],{"class":218},[208,588,589],{"class":244},"], ",[208,591,508],{"class":412},[208,593,267],{"class":240},[208,595,596],{"class":218},"\"",[208,598,600],{"class":599},"sSjpA","%d",[208,602,603],{"class":218},"\u002F%m\u002F%Y\"",[208,605,286],{"class":244},[208,607,515],{"class":412},[208,609,267],{"class":240},[208,611,612],{"class":218},"\"coerce\"\n",[208,614,615],{"class":210,"line":357},[208,616,421],{"class":244},[10,618,619],{},"The directives you will use ninety per cent of the time:",[621,622,623,639],"table",{},[624,625,626],"thead",{},[627,628,629,633,636],"tr",{},[630,631,632],"th",{},"Directive",[630,634,635],{},"Matches",[630,637,638],{},"Example",[640,641,642,658,673,688,702,724,739],"tbody",{},[627,643,644,650,653],{},[645,646,647],"td",{},[14,648,649],{},"%Y",[645,651,652],{},"4-digit year",[645,654,655],{},[14,656,657],{},"2026",[627,659,660,665,668],{},[645,661,662],{},[14,663,664],{},"%y",[645,666,667],{},"2-digit year",[645,669,670],{},[14,671,672],{},"26",[627,674,675,680,683],{},[645,676,677],{},[14,678,679],{},"%m",[645,681,682],{},"zero-padded month",[645,684,685],{},[14,686,687],{},"08",[627,689,690,694,697],{},[645,691,692],{},[14,693,600],{},[645,695,696],{},"zero-padded day",[645,698,699],{},[14,700,701],{},"15",[627,703,704,713,716],{},[645,705,706,709,710],{},[14,707,708],{},"%b"," \u002F ",[14,711,712],{},"%B",[645,714,715],{},"short \u002F full month name",[645,717,718,709,721],{},[14,719,720],{},"Aug",[14,722,723],{},"August",[627,725,726,731,734],{},[645,727,728],{},[14,729,730],{},"%H:%M:%S",[645,732,733],{},"24-hour time",[645,735,736],{},[14,737,738],{},"18:04:32",[627,740,741,746,753],{},[645,742,743],{},[14,744,745],{},"%p",[645,747,748,749,752],{},"AM\u002FPM marker (with ",[14,750,751],{},"%I",")",[645,754,755],{},[14,756,757],{},"PM",[10,759,760,761,764,765,768,769,764,772,775,776,778],{},"Two-digit years are worth a warning. Python's rule maps ",[14,762,763],{},"69","–",[14,766,767],{},"99"," to the 1900s and ",[14,770,771],{},"00",[14,773,774],{},"68"," to the 2000s, so ",[14,777,664],{}," on a birth-date column will place anyone born before 1969 in the future. If you have two-digit years, resolve the century yourself rather than trusting the cutoff.",[194,780,782],{"id":781},"step-3-handle-ambiguity-dayfirst","Step 3 — Handle ambiguity: dayfirst",[10,784,785,788],{},[14,786,787],{},"03\u002F04\u002F2026"," is 4 March to pandas and 3 April to most of Europe. pandas defaults to month-first because that is the US convention, and it does not warn you when a value is ambiguous.",[199,790,792],{"className":231,"code":791,"language":233,"meta":204,"style":204},"import pandas as pd\n\ns = pd.Series([\"03\u002F04\u002F2026\", \"15\u002F08\u002F2026\"])\n\nprint(pd.to_datetime(s, dayfirst=False, format=\"mixed\").tolist())\n# [Timestamp('2026-03-04'), Timestamp('2026-08-15')]  \u003C- 15 forces day-first here\n\nprint(pd.to_datetime(s, dayfirst=True, format=\"mixed\").tolist())\n# [Timestamp('2026-04-03'), Timestamp('2026-08-15')]\n",[14,793,794,804,808,827,831,856,861,865,888],{"__ignoreMap":204},[208,795,796,798,800,802],{"class":210,"line":211},[208,797,241],{"class":240},[208,799,245],{"class":244},[208,801,248],{"class":240},[208,803,251],{"class":244},[208,805,806],{"class":210,"line":254},[208,807,258],{"emptyLinePlaceholder":257},[208,809,810,813,815,818,821,823,825],{"class":210,"line":261},[208,811,812],{"class":244},"s ",[208,814,267],{"class":240},[208,816,817],{"class":244}," pd.Series([",[208,819,820],{"class":218},"\"03\u002F04\u002F2026\"",[208,822,286],{"class":244},[208,824,328],{"class":218},[208,826,482],{"class":244},[208,828,829],{"class":210,"line":273},[208,830,258],{"emptyLinePlaceholder":257},[208,832,833,835,838,840,842,844,846,848,850,853],{"class":210,"line":315},[208,834,487],{"class":282},[208,836,837],{"class":244},"(pd.to_datetime(s, ",[208,839,511],{"class":412},[208,841,267],{"class":240},[208,843,418],{"class":282},[208,845,286],{"class":244},[208,847,508],{"class":412},[208,849,267],{"class":240},[208,851,852],{"class":218},"\"mixed\"",[208,854,855],{"class":244},").tolist())\n",[208,857,858],{"class":210,"line":339},[208,859,860],{"class":498},"# [Timestamp('2026-03-04'), Timestamp('2026-08-15')]  \u003C- 15 forces day-first here\n",[208,862,863],{"class":210,"line":357},[208,864,258],{"emptyLinePlaceholder":257},[208,866,867,869,871,873,875,878,880,882,884,886],{"class":210,"line":395},[208,868,487],{"class":282},[208,870,837],{"class":244},[208,872,511],{"class":412},[208,874,267],{"class":240},[208,876,877],{"class":282},"True",[208,879,286],{"class":244},[208,881,508],{"class":412},[208,883,267],{"class":240},[208,885,852],{"class":218},[208,887,855],{"class":244},[208,889,890],{"class":210,"line":401},[208,891,892],{"class":498},"# [Timestamp('2026-04-03'), Timestamp('2026-08-15')]\n",[10,894,895,896,899],{},"Note what happened: ",[14,897,898],{},"15\u002F08\u002F2026"," parsed the same way under both settings, because 15 cannot be a month. That is the trap — a column can look correct in spot checks and be wrong on exactly the rows where the day is 12 or less. Roughly a third of dates in a year are ambiguous this way.",[10,901,902,903,905],{},"The reliable fix is an explicit format rather than ",[14,904,511],{},", because it rejects instead of reinterpreting:",[199,907,909],{"className":231,"code":908,"language":233,"meta":204,"style":204},"# Anything not in day\u002Fmonth\u002Fyear form becomes NaT and shows up in the report.\nparsed = pd.to_datetime(s, format=\"%d\u002F%m\u002F%Y\", errors=\"coerce\")\n",[14,910,911,916],{"__ignoreMap":204},[208,912,913],{"class":210,"line":211},[208,914,915],{"class":498},"# Anything not in day\u002Fmonth\u002Fyear form becomes NaT and shows up in the report.\n",[208,917,918,921,923,926,928,930,932,934,936,938,940,942,945],{"class":210,"line":254},[208,919,920],{"class":244},"parsed ",[208,922,267],{"class":240},[208,924,925],{"class":244}," pd.to_datetime(s, ",[208,927,508],{"class":412},[208,929,267],{"class":240},[208,931,596],{"class":218},[208,933,600],{"class":599},[208,935,603],{"class":218},[208,937,286],{"class":244},[208,939,515],{"class":412},[208,941,267],{"class":240},[208,943,944],{"class":218},"\"coerce\"",[208,946,421],{"class":244},[194,948,950],{"id":949},"step-4-coerce-then-report","Step 4 — Coerce, then report",[10,952,953,956],{},[14,954,955],{},"errors=\"coerce\""," is the right default for a batch job, but only paired with an inspection step. On its own it converts a data-quality problem into an invisible one.",[29,958,39,965,39,968,39,971,39,975,39,983,39,988,39,993,39,998,39,1003,39,1009,39,1012,39,1016,39,1019,39,1022,39,1027,39,1030,39,1036,39,1042,39,1047,39,1050,39,1054],{"viewBox":959,"role":32,"ariaLabel":960,"ariaLabelledBy":961,"xmlns":37,"style":964},"0 0 780 214","The coerce and report loop: parse with coerce, compare the resulting NaT mask against the original non-null mask to isolate genuine failures, then report the distinct bad values rather than dropping them.",[962,963],"coerce-t","coerce-d","width:100%;max-width:780px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[41,966,967],{"id":962},"Separating genuine parse failures from cells that were always blank",[45,969,970],{"id":963},"After parsing with errors set to coerce, the NaT mask contains both cells that were blank to begin with and cells that held an unparseable value. Intersecting the NaT mask with the original not-null mask isolates the second group, which is the set worth reporting. Blank cells are expected missing data and go to a separate count.",[49,972],{"x":51,"y":51,"width":973,"height":974,"fill":54},"780","214",[49,976],{"x":977,"y":978,"width":110,"height":979,"rx":980,"fill":981,"stroke":982,"style":64},"20","60","82","13","#f0f2f5","var(--line,#cdd5e6)",[66,984,987],{"x":985,"y":69,"style":986},"110","font-size:11px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","after coerce",[66,989,992],{"x":985,"y":990,"style":991},"92","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","NaT mask",[66,994,997],{"x":985,"y":995,"style":996},"116","font-size:10.5px;fill:var(--muted,#5b6780);text-anchor:middle","blanks + failures",[49,999],{"x":137,"y":979,"width":1000,"height":1001,"rx":1002,"fill":62,"stroke":63,"style":64},"48","38","9",[66,1004,1008],{"x":1005,"y":1006,"style":1007},"236","107","font-size:16px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","and",[49,1010],{"x":1011,"y":978,"width":110,"height":979,"rx":980,"fill":62,"stroke":63,"style":64},"272",[66,1013,1015],{"x":1014,"y":69,"style":986},"362","from the raw column",[66,1017,1018],{"x":1014,"y":990,"style":70},"raw.notna()",[66,1020,1021],{"x":1014,"y":995,"style":996},"something was there",[210,1023],{"x1":1024,"y1":1025,"x2":1026,"y2":1025,"stroke":63,"style":64},"452","101","500",[94,1028],{"points":1029,"fill":92},"508,101 496,95 496,107",[49,1031],{"x":1032,"y":1033,"width":1034,"height":1035,"rx":980,"fill":176,"stroke":177,"style":64},"516","24","248","70",[66,1037,1041],{"x":1038,"y":1039,"style":1040},"640","50","font-size:12.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","real failures",[66,1043,1046],{"x":1038,"y":1044,"style":1045},"72","font-size:11px;fill:var(--text,#172033);text-anchor:middle","report the distinct values",[49,1048],{"x":1032,"y":1049,"width":1034,"height":1035,"rx":980,"fill":113,"stroke":114,"style":64},"112",[66,1051,1053],{"x":1038,"y":118,"style":1052},"font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","expected blanks",[66,1055,1057],{"x":1038,"y":1056,"style":1045},"160","count them, move on",[199,1059,1061],{"className":231,"code":1060,"language":233,"meta":204,"style":204},"import pandas as pd\n\ndf = pd.read_excel(\"orders.xlsx\")\nraw = df[\"invoice_date\"].astype(\"string\").str.strip()\n\ndf[\"invoice_date\"] = pd.to_datetime(raw, format=\"mixed\", errors=\"coerce\")\n\nblank = raw.isna() | (raw == \"\")\nfailed = df[\"invoice_date\"].isna() & ~blank\n\nprint(f\"{int(blank.sum())} blank, {int(failed.sum())} unparseable\")\nif failed.any():\n    print(raw[failed].value_counts())\n    # pending    1\n",[14,1062,1063,1073,1077,1089,1110,1114,1143,1147,1171,1194,1199,1241,1250,1259],{"__ignoreMap":204},[208,1064,1065,1067,1069,1071],{"class":210,"line":211},[208,1066,241],{"class":240},[208,1068,245],{"class":244},[208,1070,248],{"class":240},[208,1072,251],{"class":244},[208,1074,1075],{"class":210,"line":254},[208,1076,258],{"emptyLinePlaceholder":257},[208,1078,1079,1081,1083,1085,1087],{"class":210,"line":261},[208,1080,459],{"class":244},[208,1082,267],{"class":240},[208,1084,464],{"class":244},[208,1086,407],{"class":218},[208,1088,421],{"class":244},[208,1090,1091,1094,1096,1099,1101,1104,1107],{"class":210,"line":273},[208,1092,1093],{"class":244},"raw ",[208,1095,267],{"class":240},[208,1097,1098],{"class":244}," df[",[208,1100,479],{"class":218},[208,1102,1103],{"class":244},"].astype(",[208,1105,1106],{"class":218},"\"string\"",[208,1108,1109],{"class":244},").str.strip()\n",[208,1111,1112],{"class":210,"line":315},[208,1113,258],{"emptyLinePlaceholder":257},[208,1115,1116,1118,1120,1122,1124,1127,1129,1131,1133,1135,1137,1139,1141],{"class":210,"line":339},[208,1117,569],{"class":244},[208,1119,479],{"class":218},[208,1121,574],{"class":244},[208,1123,267],{"class":240},[208,1125,1126],{"class":244}," pd.to_datetime(raw, ",[208,1128,508],{"class":412},[208,1130,267],{"class":240},[208,1132,852],{"class":218},[208,1134,286],{"class":244},[208,1136,515],{"class":412},[208,1138,267],{"class":240},[208,1140,944],{"class":218},[208,1142,421],{"class":244},[208,1144,1145],{"class":210,"line":357},[208,1146,258],{"emptyLinePlaceholder":257},[208,1148,1149,1152,1154,1157,1160,1163,1166,1169],{"class":210,"line":395},[208,1150,1151],{"class":244},"blank ",[208,1153,267],{"class":240},[208,1155,1156],{"class":244}," raw.isna() ",[208,1158,1159],{"class":240},"|",[208,1161,1162],{"class":244}," (raw ",[208,1164,1165],{"class":240},"==",[208,1167,1168],{"class":218}," \"\"",[208,1170,421],{"class":244},[208,1172,1173,1176,1178,1180,1182,1185,1188,1191],{"class":210,"line":401},[208,1174,1175],{"class":244},"failed ",[208,1177,267],{"class":240},[208,1179,1098],{"class":244},[208,1181,479],{"class":218},[208,1183,1184],{"class":244},"].isna() ",[208,1186,1187],{"class":240},"&",[208,1189,1190],{"class":240}," ~",[208,1192,1193],{"class":244},"blank\n",[208,1195,1197],{"class":210,"line":1196},10,[208,1198,258],{"emptyLinePlaceholder":257},[208,1200,1202,1204,1207,1210,1212,1215,1218,1221,1224,1227,1229,1231,1234,1236,1239],{"class":210,"line":1201},11,[208,1203,487],{"class":282},[208,1205,1206],{"class":244},"(",[208,1208,1209],{"class":240},"f",[208,1211,596],{"class":218},[208,1213,1214],{"class":599},"{",[208,1216,1217],{"class":282},"int",[208,1219,1220],{"class":244},"(blank.sum())",[208,1222,1223],{"class":599},"}",[208,1225,1226],{"class":218}," blank, ",[208,1228,1214],{"class":599},[208,1230,1217],{"class":282},[208,1232,1233],{"class":244},"(failed.sum())",[208,1235,1223],{"class":599},[208,1237,1238],{"class":218}," unparseable\"",[208,1240,421],{"class":244},[208,1242,1244,1247],{"class":210,"line":1243},12,[208,1245,1246],{"class":240},"if",[208,1248,1249],{"class":244}," failed.any():\n",[208,1251,1253,1256],{"class":210,"line":1252},13,[208,1254,1255],{"class":282},"    print",[208,1257,1258],{"class":244},"(raw[failed].value_counts())\n",[208,1260,1262],{"class":210,"line":1261},14,[208,1263,1264],{"class":498},"    # pending    1\n",[10,1266,1267],{},"For an ingest job, turn that count into a decision rather than a print. A handful of bad rows is data to quarantine; a third of the column failing means the format assumption is wrong:",[199,1269,1271],{"className":231,"code":1270,"language":233,"meta":204,"style":204},"rate = failed.mean()\nif rate > 0.05:\n    raise ValueError(\n        f\"{rate:.1%} of invoice_date failed to parse — \"\n        \"check the expected format before continuing.\"\n    )\n\nquarantine = df.loc[failed]\nquarantine.to_excel(\"rejects.xlsx\", index=False)\ndf = df.loc[~failed].copy()\n",[14,1272,1273,1283,1299,1310,1330,1335,1340,1344,1354,1372],{"__ignoreMap":204},[208,1274,1275,1278,1280],{"class":210,"line":211},[208,1276,1277],{"class":244},"rate ",[208,1279,267],{"class":240},[208,1281,1282],{"class":244}," failed.mean()\n",[208,1284,1285,1287,1290,1293,1296],{"class":210,"line":254},[208,1286,1246],{"class":240},[208,1288,1289],{"class":244}," rate ",[208,1291,1292],{"class":240},">",[208,1294,1295],{"class":282}," 0.05",[208,1297,1298],{"class":244},":\n",[208,1300,1301,1304,1307],{"class":210,"line":261},[208,1302,1303],{"class":240},"    raise",[208,1305,1306],{"class":282}," ValueError",[208,1308,1309],{"class":244},"(\n",[208,1311,1312,1315,1317,1319,1322,1325,1327],{"class":210,"line":273},[208,1313,1314],{"class":240},"        f",[208,1316,596],{"class":218},[208,1318,1214],{"class":599},[208,1320,1321],{"class":244},"rate",[208,1323,1324],{"class":240},":.1%",[208,1326,1223],{"class":599},[208,1328,1329],{"class":218}," of invoice_date failed to parse — \"\n",[208,1331,1332],{"class":210,"line":315},[208,1333,1334],{"class":218},"        \"check the expected format before continuing.\"\n",[208,1336,1337],{"class":210,"line":339},[208,1338,1339],{"class":244},"    )\n",[208,1341,1342],{"class":210,"line":357},[208,1343,258],{"emptyLinePlaceholder":257},[208,1345,1346,1349,1351],{"class":210,"line":395},[208,1347,1348],{"class":244},"quarantine ",[208,1350,267],{"class":240},[208,1352,1353],{"class":244}," df.loc[failed]\n",[208,1355,1356,1359,1362,1364,1366,1368,1370],{"class":210,"line":401},[208,1357,1358],{"class":244},"quarantine.to_excel(",[208,1360,1361],{"class":218},"\"rejects.xlsx\"",[208,1363,286],{"class":244},[208,1365,413],{"class":412},[208,1367,267],{"class":240},[208,1369,418],{"class":282},[208,1371,421],{"class":244},[208,1373,1374,1376,1378,1381,1384],{"class":210,"line":1196},[208,1375,459],{"class":244},[208,1377,267],{"class":240},[208,1379,1380],{"class":244}," df.loc[",[208,1382,1383],{"class":240},"~",[208,1385,1386],{"class":244},"failed].copy()\n",[10,1388,1389,1390,27],{},"Writing the rejects to their own workbook so a human can look at them is the same pattern used in ",[23,1391,1393],{"href":1392},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fhighlight-invalid-cells-in-excel-with-python\u002F","highlighting invalid cells in Excel with Python",[194,1395,1397],{"id":1396},"step-5-columns-that-mix-serials-and-text","Step 5 — Columns that mix serials and text",[10,1399,1400,1401,1403],{},"The genuinely awkward case: some rows were typed as dates in Excel (and arrive as numbers or timestamps), others were typed as text. A single ",[14,1402,519],{}," call cannot handle both, because the numbers are day counts and would be read as nanoseconds.",[10,1405,1406],{},"Split by type, parse each branch with the right rule, and recombine:",[199,1408,1410],{"className":231,"code":1409,"language":233,"meta":204,"style":204},"import pandas as pd\n\ncol = pd.Series([\"2026-08-15\", 46249, \"15\u002F08\u002F2026\", 46250.5, None])\n\nnumeric = pd.to_numeric(col, errors=\"coerce\")\n\n# Branch 1: Excel serials — day counts from the 1899-12-30 epoch.\nfrom_serial = pd.to_datetime(\n    numeric, unit=\"D\", origin=\"1899-12-30\", errors=\"coerce\"\n)\n\n# Branch 2: everything that was not a number, parsed as text.\nfrom_text = pd.to_datetime(\n    col.where(numeric.isna()), format=\"mixed\", errors=\"coerce\"\n)\n\nparsed = from_serial.fillna(from_text)\nprint(parsed.tolist())\n",[14,1411,1412,1422,1426,1458,1462,1480,1484,1489,1498,1529,1533,1537,1542,1551,1570,1575,1580,1590],{"__ignoreMap":204},[208,1413,1414,1416,1418,1420],{"class":210,"line":211},[208,1415,241],{"class":240},[208,1417,245],{"class":244},[208,1419,248],{"class":240},[208,1421,251],{"class":244},[208,1423,1424],{"class":210,"line":254},[208,1425,258],{"emptyLinePlaceholder":257},[208,1427,1428,1431,1433,1435,1437,1439,1442,1444,1446,1448,1451,1453,1456],{"class":210,"line":261},[208,1429,1430],{"class":244},"col ",[208,1432,267],{"class":240},[208,1434,817],{"class":244},[208,1436,323],{"class":218},[208,1438,286],{"class":244},[208,1440,1441],{"class":282},"46249",[208,1443,286],{"class":244},[208,1445,328],{"class":218},[208,1447,286],{"class":244},[208,1449,1450],{"class":282},"46250.5",[208,1452,286],{"class":244},[208,1454,1455],{"class":282},"None",[208,1457,482],{"class":244},[208,1459,1460],{"class":210,"line":273},[208,1461,258],{"emptyLinePlaceholder":257},[208,1463,1464,1467,1469,1472,1474,1476,1478],{"class":210,"line":315},[208,1465,1466],{"class":244},"numeric ",[208,1468,267],{"class":240},[208,1470,1471],{"class":244}," pd.to_numeric(col, ",[208,1473,515],{"class":412},[208,1475,267],{"class":240},[208,1477,944],{"class":218},[208,1479,421],{"class":244},[208,1481,1482],{"class":210,"line":339},[208,1483,258],{"emptyLinePlaceholder":257},[208,1485,1486],{"class":210,"line":357},[208,1487,1488],{"class":498},"# Branch 1: Excel serials — day counts from the 1899-12-30 epoch.\n",[208,1490,1491,1494,1496],{"class":210,"line":395},[208,1492,1493],{"class":244},"from_serial ",[208,1495,267],{"class":240},[208,1497,579],{"class":244},[208,1499,1500,1503,1506,1508,1511,1513,1516,1518,1521,1523,1525,1527],{"class":210,"line":401},[208,1501,1502],{"class":244},"    numeric, ",[208,1504,1505],{"class":412},"unit",[208,1507,267],{"class":240},[208,1509,1510],{"class":218},"\"D\"",[208,1512,286],{"class":244},[208,1514,1515],{"class":412},"origin",[208,1517,267],{"class":240},[208,1519,1520],{"class":218},"\"1899-12-30\"",[208,1522,286],{"class":244},[208,1524,515],{"class":412},[208,1526,267],{"class":240},[208,1528,612],{"class":218},[208,1530,1531],{"class":210,"line":1196},[208,1532,421],{"class":244},[208,1534,1535],{"class":210,"line":1201},[208,1536,258],{"emptyLinePlaceholder":257},[208,1538,1539],{"class":210,"line":1243},[208,1540,1541],{"class":498},"# Branch 2: everything that was not a number, parsed as text.\n",[208,1543,1544,1547,1549],{"class":210,"line":1252},[208,1545,1546],{"class":244},"from_text ",[208,1548,267],{"class":240},[208,1550,579],{"class":244},[208,1552,1553,1556,1558,1560,1562,1564,1566,1568],{"class":210,"line":1261},[208,1554,1555],{"class":244},"    col.where(numeric.isna()), ",[208,1557,508],{"class":412},[208,1559,267],{"class":240},[208,1561,852],{"class":218},[208,1563,286],{"class":244},[208,1565,515],{"class":412},[208,1567,267],{"class":240},[208,1569,612],{"class":218},[208,1571,1573],{"class":210,"line":1572},15,[208,1574,421],{"class":244},[208,1576,1578],{"class":210,"line":1577},16,[208,1579,258],{"emptyLinePlaceholder":257},[208,1581,1583,1585,1587],{"class":210,"line":1582},17,[208,1584,920],{"class":244},[208,1586,267],{"class":240},[208,1588,1589],{"class":244}," from_serial.fillna(from_text)\n",[208,1591,1593,1595],{"class":210,"line":1592},18,[208,1594,487],{"class":282},[208,1596,1597],{"class":244},"(parsed.tolist())\n",[10,1599,1600,1601,512,1604,1607],{},"Guard the serial branch against nonsense. A stray ",[14,1602,1603],{},"1",[14,1605,1606],{},"999999"," is not a date, and silently converting it to 1899 or the year 4637 poisons every downstream aggregate:",[199,1609,1611],{"className":231,"code":1610,"language":233,"meta":204,"style":204},"PLAUSIBLE = (numeric > 20_000) & (numeric \u003C 60_000)   # ~1954 to ~2064\nfrom_serial = pd.to_datetime(\n    numeric.where(PLAUSIBLE), unit=\"D\", origin=\"1899-12-30\", errors=\"coerce\"\n)\n",[14,1612,1613,1648,1656,1688],{"__ignoreMap":204},[208,1614,1615,1618,1621,1624,1626,1629,1632,1634,1636,1639,1642,1645],{"class":210,"line":211},[208,1616,1617],{"class":282},"PLAUSIBLE",[208,1619,1620],{"class":240}," =",[208,1622,1623],{"class":244}," (numeric ",[208,1625,1292],{"class":240},[208,1627,1628],{"class":282}," 20_000",[208,1630,1631],{"class":244},") ",[208,1633,1187],{"class":240},[208,1635,1623],{"class":244},[208,1637,1638],{"class":240},"\u003C",[208,1640,1641],{"class":282}," 60_000",[208,1643,1644],{"class":244},")   ",[208,1646,1647],{"class":498},"# ~1954 to ~2064\n",[208,1649,1650,1652,1654],{"class":210,"line":254},[208,1651,1493],{"class":244},[208,1653,267],{"class":240},[208,1655,579],{"class":244},[208,1657,1658,1661,1663,1666,1668,1670,1672,1674,1676,1678,1680,1682,1684,1686],{"class":210,"line":261},[208,1659,1660],{"class":244},"    numeric.where(",[208,1662,1617],{"class":282},[208,1664,1665],{"class":244},"), ",[208,1667,1505],{"class":412},[208,1669,267],{"class":240},[208,1671,1510],{"class":218},[208,1673,286],{"class":244},[208,1675,1515],{"class":412},[208,1677,267],{"class":240},[208,1679,1520],{"class":218},[208,1681,286],{"class":244},[208,1683,515],{"class":412},[208,1685,267],{"class":240},[208,1687,612],{"class":218},[208,1689,1690],{"class":210,"line":273},[208,1691,421],{"class":244},[194,1693,1695],{"id":1694},"common-pitfalls-and-fixes","Common pitfalls and fixes",[10,1697,1698],{},"The failure modes cluster into three families: the epoch is wrong, the field order is wrong, or the dtype never changed at all. Each has a distinct signature in the output, which makes them quick to tell apart once you know what to look for.",[29,1700,39,1706,39,1709,39,1712,39,1715,39,1718,39,1721,39,1725,39,1729,39,1734,39,1738,39,1744,39,1748,39,1751,39,1755,39,1758,39,1761,39,1764,39,1767,39,1770,39,1773,39,1776,39,1780,39,1783,39,1786,39,1789,39,1792,39,1795],{"viewBox":1701,"role":32,"ariaLabel":1702,"ariaLabelledBy":1703,"xmlns":37,"style":38},"0 0 800 224","Three parse failure signatures: dates clustered in 1970 mean a serial column read as nanoseconds, dates one day off mean the wrong epoch, and an object dtype means the assignment or the parse never took effect.",[1704,1705],"pfail-t","pfail-d",[41,1707,1708],{"id":1704},"Reading the signature of a failed date parse",[45,1710,1711],{"id":1705},"Three diagnosis panels. Every value landing in January 1970 means a day-count column was interpreted as nanoseconds since the Unix epoch, fixed by passing unit D and the Excel origin. Every value one day out means the origin was anchored at 1900-01-01 instead of 1899-12-30. A column still showing object dtype means either the result was never assigned back or every value failed to parse.",[49,1713],{"x":51,"y":51,"width":52,"height":1714,"fill":54},"224",[49,1716],{"x":58,"y":977,"width":1034,"height":1717,"rx":58,"fill":176,"stroke":177,"style":64},"188",[66,1719,1720],{"x":118,"y":1000,"style":1040},"everything is 1970",[66,1722,1724],{"x":118,"y":1723,"style":1045},"76","45292 read as nanoseconds",[66,1726,1728],{"x":118,"y":1727,"style":1045},"98","since the Unix epoch",[66,1730,1733],{"x":118,"y":1731,"style":1732},"126","font-size:11px;fill:var(--muted,#5b6780);text-anchor:middle","45 microseconds after",[66,1735,1737],{"x":118,"y":1736,"style":1732},"146","1970-01-01",[49,1739],{"x":1740,"y":1741,"width":132,"height":1742,"rx":1743,"fill":54,"stroke":177},"36","162","34","8",[66,1745,1747],{"x":118,"y":1746,"style":191},"184","add unit=\"D\" + origin",[49,1749],{"x":1750,"y":977,"width":1034,"height":1717,"rx":58,"fill":156,"stroke":157,"style":64},"276",[66,1752,1754],{"x":68,"y":1000,"style":1753},"font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","every date is one day out",[66,1756,1757],{"x":68,"y":1723,"style":1045},"origin set to 1900-01-01",[66,1759,1760],{"x":68,"y":1727,"style":1045},"instead of 1899-12-30",[66,1762,1763],{"x":68,"y":1731,"style":1732},"the phantom 29 Feb 1900",[66,1765,1766],{"x":68,"y":1736,"style":1732},"is no longer cancelled",[49,1768],{"x":1769,"y":1741,"width":132,"height":1742,"rx":1743,"fill":54,"stroke":157},"298",[66,1771,1772],{"x":68,"y":1746,"style":171},"origin=\"1899-12-30\"",[49,1774],{"x":1775,"y":977,"width":1034,"height":1717,"rx":58,"fill":62,"stroke":63,"style":64},"538",[66,1777,1779],{"x":1778,"y":1000,"style":70},"662","dtype is still object",[66,1781,1782],{"x":1778,"y":1723,"style":1045},"the result was not",[66,1784,1785],{"x":1778,"y":1727,"style":1045},"assigned back to the column",[66,1787,1788],{"x":1778,"y":1731,"style":1732},"or every single value",[66,1790,1791],{"x":1778,"y":1736,"style":1732},"coerced to NaT",[49,1793],{"x":1794,"y":1741,"width":132,"height":1742,"rx":1743,"fill":54,"stroke":63},"560",[66,1796,1797],{"x":1778,"y":1746,"style":151},"print dtype and NaT count",[10,1799,1800,1801,1804,1805,1807,1808,1812,1813,1816,1817,27],{},"The 1970 case is worth dwelling on because the output looks so alien that people assume the data is corrupt. It is not: ",[14,1802,1803],{},"pd.to_datetime(45292)"," with no ",[14,1806,1505],{}," treats the integer as ",[1809,1810,1811],"em",{},"nanoseconds"," since 1970, which is 45 microseconds past midnight on 1 January 1970. Every row lands within a millisecond of the same instant. Seeing a column where every value is ",[14,1814,1815],{},"1970-01-01 00:00:00.000045"," is a reliable fingerprint for a missing ",[14,1818,1819],{},"unit=\"D\"",[621,1821,1822,1835],{},[624,1823,1824],{},[627,1825,1826,1829,1832],{},[630,1827,1828],{},"Symptom",[630,1830,1831],{},"Cause",[630,1833,1834],{},"Fix",[640,1836,1837,1851,1868,1884,1903,1918,1931,1949],{},[627,1838,1839,1842,1845],{},[645,1840,1841],{},"Dates land in 1970",[645,1843,1844],{},"A day-count column parsed as nanoseconds",[645,1846,1847,1848,27],{},"Pass ",[14,1849,1850],{},"unit=\"D\", origin=\"1899-12-30\"",[627,1852,1853,1856,1859],{},[645,1854,1855],{},"Day and month swapped on some rows",[645,1857,1858],{},"Month-first inference on European data",[645,1860,1861,1862,1864,1865,27],{},"Pass an explicit ",[14,1863,508],{},", or ",[14,1866,1867],{},"dayfirst=True",[627,1869,1870,1875,1878],{},[645,1871,1872],{},[14,1873,1874],{},"ValueError: time data ... doesn't match format",[645,1876,1877],{},"One stray value in a mostly clean column",[645,1879,1880,1881,1883],{},"Add ",[14,1882,955],{}," and report the rejects.",[627,1885,1886,1893,1896],{},[645,1887,1888,1889,1892],{},"Column still ",[14,1890,1891],{},"object"," dtype",[645,1894,1895],{},"Assignment forgotten, or every value failed",[645,1897,1898,1899,1902],{},"Check ",[14,1900,1901],{},"df[col].dtype"," after parsing; print the reject counts.",[627,1904,1905,1908,1911],{},[645,1906,1907],{},"Dates shift by one day",[645,1909,1910],{},"Origin anchored at 1900-01-01",[645,1912,1913,1914,1917],{},"Use ",[14,1915,1916],{},"1899-12-30","; see the parent topic on the leap-year bug.",[627,1919,1920,1923,1926],{},[645,1921,1922],{},"Parsing is very slow",[645,1924,1925],{},"Per-value inference on a large column",[645,1927,1861,1928,1930],{},[14,1929,508],{}," string.",[627,1932,1933,1938,1943],{},[645,1934,1935],{},[14,1936,1937],{},"UserWarning: Could not infer format",[645,1939,1940,1941],{},"Mixed layouts without ",[14,1942,172],{},[645,1944,1945,1946,1948],{},"Set ",[14,1947,172],{}," deliberately, or normalise upstream.",[627,1950,1951,1954,1959],{},[645,1952,1953],{},"1965 birthdays land in 2065",[645,1955,1956,1958],{},[14,1957,664],{}," century cutoff",[645,1960,1961],{},"Avoid two-digit years; resolve the century explicitly.",[194,1963,1965],{"id":1964},"performance-and-scale-notes","Performance and scale notes",[10,1967,1968],{},"Parsing cost is dominated by whether pandas can take the vectorised path. A quick benchmark on a realistic column makes the gap concrete:",[199,1970,1972],{"className":231,"code":1971,"language":233,"meta":204,"style":204},"import time\nimport pandas as pd\n\ncol = pd.Series([\"15\u002F08\u002F2026\"] * 500_000)\n\nfor label, kwargs in [\n    (\"explicit format\", {\"format\": \"%d\u002F%m\u002F%Y\"}),\n    (\"mixed inference\", {\"format\": \"mixed\", \"dayfirst\": True}),\n]:\n    start = time.perf_counter()\n    pd.to_datetime(col, errors=\"coerce\", **kwargs)\n    print(f\"{label:\u003C18} {time.perf_counter() - start:6.2f}s\")\n",[14,1973,1974,1981,1991,1995,2015,2019,2033,2059,2085,2090,2100,2119],{"__ignoreMap":204},[208,1975,1976,1978],{"class":210,"line":211},[208,1977,241],{"class":240},[208,1979,1980],{"class":244}," time\n",[208,1982,1983,1985,1987,1989],{"class":210,"line":254},[208,1984,241],{"class":240},[208,1986,245],{"class":244},[208,1988,248],{"class":240},[208,1990,251],{"class":244},[208,1992,1993],{"class":210,"line":261},[208,1994,258],{"emptyLinePlaceholder":257},[208,1996,1997,1999,2001,2003,2005,2007,2010,2013],{"class":210,"line":273},[208,1998,1430],{"class":244},[208,2000,267],{"class":240},[208,2002,817],{"class":244},[208,2004,328],{"class":218},[208,2006,574],{"class":244},[208,2008,2009],{"class":240},"*",[208,2011,2012],{"class":282}," 500_000",[208,2014,421],{"class":244},[208,2016,2017],{"class":210,"line":315},[208,2018,258],{"emptyLinePlaceholder":257},[208,2020,2021,2024,2027,2030],{"class":210,"line":339},[208,2022,2023],{"class":240},"for",[208,2025,2026],{"class":244}," label, kwargs ",[208,2028,2029],{"class":240},"in",[208,2031,2032],{"class":244}," [\n",[208,2034,2035,2038,2041,2044,2047,2050,2052,2054,2056],{"class":210,"line":357},[208,2036,2037],{"class":244},"    (",[208,2039,2040],{"class":218},"\"explicit format\"",[208,2042,2043],{"class":244},", {",[208,2045,2046],{"class":218},"\"format\"",[208,2048,2049],{"class":244},": ",[208,2051,596],{"class":218},[208,2053,600],{"class":599},[208,2055,603],{"class":218},[208,2057,2058],{"class":244},"}),\n",[208,2060,2061,2063,2066,2068,2070,2072,2074,2076,2079,2081,2083],{"class":210,"line":395},[208,2062,2037],{"class":244},[208,2064,2065],{"class":218},"\"mixed inference\"",[208,2067,2043],{"class":244},[208,2069,2046],{"class":218},[208,2071,2049],{"class":244},[208,2073,852],{"class":218},[208,2075,286],{"class":244},[208,2077,2078],{"class":218},"\"dayfirst\"",[208,2080,2049],{"class":244},[208,2082,877],{"class":282},[208,2084,2058],{"class":244},[208,2086,2087],{"class":210,"line":401},[208,2088,2089],{"class":244},"]:\n",[208,2091,2092,2095,2097],{"class":210,"line":1196},[208,2093,2094],{"class":244},"    start ",[208,2096,267],{"class":240},[208,2098,2099],{"class":244}," time.perf_counter()\n",[208,2101,2102,2105,2107,2109,2111,2113,2116],{"class":210,"line":1201},[208,2103,2104],{"class":244},"    pd.to_datetime(col, ",[208,2106,515],{"class":412},[208,2108,267],{"class":240},[208,2110,944],{"class":218},[208,2112,286],{"class":244},[208,2114,2115],{"class":240},"**",[208,2117,2118],{"class":244},"kwargs)\n",[208,2120,2121,2123,2125,2127,2129,2131,2134,2137,2139,2142,2145,2148,2151,2154,2156,2159],{"class":210,"line":1243},[208,2122,1255],{"class":282},[208,2124,1206],{"class":244},[208,2126,1209],{"class":240},[208,2128,596],{"class":218},[208,2130,1214],{"class":599},[208,2132,2133],{"class":244},"label",[208,2135,2136],{"class":240},":\u003C18",[208,2138,1223],{"class":599},[208,2140,2141],{"class":599}," {",[208,2143,2144],{"class":244},"time.perf_counter() ",[208,2146,2147],{"class":240},"-",[208,2149,2150],{"class":244}," start",[208,2152,2153],{"class":240},":6.2f",[208,2155,1223],{"class":599},[208,2157,2158],{"class":218},"s\"",[208,2160,421],{"class":244},[10,2162,2163],{},"The explicit format finishes in a fraction of the time, and the gap widens with row count. Three habits follow from that:",[10,2165,2166,2170,2171,2173],{},[2167,2168,2169],"strong",{},"Parse once, at the boundary."," Convert on ingest, not inside every function that touches the column. Re-parsing an already-",[14,2172,16],{}," column is wasted work and can reintroduce errors.",[10,2175,2176,2179],{},[2167,2177,2178],{},"Deduplicate before parsing when cardinality is low."," A million rows covering three years hold at most ~1,100 distinct dates:",[199,2181,2183],{"className":231,"code":2182,"language":233,"meta":204,"style":204},"uniques = col.dropna().unique()\nlookup = pd.Series(\n    pd.to_datetime(uniques, format=\"%d\u002F%m\u002F%Y\", errors=\"coerce\"), index=uniques\n)\nparsed = col.map(lookup)\n",[14,2184,2185,2195,2205,2237,2241],{"__ignoreMap":204},[208,2186,2187,2190,2192],{"class":210,"line":211},[208,2188,2189],{"class":244},"uniques ",[208,2191,267],{"class":240},[208,2193,2194],{"class":244}," col.dropna().unique()\n",[208,2196,2197,2200,2202],{"class":210,"line":254},[208,2198,2199],{"class":244},"lookup ",[208,2201,267],{"class":240},[208,2203,2204],{"class":244}," pd.Series(\n",[208,2206,2207,2210,2212,2214,2216,2218,2220,2222,2224,2226,2228,2230,2232,2234],{"class":210,"line":261},[208,2208,2209],{"class":244},"    pd.to_datetime(uniques, ",[208,2211,508],{"class":412},[208,2213,267],{"class":240},[208,2215,596],{"class":218},[208,2217,600],{"class":599},[208,2219,603],{"class":218},[208,2221,286],{"class":244},[208,2223,515],{"class":412},[208,2225,267],{"class":240},[208,2227,944],{"class":218},[208,2229,1665],{"class":244},[208,2231,413],{"class":412},[208,2233,267],{"class":240},[208,2235,2236],{"class":244},"uniques\n",[208,2238,2239],{"class":210,"line":273},[208,2240,421],{"class":244},[208,2242,2243,2245,2247],{"class":210,"line":315},[208,2244,920],{"class":244},[208,2246,267],{"class":240},[208,2248,2249],{"class":244}," col.map(lookup)\n",[10,2251,2252,2255,2256,2260,2261,2265],{},[2167,2253,2254],{},"Push the work upstream where you can."," If the file comes from a database extract, having the query emit ISO dates removes the whole problem — see ",[23,2257,2259],{"href":2258},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Fexport-sql-query-results-to-excel-with-python\u002F","exporting SQL query results to Excel with Python",". And for very large workbooks, parse chunk by chunk as described in ",[23,2262,2264],{"href":2263},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fread-large-excel-file-in-chunks-with-pandas\u002F","reading large Excel files in chunks"," so peak memory stays flat.",[194,2267,2269],{"id":2268},"conclusion","Conclusion",[10,2271,2272,2273,2275,2276,2278,2279,2281,2282,2284,2285,2288,2289,2291],{},"Parsing Excel dates comes down to four decisions: whether the cells are already typed (use ",[14,2274,434],{},"), whether you know the layout (pass ",[14,2277,508],{},"), whether the data is day-first (pass ",[14,2280,511],{}," or, better, an explicit format), and what happens to values that do not parse (",[14,2283,955],{},", then ",[1809,2286,2287],{},"report","). Handle serial numbers as their own branch with the ",[14,2290,1916],{}," origin and a plausibility window. Do the parse once at the ingest boundary, and every downstream grouping, filtering and formatting step gets easier.",[194,2293,2295],{"id":2294},"frequently-asked-questions","Frequently asked questions",[10,2297,2298,2310,2311,2313,2314,2316,2317,286,2319,2321,2322,2324,2325,2327],{},[2167,2299,2300,2301,2303,2304,512,2307,2309],{},"Should I use ",[14,2302,434],{}," on ",[14,2305,2306],{},"read_excel",[14,2308,519],{}," afterwards?","\nUse ",[14,2312,434],{}," when the column is already typed as a date in Excel and you just want the dtype. Use ",[14,2315,519],{}," afterwards whenever the values are text, because it gives you the ",[14,2318,508],{},[14,2320,511],{}," and ",[14,2323,515],{}," arguments that ",[14,2326,434],{}," does not expose.",[10,2329,2330,2336,2337,2339,2340,2343],{},[2167,2331,2332,2333,2335],{},"Why is ",[14,2334,787],{}," parsed as 4 March?","\npandas defaults to month-first parsing. For European data pass ",[14,2338,1867],{},", or better, pass an explicit format like ",[14,2341,2342],{},"\"%d\u002F%m\u002F%Y\""," so nothing is inferred and anything that does not match is rejected.",[10,2345,2346,2352,2353,2356,2357,2359],{},[2167,2347,2348,2349,2351],{},"What does ",[14,2350,955],{}," actually do to bad values?","\nIt replaces each unparseable value with ",[14,2354,2355],{},"NaT"," rather than raising. That keeps a batch job running, but you must then check which rows became ",[14,2358,2355],{}," — otherwise the failures disappear silently.",[10,2361,2362,2365,2366,2321,2368,2370,2371,2373,2374,512,2377,27],{},[2167,2363,2364],{},"How do I parse a column that mixes real dates and serial numbers?","\nSplit it. Parse the numeric values with ",[14,2367,1819],{},[14,2369,1772],{},", parse the text values with ",[14,2372,519],{},", then combine the two results with ",[14,2375,2376],{},"fillna",[14,2378,2379],{},"combine_first",[10,2381,2382,2388],{},[2167,2383,2384,2385,2387],{},"Is explicit ",[14,2386,508],{}," really faster?","\nSubstantially. With a known format pandas takes a fast vectorised path; without one it falls back to per-value inference. On a column of a million values the difference is typically an order of magnitude.",[194,2390,2392],{"id":2391},"related","Related",[2394,2395,2396,2403,2410,2417,2424],"ul",{},[2397,2398,2399,2400,2402],"li",{},"Up to the parent: ",[23,2401,26],{"href":25}," — the serial model and the wider date toolkit.",[2397,2404,2405,2409],{},[23,2406,2408],{"href":2407},"\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"," — the display side of the same problem.",[2397,2411,2412,2416],{},[23,2413,2415],{"href":2414},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-rows-by-month-and-quarter-with-pandas\u002F","Group Excel Rows by Month and Quarter with pandas"," — what you can do once the column parses.",[2397,2418,2419,2423],{},[23,2420,2422],{"href":2421},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcheck-excel-data-types-with-pandas\u002F","Check Excel Data Types with pandas"," — validating dtypes across the whole sheet.",[2397,2425,2426,2430,2431,27],{},[23,2427,2429],{"href":2428},"\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Ffill-missing-values-in-excel-with-pandas-fillna\u002F","Fill Missing Values in Excel with pandas fillna"," — what to do with the rows that came back ",[14,2432,2355],{},[2434,2435,2436],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-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":204,"searchDepth":254,"depth":254,"links":2438},[2439,2440,2441,2442,2443,2444,2445,2446,2447,2448,2449],{"id":196,"depth":254,"text":197},{"id":424,"depth":254,"text":425},{"id":522,"depth":254,"text":523},{"id":781,"depth":254,"text":782},{"id":949,"depth":254,"text":950},{"id":1396,"depth":254,"text":1397},{"id":1694,"depth":254,"text":1695},{"id":1964,"depth":254,"text":1965},{"id":2268,"depth":254,"text":2269},{"id":2294,"depth":254,"text":2295},{"id":2391,"depth":254,"text":2392},"2026-08-15","Turn messy Excel date columns into real datetime64 values with pandas — parse_dates on read, explicit formats, dayfirst ambiguity, coerce-and-report, and mixed-type columns.","md",[2454,2457,2460,2463,2465],{"q":2455,"a":2456},"Should I use parse_dates on read_excel or to_datetime afterwards?","Use parse_dates when the column is already typed as a date in Excel and you just want the dtype. Use to_datetime afterwards whenever the values are text, because it gives you the format, dayfirst and errors arguments that parse_dates does not expose.",{"q":2458,"a":2459},"Why is 03\u002F04\u002F2026 parsed as 4 March?","pandas defaults to month-first parsing. For European data pass dayfirst=True, or better, pass an explicit format like \"%d\u002F%m\u002F%Y\" so nothing is inferred and anything that does not match is rejected.",{"q":2461,"a":2462},"What does errors=\"coerce\" actually do to bad values?","It replaces each unparseable value with NaT rather than raising. That keeps a batch job running, but you must then check which rows became NaT — otherwise the failures disappear silently.",{"q":2364,"a":2464},"Split it. Parse the numeric values with unit=\"D\" and origin=\"1899-12-30\", parse the text values with to_datetime, then combine the two results with fillna or combine_first.",{"q":2466,"a":2467},"Is explicit format really faster?","Substantially. With a known format pandas takes a fast vectorised path; without one it falls back to per-value inference. On a column of a million values the difference is typically an order of magnitude.",{},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fparse-excel-dates-into-python-datetimes-with-pandas",{"title":2471,"description":2472},"Parse Excel Dates with pandas (to_datetime Guide)","Reliable date parsing from Excel in Python: parse_dates, explicit format strings, dayfirst for European data, errors=coerce with a rejects report, and mixed serial\u002Ftext columns.","parse-excel-dates-into-python-datetimes-with-pandas","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fparse-excel-dates-into-python-datetimes-with-pandas\u002Findex","how-to","pXI6zRM6ZnvP_5nQqOCWd6djxwHy5SmAG41hgXLTqvE",[2478,2482],{"title":2479,"path":2480,"stem":2481,"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",{"title":2483,"path":2484,"stem":2485,"children":-1},"Working with Large Excel Files in Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python","advanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Findex",1786800027129]