From c12b445f4636d18e393bc1c261f9fef47e4d7b6a Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 16 May 2025 22:26:09 +0200 Subject: [PATCH 1/2] fix rounding issues with microseconds calculation Signed-off-by: Arthur Schiwon --- src/PhpSpreadsheet/Shared/Date.php | 28 +++++++++++++----- .../Reader/Xlsx/ExplicitDateTest.php | 7 ++++- tests/data/Reader/XLSX/explicitdate.xlsx | Bin 4955 -> 11168 bytes 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/PhpSpreadsheet/Shared/Date.php b/src/PhpSpreadsheet/Shared/Date.php index e8f23164f3..04896edf5e 100644 --- a/src/PhpSpreadsheet/Shared/Date.php +++ b/src/PhpSpreadsheet/Shared/Date.php @@ -223,13 +223,27 @@ public static function excelToDateTimeObject(float|int $excelTimestamp, null|Dat } $days = floor($excelTimestamp); $partDay = $excelTimestamp - $days; - $hms = 86400 * $partDay; - $microseconds = (int) round(fmod($hms, 1) * 1000000); - $hms = (int) floor($hms); - $hours = intdiv($hms, 3600); - $hms -= $hours * 3600; - $minutes = intdiv($hms, 60); - $seconds = $hms % 60; + $hoursInMs = 86400 * $partDay; + $wholeSeconds = (int) floor($hoursInMs); + + // flooring here might lose data due to precision issues, hence round it + $microseconds = (int) round(($hoursInMs - $wholeSeconds) * 1_000_000); + $microseconds = (int) round($microseconds, -2); + + // rounding may lead to edge cases + if ($microseconds === 1_000_000) { + $microseconds = 0; + ++$wholeSeconds; + } + if ($wholeSeconds >= 86400) { + $wholeSeconds = 0; + ++$days; + } + + $hours = intdiv($wholeSeconds, 3600); + $remainingSeconds = $wholeSeconds % 3600; + $minutes = intdiv($remainingSeconds, 60); + $seconds = $remainingSeconds % 60; if ($days >= 0) { $days = '+' . $days; diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/ExplicitDateTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/ExplicitDateTest.php index adad99aff9..9e2e8c0c03 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/ExplicitDateTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/ExplicitDateTest.php @@ -40,11 +40,16 @@ public static function testExplicitDate(): void $formatted = $sheet->getCell('B3')->getFormattedValue(); self::assertEquals(44561, $value); self::assertSame('2021-12-31', $formatted); - // Time only + // Time only, with seconds $value = $sheet->getCell('C3')->getValue(); $formatted = $sheet->getCell('C3')->getFormattedValue(); self::assertEqualsWithDelta(0.98948, $value, 0.00001); self::assertSame('23:44:52', $formatted); + // Time only, full minute + $value = $sheet->getCell('F3')->getValue(); + $formatted = $sheet->getCell('F3')->getFormattedValue(); + self::assertEqualsWithDelta(0.5673611, $value, 0.00001); + self::assertSame('13:37', $formatted); $spreadsheet->disconnectWorksheets(); } diff --git a/tests/data/Reader/XLSX/explicitdate.xlsx b/tests/data/Reader/XLSX/explicitdate.xlsx index c4017b87806695c9924de847e77019f5455f38f1..874b4b8f048922ebb5192cd105cc0d991243d5f4 100644 GIT binary patch literal 11168 zcmbVy1yq&Y)-FhQOG>A7cjpE+u|X;61~=W^B_Q1)4bt66BaO6_gmibR7dZd;MbA0k zz2hzh@xoXS?|jx=Yd&+%r6dOhjST@04-Y||U7-o_WDwl{?DCdb*UtE@J+rg5ouz@b zwI!1a_$|}JZ%Si^ExK6H0}fpgt1o4pK(cfR_H@2)eqUkr{2VDVteLZ#noo&~-2k{)^q3|O)tOn_3Wi-OSh;{lGuaa5_RfYK;tALxfA4|RRYO#8t_HY zwEO!ddcsP~%8c8#VcEil{apfVNks6=NTZZ=YfC7BdMb$5<0Q}>-Q^8RC6Y<0zdFVw z$=K9)_mq4)pLhFwm)Qf;Sqvt;6O(bVAUtu=_Fl*N64^ME^vNT~)4C+oaV9-&kM&kE%atg3N92= zs0}hjk`z(Nw?t#rVtDV$Lc~`l`!Nc243))yHb*XuG)<^jeK;`gii>PcL*!2;m5l5R zQ^nM0$HiuGz8-^gqMZawt>MB4ofvv5b(6lO#iwoCN+W$8;1uy`f5W0&l$<8=2Pu3f zEbhqF*KGcB*_f0A?;=3hq)0Id4b{er@Q~Tj_pu$6849qi9WRM4I&IhwJP9IuTa98V zIT~DEjK=6v(g*Vu=q{Ypn{5%g`WU!pO=VEj@tTsVMaC+c?|;hKaE?)Ld-mWrzT@w( zZy_NdtfBtNZ?OL5Hx6dTU}NUTudEL{Y*?+sZl3hPckm7nViHjCT;TE5LQFSuQjSkjoHzh(`H)J9}P(!h~tvJ zITZi#*}PuC5w>$(u=4npo`K67xtW=2a4$jbDBv0q_5$k54Z%4?SwTamms7(FQtxo; zhKdkeHP=yG-Zr{OkgVD4sRjgf_nyHHKhrqNyuBOgOuAhxDZ(o2(po0)=pDmgOb@&; z;n6X4ChRGmG=+!2a;*7Xa&q$fqkf-E#o9K_p*7Yezn3H3YaLj7iuu9VD8C%gGYDLo zgpVKtTT<7ik$%hxemuUEvO;brE^Ewvwgr|$l1DyxK9g*O=a*m(0^1Bg;IY7)>}oWP|3ox zFc0S_)Xb~V$G3HTw9~3Cs`1wBZpWpv#l5?RH#K)57CQq$Xktr^sxfqp4e`eKS8u}Q zh({Z*ZwNE`9EZZNc*zxoUFKP&DZF#;AK%(Sy<-4ZmWj+H@%K@#926al?`3`6%y&m` zx@)oFER-8h|3YbzLS*{pc?l6c+FkUVk(^xdzP5c{)J2D1pPE`49nmLR?<*FO3KOgkM5f$rPF9kf& zVy#Sg+-QHsXkU`4 zh0il*2#!Uxxy%<=?$BRV%N_U-RXXZ}-hV)n$Z~^J3?T^f1-^&D!8Eg%Mgd@~gy<*Q zM$o!Ob;GK3!!I@*RkRz+ygW7I`Iudi^Nnt`p01qI_OcqVMS{|TSK=5jKG#I%;7dCc zJ*|GysNrMRHv7Go$I2-(hwj8Hwl&U-9WR3$qDOB>2tGL}UGo)j`%7p{zXU%Dx5QDs zu)@v?R8P#&7DOqso+7U97zTEJi)i-*3CoV5+|JqY$i|GM6W08*X{+1A?Th)_Acyp} zrTiB4*}gF|=7PwgHy;EFQX4BMHOp-9uhesE#MI&#c)R#Rxj|+`yQ`(I0&>t_g_2^$ zwBT8|LnW?jb+&!y2nB!k)P?8J*|ip<=riCDl#64Qui%P4%B6i6q|3#% zUW+Uuc^48ZYb1pU1vYZqb?Bq>-ktbRte7{Vos+VXuQFwLho7}{~N&Zt@PKR<4{)_mO71pBUpQfxj)t=779y#yPn(B^s&uUNdGrZ$|F1g*D zEM(R1TZ0KN>l`Z?x4^pw^SSk<@4ypFb*ObP-qW8h>RgRg#(6AC2;*4aO85%aq<55G z1f+7MxvOJ*L~y1*AP0VScb5suSn&aXlIdug&s<&SUFH!u%DC%EUd73MzR+~NPz{=xe#R_x7;jUDWn zAHO|Dp4Qhu>ou+CuvdMjAS(Pj&J@$>lbokj4YY>+&#)qZQ`I9 zWB%X|!JK+NL?bP(?BXM|w^wgi4@Gjb{YHP7Vc!t1G`7bbZvK29d>&kMNEg8)C2zxa zsT*=4%;eP{DJE1DgT4<5p=SSSvZ)Q_S$txym<6Rp=SnVml^8*t9Z8`kW* zjc6e4<%`Zz540d!#FW_hTH`y!P3GEknc#x)(yMz6G8)Uzo9=8o}|-ZaJ%Gs~7A zy-bnIlE{*7EY!4^Q6BO8s*yIz_S0MD3!a#ul1fnnRPo8Bga+C5>e}QyI#(xcoH|l9M-VXtq z?CptRG3h*OUD)!1yaVjGmy4;+6S2yGu!H=6a9euOac;LrGiO(tn=1|v&LJEdNx*V* zNLDE)olSpk=}ZVbB#R;rF|1Yyb~oKA@&=18my{<$5|=fPm+VLQg6flf7z98igN?#3 zsLbuy5A30FuI1(1eA#49rOcHbF%xUm3V}4{#bj^h4*PvsL@LgL*p4={QAW0E_Fl{6 zNK*HF@gI;#S1Fim-olH=Htcji=I!9#QfE>E1X&m#$X-I&tqrMaXrQockP0%4^B|#$ za0Cl0?YQ;`$E$ht(C?UQM0_jfT(yr_ua!D+GV51FVXUml)*qNSrX(27uXf-gln{?c$e~@(t!-KqI$R5>gSlHqql4e0sHOt)Zmuyq2oLmeMm}%LmK` z*hQZ9uY`Z=b)79a_$egK$F0kM+K+#H{GB>k;>Th3+m7X_$r!uVqW!2dS+iW0S%nB^ zZ;PfxOtYVh{!D?n<$jjk2`sLm0#idCLS`wtB+yoV9+Gy79jw)*yhO=T zj~QU=q@dz`1_-;{`icBxsR58h8kdVCwiBcjtsdcdtz?jCtV?$1UV^9`7PgJr8;0 zj(1#4PKh)F5&xFxiofY6PNkwa?OFlj^>xCh!+y>vB|qrIEj=skEvHFeVH@m$zKZ=z z9M&rhs822<9SsNNiDQHzA_6H|(kB*+2ycwAWrnmZ^ zHgT$S!ou{o7uJoNuu@%~4&Egh5$6>dEfOTfpt!JM!R1xGsYSKYw(Q7)&4eLaYphbK zlBh`ol_!kJ2*V>|g)x^}QU*;j7RK)YnS4;+twwSARK?JmV-JBrl>Gg&RDfnXM`k5D zL{1L9F%>Sov)Fgb1iv-|+fD+%nk}eb#WpchU(D@F>@``9MYvgPWq#>P-ECg7C<7A3 zKO-N|rg*~}1^?q93HE<@y zpy%`w)Ex=koy+yWJuKbFBy1;-`=vYn$21@P>C)-j*gULUdQ^v1Ckt}Wp?lcN)j4XT z95GsyIgb5+niqUETJcm&Mi#M!w#u%zFk?Qa9#!62nftmW1#N?bomaCgj;E}l)yR=b8W0(2Hz;sxlrAL?r^ z3Y+FGNHt>016@$Fsx|fPB&_x>Ar3cj;+PS21fF};Ra&K`lpBAIQ~bIp5$kmY8R5|J zb-9@-$Oq!sl!p9o?`cW9C-1-4M;`Lr-#+hX z?_dpn;4Uv3XxYVrEOK~@(|b7?HeUk-fKCtukzog&xI<%ME%;}xrr9pOZC2G92W!{m zM4g>cO3ugfd4vRBlKf<3%jKbPuh>DxS=Fx6cH`Gj-qk670e- zJ5r5VRlPbn?vh3gi0r{<_%Ti>(ec`sh!0U$VLTzaMe;6#HLkKwx`Y%N!)u*!7k|`h z8_nj&<6*<$>WP(oZ$-oX=Rfb6Kka0DOm?-jtk)WFybjCJCrve+zoJezf93%YIw6VDNStcHL>lFOW}Y z-}?5tiXpJ@XFcaWF7|l52i6)x0L^XAB{#jsygA>US=|BN>AYoEODlUB;w$X|34HCgj*no9+arr)R z0#P~E*1*j=E2g(I6~M(jBS=&zBt{R+=TW~378r%)SL5WA4cBS8CjEDE8j2>(MFNpF zZs;3p=j@0=0MDARbTnLXe`JWZeej2fmlOT~FSrgB?5l*nr=L5i)$dmTC+l%0Gl4rB z6>LZEV~qu96>r4HokpR&ZjgB;w64fe-myR^=ClV&JUT zBw8I@uPH{szrM(dh;fnCUZ?~N=<_;?${g{q2%oG%AD4=x7k^V4=vbRkl9*73)@8nY zU(8CC>TFA}q=aS+nR|PPkLV$D{@rGv&Eq`y^m^S0ndTM>@Lc;EBda;S#Rs$w2Wba2 z_tLf76yUSeMie}I6EIeow;^sHD@Dtb1-FlJO#ow^XR_Ygc69T4TZYa*1MS@zbz?|q zCstK4Giw1b%`xtRe4Pj1{Z$UN5pP5k@enk~t%<}h$$z~lUxqv1)&bhqhcU@7M)~eV zM{7tw%Hr{*UX@w~A?&a@GAb$Xs2Xw~tz*>d^vjqbFsA^G!#BO{#5*DlBjQz8H2Y@^ z>1Lj=u6lnt5>DwrJDNx#&w&GRT1&#S?wk1-G{CvKcwX?cX+Fh8@sMAGOkm{z*02gG zj15|_yVoJ!8AR=zb!kMU1nDpkiMeR^Wm#pn4Z-*ut1c~Rq~(W6}6zDd$k^BmX8;La-&ATEH4 za9Rt(A`B5NLHNQ~_={}EyaU?`Gbe6ZxWZ8MbL8-qo7b4RadbKZpprtR|sHp;9<74my zHc_6zWt+fz@L1e%e)OnfDyyQOcRuoUqyUI)!e|NUEQXyUZo4mU%eW4bbC38uv)kxd z2a2P+ET=YjjXIKs=S)XZ1((F zrtKFa<|5uchq9==HM#_ouh~7?v`9$X1Z-d+#rI3dluH!j`72Igw36>T*FZRD#C8y3hUKIr52pbTM$e-WpjyoU&5?~tbaXxrTgu3XC#Tgy_awl*EW8!5|mqZr)1LHmI(`Z;}vJ5oubWXnEW z`Bj`hX*x=jG>XXWx(AMYwepA;(6U3?a%qKJUMA8LYXcZ_?Bq z;K>%8^qs_!-NnsiD+ll%kqtJE`eRLS#i~QSVxAEW7^%9$ykbxQoaRNa%Xl)CIYjFA zo#6`_$@^p%Ajb^z&LioZV;5NCYJz;?btPSKOXAwDOnqKO<4Lkg8y8R!s|Ht}Bs(mR zn(S6*JIA&cdvu`2UOgulM$%4^t)yW+dp5eN`jNqiT`31sHm02!GZC6}Y>$d%Lwq~& z1Cxx#GZ+X4=o&Y~@1izx*rMXEeTQLz^e=Ch2nOnV_Tw|!sGgTc7{nw-DkfUF-Pfn6 zcCP6*FcPBEu`nr?QtDH(4~F~swE^;t8oax5(li^N8X{;5w+{xn;Z5P&4a`369@GuB z&Tov^x-Q!+sF3<@GEkM0o``#RUnM06iH9ZsFGmGol z&WP>s(`jiMLa9h-qt3`j;YyWXH9}KlT1UHdC23hdxsSWo+(nldHgfQFt_W-ijPzR< zDAloM>t9(w-V$z2V?9U1SC_c_bB;{5Nn*}+61WyqY~K0AKy$B})DQA!=BvFNw)QCv zoWO;jP;3+JI^lM{{;8~zsasg<5uqm=60VlnErOsn7d5xn74TDE)V>_9V4Me-rt%Br zo;C)(JacC-@Z(lIj| zWq;5mq<*1>W{Tqw% z4-PDMy^uK#NXY~!@!~fH`=rZr8Po2C#jjjD0HCUF<=fAoUMWb5Xki=TsSv5&A=jGi ziDo^@Ja*&-Imbk(6JW-n%aw^ptzK0=7J5n<#B`g$&n_p=avb9H6co)H<&G(${^Zo; zk&Zm6=>Zl}gt34P3UGBdbjHPTPx}tagXM=3W1D67cH#b`uKjP8KdQcew|ub<4#Rxz z9eiu3>Jh;>nDDKa4CqTew@cM>$J)qMl6;5089cKwM~+N16(&L%{aNybTlBd!;R5^??;Hj=Vm;FL4j{szf4404XWxk zm*?9crdx@Abk%izpnOsKh9mhZ=h-@D_7t~ZW-BnF1j{S;v#8`aNzo7{7R!FuRQt`N z`fFZK^QEE@Aj zAz)p|T^-0@`nUQli0^fVpTGD~xNM>DeScVCbIYD$*Jx7a8FJPSnOD)t?WaO*c;ZU} z_|4|ypmPOcsjogNVt5JCHA90X=n`7eCrdL&gIn=9t{u_naoOROsJ79paT9T|_B2eC zo5CTf!VkV3qMYLR|C7r+?&rK1pjn6@{6?wsKTtN-Gm?#Vc z=(FT;fYsT|&@cY#GW}1SZ*o^_a3F8y^&39EBVrxp9sekAn6mE*kW5B+Nd~YBWMisO z-#BmLax(+iNVk0UN(u4&6@2ixx0Gd1>v``_6v=-q3;Y)kd!!#_s2|av1rK`JJyYzb zyZnxhZ3iaQlGYYycY_b0yBkF7dAY&EKBQJlQVV14Iy<#RP)6n}VxiyrYLrue1Q}63Al4h^gr0#sr7?;`-`o4{yk+DQ_UHem;2E^N;1> z|8E|qC2noyU~J`}tLkcFY_I(gF?tf@6#Yf8122Ez1eDTENE?J_?RWNpnhQhu**n4b zg92>qrTd>QM?OdpYHD^g`L-|Ex48!)k!8GfAubUmr0${JlWt{!i2fWG+C%G+vglmi z+24RQT8Aj_Q`;HdIi%@$-)LuBo|FLjw#PS4rfIfJB4wLZm0^Vg4EILEYqcV zBCYim)EZF^xLlf-vjvc?!uWy-0#%FBs3uq0Y>RV21!UZC0hZAR4l_UY7{v0*S@Bgi z41`5Pdr*CB6NeVdU$@W|CS1Wx3amO=CWx`*%?T+*!WhNI;^GFi$hCUJ+u`fWDGwfB z_Qqly|Z>QTnT&-2ZvhN_rt5tdG4Q|_wgky0YMjc`BW;uJBg=S`|xbDN;3 zn?piAF4%DO=2E^P*~!7Hu}8*r;++EF-$9%42=3sH7BwSAh-f*vyq6=_t#t+=)B~uY%lKO_;Gep$S8BN7Lf% z<-#lOh<5zm)NFPKNqKXcQl5tTr>W)L<7rvHu|!JiU?ga9`{9V7UL!_c%{)Z~Z;_>CJ?PO55*Ybl*3*pZxa~p1%Q}wq_qL zF#I0Luzy`=_{yo0l)Ac_u{%@V!zfqnxCmuREzXt~DAAOy_Cq6BWKlCns z5A}N*e^+(?r<3vb^rr>lhx+;Nfpw4eztjJxn*R6fr?t;Nd6&n0GW%b3vWE@-H^9@H z;-3KJ_m6+p*B+sfK(|iob`` z{o`*t{*z+<8|$em`xDECXRP0&zA zRR2e&p2o#Lsg@`I8|5(!{{0$H1J0kX(M$0+z~gKDN0g^W+MlmcMhOA&A3~Lq9L)Vm S00Dt@|8aHSL5!h(c=|suy!t!< literal 4955 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 Date: Mon, 19 May 2025 13:08:03 +0200 Subject: [PATCH 2/2] test: add one more test for datetime handling Co-authored-by: Benjamin Gehrels Signed-off-by: Arthur Schiwon --- .../Reader/Xlsx/ExplicitDateTest.php | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/ExplicitDateTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/ExplicitDateTest.php index 9e2e8c0c03..46a357e2c7 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/ExplicitDateTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/ExplicitDateTest.php @@ -4,7 +4,11 @@ namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx; +use DateTime; use PhpOffice\PhpSpreadsheet\IOFactory; +use PhpOffice\PhpSpreadsheet\Shared\Date; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Writer\Xlsx; class ExplicitDateTest extends \PHPUnit\Framework\TestCase { @@ -53,4 +57,42 @@ public static function testExplicitDate(): void $spreadsheet->disconnectWorksheets(); } + + public function testThatDateTimesCanBePersistedAndReread(): void + { + $originalDateTime = new DateTime('2020-10-21T14:55:31'); + + $dateTimeFromSpreadsheet = $this->getDateTimeFrom($this->excelSheetWithDateTime($originalDateTime)); + $dateTimeFromSpreadsheetAfterPersistAndReread = $this->getDateTimeFrom($this->persistAndReread($this->excelSheetWithDateTime($originalDateTime))); + + self::assertEquals($originalDateTime, $dateTimeFromSpreadsheet); + self::assertEquals($originalDateTime, $dateTimeFromSpreadsheetAfterPersistAndReread); + } + + private function excelSheetWithDateTime(DateTime $dateTime): Spreadsheet + { + $spreadsheet = new Spreadsheet(); + $spreadsheet->getActiveSheet()->setCellValue('A1', Date::dateTimeToExcel($dateTime)); + + return $spreadsheet; + } + + public function getDateTimeFrom(Spreadsheet $spreadsheet): DateTime + { + $value = $spreadsheet->getSheet(0)->getCell('A1')->getCalculatedValue(); + self::assertIsNumeric($value); + $value = (float) $value; + + return Date::excelToDateTimeObject($value); + } + + private function persistAndReread(Spreadsheet $spreadsheet): Spreadsheet + { + $tempPointer = tmpfile(); + $tempFileName = stream_get_meta_data($tempPointer)['uri'] ?? null; + self::assertNotNull($tempFileName, 'Temp file not created'); + (new Xlsx($spreadsheet))->save($tempFileName); + + return (new \PhpOffice\PhpSpreadsheet\Reader\Xlsx())->load($tempFileName); + } }