[{"data":1,"prerenderedAt":2195},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-text-dates-like-20240131-with-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-text-dates-like-20240131-with-pandas":2161,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-text-dates-like-20240131-with-pandas":2170,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-text-dates-like-20240131-with-pandas":2175,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-text-dates-like-20240131-with-pandas":2176},{"id":4,"title":5,"body":6,"dateModified":2126,"datePublished":2126,"description":2127,"extension":2128,"faq":2129,"keyword":2138,"kind":2139,"meta":2140,"navigation":342,"path":2149,"seo":2150,"slug":2153,"stem":2154,"topics":2155,"type":2159,"__hash__":2160},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-text-dates-like-20240131-with-pandas\u002Findex.md","Convert Dates Like 20240131, 31.01.24 and 2024031 with pandas",{"type":7,"value":8,"toc":2111},"minimark",[9,48,257,262,289,310,314,418,437,441,456,549,573,577,588,785,794,939,943,955,1138,1162,1166,1177,1323,1342,1346,1357,1383,1394,1587,1602,1606,1609,1766,1781,1949,1953,1992,1996,2022,2026,2046,2050,2056,2062,2068,2074,2078,2107],[10,11,12,13,17,18,21,22,25,26,29,30,33,34,37,38,41,42,47],"p",{},"Dates that come out of accounting systems, mainframes and older ERPs rarely arrive as dates. They arrive as ",[14,15,16],"code",{},"20240131"," — eight digits that Excel and pandas both read as the number twenty million — or as text like ",[14,19,20],{},"31.01.24",", ",[14,23,24],{},"31-Jan-24",", or ",[14,27,28],{},"2024031",", a year followed by the day of the year. Mixed in are placeholders such as ",[14,31,32],{},"00000000"," for \"no date\" and the occasional impossible value like ",[14,35,36],{},"20240231",". ",[14,39,40],{},"pd.to_datetime"," can parse all of these, but only when told the layout; letting it guess invites day-month swaps and silent failures. This page, part of ",[43,44,46],"a",{"href":45},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002F","Working with Dates and Times in Excel Data",", converts each layout explicitly and reports what did not convert.",[49,50,58,59,58,63,58,67,58,75,58,84,58,88,58,95,58,99,58,103,58,107,58,111,58,115,58,120,58,122,58,126,58,128,58,131,58,133,58,136,58,139,58,143,58,145,58,148,58,150,58,152,58,154,58,156,58,159,58,163,58,165,58,168,58,170,58,173,58,175,58,178,58,181,58,185,58,188,58,190,58,193,58,195,58,198,58,200,58,202,58,207,58,211,58,213,58,215,58,217,58,220,58,226,58,230,58,238,58,243,58,251],"svg",{"viewBox":51,"role":52,"ariaLabelledBy":53,"xmlns":56,"style":57},"44 0 671 164","img",[54,55],"txd-convert-t","txd-convert-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:671px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[60,61,62],"title",{"id":54},"Four export layouts converted to the same dates",[64,65,66],"desc",{"id":55},"Left: the same three dates written four ways: 20240131 as a number, 31.01.24, 31-Jan-24 and the day-of-year code 2024031. Right: each parsed with its own format into the real dates 2024-01-31, 2024-02-29 and 2024-03-01.",[68,69],"rect",{"x":70,"y":71,"width":72,"height":73,"fill":74},"44","0","671","164","#ffffff",[68,76],{"x":77,"y":78,"width":79,"height":80,"rx":71,"fill":81,"stroke":82,"style":83},"60.9","40.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[68,85],{"x":86,"y":78,"width":87,"height":80,"rx":71,"fill":81,"stroke":82,"style":83},"92.9","89.4",[89,90,94],"text",{"x":91,"y":92,"style":93},"137.6","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[68,96],{"x":97,"y":78,"width":98,"height":80,"rx":71,"fill":81,"stroke":82,"style":83},"182.3","91.2",[89,100,102],{"x":101,"y":92,"style":93},"227.9","B",[68,104],{"x":105,"y":78,"width":106,"height":80,"rx":71,"fill":81,"stroke":82,"style":83},"273.5","82.0",[89,108,110],{"x":109,"y":92,"style":93},"314.5","C",[68,112],{"x":77,"y":113,"width":79,"height":114,"rx":71,"fill":81,"stroke":82,"style":83},"64.0","28.0",[89,116,119],{"x":117,"y":118,"style":93},"76.9","83.0","1",[68,121],{"x":86,"y":113,"width":87,"height":114,"rx":71,"fill":74,"stroke":82,"style":83},[89,123,125],{"x":91,"y":118,"style":124},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","posting",[68,127],{"x":97,"y":113,"width":98,"height":114,"rx":71,"fill":74,"stroke":82,"style":83},[89,129,130],{"x":101,"y":118,"style":124},"entered",[68,132],{"x":105,"y":113,"width":106,"height":114,"rx":71,"fill":74,"stroke":82,"style":83},[89,134,135],{"x":109,"y":118,"style":124},"julian",[68,137],{"x":77,"y":138,"width":79,"height":114,"rx":71,"fill":81,"stroke":82,"style":83},"92.0",[89,140,142],{"x":117,"y":141,"style":93},"111.0","2",[68,144],{"x":86,"y":138,"width":87,"height":114,"rx":71,"fill":74,"stroke":82,"style":83},[89,146,16],{"x":91,"y":141,"style":147},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[68,149],{"x":97,"y":138,"width":98,"height":114,"rx":71,"fill":74,"stroke":82,"style":83},[89,151,24],{"x":101,"y":141,"style":147},[68,153],{"x":105,"y":138,"width":106,"height":114,"rx":71,"fill":74,"stroke":82,"style":83},[89,155,28],{"x":109,"y":141,"style":147},[68,157],{"x":77,"y":158,"width":79,"height":114,"rx":71,"fill":81,"stroke":82,"style":83},"120.0",[89,160,162],{"x":117,"y":161,"style":93},"139.0","3",[68,164],{"x":86,"y":158,"width":87,"height":114,"rx":71,"fill":74,"stroke":82,"style":83},[89,166,167],{"x":91,"y":161,"style":147},"20240229",[68,169],{"x":97,"y":158,"width":98,"height":114,"rx":71,"fill":74,"stroke":82,"style":83},[89,171,172],{"x":101,"y":161,"style":147},"29-Feb-24",[68,174],{"x":105,"y":158,"width":106,"height":114,"rx":71,"fill":74,"stroke":82,"style":83},[89,176,177],{"x":109,"y":161,"style":147},"2024060",[68,179],{"x":180,"y":78,"width":79,"height":80,"rx":71,"fill":81,"stroke":82,"style":83},"565.9",[68,182],{"x":183,"y":78,"width":184,"height":80,"rx":71,"fill":81,"stroke":82,"style":83},"597.9","101.2",[89,186,94],{"x":187,"y":92,"style":93},"648.5",[68,189],{"x":180,"y":113,"width":79,"height":114,"rx":71,"fill":81,"stroke":82,"style":83},[89,191,119],{"x":192,"y":118,"style":93},"581.9",[68,194],{"x":183,"y":113,"width":184,"height":114,"rx":71,"fill":74,"stroke":82,"style":83},[89,196,197],{"x":187,"y":118,"style":124},"date",[68,199],{"x":180,"y":138,"width":79,"height":114,"rx":71,"fill":81,"stroke":82,"style":83},[89,201,142],{"x":192,"y":141,"style":93},[68,203],{"x":183,"y":138,"width":184,"height":114,"rx":71,"fill":204,"stroke":205,"style":206},"#d9f4f1","var(--teal,#0f9488)","stroke-width:1.5px",[89,208,210],{"x":187,"y":141,"style":209},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","2024-01-31",[68,212],{"x":180,"y":158,"width":79,"height":114,"rx":71,"fill":81,"stroke":82,"style":83},[89,214,162],{"x":192,"y":161,"style":93},[68,216],{"x":183,"y":158,"width":184,"height":114,"rx":71,"fill":204,"stroke":205,"style":206},[89,218,219],{"x":187,"y":161,"style":209},"2024-02-29",[89,221,225],{"x":222,"y":223,"style":224},"208.2","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","as exported",[89,227,229],{"x":228,"y":223,"style":224},"632.5","parsed",[231,232],"line",{"x1":233,"y1":234,"x2":235,"y2":234,"stroke":236,"style":237},"365.5","94.0","551.9","var(--brand,#5b5cf0)","stroke-width:2px",[239,240],"polygon",{"points":241,"fill":242},"555.9,94.0 546.9,99.0 546.9,89.0","#5b5cf0",[68,244],{"x":245,"y":246,"width":247,"height":248,"rx":249,"fill":74,"stroke":82,"style":250},"392.6","61.0","136.3","22.0","11","stroke-width:1.2px",[89,252,256],{"x":253,"y":254,"style":255},"460.7","76.0","font-size:11.5px;font-weight:400;fill:var(--brand-strong,#4338ca);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","explicit formats",[258,259,261],"h2",{"id":260},"prerequisites","Prerequisites",[263,264,269],"pre",{"className":265,"code":266,"language":267,"meta":268,"style":268},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,270,271],{"__ignoreMap":268},[272,273,275,279,283,286],"span",{"class":231,"line":274},1,[272,276,278],{"class":277},"sMTad","pip",[272,280,282],{"class":281},"srMev"," install",[272,284,285],{"class":281}," pandas",[272,287,288],{"class":281}," openpyxl\n",[10,290,291,294,295,298,299,302,303,305,306,309],{},[14,292,293],{},"postings.xlsx"," has six documents with dates in four layouts: an eight-digit number, ",[14,296,297],{},"dd.mm.yy"," text, ",[14,300,301],{},"dd-Mon-yy"," text and a day-of-year code. The data includes a leap day, an impossible 31 February, a ",[14,304,71],{}," placeholder, blanks and a two-digit year of ",[14,307,308],{},"69",".",[258,311,313],{"id":312},"step-1-see-what-read_excel-returns","Step 1 — See what read_excel returns",[263,315,319],{"className":316,"code":317,"language":318,"meta":268,"style":268},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nraw = pd.read_excel(\"postings.xlsx\", dtype={\"value_date\": str, \"entered\": str, \"julian\": str})\nprint(raw.dtypes.to_dict())\nprint(raw)\n","python",[14,320,321,337,344,401,410],{"__ignoreMap":268},[272,322,323,327,331,334],{"class":231,"line":274},[272,324,326],{"class":325},"s-kum","import",[272,328,330],{"class":329},"skGVy"," pandas ",[272,332,333],{"class":325},"as",[272,335,336],{"class":329}," pd\n",[272,338,340],{"class":231,"line":339},2,[272,341,343],{"emptyLinePlaceholder":342},true,"\n",[272,345,347,350,353,356,359,361,365,367,370,373,376,380,382,385,387,389,391,394,396,398],{"class":231,"line":346},3,[272,348,349],{"class":329},"raw ",[272,351,352],{"class":325},"=",[272,354,355],{"class":329}," pd.read_excel(",[272,357,358],{"class":281},"\"postings.xlsx\"",[272,360,21],{"class":329},[272,362,364],{"class":363},"sa561","dtype",[272,366,352],{"class":325},[272,368,369],{"class":329},"{",[272,371,372],{"class":281},"\"value_date\"",[272,374,375],{"class":329},": ",[272,377,379],{"class":378},"sP0c6","str",[272,381,21],{"class":329},[272,383,384],{"class":281},"\"entered\"",[272,386,375],{"class":329},[272,388,379],{"class":378},[272,390,21],{"class":329},[272,392,393],{"class":281},"\"julian\"",[272,395,375],{"class":329},[272,397,379],{"class":378},[272,399,400],{"class":329},"})\n",[272,402,404,407],{"class":231,"line":403},4,[272,405,406],{"class":378},"print",[272,408,409],{"class":329},"(raw.dtypes.to_dict())\n",[272,411,413,415],{"class":231,"line":412},5,[272,414,406],{"class":378},[272,416,417],{"class":329},"(raw)\n",[10,419,420,422,423,426,427,429,430,432,433,436],{},[14,421,125],{}," is ",[14,424,425],{},"int64",": nothing about 20240131 tells pandas it is a date. The text columns are read as strings because of the ",[14,428,364],{}," argument — without it, ",[14,431,135],{}," would also be read as an integer. Blank cells arrive as ",[14,434,435],{},"NaN"," either way. Reading date-like codes as text is a good default: it keeps leading zeros and stops pandas guessing.",[258,438,440],{"id":439},"step-2-parse-yyyymmdd-numbers-with-an-explicit-format","Step 2 — Parse yyyymmdd numbers with an explicit format",[10,442,443,444,447,448,451,452,455],{},"Convert the integers to strings and parse them with ",[14,445,446],{},"format=\"%Y%m%d\"",". Placeholders and impossible dates will not match, so use ",[14,449,450],{},"errors=\"coerce\""," to turn them into ",[14,453,454],{},"NaT"," — and look at them immediately:",[263,457,459],{"className":316,"code":458,"language":318,"meta":268,"style":268},"posting_text = raw[\"posting\"].astype(str).str.zfill(8)\nposting = pd.to_datetime(posting_text, format=\"%Y%m%d\", errors=\"coerce\")\nprint(pd.DataFrame({\"raw\": raw[\"posting\"], \"parsed\": posting}))\n",[14,460,461,488,525],{"__ignoreMap":268},[272,462,463,466,468,471,474,477,479,482,485],{"class":231,"line":274},[272,464,465],{"class":329},"posting_text ",[272,467,352],{"class":325},[272,469,470],{"class":329}," raw[",[272,472,473],{"class":281},"\"posting\"",[272,475,476],{"class":329},"].astype(",[272,478,379],{"class":378},[272,480,481],{"class":329},").str.zfill(",[272,483,484],{"class":378},"8",[272,486,487],{"class":329},")\n",[272,489,490,493,495,498,501,503,506,510,513,515,518,520,523],{"class":231,"line":339},[272,491,492],{"class":329},"posting ",[272,494,352],{"class":325},[272,496,497],{"class":329}," pd.to_datetime(posting_text, ",[272,499,500],{"class":363},"format",[272,502,352],{"class":325},[272,504,505],{"class":281},"\"%Y%m",[272,507,509],{"class":508},"sSjpA","%d",[272,511,512],{"class":281},"\"",[272,514,21],{"class":329},[272,516,517],{"class":363},"errors",[272,519,352],{"class":325},[272,521,522],{"class":281},"\"coerce\"",[272,524,487],{"class":329},[272,526,527,529,532,535,538,540,543,546],{"class":231,"line":346},[272,528,406],{"class":378},[272,530,531],{"class":329},"(pd.DataFrame({",[272,533,534],{"class":281},"\"raw\"",[272,536,537],{"class":329},": raw[",[272,539,473],{"class":281},[272,541,542],{"class":329},"], ",[272,544,545],{"class":281},"\"parsed\"",[272,547,548],{"class":329},": posting}))\n",[10,550,551,553,554,556,557,559,560,562,563,565,566,569,570,572],{},[14,552,167],{}," parses — 2024 is a leap year — and ",[14,555,36],{}," becomes ",[14,558,454],{},", because there is no 31 February. The ",[14,561,71],{}," placeholder, padded to ",[14,564,32],{}," by ",[14,567,568],{},"zfill",", is also ",[14,571,454],{},". Those are two different situations — an invalid date that someone should fix, and a deliberate \"no date\" — and the next step keeps them apart.",[258,574,576],{"id":575},"step-3-separate-placeholders-from-invalid-dates","Step 3 — Separate placeholders from invalid dates",[10,578,579,580,21,582,21,584,587],{},"Decide which values mean \"no date\" in this export — commonly ",[14,581,71],{},[14,583,32],{},[14,585,586],{},"99991231"," or blank — and treat them as missing on purpose. Anything else that fails to parse is an error to report:",[263,589,591],{"className":316,"code":590,"language":318,"meta":268,"style":268},"PLACEHOLDERS = {\"00000000\", \"99991231\", \"\"}\n\ndef parse_dates(text, fmt):\n    text = text.fillna(\"\").astype(str).str.strip()\n    is_placeholder = text.isin(PLACEHOLDERS)\n    parsed = pd.to_datetime(text.where(~is_placeholder), format=fmt, errors=\"coerce\")\n    invalid = parsed.isna() & ~is_placeholder\n    return parsed, invalid\n\nposting, bad_posting = parse_dates(posting_text, \"%Y%m%d\")\nprint(\"invalid posting dates:\", raw.loc[bad_posting, [\"doc\", \"posting\"]].to_dict(\"records\"))\n",[14,592,593,620,624,636,656,670,702,722,731,736,755],{"__ignoreMap":268},[272,594,595,598,601,604,607,609,612,614,617],{"class":231,"line":274},[272,596,597],{"class":378},"PLACEHOLDERS",[272,599,600],{"class":325}," =",[272,602,603],{"class":329}," {",[272,605,606],{"class":281},"\"00000000\"",[272,608,21],{"class":329},[272,610,611],{"class":281},"\"99991231\"",[272,613,21],{"class":329},[272,615,616],{"class":281},"\"\"",[272,618,619],{"class":329},"}\n",[272,621,622],{"class":231,"line":339},[272,623,343],{"emptyLinePlaceholder":342},[272,625,626,629,633],{"class":231,"line":346},[272,627,628],{"class":325},"def",[272,630,632],{"class":631},"s_Opv"," parse_dates",[272,634,635],{"class":329},"(text, fmt):\n",[272,637,638,641,643,646,648,651,653],{"class":231,"line":403},[272,639,640],{"class":329},"    text ",[272,642,352],{"class":325},[272,644,645],{"class":329}," text.fillna(",[272,647,616],{"class":281},[272,649,650],{"class":329},").astype(",[272,652,379],{"class":378},[272,654,655],{"class":329},").str.strip()\n",[272,657,658,661,663,666,668],{"class":231,"line":412},[272,659,660],{"class":329},"    is_placeholder ",[272,662,352],{"class":325},[272,664,665],{"class":329}," text.isin(",[272,667,597],{"class":378},[272,669,487],{"class":329},[272,671,673,676,678,681,684,687,689,691,694,696,698,700],{"class":231,"line":672},6,[272,674,675],{"class":329},"    parsed ",[272,677,352],{"class":325},[272,679,680],{"class":329}," pd.to_datetime(text.where(",[272,682,683],{"class":325},"~",[272,685,686],{"class":329},"is_placeholder), ",[272,688,500],{"class":363},[272,690,352],{"class":325},[272,692,693],{"class":329},"fmt, ",[272,695,517],{"class":363},[272,697,352],{"class":325},[272,699,522],{"class":281},[272,701,487],{"class":329},[272,703,705,708,710,713,716,719],{"class":231,"line":704},7,[272,706,707],{"class":329},"    invalid ",[272,709,352],{"class":325},[272,711,712],{"class":329}," parsed.isna() ",[272,714,715],{"class":325},"&",[272,717,718],{"class":325}," ~",[272,720,721],{"class":329},"is_placeholder\n",[272,723,725,728],{"class":231,"line":724},8,[272,726,727],{"class":325},"    return",[272,729,730],{"class":329}," parsed, invalid\n",[272,732,734],{"class":231,"line":733},9,[272,735,343],{"emptyLinePlaceholder":342},[272,737,739,742,744,747,749,751,753],{"class":231,"line":738},10,[272,740,741],{"class":329},"posting, bad_posting ",[272,743,352],{"class":325},[272,745,746],{"class":329}," parse_dates(posting_text, ",[272,748,505],{"class":281},[272,750,509],{"class":508},[272,752,512],{"class":281},[272,754,487],{"class":329},[272,756,758,760,763,766,769,772,774,776,779,782],{"class":231,"line":757},11,[272,759,406],{"class":378},[272,761,762],{"class":329},"(",[272,764,765],{"class":281},"\"invalid posting dates:\"",[272,767,768],{"class":329},", raw.loc[bad_posting, [",[272,770,771],{"class":281},"\"doc\"",[272,773,21],{"class":329},[272,775,473],{"class":281},[272,777,778],{"class":329},"]].to_dict(",[272,780,781],{"class":281},"\"records\"",[272,783,784],{"class":329},"))\n",[10,786,787,788,790,791,793],{},"Only D3's ",[14,789,36],{}," is reported. D4's placeholder is a missing date, as the export intended. ",[14,792,586],{}," — \"valid forever\" — is another common placeholder in master data; whether it should become missing or stay as a real far-future date depends on what the column means.",[49,795,58,801,58,804,58,807,58,812,58,817,58,822,58,828,58,831,58,835,58,839,58,843,58,845,58,850,58,852,58,855,58,858,58,861,58,863,58,866,58,868,58,871,58,874,58,877,58,879,58,883,58,885,58,888,58,891,58,894,58,896,58,900,58,902,58,905,58,908,58,911,58,913,58,917,58,919,58,922,58,925,58,928,58,930,58,934,58,936],{"viewBox":796,"role":52,"ariaLabelledBy":797,"xmlns":56,"style":800},"4 0 752 276",[798,799],"txd-formats-t","txd-formats-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[60,802,803],{"id":798},"Export layouts and the format codes that parse them",[64,805,806],{"id":799},"A reference table. 20240131 parses with %Y%m%d after converting to text. 31.01.24 parses with %d.%m.%y. 31-Jan-24 parses with %d-%b-%y. 2024031, a year and day-of-year, parses with %Y%j. 2024-01-31 parses with %Y-%m-%d. Placeholders such as 00000000 are treated as missing before parsing.",[68,808],{"x":809,"y":71,"width":810,"height":811,"fill":74},"4","752","276",[68,813],{"x":814,"y":815,"width":816,"height":78,"rx":71,"fill":74,"stroke":82,"style":83},"20.0","16.0","103.2",[68,818],{"x":819,"y":815,"width":820,"height":78,"rx":71,"fill":821,"stroke":236,"style":250},"123.2","308.4","#f0f4ff",[89,823,827],{"x":824,"y":825,"style":826},"277.4","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Format code",[68,829],{"x":830,"y":815,"width":820,"height":78,"rx":71,"fill":821,"stroke":236,"style":250},"431.6",[89,832,834],{"x":833,"y":825,"style":826},"585.8","Note",[68,836],{"x":814,"y":837,"width":816,"height":838,"rx":71,"fill":81,"stroke":82,"style":83},"56.0","34.0",[89,840,16],{"x":79,"y":841,"style":842},"78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start",[68,844],{"x":819,"y":837,"width":820,"height":838,"rx":71,"fill":74,"stroke":82,"style":83},[89,846,849],{"x":824,"y":847,"style":848},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","%Y%m%d",[68,851],{"x":830,"y":837,"width":820,"height":838,"rx":71,"fill":74,"stroke":82,"style":83},[89,853,854],{"x":833,"y":847,"style":848},"convert the integer to text first",[68,856],{"x":814,"y":857,"width":816,"height":838,"rx":71,"fill":81,"stroke":82,"style":83},"90.0",[89,859,20],{"x":79,"y":860,"style":842},"112.0",[68,862],{"x":819,"y":857,"width":820,"height":838,"rx":71,"fill":74,"stroke":82,"style":83},[89,864,865],{"x":824,"y":141,"style":848},"%d.%m.%y",[68,867],{"x":830,"y":857,"width":820,"height":838,"rx":71,"fill":74,"stroke":82,"style":83},[89,869,870],{"x":833,"y":141,"style":848},"two-digit year rule applies",[68,872],{"x":814,"y":873,"width":816,"height":838,"rx":71,"fill":81,"stroke":82,"style":83},"124.0",[89,875,24],{"x":79,"y":876,"style":842},"146.0",[68,878],{"x":819,"y":873,"width":820,"height":838,"rx":71,"fill":74,"stroke":82,"style":83},[89,880,882],{"x":824,"y":881,"style":848},"145.0","%d-%b-%y",[68,884],{"x":830,"y":873,"width":820,"height":838,"rx":71,"fill":74,"stroke":82,"style":83},[89,886,887],{"x":833,"y":881,"style":848},"English month abbreviations",[68,889],{"x":814,"y":890,"width":816,"height":838,"rx":71,"fill":81,"stroke":82,"style":83},"158.0",[89,892,28],{"x":79,"y":893,"style":842},"180.0",[68,895],{"x":819,"y":890,"width":820,"height":838,"rx":71,"fill":74,"stroke":82,"style":83},[89,897,899],{"x":824,"y":898,"style":848},"179.0","%Y%j",[68,901],{"x":830,"y":890,"width":820,"height":838,"rx":71,"fill":74,"stroke":82,"style":83},[89,903,904],{"x":833,"y":898,"style":848},"day of year 001-366",[68,906],{"x":814,"y":907,"width":816,"height":838,"rx":71,"fill":81,"stroke":82,"style":83},"192.0",[89,909,210],{"x":79,"y":910,"style":842},"214.0",[68,912],{"x":819,"y":907,"width":820,"height":838,"rx":71,"fill":74,"stroke":82,"style":83},[89,914,916],{"x":824,"y":915,"style":848},"213.0","%Y-%m-%d",[68,918],{"x":830,"y":907,"width":820,"height":838,"rx":71,"fill":74,"stroke":82,"style":83},[89,920,921],{"x":833,"y":915,"style":848},"ISO; the safe default",[68,923],{"x":814,"y":924,"width":816,"height":838,"rx":71,"fill":81,"stroke":82,"style":83},"226.0",[89,926,32],{"x":79,"y":927,"style":842},"248.0",[68,929],{"x":819,"y":924,"width":820,"height":838,"rx":71,"fill":74,"stroke":82,"style":83},[89,931,933],{"x":824,"y":932,"style":848},"247.0","placeholder",[68,935],{"x":830,"y":924,"width":820,"height":838,"rx":71,"fill":74,"stroke":82,"style":83},[89,937,938],{"x":833,"y":932,"style":848},"set to missing before parsing",[258,940,942],{"id":941},"step-4-parse-the-text-layouts","Step 4 — Parse the text layouts",[10,944,945,946,948,949,951,952,954],{},"Each remaining column gets its own format. ",[14,947,865],{}," and ",[14,950,882],{}," use two-digit years; ",[14,953,899],{}," reads a four-digit year followed by the day of the year:",[263,956,958],{"className":316,"code":957,"language":318,"meta":268,"style":268},"value_date, bad_value = parse_dates(raw[\"value_date\"], \"%d.%m.%y\")\nentered, bad_entered = parse_dates(raw[\"entered\"], \"%d-%b-%y\")\njulian, bad_julian = parse_dates(raw[\"julian\"], \"%Y%j\")\n\ndates = pd.DataFrame({\"doc\": raw[\"doc\"], \"posting\": posting, \"value_date\": value_date,\n                      \"entered\": entered, \"julian\": julian})\nprint(dates.to_string(index=False))\nprint(\"invalid:\", {c: int(m.sum()) for c, m in\n                   {\"value_date\": bad_value, \"entered\": bad_entered, \"julian\": bad_julian}.items()})\n",[14,959,960,983,1011,1029,1033,1061,1074,1091,1118],{"__ignoreMap":268},[272,961,962,965,967,970,972,974,976,978,981],{"class":231,"line":274},[272,963,964],{"class":329},"value_date, bad_value ",[272,966,352],{"class":325},[272,968,969],{"class":329}," parse_dates(raw[",[272,971,372],{"class":281},[272,973,542],{"class":329},[272,975,512],{"class":281},[272,977,509],{"class":508},[272,979,980],{"class":281},".%m.%y\"",[272,982,487],{"class":329},[272,984,985,988,990,992,994,996,998,1000,1003,1006,1009],{"class":231,"line":339},[272,986,987],{"class":329},"entered, bad_entered ",[272,989,352],{"class":325},[272,991,969],{"class":329},[272,993,384],{"class":281},[272,995,542],{"class":329},[272,997,512],{"class":281},[272,999,509],{"class":508},[272,1001,1002],{"class":281},"-",[272,1004,1005],{"class":508},"%b",[272,1007,1008],{"class":281},"-%y\"",[272,1010,487],{"class":329},[272,1012,1013,1016,1018,1020,1022,1024,1027],{"class":231,"line":346},[272,1014,1015],{"class":329},"julian, bad_julian ",[272,1017,352],{"class":325},[272,1019,969],{"class":329},[272,1021,393],{"class":281},[272,1023,542],{"class":329},[272,1025,1026],{"class":281},"\"%Y%j\"",[272,1028,487],{"class":329},[272,1030,1031],{"class":231,"line":403},[272,1032,343],{"emptyLinePlaceholder":342},[272,1034,1035,1038,1040,1043,1045,1047,1049,1051,1053,1056,1058],{"class":231,"line":412},[272,1036,1037],{"class":329},"dates ",[272,1039,352],{"class":325},[272,1041,1042],{"class":329}," pd.DataFrame({",[272,1044,771],{"class":281},[272,1046,537],{"class":329},[272,1048,771],{"class":281},[272,1050,542],{"class":329},[272,1052,473],{"class":281},[272,1054,1055],{"class":329},": posting, ",[272,1057,372],{"class":281},[272,1059,1060],{"class":329},": value_date,\n",[272,1062,1063,1066,1069,1071],{"class":231,"line":672},[272,1064,1065],{"class":281},"                      \"entered\"",[272,1067,1068],{"class":329},": entered, ",[272,1070,393],{"class":281},[272,1072,1073],{"class":329},": julian})\n",[272,1075,1076,1078,1081,1084,1086,1089],{"class":231,"line":704},[272,1077,406],{"class":378},[272,1079,1080],{"class":329},"(dates.to_string(",[272,1082,1083],{"class":363},"index",[272,1085,352],{"class":325},[272,1087,1088],{"class":378},"False",[272,1090,784],{"class":329},[272,1092,1093,1095,1097,1100,1103,1106,1109,1112,1115],{"class":231,"line":724},[272,1094,406],{"class":378},[272,1096,762],{"class":329},[272,1098,1099],{"class":281},"\"invalid:\"",[272,1101,1102],{"class":329},", {c: ",[272,1104,1105],{"class":378},"int",[272,1107,1108],{"class":329},"(m.sum()) ",[272,1110,1111],{"class":325},"for",[272,1113,1114],{"class":329}," c, m ",[272,1116,1117],{"class":325},"in\n",[272,1119,1120,1123,1125,1128,1130,1133,1135],{"class":231,"line":733},[272,1121,1122],{"class":329},"                   {",[272,1124,372],{"class":281},[272,1126,1127],{"class":329},": bad_value, ",[272,1129,384],{"class":281},[272,1131,1132],{"class":329},": bad_entered, ",[272,1134,393],{"class":281},[272,1136,1137],{"class":329},": bad_julian}.items()})\n",[10,1139,1140,1141,1143,1144,21,1147,1150,1151,21,1154,1157,1158,1161],{},"Every layout produces the same calendar dates where the source agrees. ",[14,1142,1005],{}," matches English month abbreviations (",[14,1145,1146],{},"Jan",[14,1148,1149],{},"Feb","); exports in other languages — ",[14,1152,1153],{},"31-Jän-24",[14,1155,1156],{},"31-janv.-24"," — need the month names mapped to numbers first. Day-of-year 060 in 2024 is 29 February and in 2025 would be 1 March, which ",[14,1159,1160],{},"%j"," gets right because it knows the year.",[258,1163,1165],{"id":1164},"dates-with-times-attached","Dates with times attached",[10,1167,1168,1169,1172,1173,1176],{},"Log-style exports often append the time: ",[14,1170,1171],{},"20240131143000"," for 14:30 on 31 January, or a date column next to a separate ",[14,1174,1175],{},"HHMM"," time column. The same approach works — extend the format, or join the two columns into one string first:",[263,1178,1180],{"className":316,"code":1179,"language":318,"meta":268,"style":268},"stamps = pd.Series([\"20240131143000\", \"20240229081505\"])\nprint(pd.to_datetime(stamps, format=\"%Y%m%d%H%M%S\").tolist())\n\nsplit = pd.DataFrame({\"date\": [20240131, 20240229], \"time\": [930, 1745]})\njoined = split[\"date\"].astype(str) + split[\"time\"].astype(str).str.zfill(4)\nprint(pd.to_datetime(joined, format=\"%Y%m%d%H%M\").tolist())\n",[14,1181,1182,1203,1224,1228,1267,1303],{"__ignoreMap":268},[272,1183,1184,1187,1189,1192,1195,1197,1200],{"class":231,"line":274},[272,1185,1186],{"class":329},"stamps ",[272,1188,352],{"class":325},[272,1190,1191],{"class":329}," pd.Series([",[272,1193,1194],{"class":281},"\"20240131143000\"",[272,1196,21],{"class":329},[272,1198,1199],{"class":281},"\"20240229081505\"",[272,1201,1202],{"class":329},"])\n",[272,1204,1205,1207,1210,1212,1214,1216,1218,1221],{"class":231,"line":339},[272,1206,406],{"class":378},[272,1208,1209],{"class":329},"(pd.to_datetime(stamps, ",[272,1211,500],{"class":363},[272,1213,352],{"class":325},[272,1215,505],{"class":281},[272,1217,509],{"class":508},[272,1219,1220],{"class":281},"%H%M%S\"",[272,1222,1223],{"class":329},").tolist())\n",[272,1225,1226],{"class":231,"line":346},[272,1227,343],{"emptyLinePlaceholder":342},[272,1229,1230,1233,1235,1237,1240,1243,1245,1247,1249,1251,1254,1256,1259,1261,1264],{"class":231,"line":403},[272,1231,1232],{"class":329},"split ",[272,1234,352],{"class":325},[272,1236,1042],{"class":329},[272,1238,1239],{"class":281},"\"date\"",[272,1241,1242],{"class":329},": [",[272,1244,16],{"class":378},[272,1246,21],{"class":329},[272,1248,167],{"class":378},[272,1250,542],{"class":329},[272,1252,1253],{"class":281},"\"time\"",[272,1255,1242],{"class":329},[272,1257,1258],{"class":378},"930",[272,1260,21],{"class":329},[272,1262,1263],{"class":378},"1745",[272,1265,1266],{"class":329},"]})\n",[272,1268,1269,1272,1274,1277,1279,1281,1283,1286,1289,1291,1293,1295,1297,1299,1301],{"class":231,"line":412},[272,1270,1271],{"class":329},"joined ",[272,1273,352],{"class":325},[272,1275,1276],{"class":329}," split[",[272,1278,1239],{"class":281},[272,1280,476],{"class":329},[272,1282,379],{"class":378},[272,1284,1285],{"class":329},") ",[272,1287,1288],{"class":325},"+",[272,1290,1276],{"class":329},[272,1292,1253],{"class":281},[272,1294,476],{"class":329},[272,1296,379],{"class":378},[272,1298,481],{"class":329},[272,1300,809],{"class":378},[272,1302,487],{"class":329},[272,1304,1305,1307,1310,1312,1314,1316,1318,1321],{"class":231,"line":672},[272,1306,406],{"class":378},[272,1308,1309],{"class":329},"(pd.to_datetime(joined, ",[272,1311,500],{"class":363},[272,1313,352],{"class":325},[272,1315,505],{"class":281},[272,1317,509],{"class":508},[272,1319,1320],{"class":281},"%H%M\"",[272,1322,1223],{"class":329},[10,1324,1325,1328,1329,1332,1333,1336,1337,1341],{},[14,1326,1327],{},"zfill(4)"," matters for the second form. Times are exported as numbers, so 09:30 becomes 930 and 00:45 becomes 45. The parser is lenient about digit counts, which makes the unpadded forms dangerous rather than failing: ",[14,1330,1331],{},"2024013145"," parses as 04:05, and ",[14,1334,1335],{},"202401315"," — the time 00:05 — as 3 January at 01:05, with the wrong date. Padding every time to four digits makes the string unambiguous. Keep times in the same timezone as the source system; ",[43,1338,1340],{"href":1339},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-timezones-in-excel-timestamps-with-python\u002F","Handle Timezones in Excel Timestamps with Python"," covers converting them.",[258,1343,1345],{"id":1344},"step-5-check-the-two-digit-year-rule","Step 5 — Check the two-digit year rule",[10,1347,1348,1349,1352,1353,1356],{},"D5's value date is ",[14,1350,1351],{},"05.11.69",". With ",[14,1354,1355],{},"%y",", Python maps two-digit years 69–99 to 1969–1999 and 00–68 to 2000–2068:",[263,1358,1360],{"className":316,"code":1359,"language":318,"meta":268,"style":268},"print(dates.loc[4, [\"doc\", \"value_date\"]].to_dict())\n",[14,1361,1362],{"__ignoreMap":268},[272,1363,1364,1366,1369,1371,1374,1376,1378,1380],{"class":231,"line":274},[272,1365,406],{"class":378},[272,1367,1368],{"class":329},"(dates.loc[",[272,1370,809],{"class":378},[272,1372,1373],{"class":329},", [",[272,1375,771],{"class":281},[272,1377,21],{"class":329},[272,1379,372],{"class":281},[272,1381,1382],{"class":329},"]].to_dict())\n",[10,1384,1385,1386,1389,1390,1393],{},"The result is 5 November 1969, while the same document's other dates are in 2024 — almost certainly a typo for ",[14,1387,1388],{},"05.11.24",", or a system that meant 2069. The pivot is right for recent transaction dates and wrong for older ones: birth dates from the 1950s written as ",[14,1391,1392],{},"52"," would become 2052. When the rule does not fit, parse the two digits yourself and choose the century from context:",[263,1395,1397],{"className":316,"code":1396,"language":318,"meta":268,"style":268},"def century_from_context(text, cutoff_year=2030):\n    parts = text.str.extract(r\"(?P\u003Cd>\\d{2})\\.(?P\u003Cm>\\d{2})\\.(?P\u003Cy>\\d{2})\").astype(float)\n    year = 2000 + parts[\"y\"]\n    year = year.where(year \u003C= cutoff_year, year - 100)\n    return pd.to_datetime(pd.DataFrame({\"year\": year, \"month\": parts[\"m\"], \"day\": parts[\"d\"]}), errors=\"coerce\")\n\nprint(century_from_context(raw[\"value_date\"]).tolist())\n",[14,1398,1399,1417,1484,1506,1528,1571,1575],{"__ignoreMap":268},[272,1400,1401,1403,1406,1409,1411,1414],{"class":231,"line":274},[272,1402,628],{"class":325},[272,1404,1405],{"class":631}," century_from_context",[272,1407,1408],{"class":329},"(text, cutoff_year",[272,1410,352],{"class":325},[272,1412,1413],{"class":378},"2030",[272,1415,1416],{"class":329},"):\n",[272,1418,1419,1422,1424,1427,1430,1432,1434,1438,1441,1444,1447,1451,1453,1456,1458,1460,1462,1464,1466,1469,1471,1473,1475,1477,1479,1482],{"class":231,"line":339},[272,1420,1421],{"class":329},"    parts ",[272,1423,352],{"class":325},[272,1425,1426],{"class":329}," text.str.extract(",[272,1428,1429],{"class":325},"r",[272,1431,512],{"class":281},[272,1433,762],{"class":378},[272,1435,1437],{"class":1436},"sx1sP","?P\u003Cd>",[272,1439,1440],{"class":378},"\\d",[272,1442,1443],{"class":325},"{2}",[272,1445,1446],{"class":378},")",[272,1448,1450],{"class":1449},"s_b0D","\\.",[272,1452,762],{"class":378},[272,1454,1455],{"class":1436},"?P\u003Cm>",[272,1457,1440],{"class":378},[272,1459,1443],{"class":325},[272,1461,1446],{"class":378},[272,1463,1450],{"class":1449},[272,1465,762],{"class":378},[272,1467,1468],{"class":1436},"?P\u003Cy>",[272,1470,1440],{"class":378},[272,1472,1443],{"class":325},[272,1474,1446],{"class":378},[272,1476,512],{"class":281},[272,1478,650],{"class":329},[272,1480,1481],{"class":378},"float",[272,1483,487],{"class":329},[272,1485,1486,1489,1491,1494,1497,1500,1503],{"class":231,"line":346},[272,1487,1488],{"class":329},"    year ",[272,1490,352],{"class":325},[272,1492,1493],{"class":378}," 2000",[272,1495,1496],{"class":325}," +",[272,1498,1499],{"class":329}," parts[",[272,1501,1502],{"class":281},"\"y\"",[272,1504,1505],{"class":329},"]\n",[272,1507,1508,1510,1512,1515,1518,1521,1523,1526],{"class":231,"line":403},[272,1509,1488],{"class":329},[272,1511,352],{"class":325},[272,1513,1514],{"class":329}," year.where(year ",[272,1516,1517],{"class":325},"\u003C=",[272,1519,1520],{"class":329}," cutoff_year, year ",[272,1522,1002],{"class":325},[272,1524,1525],{"class":378}," 100",[272,1527,487],{"class":329},[272,1529,1530,1532,1535,1538,1541,1544,1547,1550,1552,1555,1557,1560,1563,1565,1567,1569],{"class":231,"line":412},[272,1531,727],{"class":325},[272,1533,1534],{"class":329}," pd.to_datetime(pd.DataFrame({",[272,1536,1537],{"class":281},"\"year\"",[272,1539,1540],{"class":329},": year, ",[272,1542,1543],{"class":281},"\"month\"",[272,1545,1546],{"class":329},": parts[",[272,1548,1549],{"class":281},"\"m\"",[272,1551,542],{"class":329},[272,1553,1554],{"class":281},"\"day\"",[272,1556,1546],{"class":329},[272,1558,1559],{"class":281},"\"d\"",[272,1561,1562],{"class":329},"]}), ",[272,1564,517],{"class":363},[272,1566,352],{"class":325},[272,1568,522],{"class":281},[272,1570,487],{"class":329},[272,1572,1573],{"class":231,"line":672},[272,1574,343],{"emptyLinePlaceholder":342},[272,1576,1577,1579,1582,1584],{"class":231,"line":704},[272,1578,406],{"class":378},[272,1580,1581],{"class":329},"(century_from_context(raw[",[272,1583,372],{"class":281},[272,1585,1586],{"class":329},"]).tolist())\n",[10,1588,1589,1590,1592,1593,1596,1597,1601],{},"With a cutoff of 2030, ",[14,1591,308],{}," becomes 1969 as before but ",[14,1594,1595],{},"25"," stays 2025; a plausibility check — value dates more than a few years from the posting date — is what actually catches the typo. ",[43,1598,1600],{"href":1599},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fvalidate-excel-columns-before-import-with-pandas\u002F","Validate Excel Columns Before Import with pandas"," shows how to express range checks like that.",[258,1603,1605],{"id":1604},"step-6-write-real-dates-and-the-report-of-failures","Step 6 — Write real dates, and the report of failures",[10,1607,1608],{},"Write the converted dates with a date format, and the invalid values on their own sheet so they can be corrected in the source system:",[263,1610,1612],{"className":316,"code":1611,"language":318,"meta":268,"style":268},"invalid_rows = raw.loc[bad_posting | bad_value | bad_entered | bad_julian]\nwith pd.ExcelWriter(\"postings_clean.xlsx\", engine=\"xlsxwriter\",\n                    datetime_format=\"yyyy-mm-dd\", date_format=\"yyyy-mm-dd\") as writer:\n    dates.to_excel(writer, sheet_name=\"Postings\", index=False)\n    invalid_rows.to_excel(writer, sheet_name=\"Invalid dates\", index=False)\nprint(pd.read_excel(\"postings_clean.xlsx\", sheet_name=None)[\"Invalid dates\"][\"doc\"].tolist())\n",[14,1613,1614,1640,1664,1690,1713,1735],{"__ignoreMap":268},[272,1615,1616,1619,1621,1624,1627,1630,1632,1635,1637],{"class":231,"line":274},[272,1617,1618],{"class":329},"invalid_rows ",[272,1620,352],{"class":325},[272,1622,1623],{"class":329}," raw.loc[bad_posting ",[272,1625,1626],{"class":325},"|",[272,1628,1629],{"class":329}," bad_value ",[272,1631,1626],{"class":325},[272,1633,1634],{"class":329}," bad_entered ",[272,1636,1626],{"class":325},[272,1638,1639],{"class":329}," bad_julian]\n",[272,1641,1642,1645,1648,1651,1653,1656,1658,1661],{"class":231,"line":339},[272,1643,1644],{"class":325},"with",[272,1646,1647],{"class":329}," pd.ExcelWriter(",[272,1649,1650],{"class":281},"\"postings_clean.xlsx\"",[272,1652,21],{"class":329},[272,1654,1655],{"class":363},"engine",[272,1657,352],{"class":325},[272,1659,1660],{"class":281},"\"xlsxwriter\"",[272,1662,1663],{"class":329},",\n",[272,1665,1666,1669,1671,1674,1676,1679,1681,1683,1685,1687],{"class":231,"line":346},[272,1667,1668],{"class":363},"                    datetime_format",[272,1670,352],{"class":325},[272,1672,1673],{"class":281},"\"yyyy-mm-dd\"",[272,1675,21],{"class":329},[272,1677,1678],{"class":363},"date_format",[272,1680,352],{"class":325},[272,1682,1673],{"class":281},[272,1684,1285],{"class":329},[272,1686,333],{"class":325},[272,1688,1689],{"class":329}," writer:\n",[272,1691,1692,1695,1698,1700,1703,1705,1707,1709,1711],{"class":231,"line":403},[272,1693,1694],{"class":329},"    dates.to_excel(writer, ",[272,1696,1697],{"class":363},"sheet_name",[272,1699,352],{"class":325},[272,1701,1702],{"class":281},"\"Postings\"",[272,1704,21],{"class":329},[272,1706,1083],{"class":363},[272,1708,352],{"class":325},[272,1710,1088],{"class":378},[272,1712,487],{"class":329},[272,1714,1715,1718,1720,1722,1725,1727,1729,1731,1733],{"class":231,"line":412},[272,1716,1717],{"class":329},"    invalid_rows.to_excel(writer, ",[272,1719,1697],{"class":363},[272,1721,352],{"class":325},[272,1723,1724],{"class":281},"\"Invalid dates\"",[272,1726,21],{"class":329},[272,1728,1083],{"class":363},[272,1730,352],{"class":325},[272,1732,1088],{"class":378},[272,1734,487],{"class":329},[272,1736,1737,1739,1742,1744,1746,1748,1750,1753,1756,1758,1761,1763],{"class":231,"line":672},[272,1738,406],{"class":378},[272,1740,1741],{"class":329},"(pd.read_excel(",[272,1743,1650],{"class":281},[272,1745,21],{"class":329},[272,1747,1697],{"class":363},[272,1749,352],{"class":325},[272,1751,1752],{"class":378},"None",[272,1754,1755],{"class":329},")[",[272,1757,1724],{"class":281},[272,1759,1760],{"class":329},"][",[272,1762,771],{"class":281},[272,1764,1765],{"class":329},"].tolist())\n",[10,1767,1768,1769,1772,1773,1775,1776,1780],{},"The xlsxwriter engine applies ",[14,1770,1771],{},"datetime_format"," to every date cell, so the column shows ",[14,1774,210],{}," rather than a time of midnight. ",[43,1777,1779],{"href":1778},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-dates-without-time-from-pandas-to-excel\u002F","Write Dates Without the Time Part from pandas to Excel"," explains why that option only works with xlsxwriter.",[49,1782,58,1787,58,1790,58,1793,58,1796,58,1801,58,1812,58,1818,58,1822,58,1827,58,1830,58,1833,58,1839,58,1844,58,1848,58,1852,58,1856,58,1860,58,1864,58,1867,58,1870,58,1877,58,1882,58,1884,58,1886,58,1890,58,1893,58,1898,58,1905,58,1911,58,1914,58,1917,58,1920,58,1924,58,1927,58,1930,58,1937,58,1942,58,1946],{"viewBox":1783,"role":52,"ariaLabelledBy":1784,"xmlns":56,"style":800},"4 0 752 178",[1785,1786],"txd-pipeline-t","txd-pipeline-d",[60,1788,1789],{"id":1785},"Converting export dates without losing any",[64,1791,1792],{"id":1786},"Five steps. Read date columns as text so nothing is guessed. Separate known placeholders such as 00000000. Parse each column with its explicit format and errors set to coerce. Report values that failed and were not placeholders. Write real dates with a date number format, and the failures on their own sheet.",[68,1794],{"x":809,"y":71,"width":810,"height":1795,"fill":74},"178",[68,1797],{"x":814,"y":815,"width":1798,"height":1799,"rx":1800,"fill":81,"stroke":82,"style":237},"115.2","145.5","12",[1802,1803,1805],"g",{"transform":1804},"translate(65.6,55.2) scale(1.000)",[1806,1807],"path",{"d":1808,"fill":1809,"stroke":1810,"style":1811},"M6 3h9l4 4v14H6z M15 3v4h4","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[89,1813,1817],{"x":1814,"y":1815,"style":1816},"77.6","101.8","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Read as text",[89,1819,1821],{"x":1814,"y":1820,"style":848},"121.2","dtype=str",[231,1823],{"x1":1824,"y1":1825,"x2":1826,"y2":1825,"stroke":236,"style":237},"140.2","88.8","162.2",[239,1828],{"points":1829,"fill":242},"166.2,88.8 157.2,93.8 157.2,83.8",[68,1831],{"x":1832,"y":815,"width":1798,"height":1799,"rx":1800,"fill":81,"stroke":82,"style":237},"171.2",[1802,1834,1836],{"transform":1835},"translate(216.8,29.0) scale(1.000)",[1806,1837],{"d":1838,"fill":1809,"stroke":1810,"style":1811},"M3 5h18l-7 8v6l-4 2v-8z",[89,1840,1843],{"x":1841,"y":1842,"style":1816},"228.8","75.5","Set",[89,1845,1847],{"x":1841,"y":1846,"style":1816},"93.0","placeholders",[89,1849,1851],{"x":1841,"y":1850,"style":1816},"110.5","aside",[89,1853,1855],{"x":1841,"y":1854,"style":848},"130.0","00000000,",[89,1857,1859],{"x":1841,"y":1858,"style":848},"147.5","blank",[231,1861],{"x1":1862,"y1":1825,"x2":1863,"y2":1825,"stroke":236,"style":237},"291.4","313.4",[239,1865],{"points":1866,"fill":242},"317.4,88.8 308.4,93.8 308.4,83.8",[68,1868],{"x":1869,"y":815,"width":1798,"height":1799,"rx":1800,"fill":821,"stroke":236,"style":237},"322.4",[1802,1871,1873],{"transform":1872},"translate(368.0,46.5) scale(1.000)",[1806,1874],{"d":1875,"fill":1809,"stroke":1876,"style":1811},"M4 5h16v15H4z M4 10h16 M8 3v4 M16 3v4","var(--brand-strong,#4338ca)",[89,1878,1881],{"x":1879,"y":1846,"style":1880},"380.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Parse with a",[89,1883,500],{"x":1879,"y":1850,"style":1880},[89,1885,450],{"x":1879,"y":1854,"style":848},[231,1887],{"x1":1888,"y1":1825,"x2":1889,"y2":1825,"stroke":236,"style":237},"442.6","464.6",[239,1891],{"points":1892,"fill":242},"468.6,88.8 459.6,93.8 459.6,83.8",[68,1894],{"x":1895,"y":815,"width":1798,"height":1799,"rx":1800,"fill":1896,"stroke":1897,"style":237},"473.6","#fdefd8","var(--gold,#b4740a)",[1802,1899,1901],{"transform":1900},"translate(519.2,37.8) scale(1.000)",[1806,1902],{"d":1903,"fill":1809,"stroke":1904,"style":1811},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold-ink,#7a4e06)",[89,1906,1910],{"x":1907,"y":1908,"style":1909},"531.2","84.2","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Report",[89,1912,1913],{"x":1907,"y":1815,"style":1909},"failures",[89,1915,1916],{"x":1907,"y":1820,"style":848},"not",[89,1918,1847],{"x":1907,"y":1919,"style":848},"138.8",[231,1921],{"x1":1922,"y1":1825,"x2":1923,"y2":1825,"stroke":236,"style":237},"593.8","615.8",[239,1925],{"points":1926,"fill":242},"619.8,88.8 610.8,93.8 610.8,83.8",[68,1928],{"x":1929,"y":815,"width":1798,"height":1799,"rx":1800,"fill":204,"stroke":205,"style":237},"624.8",[1802,1931,1933],{"transform":1932},"translate(670.4,46.5) scale(1.000)",[1806,1934],{"d":1935,"fill":1809,"stroke":1936,"style":1811},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","var(--teal-ink,#0b6157)",[89,1938,1941],{"x":1939,"y":1846,"style":1940},"682.4","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Write dates",[89,1943,1945],{"x":1939,"y":1944,"style":848},"112.5","+ an Invalid",[89,1947,1948],{"x":1939,"y":1854,"style":848},"sheet",[258,1950,1952],{"id":1951},"common-pitfalls","Common pitfalls",[1954,1955,1956,1967,1976,1986],"ul",{},[1957,1958,1959,1966],"li",{},[1960,1961,1962,1965],"strong",{},[14,1963,1964],{},"to_datetime"," on the integer column."," An integer is read as nanoseconds since 1970, not as yyyymmdd; convert to text and pass the format.",[1957,1968,1969,1975],{},[1960,1970,1971,1974],{},[14,1972,1973],{},"format=\"mixed\""," or no format at all."," It guesses per value and can swap day and month; state the format.",[1957,1977,1978,1983,1984,309],{},[1960,1979,1980,1982],{},[14,1981,450],{}," without a report."," Invalid dates vanish into ",[14,1985,454],{},[1957,1987,1988,1991],{},[1960,1989,1990],{},"Treating placeholders as errors."," Decide which codes mean \"no date\" and set them aside first.",[258,1993,1995],{"id":1994},"performance-notes","Performance notes",[10,1997,1998,1999,2001,2002,2004,2005,2008,2009,21,2012,21,2015,2018,2019,2021],{},"Parsing with an explicit ",[14,2000,500],{}," uses a fast path and handles millions of values per second; ",[14,2003,1973],{}," parses each value separately and is many times slower. Converting integers with ",[14,2006,2007],{},"astype(str)"," is cheap. For huge columns of yyyymmdd integers, arithmetic — ",[14,2010,2011],{},"year = n \u002F\u002F 10000",[14,2013,2014],{},"month = n \u002F\u002F 100 % 100",[14,2016,2017],{},"day = n % 100",", then ",[14,2020,40],{}," on the three columns — avoids string handling altogether.",[258,2023,2025],{"id":2024},"conclusion","Conclusion",[10,2027,2028,2029,2031,2032,2034,2035,948,2037,2039,2040,2042,2043,2045],{},"Read export date columns as text, set known placeholders such as ",[14,2030,32],{}," aside, and parse each layout with an explicit format — ",[14,2033,849],{}," for eight-digit dates, ",[14,2036,865],{},[14,2038,882],{}," for text, ",[14,2041,899],{}," for day-of-year codes — using ",[14,2044,450],{}," together with a report of what failed. Check two-digit years against context, and write real dates with a date format, with invalid values on their own sheet for correction at the source.",[258,2047,2049],{"id":2048},"frequently-asked-questions","Frequently asked questions",[10,2051,2052,2055],{},[1960,2053,2054],{},"Why does pandas read 20240131 as a number?","\nBecause it is one. A date written as eight digits looks like an integer to Excel and to pandas, so the column arrives as int64. Convert it to a string and parse it with format=\"%Y%m%d\", or parse the digits arithmetically.",[10,2057,2058,2061],{},[1960,2059,2060],{},"What does errors=\"coerce\" do?","\nIt turns values that do not match the format into NaT instead of raising. Use it together with a report of the rows that became NaT, so invalid dates such as 20240231 are seen rather than silently dropped.",[10,2063,2064,2067],{},[1960,2065,2066],{},"How does pandas interpret two-digit years like 69 or 24?","\nWith %y, Python maps 69-99 to 1969-1999 and 00-68 to 2000-2068. That is right for recent transaction dates and wrong for, say, birth dates in the 1950s. When the rule does not fit, parse the two digits yourself and add the right century.",[10,2069,2070,2073],{},[1960,2071,2072],{},"What is a Julian date in an export?","\nUsually a year followed by the day of the year, such as 2024031 for 31 January 2024. It parses with the format \"%Y%j\", where %j is the day of the year from 001 to 366.",[258,2075,2077],{"id":2076},"related","Related",[1954,2079,2080,2086,2093,2100],{},[1957,2081,2082,2083,2085],{},"Up one level: ",[43,2084,46],{"href":45}," — dates from import to report.",[1957,2087,2088,2092],{},[43,2089,2091],{"href":2090},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fparse-excel-dates-into-python-datetimes-with-pandas\u002F","Parse Excel Dates into Python datetimes with pandas"," — real Excel dates and mixed columns.",[1957,2094,2095,2099],{},[43,2096,2098],{"href":2097},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-the-1904-date-system-in-excel-files\u002F","Handle the 1904 Date System in Excel Files with Python"," — serial numbers from the other date system.",[1957,2101,2102,2106],{},[43,2103,2105],{"href":2104},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-csv-exports-with-semicolons-and-decimal-commas\u002F","Read European CSV Exports with Semicolons and Decimal Commas"," — day-first dates in CSV files.",[2108,2109,2110],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sx1sP, html code.shiki .sx1sP{--shiki-default:#22863A;--shiki-dark:#72F088}html pre.shiki code .s_b0D, html code.shiki .s_b0D{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold}",{"title":268,"searchDepth":339,"depth":339,"links":2112},[2113,2114,2115,2116,2117,2118,2119,2120,2121,2122,2123,2124,2125],{"id":260,"depth":339,"text":261},{"id":312,"depth":339,"text":313},{"id":439,"depth":339,"text":440},{"id":575,"depth":339,"text":576},{"id":941,"depth":339,"text":942},{"id":1164,"depth":339,"text":1165},{"id":1344,"depth":339,"text":1345},{"id":1604,"depth":339,"text":1605},{"id":1951,"depth":339,"text":1952},{"id":1994,"depth":339,"text":1995},{"id":2024,"depth":339,"text":2025},{"id":2048,"depth":339,"text":2049},{"id":2076,"depth":339,"text":2077},"2026-09-25","ERP and mainframe exports write dates as 20240131, 31.01.24, 31-Jan-24 or day-of-year codes, and read_excel returns them as integers or text. Convert each layout with an explicit format, handle placeholders like 00000000, invalid dates such as 20240231 and two-digit years, and report what did not convert.","md",[2130,2132,2134,2136],{"q":2054,"a":2131},"Because it is one. A date written as eight digits looks like an integer to Excel and to pandas, so the column arrives as int64. Convert it to a string and parse it with format=\"%Y%m%d\", or parse the digits arithmetically.",{"q":2060,"a":2133},"It turns values that do not match the format into NaT instead of raising. Use it together with a report of the rows that became NaT, so invalid dates such as 20240231 are seen rather than silently dropped.",{"q":2066,"a":2135},"With %y, Python maps 69-99 to 1969-1999 and 00-68 to 2000-2068. That is right for recent transaction dates and wrong for, say, birth dates in the 1950s. When the rule does not fit, parse the two digits yourself and add the right century.",{"q":2072,"a":2137},"Usually a year followed by the day of the year, such as 2024031 for 31 January 2024. It parses with the format \"%Y%j\", where %j is the day of the year from 001 to 366.","convert text dates like 20240131 to datetimes",null,{"breadcrumb":2141},[2142,2145,2148],{"name":2143,"item":2144},"Home","\u002F",{"name":2146,"item":2147},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":46,"item":45},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-text-dates-like-20240131-with-pandas",{"title":2151,"description":2152},"Convert yyyymmdd Numbers and Text Dates in pandas (format=\"%Y%m%d\")","Turn numeric and text dates from Excel exports into real dates with pandas to_datetime: 20240131 read as an integer, format codes for dd.mm.yy and dd-Mon-yy, day-of-year dates, placeholders, invalid dates and two-digit year rules.","convert-text-dates-like-20240131-with-pandas","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-text-dates-like-20240131-with-pandas\u002Findex",[2156,2157,2158],"dates-and-times","data-cleaning","pandas","how-to","TwskR2E8mkK2PbZc6Wx2FVwF-d6AtjL3iGAb98flqQg",[2162,2166],{"title":2163,"path":2164,"stem":2165,"children":-1},"Convert Excel Time Values and Durations in Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-excel-time-values-and-durations","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-excel-time-values-and-durations\u002Findex",{"title":2167,"path":2168,"stem":2169,"children":-1},"Convert Text Months and Weekdays to Dates","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-text-months-and-weekdays-to-dates","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-text-months-and-weekdays-to-dates\u002Findex",[2171,2173],{"path":2172,"title":2146},"\u002Fadvanced-data-transformation-and-cleaning",{"path":2174,"title":46},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data",[],[2177,2180,2183,2186,2189,2192],{"path":2178,"title":2179,"kind":2159},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-overdue-dates-with-openpyxl","Highlight Overdue Dates in Excel with openpyxl",{"path":2181,"title":2182,"kind":2159},"\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":2184,"title":2185,"kind":2159},"\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":2187,"title":2188,"kind":2159},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcalculate-moving-averages-with-pandas-rolling","Calculate Moving Averages with pandas rolling",{"path":2190,"title":2191,"kind":2159},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Feomonth-and-edate-equivalents-in-pandas","EOMONTH and EDATE Equivalents in pandas",{"path":2193,"title":2194,"kind":2159},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fyearfrac-and-days360-equivalents-in-python","YEARFRAC and DAYS360 Equivalents in Python",1790972103256]