PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
tablepress / libraries / vendor / PhpSpreadsheet / Style / NumberFormat / DateFormatter.php

DateFormatter.php in TablePress – Tables in WordPress made easy 3.4, at libraries/vendor/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php

224 lines 7.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat;
4
5 use TablePress\Composer\Pcre\Preg;
6 use TablePress\PhpOffice\PhpSpreadsheet\Shared\Date;
7 use TablePress\PhpOffice\PhpSpreadsheet\Shared\StringHelper;
8 use Throwable;
9
10 class DateFormatter
11 {
12 /**
13 * Search/replace values to convert Excel date/time format masks to PHP format masks.
14 */
15 private const DATE_FORMAT_REPLACEMENTS = [
16 // first remove escapes related to non-format characters
17 '\\' => '',
18 // 12-hour suffix
19 'am/pm' => 'A',
20 // 4-digit year
21 'e' => 'Y',
22 'yyyy' => 'Y',
23 // 2-digit year
24 'yy' => 'y',
25 // first letter of month - no php equivalent
26 'mmmmm' => 'M',
27 // full month name
28 'mmmm' => 'F',
29 // short month name
30 'mmm' => 'M',
31 // mm is minutes if time, but can also be month w/leading zero
32 // so we try to identify times be the inclusion of a : separator in the mask
33 // It isn't perfect, but the best way I know how
34 ':mm' => ':i',
35 'mm:' => 'i:',
36 // full day of week name
37 'dddd' => 'l',
38 // short day of week name
39 'ddd' => 'D',
40 // days leading zero
41 'dd' => 'd',
42 // days no leading zero
43 'd' => 'j',
44 // fractional seconds - no php equivalent
45 '.s' => '',
46 ];
47
48 /**
49 * Search/replace values to convert Excel date/time format masks hours to PHP format masks (24 hr clock).
50 */
51 private const DATE_FORMAT_REPLACEMENTS24 = [
52 'hh' => 'H',
53 'h' => 'G',
54 // month leading zero
55 'mm' => 'm',
56 // month no leading zero
57 'm' => 'n',
58 // seconds
59 'ss' => 's',
60 ];
61
62 /**
63 * Search/replace values to convert Excel date/time format masks hours to PHP format masks (12 hr clock).
64 */
65 private const DATE_FORMAT_REPLACEMENTS12 = [
66 'hh' => 'h',
67 'h' => 'g',
68 // month leading zero
69 'mm' => 'm',
70 // month no leading zero
71 'm' => 'n',
72 // seconds
73 'ss' => 's',
74 ];
75
76 private const HOURS_IN_DAY = 24;
77 private const MINUTES_IN_DAY = 60 * self::HOURS_IN_DAY;
78 private const SECONDS_IN_DAY = 60 * self::MINUTES_IN_DAY;
79 private const INTERVAL_PRECISION = 10;
80 private const INTERVAL_LEADING_ZERO = [
81 '[hh]',
82 '[mm]',
83 '[ss]',
84 ];
85 private const INTERVAL_ROUND_PRECISION = [
86 // hours and minutes truncate
87 '[h]' => self::INTERVAL_PRECISION,
88 '[hh]' => self::INTERVAL_PRECISION,
89 '[m]' => self::INTERVAL_PRECISION,
90 '[mm]' => self::INTERVAL_PRECISION,
91 // seconds round
92 '[s]' => 0,
93 '[ss]' => 0,
94 ];
95 private const INTERVAL_MULTIPLIER = [
96 '[h]' => self::HOURS_IN_DAY,
97 '[hh]' => self::HOURS_IN_DAY,
98 '[m]' => self::MINUTES_IN_DAY,
99 '[mm]' => self::MINUTES_IN_DAY,
100 '[s]' => self::SECONDS_IN_DAY,
101 '[ss]' => self::SECONDS_IN_DAY,
102 ];
103
104 /** @param mixed $value */
105 private static function tryInterval(bool &$seekingBracket, string &$block, $value, string $format): void
106 {
107 if ($seekingBracket) {
108 if (str_contains($block, $format)) {
109 $hours = (string) (int) round(
110 self::INTERVAL_MULTIPLIER[$format] * $value,
111 self::INTERVAL_ROUND_PRECISION[$format]
112 );
113 if (strlen($hours) === 1 && in_array($format, self::INTERVAL_LEADING_ZERO, true)) {
114 $hours = "0$hours";
115 }
116 $block = str_replace($format, $hours, $block);
117 $seekingBracket = false;
118 }
119 }
120 }
121
122 /** @param mixed $value value to be formatted */
123 public static function format($value, string $format): string
124 {
125 if ($value < 0 && Preg::isMatch('/^\[?[hms]/i', $format)) {
126 return '-' . self::format(-$value, $format);
127 }
128 // strip off first part containing e.g. [$-F800] or [$USD-409]
129 // general syntax: [$<Currency string>-<language info>]
130 // language info is in hexadecimal
131 // strip off chinese part like [DBNum1][$-804]
132 $format = Preg::replace('/^(\[DBNum\d\])*(\[\$[^\]]*\])/i', '', $format);
133
134 // OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case;
135 // but we don't want to change any quoted strings
136 $format = Preg::replaceCallback('/(?:^|")([^"]*)(?:$|")/', \Closure::fromCallable([self::class, 'setLowerCaseCallback']), $format);
137
138 // Only process the non-quoted blocks for date format characters
139
140 $blocks = explode('"', $format);
141 foreach ($blocks as $key => &$block) {
142 if ($key % 2 == 0) {
143 $block = strtr($block, self::DATE_FORMAT_REPLACEMENTS);
144 if (!strpos($block, 'A')) {
145 // 24-hour time format
146 // when [h]:mm format, the [h] should replace to the hours of the value * 24
147 $seekingBracket = true;
148 self::tryInterval($seekingBracket, $block, $value, '[h]');
149 self::tryInterval($seekingBracket, $block, $value, '[hh]');
150 self::tryInterval($seekingBracket, $block, $value, '[mm]');
151 self::tryInterval($seekingBracket, $block, $value, '[m]');
152 self::tryInterval($seekingBracket, $block, $value, '[s]');
153 self::tryInterval($seekingBracket, $block, $value, '[ss]');
154 $block = strtr($block, self::DATE_FORMAT_REPLACEMENTS24);
155 } else {
156 // 12-hour time format
157 $block = strtr($block, self::DATE_FORMAT_REPLACEMENTS12);
158 }
159 }
160 }
161 $format = implode('"', $blocks);
162
163 // escape any quoted characters so that DateTime format() will render them correctly
164 $format = Preg::replaceCallback('/"(.*)"/U', \Closure::fromCallable([self::class, 'escapeQuotesCallback']), $format);
165
166 try {
167 $dateObj = Date::excelToDateTimeObject($value);
168 } catch (Throwable $exception) {
169 return StringHelper::convertToString($value);
170 }
171 // If the colon preceding minute had been quoted, as happens in
172 // Excel 2003 XML formats, m will not have been changed to i above.
173 // Change it now.
174 $format = Preg::replace('/\\\:m/', ':i', $format);
175 $microseconds = (int) $dateObj->format('u');
176 if (str_contains($format, ':s.000')) {
177 $milliseconds = (int) round($microseconds / 1000.0);
178 if ($milliseconds === 1000) {
179 $milliseconds = 0;
180 $dateObj->modify('+1 second');
181 }
182 $dateObj->modify("-$microseconds microseconds");
183 $format = str_replace(':s.000', ':s.' . sprintf('%03d', $milliseconds), $format);
184 } elseif (str_contains($format, ':s.00')) {
185 $centiseconds = (int) round($microseconds / 10000.0);
186 if ($centiseconds === 100) {
187 $centiseconds = 0;
188 $dateObj->modify('+1 second');
189 }
190 $dateObj->modify("-$microseconds microseconds");
191 $format = str_replace(':s.00', ':s.' . sprintf('%02d', $centiseconds), $format);
192 } elseif (str_contains($format, ':s.0')) {
193 $deciseconds = (int) round($microseconds / 100000.0);
194 if ($deciseconds === 10) {
195 $deciseconds = 0;
196 $dateObj->modify('+1 second');
197 }
198 $dateObj->modify("-$microseconds microseconds");
199 $format = str_replace(':s.0', ':s.' . sprintf('%1d', $deciseconds), $format);
200 } else { // no fractional second
201 if ($microseconds >= 500000) {
202 $dateObj->modify('+1 second');
203 }
204 $dateObj->modify("-$microseconds microseconds");
205 }
206
207 return $dateObj->format($format);
208 }
209
210 /** @param array<?string> $matches */
211 private static function setLowercaseCallback(array $matches): string
212 {
213 /** @var string[] $matches */
214 return mb_strtolower($matches[0]);
215 }
216
217 /** @param array<?string> $matches */
218 private static function escapeQuotesCallback(array $matches): string
219 {
220 /** @var string[] $matches */
221 return '\\' . implode('\\', mb_str_split($matches[1], 1, 'UTF-8'));
222 }
223 }
224