[{"data":1,"prerenderedAt":1894},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-durations-over-24-hours-in-excel":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-durations-over-24-hours-in-excel":1861,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-durations-over-24-hours-in-excel":1869,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-durations-over-24-hours-in-excel":1874,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-durations-over-24-hours-in-excel":1875},{"id":4,"title":5,"body":6,"dateModified":1828,"datePublished":1828,"description":1829,"extension":1830,"faq":1831,"keyword":1840,"kind":1841,"meta":1842,"navigation":277,"path":1850,"seo":1851,"slug":1854,"stem":1855,"topics":1856,"type":1859,"__hash__":1860},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-durations-over-24-hours-in-excel\u002Findex.md","Format Durations Over 24 Hours in Excel with Python",{"type":7,"value":8,"toc":1814},"minimark",[9,33,182,187,218,224,228,235,445,466,470,491,716,726,744,945,949,956,1018,1029,1033,1036,1294,1303,1417,1427,1431,1444,1611,1625,1629,1643,1647,1709,1713,1720,1724,1739,1743,1756,1765,1771,1777,1781,1810],[10,11,12,13,17,18,21,22,26,27,32],"p",{},"Durations are everywhere in operational reports — ticket resolution times, machine run hours, time worked per person per week — and they are the values most often displayed wrongly. A ticket open for 26 hours 30 minutes shows as ",[14,15,16],"code",{},"2:30",", as ",[14,19,20],{},"1",", or as a date in January 1900, depending on how it was written. All three come from the same fact: Excel stores a duration as a number of ",[23,24,25],"strong",{},"days",", and the number format decides whether that is shown as a time of day, a date, a whole number or total elapsed time. This page, part of ",[28,29,31],"a",{"href":30},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002F","Applying Number and Date Formats in Excel",", writes durations from pandas and openpyxl so they display as intended.",[34,35,43,44,43,48,43,52,43,60,43,68,43,75,43,82,43,85,43,89,43,94,43,100,43,102,43,106,43,108,43,111,43,114,43,118,43,120,43,123,43,125,43,128,43,131,43,135,43,137,43,141,43,143,43,146,43,149,43,153,43,155,43,159,43,161,43,164,43,167,43,171,43,173,43,177,43,179],"svg",{"viewBox":36,"role":37,"ariaLabelledBy":38,"xmlns":41,"style":42},"4 0 752 242","img",[39,40],"dur-formats-t","dur-formats-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  ",[45,46,47],"title",{"id":39},"One stored value, five ways to display it",[49,50,51],"desc",{"id":40},"A table showing a cell holding 1.104167 days, which is 26 hours 30 minutes, under different number formats. The format 0 shows 1. The format h:mm shows 2:30 because it wraps at 24 hours. The format [h]:mm shows 26:30. The format [mm] shows 1590 minutes. A decimal hours cell holding the value times 24 with format 0.00 shows 26.50.",[53,54],"rect",{"x":55,"y":56,"width":57,"height":58,"fill":59},"4","0","752","242","#ffffff",[53,61],{"x":62,"y":63,"width":64,"height":65,"rx":56,"fill":59,"stroke":66,"style":67},"20.0","16.0","145.8","40.0","var(--line,#cdd5e6)","stroke-width:1px",[53,69],{"x":70,"y":63,"width":71,"height":65,"rx":56,"fill":72,"stroke":73,"style":74},"165.8","287.1","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[76,77,81],"text",{"x":78,"y":79,"style":80},"309.3","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Displays",[53,83],{"x":84,"y":63,"width":71,"height":65,"rx":56,"fill":72,"stroke":73,"style":74},"452.9",[76,86,88],{"x":87,"y":79,"style":80},"596.4","Meaning",[53,90],{"x":62,"y":91,"width":64,"height":92,"rx":56,"fill":93,"stroke":66,"style":67},"56.0","34.0","#e7ebef",[76,95,99],{"x":96,"y":97,"style":98},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","0 (pandas default)",[53,101],{"x":70,"y":91,"width":71,"height":92,"rx":56,"fill":59,"stroke":66,"style":67},[76,103,20],{"x":78,"y":104,"style":105},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[53,107],{"x":84,"y":91,"width":71,"height":92,"rx":56,"fill":59,"stroke":66,"style":67},[76,109,110],{"x":87,"y":104,"style":105},"whole days — wrong",[53,112],{"x":62,"y":113,"width":64,"height":92,"rx":56,"fill":93,"stroke":66,"style":67},"90.0",[76,115,117],{"x":96,"y":116,"style":98},"112.0","h:mm",[53,119],{"x":70,"y":113,"width":71,"height":92,"rx":56,"fill":59,"stroke":66,"style":67},[76,121,16],{"x":78,"y":122,"style":105},"111.0",[53,124],{"x":84,"y":113,"width":71,"height":92,"rx":56,"fill":59,"stroke":66,"style":67},[76,126,127],{"x":87,"y":122,"style":105},"time of day — wraps at 24h",[53,129],{"x":62,"y":130,"width":64,"height":92,"rx":56,"fill":93,"stroke":66,"style":67},"124.0",[76,132,134],{"x":96,"y":133,"style":98},"146.0","[h]:mm",[53,136],{"x":70,"y":130,"width":71,"height":92,"rx":56,"fill":59,"stroke":66,"style":67},[76,138,140],{"x":78,"y":139,"style":105},"145.0","26:30",[53,142],{"x":84,"y":130,"width":71,"height":92,"rx":56,"fill":59,"stroke":66,"style":67},[76,144,145],{"x":87,"y":139,"style":105},"total elapsed hours",[53,147],{"x":62,"y":148,"width":64,"height":92,"rx":56,"fill":93,"stroke":66,"style":67},"158.0",[76,150,152],{"x":96,"y":151,"style":98},"180.0","[mm]",[53,154],{"x":70,"y":148,"width":71,"height":92,"rx":56,"fill":59,"stroke":66,"style":67},[76,156,158],{"x":78,"y":157,"style":105},"179.0","1590",[53,160],{"x":84,"y":148,"width":71,"height":92,"rx":56,"fill":59,"stroke":66,"style":67},[76,162,163],{"x":87,"y":157,"style":105},"total elapsed minutes",[53,165],{"x":62,"y":166,"width":64,"height":92,"rx":56,"fill":93,"stroke":66,"style":67},"192.0",[76,168,170],{"x":96,"y":169,"style":98},"214.0","×24 with 0.00",[53,172],{"x":70,"y":166,"width":71,"height":92,"rx":56,"fill":59,"stroke":66,"style":67},[76,174,176],{"x":78,"y":175,"style":105},"213.0","26.50",[53,178],{"x":84,"y":166,"width":71,"height":92,"rx":56,"fill":59,"stroke":66,"style":67},[76,180,181],{"x":87,"y":175,"style":105},"decimal hours",[183,184,186],"h2",{"id":185},"prerequisites","Prerequisites",[188,189,194],"pre",{"className":190,"code":191,"language":192,"meta":193,"style":193},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl xlsxwriter\n","bash","",[14,195,196],{"__ignoreMap":193},[197,198,201,205,209,212,215],"span",{"class":199,"line":200},"line",1,[197,202,204],{"class":203},"sMTad","pip",[197,206,208],{"class":207},"srMev"," install",[197,210,211],{"class":207}," pandas",[197,213,214],{"class":207}," openpyxl",[197,216,217],{"class":207}," xlsxwriter\n",[10,219,220,223],{},[14,221,222],{},"tickets.xlsx"," has four support tickets with opened and closed timestamps; the report shows how long each was open.",[183,225,227],{"id":226},"step-1-see-what-pandas-writes-for-a-timedelta","Step 1 — See what pandas writes for a timedelta",[10,229,230,231,234],{},"Subtracting two datetime columns gives a timedelta column. Write it with ",[14,232,233],{},"to_excel"," and look at what reaches the file:",[188,236,240],{"className":237,"code":238,"language":239,"meta":193,"style":193},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\nfrom openpyxl import load_workbook\n\ntickets = pd.read_excel(\"tickets.xlsx\")\ntickets[\"Open for\"] = tickets[\"Closed\"] - tickets[\"Opened\"]\nprint(tickets[[\"Ticket\", \"Open for\"]].to_string(index=False))\n\ntickets.to_excel(\"durations_default.xlsx\", index=False)\nws = load_workbook(\"durations_default.xlsx\").active\nprint([(round(c.value, 4), c.number_format) for c in ws[\"D\"][1:]])\n","python",[14,241,242,258,272,279,297,330,363,368,387,403],{"__ignoreMap":193},[197,243,244,248,252,255],{"class":199,"line":200},[197,245,247],{"class":246},"s-kum","import",[197,249,251],{"class":250},"skGVy"," pandas ",[197,253,254],{"class":246},"as",[197,256,257],{"class":250}," pd\n",[197,259,261,264,267,269],{"class":199,"line":260},2,[197,262,263],{"class":246},"from",[197,265,266],{"class":250}," openpyxl ",[197,268,247],{"class":246},[197,270,271],{"class":250}," load_workbook\n",[197,273,275],{"class":199,"line":274},3,[197,276,278],{"emptyLinePlaceholder":277},true,"\n",[197,280,282,285,288,291,294],{"class":199,"line":281},4,[197,283,284],{"class":250},"tickets ",[197,286,287],{"class":246},"=",[197,289,290],{"class":250}," pd.read_excel(",[197,292,293],{"class":207},"\"tickets.xlsx\"",[197,295,296],{"class":250},")\n",[197,298,300,303,306,309,311,314,317,319,322,324,327],{"class":199,"line":299},5,[197,301,302],{"class":250},"tickets[",[197,304,305],{"class":207},"\"Open for\"",[197,307,308],{"class":250},"] ",[197,310,287],{"class":246},[197,312,313],{"class":250}," tickets[",[197,315,316],{"class":207},"\"Closed\"",[197,318,308],{"class":250},[197,320,321],{"class":246},"-",[197,323,313],{"class":250},[197,325,326],{"class":207},"\"Opened\"",[197,328,329],{"class":250},"]\n",[197,331,333,337,340,343,346,348,351,355,357,360],{"class":199,"line":332},6,[197,334,336],{"class":335},"sP0c6","print",[197,338,339],{"class":250},"(tickets[[",[197,341,342],{"class":207},"\"Ticket\"",[197,344,345],{"class":250},", ",[197,347,305],{"class":207},[197,349,350],{"class":250},"]].to_string(",[197,352,354],{"class":353},"sa561","index",[197,356,287],{"class":246},[197,358,359],{"class":335},"False",[197,361,362],{"class":250},"))\n",[197,364,366],{"class":199,"line":365},7,[197,367,278],{"emptyLinePlaceholder":277},[197,369,371,374,377,379,381,383,385],{"class":199,"line":370},8,[197,372,373],{"class":250},"tickets.to_excel(",[197,375,376],{"class":207},"\"durations_default.xlsx\"",[197,378,345],{"class":250},[197,380,354],{"class":353},[197,382,287],{"class":246},[197,384,359],{"class":335},[197,386,296],{"class":250},[197,388,390,393,395,398,400],{"class":199,"line":389},9,[197,391,392],{"class":250},"ws ",[197,394,287],{"class":246},[197,396,397],{"class":250}," load_workbook(",[197,399,376],{"class":207},[197,401,402],{"class":250},").active\n",[197,404,406,408,411,414,417,419,422,425,428,431,434,437,440,442],{"class":199,"line":405},10,[197,407,336],{"class":335},[197,409,410],{"class":250},"([(",[197,412,413],{"class":335},"round",[197,415,416],{"class":250},"(c.value, ",[197,418,55],{"class":335},[197,420,421],{"class":250},"), c.number_format) ",[197,423,424],{"class":246},"for",[197,426,427],{"class":250}," c ",[197,429,430],{"class":246},"in",[197,432,433],{"class":250}," ws[",[197,435,436],{"class":207},"\"D\"",[197,438,439],{"class":250},"][",[197,441,20],{"class":335},[197,443,444],{"class":250},":]])\n",[10,446,447,448,451,452,454,455,345,457,345,459,462,463,465],{},"Each duration is stored as a fraction of a day — T-101's 26 hours 30 minutes is ",[14,449,450],{},"1.1042"," — with the number format ",[14,453,56],{},", which rounds to whole days. In Excel the column shows ",[14,456,20],{},[14,458,56],{},[14,460,461],{},"2"," and ",[14,464,56],{},": technically days, practically useless. The stored numbers are right; only the format needs changing.",[183,467,469],{"id":468},"step-2-apply-a-bracketed-hours-format","Step 2 — Apply a bracketed hours format",[10,471,472,473,477,478,480,481,483,484,486,487,490],{},"Hours in square brackets mean ",[474,475,476],"em",{},"total elapsed"," hours. ",[14,479,134],{}," shows 26:30; ",[14,482,117],{},", without brackets, shows the time of day and wraps every 24 hours, so the same value shows 2:30. There is one catch when applying it: pandas gives every timedelta cell its own ",[14,485,56],{}," format, and a cell's own format beats the column's, so xlsxwriter's ",[14,488,489],{},"set_column"," alone changes nothing. Rewrite the duration cells with the format attached:",[188,492,494],{"className":237,"code":493,"language":239,"meta":193,"style":193},"with pd.ExcelWriter(\"durations.xlsx\", engine=\"xlsxwriter\", datetime_format=\"yyyy-mm-dd hh:mm\") as writer:\n    tickets.to_excel(writer, index=False, sheet_name=\"Tickets\")\n    wb, ws = writer.book, writer.sheets[\"Tickets\"]\n    elapsed = wb.add_format({\"num_format\": \"[h]:mm\"})\n    for row, value in enumerate(tickets[\"Open for\"], start=1):\n        ws.write_number(row, 3, value.total_seconds() \u002F 86400, elapsed)\n    ws.set_column(\"B:C\", 18)\n    ws.set_column(\"D:D\", 12)\n\ncheck = load_workbook(\"durations.xlsx\").active\nprint([(c.value, c.number_format) for c in check[\"D\"][1:]])\n",[14,495,496,535,558,572,594,625,645,660,674,678,691],{"__ignoreMap":193},[197,497,498,501,504,507,509,512,514,517,519,522,524,527,530,532],{"class":199,"line":200},[197,499,500],{"class":246},"with",[197,502,503],{"class":250}," pd.ExcelWriter(",[197,505,506],{"class":207},"\"durations.xlsx\"",[197,508,345],{"class":250},[197,510,511],{"class":353},"engine",[197,513,287],{"class":246},[197,515,516],{"class":207},"\"xlsxwriter\"",[197,518,345],{"class":250},[197,520,521],{"class":353},"datetime_format",[197,523,287],{"class":246},[197,525,526],{"class":207},"\"yyyy-mm-dd hh:mm\"",[197,528,529],{"class":250},") ",[197,531,254],{"class":246},[197,533,534],{"class":250}," writer:\n",[197,536,537,540,542,544,546,548,551,553,556],{"class":199,"line":260},[197,538,539],{"class":250},"    tickets.to_excel(writer, ",[197,541,354],{"class":353},[197,543,287],{"class":246},[197,545,359],{"class":335},[197,547,345],{"class":250},[197,549,550],{"class":353},"sheet_name",[197,552,287],{"class":246},[197,554,555],{"class":207},"\"Tickets\"",[197,557,296],{"class":250},[197,559,560,563,565,568,570],{"class":199,"line":274},[197,561,562],{"class":250},"    wb, ws ",[197,564,287],{"class":246},[197,566,567],{"class":250}," writer.book, writer.sheets[",[197,569,555],{"class":207},[197,571,329],{"class":250},[197,573,574,577,579,582,585,588,591],{"class":199,"line":281},[197,575,576],{"class":250},"    elapsed ",[197,578,287],{"class":246},[197,580,581],{"class":250}," wb.add_format({",[197,583,584],{"class":207},"\"num_format\"",[197,586,587],{"class":250},": ",[197,589,590],{"class":207},"\"[h]:mm\"",[197,592,593],{"class":250},"})\n",[197,595,596,599,602,604,607,610,612,615,618,620,622],{"class":199,"line":299},[197,597,598],{"class":246},"    for",[197,600,601],{"class":250}," row, value ",[197,603,430],{"class":246},[197,605,606],{"class":335}," enumerate",[197,608,609],{"class":250},"(tickets[",[197,611,305],{"class":207},[197,613,614],{"class":250},"], ",[197,616,617],{"class":353},"start",[197,619,287],{"class":246},[197,621,20],{"class":335},[197,623,624],{"class":250},"):\n",[197,626,627,630,633,636,639,642],{"class":199,"line":332},[197,628,629],{"class":250},"        ws.write_number(row, ",[197,631,632],{"class":335},"3",[197,634,635],{"class":250},", value.total_seconds() ",[197,637,638],{"class":246},"\u002F",[197,640,641],{"class":335}," 86400",[197,643,644],{"class":250},", elapsed)\n",[197,646,647,650,653,655,658],{"class":199,"line":365},[197,648,649],{"class":250},"    ws.set_column(",[197,651,652],{"class":207},"\"B:C\"",[197,654,345],{"class":250},[197,656,657],{"class":335},"18",[197,659,296],{"class":250},[197,661,662,664,667,669,672],{"class":199,"line":370},[197,663,649],{"class":250},[197,665,666],{"class":207},"\"D:D\"",[197,668,345],{"class":250},[197,670,671],{"class":335},"12",[197,673,296],{"class":250},[197,675,676],{"class":199,"line":389},[197,677,278],{"emptyLinePlaceholder":277},[197,679,680,683,685,687,689],{"class":199,"line":405},[197,681,682],{"class":250},"check ",[197,684,287],{"class":246},[197,686,397],{"class":250},[197,688,506],{"class":207},[197,690,402],{"class":250},[197,692,694,696,699,701,703,705,708,710,712,714],{"class":199,"line":693},11,[197,695,336],{"class":335},[197,697,698],{"class":250},"([(c.value, c.number_format) ",[197,700,424],{"class":246},[197,702,427],{"class":250},[197,704,430],{"class":246},[197,706,707],{"class":250}," check[",[197,709,436],{"class":207},[197,711,439],{"class":250},[197,713,20],{"class":335},[197,715,444],{"class":250},[10,717,718,719,721,722,725],{},"Dividing the seconds by 86,400 gives the fraction of a day Excel expects. The ",[14,720,521],{}," argument handles the Opened and Closed timestamps the same way — pandas applies it to every datetime cell it writes. With the openpyxl engine, loop over the column after writing and set ",[14,723,724],{},"cell.number_format = \"[h]:mm\""," on each cell.",[10,727,728,729,732,733,736,737,739,740,743],{},"The column now reads 26:30, 7:45, 67:45 and 0:40. With a bracketed format, openpyxl also reads the cells back as ",[14,730,731],{},"timedelta"," values rather than floats — it recognises a duration format. ",[14,734,735],{},"[h]:mm:ss"," adds seconds; ",[14,738,152],{}," shows total minutes (1590 for T-101) and ",[14,741,742],{},"[ss]"," total seconds, useful for short machine cycle times.",[34,745,43,751,43,754,43,757,43,762,43,768,43,773,43,777,43,783,43,787,43,791,43,795,43,799,43,803,43,807,43,811,43,815,43,817,43,821,43,823,43,826,43,828,43,831,43,833,43,836,43,839,43,842,43,844,43,848,43,850,43,853,43,855,43,858,43,863,43,866,43,869,43,872,43,874,43,877,43,879,43,882,43,884,43,887,43,889,43,892,43,895,43,898,43,900,43,903,43,905,43,908,43,910,43,913,43,915,43,918,43,921,43,925,43,927,43,930,43,932,43,935,43,937,43,940,43,942],{"viewBox":746,"role":37,"ariaLabelledBy":747,"xmlns":41,"style":750},"137 0 485 228",[748,749],"dur-sheet-t","dur-sheet-d","width:100%;max-width:485px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[45,752,753],{"id":748},"Ticket durations with the [h]:mm format",[49,755,756],{"id":749},"The Tickets sheet with durations formatted as [h]:mm. T-101 was open for 26:30, T-102 for 7:45, T-103 for 67:45 and T-104 for 0:40. Without the brackets, T-101 would show 2:30 and T-103 19:45.",[53,758],{"x":759,"y":56,"width":760,"height":761,"fill":59},"137","485","228",[76,763,767],{"x":764,"y":765,"style":766},"380.0","31.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","durations.xlsx — Open for, formatted [h]:mm",[53,769],{"x":770,"y":771,"width":96,"height":772,"rx":56,"fill":93,"stroke":66,"style":67},"153.6","48.0","24.0",[53,774],{"x":775,"y":771,"width":776,"height":772,"rx":56,"fill":93,"stroke":66,"style":67},"185.6","62.7",[76,778,782],{"x":779,"y":780,"style":781},"216.9","65.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[53,784],{"x":785,"y":771,"width":786,"height":772,"rx":56,"fill":93,"stroke":66,"style":67},"248.3","139.3",[76,788,790],{"x":789,"y":780,"style":781},"317.9","B",[53,792],{"x":793,"y":771,"width":794,"height":772,"rx":56,"fill":93,"stroke":66,"style":67},"387.5","141.0",[76,796,798],{"x":797,"y":780,"style":781},"458.0","C",[53,800],{"x":801,"y":771,"width":802,"height":772,"rx":56,"fill":93,"stroke":66,"style":67},"528.5","77.9",[76,804,806],{"x":805,"y":780,"style":781},"567.5","D",[53,808],{"x":770,"y":809,"width":96,"height":810,"rx":56,"fill":93,"stroke":66,"style":67},"72.0","28.0",[76,812,20],{"x":813,"y":814,"style":781},"169.6","91.0",[53,816],{"x":775,"y":809,"width":776,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,818,820],{"x":779,"y":814,"style":819},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Ticket",[53,822],{"x":785,"y":809,"width":786,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,824,825],{"x":789,"y":814,"style":819},"Opened",[53,827],{"x":793,"y":809,"width":794,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,829,830],{"x":797,"y":814,"style":819},"Closed",[53,832],{"x":801,"y":809,"width":802,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,834,835],{"x":805,"y":814,"style":819},"Open for",[53,837],{"x":770,"y":838,"width":96,"height":810,"rx":56,"fill":93,"stroke":66,"style":67},"100.0",[76,840,461],{"x":813,"y":841,"style":781},"119.0",[53,843],{"x":775,"y":838,"width":776,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,845,847],{"x":779,"y":841,"style":846},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","T-101",[53,849],{"x":785,"y":838,"width":786,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,851,852],{"x":789,"y":841,"style":846},"2026-09-21 08:00",[53,854],{"x":793,"y":838,"width":794,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,856,857],{"x":797,"y":841,"style":846},"2026-09-22 10:30",[53,859],{"x":801,"y":838,"width":802,"height":810,"rx":56,"fill":860,"stroke":861,"style":862},"#d9f4f1","var(--teal,#0f9488)","stroke-width:1.5px",[76,864,140],{"x":805,"y":841,"style":865},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[53,867],{"x":770,"y":868,"width":96,"height":810,"rx":56,"fill":93,"stroke":66,"style":67},"128.0",[76,870,632],{"x":813,"y":871,"style":781},"147.0",[53,873],{"x":775,"y":868,"width":776,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,875,876],{"x":779,"y":871,"style":846},"T-102",[53,878],{"x":785,"y":868,"width":786,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,880,881],{"x":789,"y":871,"style":846},"2026-09-21 09:15",[53,883],{"x":793,"y":868,"width":794,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,885,886],{"x":797,"y":871,"style":846},"2026-09-21 17:00",[53,888],{"x":801,"y":868,"width":802,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,890,891],{"x":805,"y":871,"style":846},"7:45",[53,893],{"x":770,"y":894,"width":96,"height":810,"rx":56,"fill":93,"stroke":66,"style":67},"156.0",[76,896,55],{"x":813,"y":897,"style":781},"175.0",[53,899],{"x":775,"y":894,"width":776,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,901,902],{"x":779,"y":897,"style":846},"T-103",[53,904],{"x":785,"y":894,"width":786,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,906,907],{"x":789,"y":897,"style":846},"2026-09-22 14:00",[53,909],{"x":793,"y":894,"width":794,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,911,912],{"x":797,"y":897,"style":846},"2026-09-25 09:45",[53,914],{"x":801,"y":894,"width":802,"height":810,"rx":56,"fill":860,"stroke":861,"style":862},[76,916,917],{"x":805,"y":897,"style":865},"67:45",[53,919],{"x":770,"y":920,"width":96,"height":810,"rx":56,"fill":93,"stroke":66,"style":67},"184.0",[76,922,924],{"x":813,"y":923,"style":781},"203.0","5",[53,926],{"x":775,"y":920,"width":776,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,928,929],{"x":779,"y":923,"style":846},"T-104",[53,931],{"x":785,"y":920,"width":786,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,933,934],{"x":789,"y":923,"style":846},"2026-09-23 10:30",[53,936],{"x":793,"y":920,"width":794,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,938,939],{"x":797,"y":923,"style":846},"2026-09-23 11:10",[53,941],{"x":801,"y":920,"width":802,"height":810,"rx":56,"fill":59,"stroke":66,"style":67},[76,943,944],{"x":805,"y":923,"style":846},"0:40",[183,946,948],{"id":947},"step-3-show-decimal-hours-when-people-calculate-with-them","Step 3 — Show decimal hours when people calculate with them",[10,950,951,952,955],{},"Hours worked are often multiplied by a rate, and ",[14,953,954],{},"26:30 × 45"," means nothing to most readers. Decimal hours — 26.5 — are easier to use. Because Excel stores durations in days, decimal hours are the duration times 24. In pandas, convert the timedelta directly:",[188,957,959],{"className":237,"code":958,"language":239,"meta":193,"style":193},"tickets[\"Hours\"] = (tickets[\"Open for\"].dt.total_seconds() \u002F 3600).round(2)\nprint(tickets[[\"Ticket\", \"Open for\", \"Hours\"]].to_string(index=False))\n",[14,960,961,992],{"__ignoreMap":193},[197,962,963,965,968,970,972,975,977,980,982,985,988,990],{"class":199,"line":200},[197,964,302],{"class":250},[197,966,967],{"class":207},"\"Hours\"",[197,969,308],{"class":250},[197,971,287],{"class":246},[197,973,974],{"class":250}," (tickets[",[197,976,305],{"class":207},[197,978,979],{"class":250},"].dt.total_seconds() ",[197,981,638],{"class":246},[197,983,984],{"class":335}," 3600",[197,986,987],{"class":250},").round(",[197,989,461],{"class":335},[197,991,296],{"class":250},[197,993,994,996,998,1000,1002,1004,1006,1008,1010,1012,1014,1016],{"class":199,"line":260},[197,995,336],{"class":335},[197,997,339],{"class":250},[197,999,342],{"class":207},[197,1001,345],{"class":250},[197,1003,305],{"class":207},[197,1005,345],{"class":250},[197,1007,967],{"class":207},[197,1009,350],{"class":250},[197,1011,354],{"class":353},[197,1013,287],{"class":246},[197,1015,359],{"class":335},[197,1017,362],{"class":250},[10,1019,1020,1021,1024,1025,1028],{},"T-101 is 26.5 hours and T-103 67.75. Write this column with a ",[14,1022,1023],{},"0.00"," format. When the workbook should keep both and stay live, write a formula instead — ",[14,1026,1027],{},"=D2*24"," — so the decimal column follows any edit to the duration.",[183,1030,1032],{"id":1031},"step-4-total-durations-with-formulas-that-keep-the-format","Step 4 — Total durations with formulas that keep the format",[10,1034,1035],{},"A SUM of durations is itself a duration, and needs the bracketed format too — the total of a week's hours is almost always over 24. Write the totals with openpyxl and check how they display:",[188,1037,1039],{"className":237,"code":1038,"language":239,"meta":193,"style":193},"from datetime import timedelta\nfrom openpyxl import Workbook\n\nwb = Workbook(); ws = wb.active\nws.append([\"Ticket\", \"Open for\", \"Hours\"])\nfor i, r in enumerate(tickets.itertuples(index=False), start=2):\n    ws.append([r.Ticket, r[3].to_pytimedelta(), f\"=B{i}*24\"])\nws.append([\"Total\", \"=SUM(B2:B5)\", \"=SUM(C2:C5)\"])\nfor row in ws.iter_rows(min_row=2, min_col=2, max_col=3):\n    row[0].number_format = \"[h]:mm\"\n    row[1].number_format = \"0.00\"\nwb.save(\"duration_totals.xlsx\")\nprint(sum(tickets[\"Open for\"], timedelta()), \"=\", tickets[\"Hours\"].sum(), \"hours\")\n",[14,1040,1041,1053,1064,1068,1083,1101,1132,1163,1182,1221,1236,1249,1260],{"__ignoreMap":193},[197,1042,1043,1045,1048,1050],{"class":199,"line":200},[197,1044,263],{"class":246},[197,1046,1047],{"class":250}," datetime ",[197,1049,247],{"class":246},[197,1051,1052],{"class":250}," timedelta\n",[197,1054,1055,1057,1059,1061],{"class":199,"line":260},[197,1056,263],{"class":246},[197,1058,266],{"class":250},[197,1060,247],{"class":246},[197,1062,1063],{"class":250}," Workbook\n",[197,1065,1066],{"class":199,"line":274},[197,1067,278],{"emptyLinePlaceholder":277},[197,1069,1070,1073,1075,1078,1080],{"class":199,"line":281},[197,1071,1072],{"class":250},"wb ",[197,1074,287],{"class":246},[197,1076,1077],{"class":250}," Workbook(); ws ",[197,1079,287],{"class":246},[197,1081,1082],{"class":250}," wb.active\n",[197,1084,1085,1088,1090,1092,1094,1096,1098],{"class":199,"line":299},[197,1086,1087],{"class":250},"ws.append([",[197,1089,342],{"class":207},[197,1091,345],{"class":250},[197,1093,305],{"class":207},[197,1095,345],{"class":250},[197,1097,967],{"class":207},[197,1099,1100],{"class":250},"])\n",[197,1102,1103,1105,1108,1110,1112,1115,1117,1119,1121,1124,1126,1128,1130],{"class":199,"line":332},[197,1104,424],{"class":246},[197,1106,1107],{"class":250}," i, r ",[197,1109,430],{"class":246},[197,1111,606],{"class":335},[197,1113,1114],{"class":250},"(tickets.itertuples(",[197,1116,354],{"class":353},[197,1118,287],{"class":246},[197,1120,359],{"class":335},[197,1122,1123],{"class":250},"), ",[197,1125,617],{"class":353},[197,1127,287],{"class":246},[197,1129,461],{"class":335},[197,1131,624],{"class":250},[197,1133,1134,1137,1139,1142,1145,1148,1152,1155,1158,1161],{"class":199,"line":365},[197,1135,1136],{"class":250},"    ws.append([r.Ticket, r[",[197,1138,632],{"class":335},[197,1140,1141],{"class":250},"].to_pytimedelta(), ",[197,1143,1144],{"class":246},"f",[197,1146,1147],{"class":207},"\"=B",[197,1149,1151],{"class":1150},"sSjpA","{",[197,1153,1154],{"class":250},"i",[197,1156,1157],{"class":1150},"}",[197,1159,1160],{"class":207},"*24\"",[197,1162,1100],{"class":250},[197,1164,1165,1167,1170,1172,1175,1177,1180],{"class":199,"line":370},[197,1166,1087],{"class":250},[197,1168,1169],{"class":207},"\"Total\"",[197,1171,345],{"class":250},[197,1173,1174],{"class":207},"\"=SUM(B2:B5)\"",[197,1176,345],{"class":250},[197,1178,1179],{"class":207},"\"=SUM(C2:C5)\"",[197,1181,1100],{"class":250},[197,1183,1184,1186,1189,1191,1194,1197,1199,1201,1203,1206,1208,1210,1212,1215,1217,1219],{"class":199,"line":389},[197,1185,424],{"class":246},[197,1187,1188],{"class":250}," row ",[197,1190,430],{"class":246},[197,1192,1193],{"class":250}," ws.iter_rows(",[197,1195,1196],{"class":353},"min_row",[197,1198,287],{"class":246},[197,1200,461],{"class":335},[197,1202,345],{"class":250},[197,1204,1205],{"class":353},"min_col",[197,1207,287],{"class":246},[197,1209,461],{"class":335},[197,1211,345],{"class":250},[197,1213,1214],{"class":353},"max_col",[197,1216,287],{"class":246},[197,1218,632],{"class":335},[197,1220,624],{"class":250},[197,1222,1223,1226,1228,1231,1233],{"class":199,"line":405},[197,1224,1225],{"class":250},"    row[",[197,1227,56],{"class":335},[197,1229,1230],{"class":250},"].number_format ",[197,1232,287],{"class":246},[197,1234,1235],{"class":207}," \"[h]:mm\"\n",[197,1237,1238,1240,1242,1244,1246],{"class":199,"line":693},[197,1239,1225],{"class":250},[197,1241,20],{"class":335},[197,1243,1230],{"class":250},[197,1245,287],{"class":246},[197,1247,1248],{"class":207}," \"0.00\"\n",[197,1250,1252,1255,1258],{"class":199,"line":1251},12,[197,1253,1254],{"class":250},"wb.save(",[197,1256,1257],{"class":207},"\"duration_totals.xlsx\"",[197,1259,296],{"class":250},[197,1261,1263,1265,1268,1271,1273,1275,1278,1281,1284,1286,1289,1292],{"class":199,"line":1262},13,[197,1264,336],{"class":335},[197,1266,1267],{"class":250},"(",[197,1269,1270],{"class":335},"sum",[197,1272,609],{"class":250},[197,1274,305],{"class":207},[197,1276,1277],{"class":250},"], timedelta()), ",[197,1279,1280],{"class":207},"\"=\"",[197,1282,1283],{"class":250},", tickets[",[197,1285,967],{"class":207},[197,1287,1288],{"class":250},"].sum(), ",[197,1290,1291],{"class":207},"\"hours\"",[197,1293,296],{"class":250},[10,1295,1296,1297,1299,1300,1302],{},"openpyxl writes a Python ",[14,1298,731],{}," as a fraction of a day, like pandas, and the format makes it display as elapsed time. Opened in Excel, the total shows 102:40 and 102.67 hours. Formatted ",[14,1301,117],{}," instead, the same total would show 6:40 — the classic \"my weekly hours don't add up\" report.",[34,1304,43,1309,43,1312,43,1315,43,1318,43,1325,43,1336,43,1340,43,1344,43,1349,43,1356,43,1360,43,1365,43,1369,43,1374,43,1377,43,1384,43,1388,43,1392,43,1396,43,1401,43,1404,43,1409,43,1412],{"viewBox":1305,"role":37,"ariaLabelledBy":1306,"xmlns":41,"style":42},"4 0 752 207",[1307,1308],"dur-total-t","dur-total-d",[45,1310,1311],{"id":1307},"The same weekly total with and without brackets",[49,1313,1314],{"id":1308},"Two panels showing the total of the four ticket durations, 102 hours 40 minutes. Formatted h:mm, the total shows 6:40, the hours left over after four whole days. Formatted [h]:mm, it shows 102:40, the real total.",[53,1316],{"x":55,"y":56,"width":57,"height":1317,"fill":59},"207",[53,1319],{"x":62,"y":63,"width":1320,"height":897,"rx":1321,"fill":1322,"stroke":1323,"style":1324},"351.0","14","#fee8f2","var(--accent,#f43f8f)","stroke-width:2px",[1326,1327,1329],"g",{"transform":1328},"translate(182.5,29.0) scale(1.083)",[1330,1331],"path",{"d":1332,"fill":1333,"stroke":1334,"style":1335},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","none","var(--accent-ink,#be185d)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[76,1337,117],{"x":1338,"y":97,"style":1339},"195.5","font-size:14px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle",[199,1341],{"x1":1342,"y1":814,"x2":1343,"y2":814,"stroke":1323,"style":67},"36.0","355.0",[76,1345,1348],{"x":1346,"y":122,"style":1347},"60.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","total shows 6:40",[1326,1350,1352],{"transform":1351},"translate(38.5,99.5) scale(0.625)",[1330,1353],{"d":1354,"fill":1333,"stroke":1323,"style":1355},"M6 6l12 12 M18 6L6 18","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[76,1357,1359],{"x":1346,"y":1358,"style":1347},"137.0","four whole days vanish",[1326,1361,1363],{"transform":1362},"translate(38.5,125.5) scale(0.625)",[1330,1364],{"d":1354,"fill":1333,"stroke":1323,"style":1355},[76,1366,1368],{"x":1346,"y":1367,"style":1347},"163.0","nobody notices until payroll",[1326,1370,1372],{"transform":1371},"translate(38.5,151.5) scale(0.625)",[1330,1373],{"d":1354,"fill":1333,"stroke":1323,"style":1355},[53,1375],{"x":1376,"y":63,"width":1320,"height":897,"rx":1321,"fill":860,"stroke":861,"style":1324},"389.0",[1326,1378,1380],{"transform":1379},"translate(551.5,29.0) scale(1.083)",[1330,1381],{"d":1382,"fill":1333,"stroke":1383,"style":1335},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[76,1385,134],{"x":1386,"y":97,"style":1387},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[199,1389],{"x1":1390,"y1":814,"x2":1391,"y2":814,"stroke":861,"style":67},"405.0","724.0",[76,1393,1395],{"x":1394,"y":122,"style":1347},"429.0","total shows 102:40",[1326,1397,1399],{"transform":1398},"translate(407.5,99.5) scale(0.625)",[1330,1400],{"d":1382,"fill":1333,"stroke":861,"style":1355},[76,1402,1403],{"x":1394,"y":1358,"style":1347},"matches 102.67 decimal hours",[1326,1405,1407],{"transform":1406},"translate(407.5,125.5) scale(0.625)",[1330,1408],{"d":1382,"fill":1333,"stroke":861,"style":1355},[76,1410,1411],{"x":1394,"y":1367,"style":1347},"same stored value",[1326,1413,1415],{"transform":1414},"translate(407.5,151.5) scale(0.625)",[1330,1416],{"d":1382,"fill":1333,"stroke":861,"style":1355},[10,1418,1419,1420,1423,1424,1426],{},"When the sheet keeps the start and end times, the duration can also be a live formula: ",[14,1421,1422],{},"=C2-B2"," subtracts two datetimes and gives a fraction of a day, so the same ",[14,1425,134],{}," format applies. That keeps the duration correct if someone corrects a timestamp by hand, at the cost of the cached-value issue every Python-written formula has — programs reading the file see no result until a spreadsheet application has recalculated it.",[183,1428,1430],{"id":1429},"step-5-read-durations-back-correctly","Step 5 — Read durations back correctly",[10,1432,1433,1434,1436,1437,1439,1440,1443],{},"How openpyxl and pandas read a duration depends on its format. With a bracketed format it comes back as a ",[14,1435,731],{},"; with a time-of-day format such as ",[14,1438,117],{}," it comes back as a ",[23,1441,1442],{},"datetime in 1900",", and with a general format as a plain number of days:",[188,1445,1447],{"className":237,"code":1446,"language":239,"meta":193,"style":193},"from datetime import timedelta\nwb = Workbook(); ws = wb.active\nfor fmt in [\"[h]:mm\", \"h:mm\", \"General\"]:\n    ws.append([fmt, timedelta(hours=26, minutes=30)])\n    ws.cell(ws.max_row, 2).number_format = fmt\nwb.save(\"read_back.xlsx\")\nfor fmt, value in load_workbook(\"read_back.xlsx\").active.iter_rows(values_only=True):\n    print(f\"{fmt:8} {value!r}\")\n",[14,1448,1449,1459,1471,1498,1524,1539,1548,1574],{"__ignoreMap":193},[197,1450,1451,1453,1455,1457],{"class":199,"line":200},[197,1452,263],{"class":246},[197,1454,1047],{"class":250},[197,1456,247],{"class":246},[197,1458,1052],{"class":250},[197,1460,1461,1463,1465,1467,1469],{"class":199,"line":260},[197,1462,1072],{"class":250},[197,1464,287],{"class":246},[197,1466,1077],{"class":250},[197,1468,287],{"class":246},[197,1470,1082],{"class":250},[197,1472,1473,1475,1478,1480,1483,1485,1487,1490,1492,1495],{"class":199,"line":274},[197,1474,424],{"class":246},[197,1476,1477],{"class":250}," fmt ",[197,1479,430],{"class":246},[197,1481,1482],{"class":250}," [",[197,1484,590],{"class":207},[197,1486,345],{"class":250},[197,1488,1489],{"class":207},"\"h:mm\"",[197,1491,345],{"class":250},[197,1493,1494],{"class":207},"\"General\"",[197,1496,1497],{"class":250},"]:\n",[197,1499,1500,1503,1506,1508,1511,1513,1516,1518,1521],{"class":199,"line":281},[197,1501,1502],{"class":250},"    ws.append([fmt, timedelta(",[197,1504,1505],{"class":353},"hours",[197,1507,287],{"class":246},[197,1509,1510],{"class":335},"26",[197,1512,345],{"class":250},[197,1514,1515],{"class":353},"minutes",[197,1517,287],{"class":246},[197,1519,1520],{"class":335},"30",[197,1522,1523],{"class":250},")])\n",[197,1525,1526,1529,1531,1534,1536],{"class":199,"line":299},[197,1527,1528],{"class":250},"    ws.cell(ws.max_row, ",[197,1530,461],{"class":335},[197,1532,1533],{"class":250},").number_format ",[197,1535,287],{"class":246},[197,1537,1538],{"class":250}," fmt\n",[197,1540,1541,1543,1546],{"class":199,"line":332},[197,1542,1254],{"class":250},[197,1544,1545],{"class":207},"\"read_back.xlsx\"",[197,1547,296],{"class":250},[197,1549,1550,1552,1555,1557,1559,1561,1564,1567,1569,1572],{"class":199,"line":365},[197,1551,424],{"class":246},[197,1553,1554],{"class":250}," fmt, value ",[197,1556,430],{"class":246},[197,1558,397],{"class":250},[197,1560,1545],{"class":207},[197,1562,1563],{"class":250},").active.iter_rows(",[197,1565,1566],{"class":353},"values_only",[197,1568,287],{"class":246},[197,1570,1571],{"class":335},"True",[197,1573,624],{"class":250},[197,1575,1576,1579,1581,1583,1586,1588,1591,1594,1596,1599,1602,1605,1607,1609],{"class":199,"line":370},[197,1577,1578],{"class":335},"    print",[197,1580,1267],{"class":250},[197,1582,1144],{"class":246},[197,1584,1585],{"class":207},"\"",[197,1587,1151],{"class":1150},[197,1589,1590],{"class":250},"fmt",[197,1592,1593],{"class":246},":8",[197,1595,1157],{"class":1150},[197,1597,1598],{"class":1150}," {",[197,1600,1601],{"class":250},"value",[197,1603,1604],{"class":246},"!r",[197,1606,1157],{"class":1150},[197,1608,1585],{"class":207},[197,1610,296],{"class":250},[10,1612,1613,1614,1616,1617,1620,1621,1624],{},"The ",[14,1615,117],{}," cell reads back as 1 January 1900, 02:30 — the duration has been interpreted as a date and lost its day. When reading a sheet somebody else formatted, convert numbers of days with ",[14,1618,1619],{},"pd.to_timedelta(values, unit=\"D\")",", and for datetimes near 1900, subtract ",[14,1622,1623],{},"datetime(1899, 12, 31)"," — the day before Excel's day 1 — to recover the duration, checking a known value first.",[183,1626,1628],{"id":1627},"negative-durations","Negative durations",[10,1630,1631,1632,1635,1636,1639,1640,1642],{},"A negative duration — a job that finished before its target, a variance against planned hours — cannot be displayed as time in Excel's default 1900 date system: the cell fills with ",[14,1633,1634],{},"#####",". Display such values as decimal hours, which can be negative, or as text built in Python, such as ",[14,1637,1638],{},"-2:15",". LibreOffice happily shows negative times, so a file that looks fine there can still show ",[14,1641,1634],{}," in Excel; test in Excel when negatives are possible.",[183,1644,1646],{"id":1645},"common-pitfalls","Common pitfalls",[1648,1649,1650,1662,1671,1679,1685,1691,1700],"ul",{},[1651,1652,1653,1659,1660,725],"li",{},[23,1654,1655,1656,1658],{},"Leaving pandas' ",[14,1657,56],{}," format."," Timedeltas show as whole days; set ",[14,1661,134],{},[1651,1663,1664,1670],{},[23,1665,1666,1667,1669],{},"Relying on ",[14,1668,489],{},"."," pandas' own cell formats override column formats; format the cells.",[1651,1672,1673,1678],{},[23,1674,1675,1677],{},[14,1676,117],{}," for durations."," It wraps at 24 hours; always bracket the hours.",[1651,1680,1681,1684],{},[23,1682,1683],{},"Forgetting the total row."," SUM results need the bracketed format too.",[1651,1686,1687,1690],{},[23,1688,1689],{},"Multiplying durations by rates."," Convert to decimal hours first (×24).",[1651,1692,1693,1696,1697,1699],{},[23,1694,1695],{},"Negative times."," They show as ",[14,1698,1634],{}," in Excel; use decimal hours.",[1651,1701,1702,1708],{},[23,1703,1704,1705,1707],{},"Reading ",[14,1706,117],{}," cells back."," They arrive as 1900 datetimes; convert them explicitly.",[183,1710,1712],{"id":1711},"performance-notes","Performance notes",[10,1714,1715,1716,1719],{},"Formats are applied per column with xlsxwriter or per cell with openpyxl; either is fast. Computing decimal hours with ",[14,1717,1718],{},"dt.total_seconds()"," is vectorised, so there is no reason to loop over rows.",[183,1721,1723],{"id":1722},"conclusion","Conclusion",[10,1725,1726,1727,1729,1730,1732,1733,1735,1736,1738],{},"Excel stores durations as fractions of a day, and the format decides how they appear. After writing a pandas timedelta column, replace its ",[14,1728,56],{}," format with ",[14,1731,134],{}," or ",[14,1734,735],{},", use ",[14,1737,152],{}," for total minutes, and give totals the same bracketed format. Show decimal hours — duration × 24 — when people will calculate with the figures or when values can be negative, and read cells back with their format in mind.",[183,1740,1742],{"id":1741},"frequently-asked-questions","Frequently asked questions",[10,1744,1745,1748,1749,1752,1753,1755],{},[23,1746,1747],{},"How do I show more than 24 hours in an Excel cell?","\nUse a number format with the hours in square brackets, such as ",[197,1750,1751],{},"h",":mm or ",[197,1754,1751],{},":mm:ss. The brackets tell Excel to show total elapsed hours; without them, h:mm shows only the hours past the last whole day, so 26:30 displays as 2:30.",[10,1757,1758,1761,1762,1764],{},[23,1759,1760],{},"Why does my pandas timedelta column show 1 or 0 in Excel?","\npandas writes a timedelta as a number of days — 26.5 hours becomes 1.104 — and gives it the format \"0\", which rounds to whole days. Set the column's number format to ",[197,1763,1751],{},":mm after writing.",[10,1766,1767,1770],{},[23,1768,1769],{},"How do I show a duration as decimal hours in Excel?","\nMultiply the duration by 24, because Excel stores durations as fractions of a day. Write the decimal hours from pandas with total_seconds() \u002F 3600, or use a formula such as =B2*24 with a number format like 0.00.",[10,1772,1773,1776],{},[23,1774,1775],{},"Why does a negative duration show as","\nExcel's default 1900 date system cannot display negative dates or times, so a negative duration formatted as time appears as #####. Show negative differences as decimal hours, or as text built in Python.",[183,1778,1780],{"id":1779},"related","Related",[1648,1782,1783,1789,1796,1803],{},[1651,1784,1785,1786,1788],{},"Up one level: ",[28,1787,31],{"href":30}," — number formats in general.",[1651,1790,1791,1795],{},[28,1792,1794],{"href":1793},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-dates-in-excel-cells-with-python\u002F","Format Dates in Excel Cells with Python"," — dates rather than durations.",[1651,1797,1798,1802],{},[28,1799,1801],{"href":1800},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fconsolidate-timesheets-from-many-workbooks\u002F","Consolidate Timesheets from Many Workbooks"," — hours worked in bulk.",[1651,1804,1805,1809],{},[28,1806,1808],{"href":1807},"\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"," — where negative times are allowed.",[1811,1812,1813],"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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":193,"searchDepth":260,"depth":260,"links":1815},[1816,1817,1818,1819,1820,1821,1822,1823,1824,1825,1826,1827],{"id":185,"depth":260,"text":186},{"id":226,"depth":260,"text":227},{"id":468,"depth":260,"text":469},{"id":947,"depth":260,"text":948},{"id":1031,"depth":260,"text":1032},{"id":1429,"depth":260,"text":1430},{"id":1627,"depth":260,"text":1628},{"id":1645,"depth":260,"text":1646},{"id":1711,"depth":260,"text":1712},{"id":1722,"depth":260,"text":1723},{"id":1741,"depth":260,"text":1742},{"id":1779,"depth":260,"text":1780},"2026-09-25","Write time spent, machine run time or hours worked to Excel so 26 hours 30 minutes shows as 26:30, not 2:30 or 1. Understand how Excel stores durations, fix pandas timedelta columns that arrive formatted as whole days, use [h]:mm and [mm] formats, show decimal hours, total durations with formulas, and avoid negative-time errors.","md",[1832,1834,1836,1838],{"q":1747,"a":1833},"Use a number format with the hours in square brackets, such as [h]:mm or [h]:mm:ss. The brackets tell Excel to show total elapsed hours; without them, h:mm shows only the hours past the last whole day, so 26:30 displays as 2:30.",{"q":1760,"a":1835},"pandas writes a timedelta as a number of days — 26.5 hours becomes 1.104 — and gives it the format \"0\", which rounds to whole days. Set the column's number format to [h]:mm after writing.",{"q":1769,"a":1837},"Multiply the duration by 24, because Excel stores durations as fractions of a day. Write the decimal hours from pandas with total_seconds() \u002F 3600, or use a formula such as =B2*24 with a number format like 0.00.",{"q":1775,"a":1839},"Excel's default 1900 date system cannot display negative dates or times, so a negative duration formatted as time appears as #####. Show negative differences as decimal hours, or as text built in Python.","format durations over 24 hours in excel",null,{"breadcrumb":1843},[1844,1846,1849],{"name":1845,"item":638},"Home",{"name":1847,"item":1848},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":31,"item":30},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-durations-over-24-hours-in-excel",{"title":1852,"description":1853},"Excel Durations Over 24 Hours from Python: [h]:mm Format","Format durations in Excel from pandas and openpyxl: timedelta written as fractions of a day with format 0, [h]:mm:ss versus h:mm, elapsed minutes with [mm], decimal hours, SUM of durations, reading durations back and the negative time ##### problem.","format-durations-over-24-hours-in-excel","formatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-durations-over-24-hours-in-excel\u002Findex",[1857,1858],"number-formats","dates-and-times","how-to","-jBO1608HulesMpm7X4F7KL9_kwhTDEJrWc1r8pCS18",[1862,1865],{"title":1794,"path":1863,"stem":1864,"children":-1},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-dates-in-excel-cells-with-python","formatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-dates-in-excel-cells-with-python\u002Findex",{"title":1866,"path":1867,"stem":1868,"children":-1},"Format Excel Cells as Currency with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-excel-cells-as-currency-with-python","formatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-excel-cells-as-currency-with-python\u002Findex",[1870,1872],{"path":1871,"title":31},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel",{"path":1873,"title":1847},"\u002Fformatting-and-charting-excel-reports-with-python",[],[1876,1879,1882,1885,1888,1891],{"path":1877,"title":1878,"kind":1859},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-currency-strings-to-numbers-with-pandas","Convert Currency Strings Like \"$1,234.56\" and \"(500)\" to Numbers with pandas",{"path":1880,"title":1881,"kind":1859},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-percentage-strings-to-numbers-with-pandas","Convert Percentage Strings Like \"12.5%\" to Numbers with pandas",{"path":1883,"title":1884,"kind":1859},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fexcel-text-function-equivalent-in-pandas","Excel TEXT Function Equivalent in pandas",{"path":1886,"title":1887,"kind":1859},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmatch-excel-rounding-in-pandas","Match Excel Rounding in pandas",{"path":1889,"title":1890,"kind":1859},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fconvert-excel-time-values-and-durations","Convert Excel Time Values and Durations in Python",{"path":1892,"title":1893,"kind":1859},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-variance-arrows-and-trend-indicators-to-a-dashboard","Add Variance Arrows and Trend Indicators to a Dashboard",1790972106962]