@@ -8,10 +8,8 @@ class DateFormatter
8
8
{
9
9
/**
10
10
* Search/replace values to convert Excel date/time format masks to PHP format masks.
11
- *
12
- * @var array
13
11
*/
14
- private static $ dateFormatReplacements = [
12
+ private const DATE_FORMAT_REPLACEMENTS = [
15
13
// first remove escapes related to non-format characters
16
14
'\\' => '' ,
17
15
// 12-hour suffix
@@ -32,10 +30,6 @@ class DateFormatter
32
30
// It isn't perfect, but the best way I know how
33
31
':mm ' => ':i ' ,
34
32
'mm: ' => 'i: ' ,
35
- // month leading zero
36
- 'mm ' => 'm ' ,
37
- // month no leading zero
38
- 'm ' => 'n ' ,
39
33
// full day of week name
40
34
'dddd ' => 'l ' ,
41
35
// short day of week name
@@ -44,44 +38,97 @@ class DateFormatter
44
38
'dd ' => 'd ' ,
45
39
// days no leading zero
46
40
'd ' => 'j ' ,
47
- // seconds
48
- 'ss ' => 's ' ,
49
41
// fractional seconds - no php equivalent
50
42
'.s ' => '' ,
51
43
];
52
44
53
45
/**
54
46
* Search/replace values to convert Excel date/time format masks hours to PHP format masks (24 hr clock).
55
- *
56
- * @var array
57
47
*/
58
- private static $ dateFormatReplacements24 = [
48
+ private const DATE_FORMAT_REPLACEMENTS24 = [
59
49
'hh ' => 'H ' ,
60
50
'h ' => 'G ' ,
51
+ // month leading zero
52
+ 'mm ' => 'm ' ,
53
+ // month no leading zero
54
+ 'm ' => 'n ' ,
55
+ // seconds
56
+ 'ss ' => 's ' ,
61
57
];
62
58
63
59
/**
64
60
* Search/replace values to convert Excel date/time format masks hours to PHP format masks (12 hr clock).
65
- *
66
- * @var array
67
61
*/
68
- private static $ dateFormatReplacements12 = [
62
+ private const DATE_FORMAT_REPLACEMENTS12 = [
69
63
'hh ' => 'h ' ,
70
64
'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 ,
71
99
];
72
100
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 */
73
120
public static function format ($ value , string $ format ): string
74
121
{
75
122
// strip off first part containing e.g. [$-F800] or [$USD-409]
76
123
// general syntax: [$<Currency string>-<language info>]
77
124
// language info is in hexadecimal
78
125
// strip off chinese part like [DBNum1][$-804]
79
- $ format = preg_replace ('/^(\[DBNum\d\])*(\[\$[^\]]*\])/i ' , '' , $ format ) ?? '' ;
126
+ $ format = ( string ) preg_replace ('/^(\[DBNum\d\])*(\[\$[^\]]*\])/i ' , '' , $ format );
80
127
81
128
// OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case;
82
129
// but we don't want to change any quoted strings
83
130
/** @var callable */
84
- $ callable = [' self ' , 'setLowercaseCallback ' ];
131
+ $ callable = [self ::class , 'setLowercaseCallback ' ];
85
132
$ format = preg_replace_callback ('/(?:^|")([^"]*)(?:$|")/ ' , $ callable , $ format );
86
133
87
134
// Only process the non-quoted blocks for date format characters
@@ -90,45 +137,46 @@ public static function format($value, string $format): string
90
137
$ blocks = explode ('" ' , $ format );
91
138
foreach ($ blocks as $ key => &$ block ) {
92
139
if ($ key % 2 == 0 ) {
93
- $ block = strtr ($ block , self ::$ dateFormatReplacements );
140
+ $ block = strtr ($ block , self ::DATE_FORMAT_REPLACEMENTS );
94
141
if (!strpos ($ block , 'A ' )) {
95
142
// 24-hour time format
96
143
// 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 );
104
152
} else {
105
153
// 12-hour time format
106
- $ block = strtr ($ block , self ::$ dateFormatReplacements12 );
154
+ $ block = strtr ($ block , self ::DATE_FORMAT_REPLACEMENTS12 );
107
155
}
108
156
}
109
157
}
110
158
$ format = implode ('" ' , $ blocks );
111
159
112
160
// escape any quoted characters so that DateTime format() will render them correctly
113
161
/** @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 );
116
164
117
165
$ dateObj = Date::excelToDateTimeObject ($ value );
118
166
// If the colon preceding minute had been quoted, as happens in
119
167
// Excel 2003 XML formats, m will not have been changed to i above.
120
168
// Change it now.
121
- $ format = \preg_replace ('/ \\\\:m/ ' , ':i ' , $ format );
169
+ $ format = ( string ) \preg_replace ('/ \\\\:m/ ' , ':i ' , $ format );
122
170
123
171
return $ dateObj ->format ($ format );
124
172
}
125
173
126
- private static function setLowercaseCallback ($ matches ): string
174
+ private static function setLowercaseCallback (array $ matches ): string
127
175
{
128
176
return mb_strtolower ($ matches [0 ]);
129
177
}
130
178
131
- private static function escapeQuotesCallback ($ matches ): string
179
+ private static function escapeQuotesCallback (array $ matches ): string
132
180
{
133
181
return '\\' . implode ('\\' , str_split ($ matches [1 ]));
134
182
}
0 commit comments