@@ -8,10 +8,8 @@ class DateFormatter
88{
99 /**
1010 * Search/replace values to convert Excel date/time format masks to PHP format masks.
11- *
12- * @var array
1311 */
14- private static $ dateFormatReplacements = [
12+ private const DATE_FORMAT_REPLACEMENTS = [
1513 // first remove escapes related to non-format characters
1614 '\\' => '' ,
1715 // 12-hour suffix
@@ -32,10 +30,6 @@ class DateFormatter
3230 // It isn't perfect, but the best way I know how
3331 ':mm ' => ':i ' ,
3432 'mm: ' => 'i: ' ,
35- // month leading zero
36- 'mm ' => 'm ' ,
37- // month no leading zero
38- 'm ' => 'n ' ,
3933 // full day of week name
4034 'dddd ' => 'l ' ,
4135 // short day of week name
@@ -44,44 +38,97 @@ class DateFormatter
4438 'dd ' => 'd ' ,
4539 // days no leading zero
4640 'd ' => 'j ' ,
47- // seconds
48- 'ss ' => 's ' ,
4941 // fractional seconds - no php equivalent
5042 '.s ' => '' ,
5143 ];
5244
5345 /**
5446 * Search/replace values to convert Excel date/time format masks hours to PHP format masks (24 hr clock).
55- *
56- * @var array
5747 */
58- private static $ dateFormatReplacements24 = [
48+ private const DATE_FORMAT_REPLACEMENTS24 = [
5949 'hh ' => 'H ' ,
6050 'h ' => 'G ' ,
51+ // month leading zero
52+ 'mm ' => 'm ' ,
53+ // month no leading zero
54+ 'm ' => 'n ' ,
55+ // seconds
56+ 'ss ' => 's ' ,
6157 ];
6258
6359 /**
6460 * Search/replace values to convert Excel date/time format masks hours to PHP format masks (12 hr clock).
65- *
66- * @var array
6761 */
68- private static $ dateFormatReplacements12 = [
62+ private const DATE_FORMAT_REPLACEMENTS12 = [
6963 'hh ' => 'h ' ,
7064 'h ' => 'g ' ,
65+ // month leading zero
66+ 'mm ' => 'm ' ,
67+ // month no leading zero
68+ 'm ' => 'n ' ,
69+ // seconds
70+ 'ss ' => 's ' ,
71+ ];
72+
73+ private const HOURS_IN_DAY = 24 ;
74+ private const MINUTES_IN_DAY = 60 * self ::HOURS_IN_DAY ;
75+ private const SECONDS_IN_DAY = 60 * self ::MINUTES_IN_DAY ;
76+ private const INTERVAL_PRECISION = 10 ;
77+ private const INTERVAL_LEADING_ZERO = [
78+ '[hh] ' ,
79+ '[mm] ' ,
80+ '[ss] ' ,
81+ ];
82+ private const INTERVAL_ROUND_PRECISION = [
83+ // hours and minutes truncate
84+ '[h] ' => self ::INTERVAL_PRECISION ,
85+ '[hh] ' => self ::INTERVAL_PRECISION ,
86+ '[m] ' => self ::INTERVAL_PRECISION ,
87+ '[mm] ' => self ::INTERVAL_PRECISION ,
88+ // seconds round
89+ '[s] ' => 0 ,
90+ '[ss] ' => 0 ,
91+ ];
92+ private const INTERVAL_MULTIPLIER = [
93+ '[h] ' => self ::HOURS_IN_DAY ,
94+ '[hh] ' => self ::HOURS_IN_DAY ,
95+ '[m] ' => self ::MINUTES_IN_DAY ,
96+ '[mm] ' => self ::MINUTES_IN_DAY ,
97+ '[s] ' => self ::SECONDS_IN_DAY ,
98+ '[ss] ' => self ::SECONDS_IN_DAY ,
7199 ];
72100
101+ /** @param mixed $value */
102+ private static function tryInterval (bool &$ seekingBracket , string &$ block , $ value , string $ format ): void
103+ {
104+ if ($ seekingBracket ) {
105+ if (false !== strpos ($ block , $ format )) {
106+ $ hours = (string ) (int ) round (
107+ self ::INTERVAL_MULTIPLIER [$ format ] * $ value ,
108+ self ::INTERVAL_ROUND_PRECISION [$ format ]
109+ );
110+ if (strlen ($ hours ) === 1 && in_array ($ format , self ::INTERVAL_LEADING_ZERO , true )) {
111+ $ hours = "0 $ hours " ;
112+ }
113+ $ block = str_replace ($ format , $ hours , $ block );
114+ $ seekingBracket = false ;
115+ }
116+ }
117+ }
118+
119+ /** @param mixed $value */
73120 public static function format ($ value , string $ format ): string
74121 {
75122 // strip off first part containing e.g. [$-F800] or [$USD-409]
76123 // general syntax: [$<Currency string>-<language info>]
77124 // language info is in hexadecimal
78125 // strip off chinese part like [DBNum1][$-804]
79- $ format = preg_replace ('/^(\[DBNum\d\])*(\[\$[^\]]*\])/i ' , '' , $ format ) ?? '' ;
126+ $ format = ( string ) preg_replace ('/^(\[DBNum\d\])*(\[\$[^\]]*\])/i ' , '' , $ format );
80127
81128 // OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case;
82129 // but we don't want to change any quoted strings
83130 /** @var callable */
84- $ callable = [' self ' , 'setLowercaseCallback ' ];
131+ $ callable = [self ::class , 'setLowercaseCallback ' ];
85132 $ format = preg_replace_callback ('/(?:^|")([^"]*)(?:$|")/ ' , $ callable , $ format );
86133
87134 // Only process the non-quoted blocks for date format characters
@@ -90,45 +137,46 @@ public static function format($value, string $format): string
90137 $ blocks = explode ('" ' , $ format );
91138 foreach ($ blocks as $ key => &$ block ) {
92139 if ($ key % 2 == 0 ) {
93- $ block = strtr ($ block , self ::$ dateFormatReplacements );
140+ $ block = strtr ($ block , self ::DATE_FORMAT_REPLACEMENTS );
94141 if (!strpos ($ block , 'A ' )) {
95142 // 24-hour time format
96143 // when [h]:mm format, the [h] should replace to the hours of the value * 24
97- if (false !== strpos ($ block , '[h] ' )) {
98- $ hours = (int ) ($ value * 24 );
99- $ block = str_replace ('[h] ' , $ hours , $ block );
100-
101- continue ;
102- }
103- $ block = strtr ($ block , self ::$ dateFormatReplacements24 );
144+ $ seekingBracket = true ;
145+ self ::tryInterval ($ seekingBracket , $ block , $ value , '[h] ' );
146+ self ::tryInterval ($ seekingBracket , $ block , $ value , '[hh] ' );
147+ self ::tryInterval ($ seekingBracket , $ block , $ value , '[mm] ' );
148+ self ::tryInterval ($ seekingBracket , $ block , $ value , '[m] ' );
149+ self ::tryInterval ($ seekingBracket , $ block , $ value , '[s] ' );
150+ self ::tryInterval ($ seekingBracket , $ block , $ value , '[ss] ' );
151+ $ block = strtr ($ block , self ::DATE_FORMAT_REPLACEMENTS24 );
104152 } else {
105153 // 12-hour time format
106- $ block = strtr ($ block , self ::$ dateFormatReplacements12 );
154+ $ block = strtr ($ block , self ::DATE_FORMAT_REPLACEMENTS12 );
107155 }
108156 }
109157 }
110158 $ format = implode ('" ' , $ blocks );
111159
112160 // escape any quoted characters so that DateTime format() will render them correctly
113161 /** @var callable */
114- $ callback = [' self ' , 'escapeQuotesCallback ' ];
115- $ format = preg_replace_callback ('/"(.*)"/U ' , $ callback , $ format );
162+ $ callback = [self ::class , 'escapeQuotesCallback ' ];
163+ $ format = ( string ) preg_replace_callback ('/"(.*)"/U ' , $ callback , $ format );
116164
117165 $ dateObj = Date::excelToDateTimeObject ($ value );
118166 // If the colon preceding minute had been quoted, as happens in
119167 // Excel 2003 XML formats, m will not have been changed to i above.
120168 // Change it now.
121- $ format = \preg_replace ('/ \\\\:m/ ' , ':i ' , $ format );
169+ $ format = ( string ) \preg_replace ('/ \\\\:m/ ' , ':i ' , $ format );
122170
123171 return $ dateObj ->format ($ format );
124172 }
125173
126- private static function setLowercaseCallback ($ matches ): string
174+ private static function setLowercaseCallback (array $ matches ): string
127175 {
128176 return mb_strtolower ($ matches [0 ]);
129177 }
130178
131- private static function escapeQuotesCallback ($ matches ): string
179+ private static function escapeQuotesCallback (array $ matches ): string
132180 {
133181 return '\\' . implode ('\\' , str_split ($ matches [1 ]));
134182 }
0 commit comments