[{"data":1,"prerenderedAt":2034},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-excel-time-values-and-durations":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-excel-time-values-and-durations":2000,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-excel-time-values-and-durations":2009,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-excel-time-values-and-durations":2014,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-excel-time-values-and-durations":2015},{"id":4,"title":5,"body":6,"dateModified":1966,"datePublished":1966,"description":1967,"extension":1968,"faq":1969,"keyword":1978,"kind":1979,"meta":1980,"navigation":241,"path":1988,"seo":1989,"slug":1992,"stem":1993,"topics":1994,"type":1998,"__hash__":1999},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-excel-time-values-and-durations\u002Findex.md","Convert Excel Time Values and Durations in Python",{"type":7,"value":8,"toc":1954},"minimark",[9,28,177,182,210,213,598,634,638,643,796,803,807,810,922,929,932,978,985,1108,1112,1121,1257,1276,1280,1286,1454,1468,1472,1475,1653,1661,1806,1810,1869,1873,1882,1886,1896,1902,1908,1917,1921,1950],[10,11,12,13,17,18,21,22,27],"p",{},"Timesheets, call logs and machine run times all end up in Excel as times and durations — and all of them are stored the same way underneath: as a fraction of a day. 09:00 is 0.375, a 90-minute call is 0.0625, and a 30-hour job is 1.25. What pandas gives you back depends on how each cell was formatted: a ",[14,15,16],"code",{},"datetime.time",", a ",[14,19,20],{},"Timedelta",", or a date in January 1900. This guide, part of ",[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",", converts every variant into timedeltas you can add, average and compare, and writes them back so Excel shows hours rather than a bare number.",[29,30,38,39,38,43,38,47,38,55,38,63,38,70,38,77,38,80,38,84,38,89,38,95,38,97,38,102,38,104,38,107,38,110,38,114,38,116,38,120,38,122,38,125,38,128,38,131,38,133,38,137,38,139,38,142,38,145,38,149,38,151,38,154,38,156,38,159,38,162,38,166,38,168,38,172,38,174],"svg",{"viewBox":31,"role":32,"ariaLabelledBy":33,"xmlns":36,"style":37},"4 0 752 242","img",[34,35],"tm-types-t","tm-types-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[40,41,42],"title",{"id":34},"What pandas returns for the same kind of value",[44,45,46],"desc",{"id":35},"A cell holding 0.375 formatted hh:mm is read as the time 09:00. A cell holding 1.25 formatted [h]:mm is read as a Timedelta of 1 day 6 hours. The same 1.25 formatted h:mm is read as the datetime 1900-01-01 06:00. Durations typed as text such as 1h 15m stay strings, and decimal hours stay floats.",[48,49],"rect",{"x":50,"y":51,"width":52,"height":53,"fill":54},"4","0","752","242","#ffffff",[48,56],{"x":57,"y":58,"width":59,"height":60,"rx":51,"fill":54,"stroke":61,"style":62},"20.0","16.0","85.2","40.0","var(--line,#cdd5e6)","stroke-width:1px",[48,64],{"x":65,"y":58,"width":66,"height":60,"rx":51,"fill":67,"stroke":68,"style":69},"105.2","317.4","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[71,72,76],"text",{"x":73,"y":74,"style":75},"263.9","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Cell format",[48,78],{"x":79,"y":58,"width":66,"height":60,"rx":51,"fill":67,"stroke":68,"style":69},"422.6",[71,81,83],{"x":82,"y":74,"style":75},"581.3","pandas value",[48,85],{"x":57,"y":86,"width":59,"height":87,"rx":51,"fill":88,"stroke":61,"style":62},"56.0","34.0","#e7ebef",[71,90,94],{"x":91,"y":92,"style":93},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","0.375",[48,96],{"x":65,"y":86,"width":66,"height":87,"rx":51,"fill":54,"stroke":61,"style":62},[71,98,101],{"x":73,"y":99,"style":100},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","hh:mm",[48,103],{"x":79,"y":86,"width":66,"height":87,"rx":51,"fill":54,"stroke":61,"style":62},[71,105,106],{"x":82,"y":99,"style":100},"time 09:00",[48,108],{"x":57,"y":109,"width":59,"height":87,"rx":51,"fill":88,"stroke":61,"style":62},"90.0",[71,111,113],{"x":91,"y":112,"style":93},"112.0","1.25",[48,115],{"x":65,"y":109,"width":66,"height":87,"rx":51,"fill":54,"stroke":61,"style":62},[71,117,119],{"x":73,"y":118,"style":100},"111.0","[h]:mm",[48,121],{"x":79,"y":109,"width":66,"height":87,"rx":51,"fill":54,"stroke":61,"style":62},[71,123,124],{"x":82,"y":118,"style":100},"Timedelta 1 day 06:00",[48,126],{"x":57,"y":127,"width":59,"height":87,"rx":51,"fill":88,"stroke":61,"style":62},"124.0",[71,129,113],{"x":91,"y":130,"style":93},"146.0",[48,132],{"x":65,"y":127,"width":66,"height":87,"rx":51,"fill":54,"stroke":61,"style":62},[71,134,136],{"x":73,"y":135,"style":100},"145.0","h:mm",[48,138],{"x":79,"y":127,"width":66,"height":87,"rx":51,"fill":54,"stroke":61,"style":62},[71,140,141],{"x":82,"y":135,"style":100},"datetime 1900-01-01 06:00",[48,143],{"x":57,"y":144,"width":59,"height":87,"rx":51,"fill":88,"stroke":61,"style":62},"158.0",[71,146,148],{"x":91,"y":147,"style":93},"180.0","\"1h 15m\"",[48,150],{"x":65,"y":144,"width":66,"height":87,"rx":51,"fill":54,"stroke":61,"style":62},[71,152,71],{"x":73,"y":153,"style":100},"179.0",[48,155],{"x":79,"y":144,"width":66,"height":87,"rx":51,"fill":54,"stroke":61,"style":62},[71,157,158],{"x":82,"y":153,"style":100},"str",[48,160],{"x":57,"y":161,"width":59,"height":87,"rx":51,"fill":88,"stroke":61,"style":62},"192.0",[71,163,165],{"x":91,"y":164,"style":93},"214.0","2.5",[48,167],{"x":65,"y":161,"width":66,"height":87,"rx":51,"fill":54,"stroke":61,"style":62},[71,169,171],{"x":73,"y":170,"style":100},"213.0","General",[48,173],{"x":79,"y":161,"width":66,"height":87,"rx":51,"fill":54,"stroke":61,"style":62},[71,175,176],{"x":82,"y":170,"style":100},"float 2.5",[178,179,181],"h2",{"id":180},"prerequisites","Prerequisites",[183,184,189],"pre",{"className":185,"code":186,"language":187,"meta":188,"style":188},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,190,191],{"__ignoreMap":188},[192,193,196,200,204,207],"span",{"class":194,"line":195},"line",1,[192,197,199],{"class":198},"sMTad","pip",[192,201,203],{"class":202},"srMev"," install",[192,205,206],{"class":202}," pandas",[192,208,209],{"class":202}," openpyxl\n",[10,211,212],{},"A timesheet with every variant — times of day, a shift that crosses midnight, durations with and without square brackets, durations typed as text and decimal hours:",[183,214,218],{"className":215,"code":216,"language":217,"meta":188,"style":188},"language-python shiki shiki-themes github-light github-dark-high-contrast","import datetime as dt\n\nimport pandas as pd\nfrom openpyxl import Workbook\n\nwb = Workbook()\nws = wb.active\nws.append([\"task\", \"start\", \"end\", \"break\", \"run_time\", \"typed\", \"hours_dec\"])\nfor row in [(\"A\", 0.375, 0.5208333, 0.0208333, 1.25, \"2:30\", 2.5),\n            (\"B\", 0.875, 0.0625, 0.0104167, 0.0625, \"45 min\", 0.75),\n            (\"C\", 0.5, 0.75, 0.0208333, 2.5, \"1h 15m\", 1.25)]:\n    ws.append(list(row))\nfor row in ws.iter_rows(min_row=2):\n    row[1].number_format = row[2].number_format = \"hh:mm\"\n    row[3].number_format = \"h:mm\"\n    row[4].number_format = \"[h]:mm\"\nwb.save(\"timesheet.xlsx\")\n\ndf = pd.read_excel(\"timesheet.xlsx\")\nprint(df.dtypes.to_dict())\n","python",[14,219,220,236,243,256,270,275,287,298,341,390,430,466,478,502,528,543,557,569,574,589],{"__ignoreMap":188},[192,221,222,226,230,233],{"class":194,"line":195},[192,223,225],{"class":224},"s-kum","import",[192,227,229],{"class":228},"skGVy"," datetime ",[192,231,232],{"class":224},"as",[192,234,235],{"class":228}," dt\n",[192,237,239],{"class":194,"line":238},2,[192,240,242],{"emptyLinePlaceholder":241},true,"\n",[192,244,246,248,251,253],{"class":194,"line":245},3,[192,247,225],{"class":224},[192,249,250],{"class":228}," pandas ",[192,252,232],{"class":224},[192,254,255],{"class":228}," pd\n",[192,257,259,262,265,267],{"class":194,"line":258},4,[192,260,261],{"class":224},"from",[192,263,264],{"class":228}," openpyxl ",[192,266,225],{"class":224},[192,268,269],{"class":228}," Workbook\n",[192,271,273],{"class":194,"line":272},5,[192,274,242],{"emptyLinePlaceholder":241},[192,276,278,281,284],{"class":194,"line":277},6,[192,279,280],{"class":228},"wb ",[192,282,283],{"class":224},"=",[192,285,286],{"class":228}," Workbook()\n",[192,288,290,293,295],{"class":194,"line":289},7,[192,291,292],{"class":228},"ws ",[192,294,283],{"class":224},[192,296,297],{"class":228}," wb.active\n",[192,299,301,304,307,310,313,315,318,320,323,325,328,330,333,335,338],{"class":194,"line":300},8,[192,302,303],{"class":228},"ws.append([",[192,305,306],{"class":202},"\"task\"",[192,308,309],{"class":228},", ",[192,311,312],{"class":202},"\"start\"",[192,314,309],{"class":228},[192,316,317],{"class":202},"\"end\"",[192,319,309],{"class":228},[192,321,322],{"class":202},"\"break\"",[192,324,309],{"class":228},[192,326,327],{"class":202},"\"run_time\"",[192,329,309],{"class":228},[192,331,332],{"class":202},"\"typed\"",[192,334,309],{"class":228},[192,336,337],{"class":202},"\"hours_dec\"",[192,339,340],{"class":228},"])\n",[192,342,344,347,350,353,356,359,361,364,366,369,371,374,376,378,380,383,385,387],{"class":194,"line":343},9,[192,345,346],{"class":224},"for",[192,348,349],{"class":228}," row ",[192,351,352],{"class":224},"in",[192,354,355],{"class":228}," [(",[192,357,358],{"class":202},"\"A\"",[192,360,309],{"class":228},[192,362,94],{"class":363},"sP0c6",[192,365,309],{"class":228},[192,367,368],{"class":363},"0.5208333",[192,370,309],{"class":228},[192,372,373],{"class":363},"0.0208333",[192,375,309],{"class":228},[192,377,113],{"class":363},[192,379,309],{"class":228},[192,381,382],{"class":202},"\"2:30\"",[192,384,309],{"class":228},[192,386,165],{"class":363},[192,388,389],{"class":228},"),\n",[192,391,393,396,399,401,404,406,409,411,414,416,418,420,423,425,428],{"class":194,"line":392},10,[192,394,395],{"class":228},"            (",[192,397,398],{"class":202},"\"B\"",[192,400,309],{"class":228},[192,402,403],{"class":363},"0.875",[192,405,309],{"class":228},[192,407,408],{"class":363},"0.0625",[192,410,309],{"class":228},[192,412,413],{"class":363},"0.0104167",[192,415,309],{"class":228},[192,417,408],{"class":363},[192,419,309],{"class":228},[192,421,422],{"class":202},"\"45 min\"",[192,424,309],{"class":228},[192,426,427],{"class":363},"0.75",[192,429,389],{"class":228},[192,431,433,435,438,440,443,445,447,449,451,453,455,457,459,461,463],{"class":194,"line":432},11,[192,434,395],{"class":228},[192,436,437],{"class":202},"\"C\"",[192,439,309],{"class":228},[192,441,442],{"class":363},"0.5",[192,444,309],{"class":228},[192,446,427],{"class":363},[192,448,309],{"class":228},[192,450,373],{"class":363},[192,452,309],{"class":228},[192,454,165],{"class":363},[192,456,309],{"class":228},[192,458,148],{"class":202},[192,460,309],{"class":228},[192,462,113],{"class":363},[192,464,465],{"class":228},")]:\n",[192,467,469,472,475],{"class":194,"line":468},12,[192,470,471],{"class":228},"    ws.append(",[192,473,474],{"class":363},"list",[192,476,477],{"class":228},"(row))\n",[192,479,481,483,485,487,490,494,496,499],{"class":194,"line":480},13,[192,482,346],{"class":224},[192,484,349],{"class":228},[192,486,352],{"class":224},[192,488,489],{"class":228}," ws.iter_rows(",[192,491,493],{"class":492},"sa561","min_row",[192,495,283],{"class":224},[192,497,498],{"class":363},"2",[192,500,501],{"class":228},"):\n",[192,503,505,508,511,514,516,519,521,523,525],{"class":194,"line":504},14,[192,506,507],{"class":228},"    row[",[192,509,510],{"class":363},"1",[192,512,513],{"class":228},"].number_format ",[192,515,283],{"class":224},[192,517,518],{"class":228}," row[",[192,520,498],{"class":363},[192,522,513],{"class":228},[192,524,283],{"class":224},[192,526,527],{"class":202}," \"hh:mm\"\n",[192,529,531,533,536,538,540],{"class":194,"line":530},15,[192,532,507],{"class":228},[192,534,535],{"class":363},"3",[192,537,513],{"class":228},[192,539,283],{"class":224},[192,541,542],{"class":202}," \"h:mm\"\n",[192,544,546,548,550,552,554],{"class":194,"line":545},16,[192,547,507],{"class":228},[192,549,50],{"class":363},[192,551,513],{"class":228},[192,553,283],{"class":224},[192,555,556],{"class":202}," \"[h]:mm\"\n",[192,558,560,563,566],{"class":194,"line":559},17,[192,561,562],{"class":228},"wb.save(",[192,564,565],{"class":202},"\"timesheet.xlsx\"",[192,567,568],{"class":228},")\n",[192,570,572],{"class":194,"line":571},18,[192,573,242],{"emptyLinePlaceholder":241},[192,575,577,580,582,585,587],{"class":194,"line":576},19,[192,578,579],{"class":228},"df ",[192,581,283],{"class":224},[192,583,584],{"class":228}," pd.read_excel(",[192,586,565],{"class":202},[192,588,568],{"class":228},[192,590,592,595],{"class":194,"line":591},20,[192,593,594],{"class":363},"print",[192,596,597],{"class":228},"(df.dtypes.to_dict())\n",[10,599,600,309,603,606,607,610,611,614,615,617,618,621,622,625,626,629,630,633],{},[14,601,602],{},"start",[14,604,605],{},"end"," and ",[14,608,609],{},"break"," come back as ",[14,612,613],{},"object"," columns of ",[14,616,16],{}," values, ",[14,619,620],{},"run_time"," — formatted with square brackets — as a proper ",[14,623,624],{},"timedelta64"," column, ",[14,627,628],{},"typed"," as text and ",[14,631,632],{},"hours_dec"," as floats.",[178,635,637],{"id":636},"step-1-turn-times-of-day-into-timedeltas","Step 1 — Turn times of day into timedeltas",[10,639,640,642],{},[14,641,16],{}," objects cannot be subtracted from each other. Convert them to timedeltas since midnight by counting their seconds:",[183,644,646],{"className":215,"code":645,"language":217,"meta":188,"style":188},"def time_to_td(series):\n    seconds = series.map(lambda t: t.hour * 3600 + t.minute * 60 + t.second + t.microsecond \u002F 1e6\n                         if pd.notna(t) else None)\n    return pd.to_timedelta(seconds, unit=\"s\").round(\"s\")\n\nstart = time_to_td(df[\"start\"])\nend = time_to_td(df[\"end\"])\nprint(start.tolist()[:2])     # [Timedelta('0 days 09:00:00'), Timedelta('0 days 21:00:00')]\n",[14,647,648,660,710,726,749,753,767,780],{"__ignoreMap":188},[192,649,650,653,657],{"class":194,"line":195},[192,651,652],{"class":224},"def",[192,654,656],{"class":655},"s_Opv"," time_to_td",[192,658,659],{"class":228},"(series):\n",[192,661,662,665,667,670,673,676,679,682,685,688,690,693,695,698,701,704,707],{"class":194,"line":238},[192,663,664],{"class":228},"    seconds ",[192,666,283],{"class":224},[192,668,669],{"class":228}," series.map(",[192,671,672],{"class":224},"lambda",[192,674,675],{"class":228}," t: t.hour ",[192,677,678],{"class":224},"*",[192,680,681],{"class":363}," 3600",[192,683,684],{"class":224}," +",[192,686,687],{"class":228}," t.minute ",[192,689,678],{"class":224},[192,691,692],{"class":363}," 60",[192,694,684],{"class":224},[192,696,697],{"class":228}," t.second ",[192,699,700],{"class":224},"+",[192,702,703],{"class":228}," t.microsecond ",[192,705,706],{"class":224},"\u002F",[192,708,709],{"class":363}," 1e6\n",[192,711,712,715,718,721,724],{"class":194,"line":245},[192,713,714],{"class":224},"                         if",[192,716,717],{"class":228}," pd.notna(t) ",[192,719,720],{"class":224},"else",[192,722,723],{"class":363}," None",[192,725,568],{"class":228},[192,727,728,731,734,737,739,742,745,747],{"class":194,"line":258},[192,729,730],{"class":224},"    return",[192,732,733],{"class":228}," pd.to_timedelta(seconds, ",[192,735,736],{"class":492},"unit",[192,738,283],{"class":224},[192,740,741],{"class":202},"\"s\"",[192,743,744],{"class":228},").round(",[192,746,741],{"class":202},[192,748,568],{"class":228},[192,750,751],{"class":194,"line":272},[192,752,242],{"emptyLinePlaceholder":241},[192,754,755,758,760,763,765],{"class":194,"line":277},[192,756,757],{"class":228},"start ",[192,759,283],{"class":224},[192,761,762],{"class":228}," time_to_td(df[",[192,764,312],{"class":202},[192,766,340],{"class":228},[192,768,769,772,774,776,778],{"class":194,"line":289},[192,770,771],{"class":228},"end ",[192,773,283],{"class":224},[192,775,762],{"class":228},[192,777,317],{"class":202},[192,779,340],{"class":228},[192,781,782,784,787,789,792],{"class":194,"line":300},[192,783,594],{"class":363},[192,785,786],{"class":228},"(start.tolist()[:",[192,788,498],{"class":363},[192,790,791],{"class":228},"])     ",[192,793,795],{"class":794},"s-wDw","# [Timedelta('0 days 09:00:00'), Timedelta('0 days 21:00:00')]\n",[10,797,798,799,802],{},"The function counts the seconds since midnight and rounds to the nearest whole second. That rounding matters: a time typed as 12:30 is stored as a binary fraction that openpyxl reads as 12:29:59.997, and truncating instead of rounding would lose a second on every such value. Drop the ",[14,800,801],{},".round(\"s\")"," only if your data genuinely records fractions of a second.",[178,804,806],{"id":805},"step-2-shifts-that-cross-midnight","Step 2 — Shifts that cross midnight",[10,808,809],{},"Task B starts at 21:00 and ends at 01:30. Subtracting gives a negative duration. Add a day wherever the end is earlier than the start:",[183,811,813],{"className":215,"code":812,"language":217,"meta":188,"style":188},"worked = end - start\nworked = worked.where(worked >= pd.Timedelta(0), worked + pd.Timedelta(days=1))\nbreaks = time_to_td(df[\"break\"])\nnet = worked - breaks\nprint((net \u002F pd.Timedelta(hours=1)).round(2).tolist())      # [3.0, 4.25, 5.5]\n",[14,814,815,831,865,878,893],{"__ignoreMap":188},[192,816,817,820,822,825,828],{"class":194,"line":195},[192,818,819],{"class":228},"worked ",[192,821,283],{"class":224},[192,823,824],{"class":228}," end ",[192,826,827],{"class":224},"-",[192,829,830],{"class":228}," start\n",[192,832,833,835,837,840,843,846,848,851,853,855,858,860,862],{"class":194,"line":238},[192,834,819],{"class":228},[192,836,283],{"class":224},[192,838,839],{"class":228}," worked.where(worked ",[192,841,842],{"class":224},">=",[192,844,845],{"class":228}," pd.Timedelta(",[192,847,51],{"class":363},[192,849,850],{"class":228},"), worked ",[192,852,700],{"class":224},[192,854,845],{"class":228},[192,856,857],{"class":492},"days",[192,859,283],{"class":224},[192,861,510],{"class":363},[192,863,864],{"class":228},"))\n",[192,866,867,870,872,874,876],{"class":194,"line":245},[192,868,869],{"class":228},"breaks ",[192,871,283],{"class":224},[192,873,762],{"class":228},[192,875,322],{"class":202},[192,877,340],{"class":228},[192,879,880,883,885,888,890],{"class":194,"line":258},[192,881,882],{"class":228},"net ",[192,884,283],{"class":224},[192,886,887],{"class":228}," worked ",[192,889,827],{"class":224},[192,891,892],{"class":228}," breaks\n",[192,894,895,897,900,902,904,907,909,911,914,916,919],{"class":194,"line":272},[192,896,594],{"class":363},[192,898,899],{"class":228},"((net ",[192,901,706],{"class":224},[192,903,845],{"class":228},[192,905,906],{"class":492},"hours",[192,908,283],{"class":224},[192,910,510],{"class":363},[192,912,913],{"class":228},")).round(",[192,915,498],{"class":363},[192,917,918],{"class":228},").tolist())      ",[192,920,921],{"class":794},"# [3.0, 4.25, 5.5]\n",[10,923,924,925,928],{},"Dividing a timedelta by ",[14,926,927],{},"pd.Timedelta(hours=1)"," gives decimal hours, the form payroll and billing systems usually want. The rule \"negative means next day\" assumes no shift is 24 hours or longer; for those, store full start and end datetimes rather than times of day.",[10,930,931],{},"Once everything is a timedelta, totals and averages are ordinary pandas operations, and they stay exact — no fractions of a day to round:",[183,933,935],{"className":215,"code":934,"language":217,"meta":188,"style":188},"print(net.sum(), \"|\", net.mean())       # 0 days 12:45:00 | 0 days 04:15:00\nprint(net.sum() > pd.Timedelta(hours=12))                 # True\n",[14,936,937,953],{"__ignoreMap":188},[192,938,939,941,944,947,950],{"class":194,"line":195},[192,940,594],{"class":363},[192,942,943],{"class":228},"(net.sum(), ",[192,945,946],{"class":202},"\"|\"",[192,948,949],{"class":228},", net.mean())       ",[192,951,952],{"class":794},"# 0 days 12:45:00 | 0 days 04:15:00\n",[192,954,955,957,960,963,965,967,969,972,975],{"class":194,"line":238},[192,956,594],{"class":363},[192,958,959],{"class":228},"(net.sum() ",[192,961,962],{"class":224},">",[192,964,845],{"class":228},[192,966,906],{"class":492},[192,968,283],{"class":224},[192,970,971],{"class":363},"12",[192,973,974],{"class":228},"))                 ",[192,976,977],{"class":794},"# True\n",[10,979,980,981,984],{},"Comparisons with ",[14,982,983],{},"pd.Timedelta(hours=12)"," read like the business rule they implement — \"more than twelve hours this week\" — which is harder to see in a formula comparing a cell with 0.5.",[29,986,38,991,38,994,38,997,38,1000,38,1007,38,1012,38,1023,38,1029,38,1033,38,1037,38,1041,38,1045,38,1052,38,1058,38,1062,38,1064,38,1066,38,1069,38,1074,38,1078,38,1080,38,1082,38,1084,38,1089,38,1096,38,1101,38,1104,38,1106],{"viewBox":987,"role":32,"ariaLabelledBy":988,"xmlns":36,"style":37},"4 0 752 279",[989,990],"tm-shift-t","tm-shift-d",[40,992,993],{"id":989},"A shift from 21:00 to 01:30",[44,995,996],{"id":990},"The shift starts at 21:00 and ends at 01:30 the next morning. Subtracting the times gives minus 19.5 hours, and adding one day gives the correct 4.5 hours worked before the 15-minute break is deducted.",[48,998],{"x":50,"y":51,"width":52,"height":999,"fill":54},"279",[194,1001],{"x1":1002,"y1":1003,"x2":1004,"y2":1003,"stroke":1005,"style":1006},"94.0","139.5","666.0","var(--muted,#5b6780)","stroke-width:2.5px",[48,1008],{"x":57,"y":58,"width":1009,"height":1010,"rx":971,"fill":67,"stroke":68,"style":1011},"172.0","91.5","stroke-width:2px",[1013,1014,1016],"g",{"transform":1015},"translate(94.0,29.0) scale(1.000)",[1017,1018],"path",{"d":1019,"fill":1020,"stroke":1021,"style":1022},"M12 3a9 9 0 1 0 .01 0z M12 7v5l3.5 2","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[71,1024,1028],{"x":1025,"y":1026,"style":1027},"106.0","74.5","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","21:00",[71,1030,602],{"x":1025,"y":1031,"style":1032},"93.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[194,1034],{"x1":1025,"y1":1035,"x2":1025,"y2":1003,"stroke":1005,"style":1036},"107.5","stroke-width:1.5px",[1038,1039],"circle",{"cx":1025,"cy":1003,"r":1040,"fill":68,"stroke":54,"style":1011},"7.0",[48,1042],{"x":1043,"y":1044,"width":1009,"height":1010,"rx":971,"fill":88,"stroke":61,"style":1011},"202.7","171.5",[1013,1046,1048],{"transform":1047},"translate(276.7,184.5) scale(1.000)",[1017,1049],{"d":1050,"fill":1020,"stroke":1051,"style":1022},"M4 5h16v15H4z M4 10h16 M8 3v4 M16 3v4","var(--text,#172033)",[71,1053,1057],{"x":1054,"y":1055,"style":1056},"288.7","230.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","00:00",[71,1059,1061],{"x":1054,"y":1060,"style":1032},"248.5","midnight",[194,1063],{"x1":1054,"y1":1003,"x2":1054,"y2":1044,"stroke":1005,"style":1036},[1038,1065],{"cx":1054,"cy":1003,"r":1040,"fill":1005,"stroke":54,"style":1011},[48,1067],{"x":1068,"y":58,"width":1009,"height":1010,"rx":971,"fill":67,"stroke":68,"style":1011},"385.3",[1013,1070,1072],{"transform":1071},"translate(459.3,29.0) scale(1.000)",[1017,1073],{"d":1019,"fill":1020,"stroke":1021,"style":1022},[71,1075,1077],{"x":1076,"y":1026,"style":1027},"471.3","01:30",[71,1079,605],{"x":1076,"y":1031,"style":1032},[194,1081],{"x1":1076,"y1":1035,"x2":1076,"y2":1003,"stroke":1005,"style":1036},[1038,1083],{"cx":1076,"cy":1003,"r":1040,"fill":68,"stroke":54,"style":1011},[48,1085],{"x":1086,"y":1044,"width":1009,"height":1010,"rx":971,"fill":1087,"stroke":1088,"style":1011},"568.0","#d9f4f1","var(--teal,#0f9488)",[1013,1090,1092],{"transform":1091},"translate(642.0,184.5) scale(1.000)",[1017,1093],{"d":1094,"fill":1020,"stroke":1095,"style":1022},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[71,1097,1100],{"x":1098,"y":1055,"style":1099},"654.0","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","4:30",[71,1102,1103],{"x":1098,"y":1060,"style":1032},"−19:30 + 24:00",[194,1105],{"x1":1098,"y1":1003,"x2":1098,"y2":1044,"stroke":1005,"style":1036},[1038,1107],{"cx":1098,"cy":1003,"r":1040,"fill":1088,"stroke":54,"style":1011},[178,1109,1111],{"id":1110},"step-3-durations-formatted-without-square-brackets","Step 3 — Durations formatted without square brackets",[10,1113,1114,1115,1117,1118,1120],{},"The ",[14,1116,609],{}," column holds breaks of 30 and 15 minutes formatted as ",[14,1119,136],{},", so they come back as times of day — 00:30 and 00:15 — which Step 1 already handled. The real trap is a duration over 24 hours with that format. openpyxl then returns a date in January 1900:",[183,1122,1124],{"className":215,"code":1123,"language":217,"meta":188,"style":188},"from openpyxl import load_workbook\n\nwb = Workbook()\nws = wb.active\nws[\"A1\"] = 1.25                 # 30 hours\nws[\"A1\"].number_format = \"h:mm\"\nwb.save(\"wrapped.xlsx\")\nvalue = load_workbook(\"wrapped.xlsx\").active[\"A1\"].value\nprint(value)                    # 1900-01-01 06:00:00\nprint(value - dt.datetime(1899, 12, 31))    # 1 day, 6:00:00\n",[14,1125,1126,1137,1141,1149,1157,1176,1188,1197,1217,1227],{"__ignoreMap":188},[192,1127,1128,1130,1132,1134],{"class":194,"line":195},[192,1129,261],{"class":224},[192,1131,264],{"class":228},[192,1133,225],{"class":224},[192,1135,1136],{"class":228}," load_workbook\n",[192,1138,1139],{"class":194,"line":238},[192,1140,242],{"emptyLinePlaceholder":241},[192,1142,1143,1145,1147],{"class":194,"line":245},[192,1144,280],{"class":228},[192,1146,283],{"class":224},[192,1148,286],{"class":228},[192,1150,1151,1153,1155],{"class":194,"line":258},[192,1152,292],{"class":228},[192,1154,283],{"class":224},[192,1156,297],{"class":228},[192,1158,1159,1162,1165,1168,1170,1173],{"class":194,"line":272},[192,1160,1161],{"class":228},"ws[",[192,1163,1164],{"class":202},"\"A1\"",[192,1166,1167],{"class":228},"] ",[192,1169,283],{"class":224},[192,1171,1172],{"class":363}," 1.25",[192,1174,1175],{"class":794},"                 # 30 hours\n",[192,1177,1178,1180,1182,1184,1186],{"class":194,"line":277},[192,1179,1161],{"class":228},[192,1181,1164],{"class":202},[192,1183,513],{"class":228},[192,1185,283],{"class":224},[192,1187,542],{"class":202},[192,1189,1190,1192,1195],{"class":194,"line":289},[192,1191,562],{"class":228},[192,1193,1194],{"class":202},"\"wrapped.xlsx\"",[192,1196,568],{"class":228},[192,1198,1199,1202,1204,1207,1209,1212,1214],{"class":194,"line":300},[192,1200,1201],{"class":228},"value ",[192,1203,283],{"class":224},[192,1205,1206],{"class":228}," load_workbook(",[192,1208,1194],{"class":202},[192,1210,1211],{"class":228},").active[",[192,1213,1164],{"class":202},[192,1215,1216],{"class":228},"].value\n",[192,1218,1219,1221,1224],{"class":194,"line":343},[192,1220,594],{"class":363},[192,1222,1223],{"class":228},"(value)                    ",[192,1225,1226],{"class":794},"# 1900-01-01 06:00:00\n",[192,1228,1229,1231,1234,1236,1239,1242,1244,1246,1248,1251,1254],{"class":194,"line":392},[192,1230,594],{"class":363},[192,1232,1233],{"class":228},"(value ",[192,1235,827],{"class":224},[192,1237,1238],{"class":228}," dt.datetime(",[192,1240,1241],{"class":363},"1899",[192,1243,309],{"class":228},[192,1245,971],{"class":363},[192,1247,309],{"class":228},[192,1249,1250],{"class":363},"31",[192,1252,1253],{"class":228},"))    ",[192,1255,1256],{"class":794},"# 1 day, 6:00:00\n",[10,1258,1259,1260,1262,1263,1266,1267,1270,1271,1275],{},"Excel shows \"6:00\" for that cell too — the ",[14,1261,136],{}," format wraps at 24 hours — but the stored value is 30 hours. Subtracting 31 December 1899, the day before serial number 1, recovers it. Check every duration column for values that came back as ",[14,1264,1265],{},"datetime",", because a job that ran for 30 hours can otherwise be reported as six. A quick ",[14,1268,1269],{},"df[col].map(type).value_counts()"," shows at a glance whether a column mixes times, timedeltas and datetimes. Excel's serial numbers in general are covered in ",[23,1272,1274],{"href":1273},"\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",".",[178,1277,1279],{"id":1278},"step-4-durations-typed-as-text-and-decimal-hours","Step 4 — Durations typed as text and decimal hours",[10,1281,1282,1285],{},[14,1283,1284],{},"pd.to_timedelta"," parses many text forms directly — \"1h 15m\", \"45 min\", \"90 minutes\" — but not \"2:30\", which it expects as \"2:30:00\". Pad colon forms with seconds, then parse:",[183,1287,1289],{"className":215,"code":1288,"language":217,"meta":188,"style":188},"typed = df[\"typed\"].str.strip().str.replace(r\"^(\\d+):(\\d{2})$\", r\"\\1:\\2:00\", regex=True)\nparsed = pd.to_timedelta(typed, errors=\"coerce\")\nprint(parsed.tolist())\n# [Timedelta('0 days 02:30:00'), Timedelta('0 days 00:45:00'), Timedelta('0 days 01:15:00')]\nprint(df.loc[parsed.isna(), \"typed\"].tolist())     # [] - nothing failed\n\nhours = pd.to_timedelta(df[\"hours_dec\"], unit=\"h\")\nprint(hours.tolist()[0])                            # 0 days 02:30:00\n",[14,1290,1291,1364,1384,1391,1396,1411,1415,1439],{"__ignoreMap":188},[192,1292,1293,1296,1298,1301,1303,1306,1309,1312,1315,1317,1320,1323,1326,1329,1332,1334,1336,1338,1340,1344,1346,1349,1352,1354,1357,1359,1362],{"class":194,"line":195},[192,1294,1295],{"class":228},"typed ",[192,1297,283],{"class":224},[192,1299,1300],{"class":228}," df[",[192,1302,332],{"class":202},[192,1304,1305],{"class":228},"].str.strip().str.replace(",[192,1307,1308],{"class":224},"r",[192,1310,1311],{"class":202},"\"",[192,1313,1314],{"class":363},"^(\\d",[192,1316,700],{"class":224},[192,1318,1319],{"class":363},")",[192,1321,1322],{"class":202},":",[192,1324,1325],{"class":363},"(\\d",[192,1327,1328],{"class":224},"{2}",[192,1330,1331],{"class":363},")$",[192,1333,1311],{"class":202},[192,1335,309],{"class":228},[192,1337,1308],{"class":224},[192,1339,1311],{"class":202},[192,1341,1343],{"class":1342},"sx1sP","\\1",[192,1345,1322],{"class":202},[192,1347,1348],{"class":1342},"\\2",[192,1350,1351],{"class":202},":00\"",[192,1353,309],{"class":228},[192,1355,1356],{"class":492},"regex",[192,1358,283],{"class":224},[192,1360,1361],{"class":363},"True",[192,1363,568],{"class":228},[192,1365,1366,1369,1371,1374,1377,1379,1382],{"class":194,"line":238},[192,1367,1368],{"class":228},"parsed ",[192,1370,283],{"class":224},[192,1372,1373],{"class":228}," pd.to_timedelta(typed, ",[192,1375,1376],{"class":492},"errors",[192,1378,283],{"class":224},[192,1380,1381],{"class":202},"\"coerce\"",[192,1383,568],{"class":228},[192,1385,1386,1388],{"class":194,"line":245},[192,1387,594],{"class":363},[192,1389,1390],{"class":228},"(parsed.tolist())\n",[192,1392,1393],{"class":194,"line":258},[192,1394,1395],{"class":794},"# [Timedelta('0 days 02:30:00'), Timedelta('0 days 00:45:00'), Timedelta('0 days 01:15:00')]\n",[192,1397,1398,1400,1403,1405,1408],{"class":194,"line":272},[192,1399,594],{"class":363},[192,1401,1402],{"class":228},"(df.loc[parsed.isna(), ",[192,1404,332],{"class":202},[192,1406,1407],{"class":228},"].tolist())     ",[192,1409,1410],{"class":794},"# [] - nothing failed\n",[192,1412,1413],{"class":194,"line":277},[192,1414,242],{"emptyLinePlaceholder":241},[192,1416,1417,1420,1422,1425,1427,1430,1432,1434,1437],{"class":194,"line":289},[192,1418,1419],{"class":228},"hours ",[192,1421,283],{"class":224},[192,1423,1424],{"class":228}," pd.to_timedelta(df[",[192,1426,337],{"class":202},[192,1428,1429],{"class":228},"], ",[192,1431,736],{"class":492},[192,1433,283],{"class":224},[192,1435,1436],{"class":202},"\"h\"",[192,1438,568],{"class":228},[192,1440,1441,1443,1446,1448,1451],{"class":194,"line":300},[192,1442,594],{"class":363},[192,1444,1445],{"class":228},"(hours.tolist()[",[192,1447,51],{"class":363},[192,1449,1450],{"class":228},"])                            ",[192,1452,1453],{"class":794},"# 0 days 02:30:00\n",[10,1455,1456,1459,1460,1463,1464,1467],{},[14,1457,1458],{},"errors=\"coerce\""," turns anything unparseable into ",[14,1461,1462],{},"NaT","; list those values rather than letting them drop out of a total. Decimal hours convert with ",[14,1465,1466],{},"unit=\"h\"",". Decide once which unit a numeric column is in: 2.5 could be hours, days or minutes, and only the sheet's owner knows.",[178,1469,1471],{"id":1470},"step-5-write-durations-back-so-excel-shows-hours","Step 5 — Write durations back so Excel shows hours",[10,1473,1474],{},"pandas writes timedeltas to Excel as a number of days with a plain number format, so 1.25 days appears as \"1\" in the cell. Set a duration format on those cells after writing:",[183,1476,1478],{"className":215,"code":1477,"language":217,"meta":188,"style":188},"out = pd.DataFrame({\"task\": df[\"task\"], \"net\": net, \"run_time\": df[\"run_time\"]})\nout.to_excel(\"hours.xlsx\", index=False)\n\nwb = load_workbook(\"hours.xlsx\")\nws = wb.active\nfor row in ws.iter_rows(min_row=2, min_col=2, max_col=3):\n    for cell in row:\n        cell.number_format = \"[h]:mm\"\nwb.save(\"hours.xlsx\")\ncell = load_workbook(\"hours.xlsx\").active[\"C2\"]\nprint(cell.value, cell.number_format)    # 1 day, 6:00:00 [h]:mm\n",[14,1479,1480,1514,1534,1538,1550,1558,1594,1607,1616,1624,1643],{"__ignoreMap":188},[192,1481,1482,1485,1487,1490,1492,1495,1497,1499,1502,1505,1507,1509,1511],{"class":194,"line":195},[192,1483,1484],{"class":228},"out ",[192,1486,283],{"class":224},[192,1488,1489],{"class":228}," pd.DataFrame({",[192,1491,306],{"class":202},[192,1493,1494],{"class":228},": df[",[192,1496,306],{"class":202},[192,1498,1429],{"class":228},[192,1500,1501],{"class":202},"\"net\"",[192,1503,1504],{"class":228},": net, ",[192,1506,327],{"class":202},[192,1508,1494],{"class":228},[192,1510,327],{"class":202},[192,1512,1513],{"class":228},"]})\n",[192,1515,1516,1519,1522,1524,1527,1529,1532],{"class":194,"line":238},[192,1517,1518],{"class":228},"out.to_excel(",[192,1520,1521],{"class":202},"\"hours.xlsx\"",[192,1523,309],{"class":228},[192,1525,1526],{"class":492},"index",[192,1528,283],{"class":224},[192,1530,1531],{"class":363},"False",[192,1533,568],{"class":228},[192,1535,1536],{"class":194,"line":245},[192,1537,242],{"emptyLinePlaceholder":241},[192,1539,1540,1542,1544,1546,1548],{"class":194,"line":258},[192,1541,280],{"class":228},[192,1543,283],{"class":224},[192,1545,1206],{"class":228},[192,1547,1521],{"class":202},[192,1549,568],{"class":228},[192,1551,1552,1554,1556],{"class":194,"line":272},[192,1553,292],{"class":228},[192,1555,283],{"class":224},[192,1557,297],{"class":228},[192,1559,1560,1562,1564,1566,1568,1570,1572,1574,1576,1579,1581,1583,1585,1588,1590,1592],{"class":194,"line":277},[192,1561,346],{"class":224},[192,1563,349],{"class":228},[192,1565,352],{"class":224},[192,1567,489],{"class":228},[192,1569,493],{"class":492},[192,1571,283],{"class":224},[192,1573,498],{"class":363},[192,1575,309],{"class":228},[192,1577,1578],{"class":492},"min_col",[192,1580,283],{"class":224},[192,1582,498],{"class":363},[192,1584,309],{"class":228},[192,1586,1587],{"class":492},"max_col",[192,1589,283],{"class":224},[192,1591,535],{"class":363},[192,1593,501],{"class":228},[192,1595,1596,1599,1602,1604],{"class":194,"line":289},[192,1597,1598],{"class":224},"    for",[192,1600,1601],{"class":228}," cell ",[192,1603,352],{"class":224},[192,1605,1606],{"class":228}," row:\n",[192,1608,1609,1612,1614],{"class":194,"line":300},[192,1610,1611],{"class":228},"        cell.number_format ",[192,1613,283],{"class":224},[192,1615,556],{"class":202},[192,1617,1618,1620,1622],{"class":194,"line":343},[192,1619,562],{"class":228},[192,1621,1521],{"class":202},[192,1623,568],{"class":228},[192,1625,1626,1629,1631,1633,1635,1637,1640],{"class":194,"line":392},[192,1627,1628],{"class":228},"cell ",[192,1630,283],{"class":224},[192,1632,1206],{"class":228},[192,1634,1521],{"class":202},[192,1636,1211],{"class":228},[192,1638,1639],{"class":202},"\"C2\"",[192,1641,1642],{"class":228},"]\n",[192,1644,1645,1647,1650],{"class":194,"line":432},[192,1646,594],{"class":363},[192,1648,1649],{"class":228},"(cell.value, cell.number_format)    ",[192,1651,1652],{"class":794},"# 1 day, 6:00:00 [h]:mm\n",[10,1654,1655,1656,1660],{},"The square brackets make Excel show 30:00 instead of wrapping to 6:00, and the stored value — a fraction of a day — lets Excel users sum and average the column with ordinary formulas. If the report is for a payroll system rather than people, write decimal hours instead; see ",[23,1657,1659],{"href":1658},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-numbers-in-thousands-and-millions-in-excel\u002F","Format Numbers in Thousands and Millions in Excel"," for number formats in general.",[29,1662,38,1667,38,1670,38,1673,38,1676,38,1680,38,1685,38,1691,38,1694,38,1698,38,1703,38,1708,38,1713,38,1720,38,1725,38,1729,38,1732,38,1736,38,1739,38,1742,38,1747,38,1751,38,1754,38,1757,38,1761,38,1764,38,1767,38,1773,38,1779,38,1783,38,1787,38,1790,38,1793,38,1799,38,1803],{"viewBox":1663,"role":32,"ariaLabelledBy":1664,"xmlns":36,"style":37},"4 0 752 142",[1665,1666],"tm-flow-t","tm-flow-d",[40,1668,1669],{"id":1665},"A timesheet from cells to totals and back",[44,1671,1672],{"id":1666},"Times of day become timedeltas since midnight, overnight shifts get a day added, wrapped durations read as 1900 dates are recovered, text durations are parsed, everything is summed as timedeltas and written back with an [h]:mm format.",[48,1674],{"x":50,"y":51,"width":52,"height":1675,"fill":54},"142",[48,1677],{"x":57,"y":58,"width":1678,"height":1679,"rx":971,"fill":67,"stroke":68,"style":1011},"115.2","110.5",[1013,1681,1683],{"transform":1682},"translate(65.6,29.0) scale(1.000)",[1017,1684],{"d":1019,"fill":1020,"stroke":1021,"style":1022},[71,1686,1690],{"x":1687,"y":1688,"style":1689},"77.6","75.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Times →",[71,1692,1693],{"x":1687,"y":1031,"style":1689},"timedelta",[71,1695,1697],{"x":1687,"y":1696,"style":100},"112.5","since midnight",[194,1699],{"x1":1700,"y1":1701,"x2":1702,"y2":1701,"stroke":68,"style":1011},"140.2","71.2","162.2",[1704,1705],"polygon",{"points":1706,"fill":1707},"166.2,71.2 157.2,76.2 157.2,66.2","#5b5cf0",[48,1709],{"x":1710,"y":58,"width":1678,"height":1679,"rx":971,"fill":1711,"stroke":1712,"style":1011},"171.2","#fdefd8","var(--gold,#b4740a)",[1013,1714,1716],{"transform":1715},"translate(216.8,29.0) scale(1.000)",[1017,1717],{"d":1718,"fill":1020,"stroke":1719,"style":1022},"M20 11a8 8 0 1 0-2.3 5.7 M20 4v7h-7","var(--gold-ink,#7a4e06)",[71,1721,1724],{"x":1722,"y":1688,"style":1723},"228.8","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Fix overnight",[71,1726,1728],{"x":1722,"y":1727,"style":100},"95.0","+1 day if",[71,1730,1731],{"x":1722,"y":1696,"style":100},"negative",[194,1733],{"x1":1734,"y1":1701,"x2":1735,"y2":1701,"stroke":68,"style":1011},"291.4","313.4",[1704,1737],{"points":1738,"fill":1707},"317.4,71.2 308.4,76.2 308.4,66.2",[48,1740],{"x":1741,"y":58,"width":1678,"height":1679,"rx":971,"fill":67,"stroke":68,"style":1011},"322.4",[1013,1743,1745],{"transform":1744},"translate(368.0,29.0) scale(1.000)",[1017,1746],{"d":1050,"fill":1020,"stroke":1021,"style":1022},[71,1748,1750],{"x":1749,"y":1688,"style":1689},"380.0","Recover",[71,1752,1753],{"x":1749,"y":1031,"style":1689},"wrapped",[71,1755,1756],{"x":1749,"y":1696,"style":100},"− 1899-12-31",[194,1758],{"x1":1759,"y1":1701,"x2":1760,"y2":1701,"stroke":68,"style":1011},"442.6","464.6",[1704,1762],{"points":1763,"fill":1707},"468.6,71.2 459.6,76.2 459.6,66.2",[48,1765],{"x":1766,"y":58,"width":1678,"height":1679,"rx":971,"fill":1087,"stroke":1088,"style":1011},"473.6",[1013,1768,1770],{"transform":1769},"translate(519.2,37.8) scale(1.000)",[1017,1771],{"d":1772,"fill":1020,"stroke":1095,"style":1022},"M3 5h18l-7 8v6l-4 2v-8z",[71,1774,1778],{"x":1775,"y":1776,"style":1777},"531.2","84.2","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Parse text",[71,1780,1782],{"x":1775,"y":1781,"style":100},"103.8","1h 15m, 2:30",[194,1784],{"x1":1785,"y1":1701,"x2":1786,"y2":1701,"stroke":68,"style":1011},"593.8","615.8",[1704,1788],{"points":1789,"fill":1707},"619.8,71.2 610.8,76.2 610.8,66.2",[48,1791],{"x":1792,"y":58,"width":1678,"height":1679,"rx":971,"fill":67,"stroke":68,"style":1011},"624.8",[1013,1794,1796],{"transform":1795},"translate(670.4,37.8) scale(1.000)",[1017,1797],{"d":1798,"fill":1020,"stroke":1021,"style":1022},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6",[71,1800,1802],{"x":1801,"y":1776,"style":1689},"682.4","Write [h]:mm",[71,1804,1805],{"x":1801,"y":1781,"style":100},"number_format",[178,1807,1809],{"id":1808},"common-pitfalls","Common pitfalls",[1811,1812,1813,1824,1835,1841,1850,1859],"ul",{},[1814,1815,1816,1820,1821,1823],"li",{},[1817,1818,1819],"strong",{},"Summing time objects."," ",[14,1822,16],{}," values cannot be added; pandas raises or silently returns an object column. Convert to timedeltas first.",[1814,1825,1826,1820,1829,1831,1832,1834],{},[1817,1827,1828],{},"Formats that wrap at 24 hours.",[14,1830,136],{}," shows 30 hours as 6:00 in Excel and openpyxl reads it as a 1900 date. Use ",[14,1833,119],{}," for durations everywhere.",[1814,1836,1837,1840],{},[1817,1838,1839],{},"Floating-point seconds."," 0.5208333 days is 12:29:59.997; round to whole seconds or minutes before comparing durations.",[1814,1842,1843,1846,1847,1275],{},[1817,1844,1845],{},"The 1904 date system."," Workbooks created on older Macs count days from 1904, so the same serial number means a different date. Durations are unaffected, but a wrapped duration recovered with the 1899-12-31 trick needs the 1904 epoch instead; openpyxl exposes it as ",[14,1848,1849],{},"wb.epoch",[1814,1851,1852,1855,1856,1858],{},[1817,1853,1854],{},"Durations stored as text by an export."," Some systems write \"02:30:00\" as a string. ",[14,1857,1284],{}," parses it directly, so check the column type before assuming a conversion is needed.",[1814,1860,1861,1864,1865,1275],{},[1817,1862,1863],{},"Mixing time zones."," Times of day have no zone; if shifts span a daylight-saving change, store full timestamps — see ",[23,1866,1868],{"href":1867},"\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",[178,1870,1872],{"id":1871},"conclusion","Conclusion",[10,1874,1875,1876,1878,1879,1881],{},"Excel times and durations are fractions of a day, and their Python type depends on the cell format. Convert times of day to timedeltas since midnight, add a day to negative shift lengths, recover wrapped durations that arrive as 1900 dates, parse text durations after padding colon forms, and convert decimal hours with ",[14,1877,1466],{},". When writing back, give duration cells an ",[14,1880,119],{}," format so Excel shows hours, not a bare number of days.",[178,1883,1885],{"id":1884},"frequently-asked-questions","Frequently asked questions",[10,1887,1888,1891,1892,1895],{},[1817,1889,1890],{},"Why does pandas read an Excel duration as a time instead of a timedelta?","\nThe type depends on the cell's number format. A cell formatted h:mm or hh:mm is read as a time of day, while ",[192,1893,1894],{},"h",":mm (with square brackets) is read as a timedelta. Convert time objects to timedeltas by counting the seconds since midnight.",[10,1897,1898,1901],{},[1817,1899,1900],{},"Why does a 30-hour duration come back as 1900-01-01 06:00?","\nExcel stores 30 hours as 1.25 days. With a plain h:mm format, openpyxl treats the value as a date and time — day 1 of the 1900 date system at 06:00. Subtract datetime(1899, 12, 31) to recover the duration.",[10,1903,1904,1907],{},[1817,1905,1906],{},"How do I calculate hours for a shift that ends after midnight?","\nSubtract start from end; if the result is negative, add one day. Then divide the timedelta by pd.Timedelta(hours=1) to get decimal hours.",[10,1909,1910,1913,1914,1916],{},[1817,1911,1912],{},"Why do my durations show as 1 or 0 in Excel after writing them with pandas?","\npandas writes timedeltas as a number of days with a plain number format. Set the cells' number format to ",[192,1915,1894],{},":mm:ss afterwards so Excel displays them as hours and minutes.",[178,1918,1920],{"id":1919},"related","Related",[1811,1922,1923,1929,1936,1943],{},[1814,1924,1925,1926,1928],{},"Up one level: ",[23,1927,26],{"href":25}," — dates and times from Excel.",[1814,1930,1931,1935],{},[23,1932,1934],{"href":1933},"\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"," — the date side of the same problem.",[1814,1937,1938,1942],{},[23,1939,1941],{"href":1940},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-business-days-between-excel-dates-with-pandas\u002F","Calculate Business Days Between Excel Dates with pandas"," — elapsed working time across days.",[1814,1944,1945,1949],{},[23,1946,1948],{"href":1947},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmatch-excel-rounding-in-pandas\u002F","Match Excel Rounding in pandas"," — rounding decimal hours like Excel.",[1951,1952,1953],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sx1sP, html code.shiki .sx1sP{--shiki-default:#22863A;--shiki-dark:#72F088}",{"title":188,"searchDepth":238,"depth":238,"links":1955},[1956,1957,1958,1959,1960,1961,1962,1963,1964,1965],{"id":180,"depth":238,"text":181},{"id":636,"depth":238,"text":637},{"id":805,"depth":238,"text":806},{"id":1110,"depth":238,"text":1111},{"id":1278,"depth":238,"text":1279},{"id":1470,"depth":238,"text":1471},{"id":1808,"depth":238,"text":1809},{"id":1871,"depth":238,"text":1872},{"id":1884,"depth":238,"text":1885},{"id":1919,"depth":238,"text":1920},"2026-09-23","Excel stores times and durations as fractions of a day, and pandas returns them as time objects, timedeltas or 1900 dates depending on the cell format. Turn them all into timedeltas, handle shifts that cross midnight, parse durations typed as text and write durations back so Excel shows hours, not \"1\".","md",[1970,1972,1974,1976],{"q":1890,"a":1971},"The type depends on the cell's number format. A cell formatted h:mm or hh:mm is read as a time of day, while [h]:mm (with square brackets) is read as a timedelta. Convert time objects to timedeltas by counting the seconds since midnight.",{"q":1900,"a":1973},"Excel stores 30 hours as 1.25 days. With a plain h:mm format, openpyxl treats the value as a date and time — day 1 of the 1900 date system at 06:00. Subtract datetime(1899, 12, 31) to recover the duration.",{"q":1906,"a":1975},"Subtract start from end; if the result is negative, add one day. Then divide the timedelta by pd.Timedelta(hours=1) to get decimal hours.",{"q":1912,"a":1977},"pandas writes timedeltas as a number of days with a plain number format. Set the cells' number format to [h]:mm:ss afterwards so Excel displays them as hours and minutes.","convert excel time values and durations in python",null,{"breadcrumb":1981},[1982,1984,1987],{"name":1983,"item":706},"Home",{"name":1985,"item":1986},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":26,"item":25},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-excel-time-values-and-durations",{"title":1990,"description":1991},"Convert Excel Time Values and Durations in Python (pandas)","Read Excel times and durations with pandas and openpyxl — time objects vs timedelta vs 1900-01-01 datetimes, converting to timedelta, overnight shifts, decimal hours, text like \"1h 15m\", and writing [h]:mm durations back to Excel.","convert-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",[1995,1996,1997],"dates-and-times","number-formats","pandas","how-to","9fmJofx0nybqcToxE6kgwLs1t24gQHtpg-OJD0uDo38",[2001,2005],{"title":2002,"path":2003,"stem":2004,"children":-1},"Calculate Working Hours Between Two Timestamps","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-working-hours-between-two-timestamps","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-working-hours-between-two-timestamps\u002Findex",{"title":2006,"path":2007,"stem":2008,"children":-1},"Convert Dates Like 20240131, 31.01.24 and 2024031 with pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-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",[2010,2012],{"path":2011,"title":1985},"\u002Fadvanced-data-transformation-and-cleaning",{"path":2013,"title":26},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data",[],[2016,2019,2022,2025,2028,2031],{"path":2017,"title":2018,"kind":1998},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-overdue-dates-with-openpyxl","Highlight Overdue Dates in Excel with openpyxl",{"path":2020,"title":2021,"kind":1998},"\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":2023,"title":2024,"kind":1998},"\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":2026,"title":2027,"kind":1998},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcalculate-moving-averages-with-pandas-rolling","Calculate Moving Averages with pandas rolling",{"path":2029,"title":2030,"kind":1998},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Feomonth-and-edate-equivalents-in-pandas","EOMONTH and EDATE Equivalents in pandas",{"path":2032,"title":2033,"kind":1998},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fyearfrac-and-days360-equivalents-in-python","YEARFRAC and DAYS360 Equivalents in Python",1790972103233]