Commit | Line | Data |
---|---|---|
11ade432 AE |
1 | <?php |
2 | namespace wcf\util; | |
11ade432 | 3 | use wcf\data\language\Language; |
ec1b1daf | 4 | use wcf\data\user\User; |
b4cbf821 | 5 | use wcf\system\exception\SystemException; |
ec1b1daf | 6 | use wcf\system\WCF; |
11ade432 AE |
7 | |
8 | /** | |
9 | * Contains date-related functions. | |
9f959ced MS |
10 | * |
11 | * @author Marcel Werk | |
c839bd49 | 12 | * @copyright 2001-2018 WoltLab GmbH |
11ade432 | 13 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
e71525e4 | 14 | * @package WoltLabSuite\Core\Util |
11ade432 | 15 | */ |
18284789 | 16 | final class DateUtil { |
11ade432 AE |
17 | /** |
18 | * name of the default date format language variable | |
9f959ced | 19 | * @var string |
11ade432 | 20 | */ |
e89c399a | 21 | const DATE_FORMAT = 'wcf.date.dateFormat'; |
11ade432 AE |
22 | |
23 | /** | |
24 | * name of the default time format language variable | |
9f959ced | 25 | * @var string |
11ade432 | 26 | */ |
e89c399a | 27 | const TIME_FORMAT = 'wcf.date.timeFormat'; |
11ade432 | 28 | |
3b007c94 JR |
29 | /** |
30 | * format the interval to be used as a standalone phrase | |
31 | * @var integer | |
32 | */ | |
33 | const FORMAT_DEFAULT = 1; | |
34 | ||
35 | /** | |
36 | * format the interval to be used as a phrase in a sentence | |
37 | * @var integer | |
38 | */ | |
39 | const FORMAT_SENTENCE = 2; | |
40 | ||
41 | /** | |
42 | * format the interval without direction | |
43 | * @var integer | |
44 | */ | |
45 | const FORMAT_PLAIN = 3; | |
46 | ||
11ade432 AE |
47 | /** |
48 | * list of available time zones | |
7a23a706 | 49 | * @var string[] |
11ade432 | 50 | */ |
058cbd6a | 51 | protected static $availableTimezones = [ |
523fdcf2 MW |
52 | // there is not support for UTC-12:00 in php |
53 | // '...', // (UTC-12:00) International Date Line West | |
54 | 'Pacific/Samoa', // (UTC-11:00) Midway Island, American Samoa | |
55 | 'Pacific/Honolulu', // (UTC-10:00) Hawaii | |
56 | 'America/Anchorage', // (UTC-09:00) Alaska | |
57 | 'America/Los_Angeles', // (UTC-08:00) Pacific Time (US & Canada), Tijuana, Baja California | |
58 | 'America/Phoenix', // (UTC-07:00) Arizona | |
59 | 'America/Chihuahua', // (UTC-07:00) Chihuahua, Mazatlan | |
60 | 'America/Denver', // (UTC-07:00) Mountain Time (US & Canada) | |
61 | 'America/Chicago', // (UTC-06:00) Central Time (US & Canada) | |
62 | 'America/Mexico_City', // (UTC-06:00) Mexico City, Monterrey | |
63 | 'America/Tegucigalpa', // (UTC-06:00) Central America | |
64 | 'America/Regina', // (UTC-06:00) Saskatchewan | |
65 | 'America/Bogota', // (UTC-05:00) Bogota, Lima | |
66 | 'America/New_York', // (UTC-05:00) Eastern Time (US & Canada) | |
67 | 'America/Indiana/Indianapolis', // (UTC-05:00) Indiana (East) | |
68 | 'America/Rio_Branco', // (UTC-05:00) Rio Branco | |
69 | 'America/Caracas', // (UTC-04:30) Caracas | |
70 | 'America/Asuncion', // (UTC-04:00) Asuncion | |
71 | 'America/Halifax', // (UTC-04:00) Atlantic Time (Canada) | |
72 | 'America/Cuiaba', // (UTC-04:00) Cuiaba | |
73 | 'America/La_Paz', // (UTC-04:00) Georgetown, La Paz, Manaus | |
74 | 'America/Santiago', // (UTC-04:00) Santiago | |
75 | 'America/St_Johns', // (UTC-03:30) Newfoundland | |
76 | 'America/Sao_Paulo', // (UTC-03:00) Brasilia | |
77 | 'America/Argentina/Buenos_Aires', // (UTC-03:00) Buenos Aires | |
78 | 'America/Cayenne', // (UTC-03:00) Cayenne | |
79 | 'America/Godthab', // (UTC-03:00) Greenland | |
80 | 'America/Montevideo', // (UTC-03:00) Montevideo | |
81 | 'Atlantic/South_Georgia', // (UTC-02:00) Mid-Atlantic | |
82 | 'Atlantic/Azores', // (UTC-01:00) Azores | |
83 | 'Atlantic/Cape_Verde', // (UTC-01:00) Cape Verde Is. | |
84 | 'Africa/Casablanca', // (UTC) Casablanca | |
85 | 'Europe/London', // (UTC) Dublin, Lisbon, London | |
86 | 'Africa/Monrovia', // (UTC) Monrovia, Reykjavik | |
87 | 'Europe/Berlin', // (UTC+01:00) Amsterdam, Berlin, Rome, Stockholm, Vienna | |
88 | 'Europe/Belgrade', // (UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague | |
89 | 'Europe/Paris', // (UTC+01:00) Brussels, Copenhagen, Madrid, Paris | |
90 | 'Europe/Sarajevo', // (UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb | |
91 | 'Africa/Algiers', // (UTC+01:00) West Central Africa | |
92 | 'Africa/Windhoek', // (UTC+01:00) Windhoek | |
93 | 'Europe/Athens', // (UTC+02:00) Athens, Bucharest, Istanbul | |
94 | 'Asia/Beirut', // (UTC+02:00) Beirut | |
95 | 'Asia/Damascus', // (UTC+02:00) Damascus | |
96 | 'Africa/Harare', // (UTC+02:00) Harare, Pretoria | |
97 | 'Europe/Helsinki', // (UTC+02:00) Helsinki, Kiev, Riga, Sofia, Tallinn, Vilnius | |
98 | 'Asia/Jerusalem', // (UTC+02:00) Jerusalem | |
99 | 'Africa/Cairo', // (UTC+02:00) Cairo | |
bfdd4392 | 100 | 'Europe/Kaliningrad', // (UTC+02:00) Kaliningrad |
523fdcf2 MW |
101 | 'Asia/Amman', // (UTC+03:00) Amman |
102 | 'Asia/Baghdad', // (UTC+03:00) Baghdad | |
bfdd4392 MW |
103 | 'Europe/Minsk', // (UTC+03:00) Minsk |
104 | 'Europe/Moscow', // (UTC+03:00) Moscow, Volgograd | |
523fdcf2 MW |
105 | 'Asia/Kuwait', // (UTC+03:00) Kuwait, Riyadh |
106 | 'Africa/Nairobi', // (UTC+03:00) Nairobi | |
107 | 'Asia/Tehran', // (UTC+03:30) Tehran | |
108 | 'Asia/Muscat', // (UTC+04:00) Muscat | |
109 | 'Asia/Baku', // (UTC+04:00) Baku | |
110 | 'Asia/Yerevan', // (UTC+04:00) Yerevan | |
523fdcf2 MW |
111 | 'Indian/Mauritius', // (UTC+04:00) Port Loius |
112 | 'Asia/Tbilisi', // (UTC+04:00) Tbilisi | |
113 | 'Asia/Kabul', // (UTC+04:30) Kabul | |
114 | 'Asia/Karachi', // (UTC+05:00) Karachi | |
bfdd4392 | 115 | 'Asia/Yekaterinburg', // (UTC+05:00) Ekaterinburg |
523fdcf2 MW |
116 | 'Asia/Tashkent', // (UTC+05:00) Tashkent |
117 | 'Asia/Kolkata', // (UTC+05:30) Calcutta, New Dehli | |
118 | 'Asia/Colombo', // (UTC+05:30) Sri Jayawardenepura | |
119 | 'Asia/Katmandu', // (UTC+05:45) Kathmandu | |
120 | 'Asia/Almaty', // (UTC+06:00) Almaty | |
121 | 'Asia/Dhaka', // (UTC+06:00) Dhaka | |
bfdd4392 | 122 | 'Asia/Novosibirsk', // (UTC+06:00) Novosibirsk |
523fdcf2 MW |
123 | 'Asia/Rangoon', // (UTC+06:30) Yangon (Rangoon) |
124 | 'Asia/Bangkok', // (UTC+07:00) Bangkok, Jakarta | |
bfdd4392 MW |
125 | 'Asia/Krasnoyarsk', // (UTC+07:00) Krasnoyarsk |
126 | 'Asia/Irkutsk', // (UTC+08:00) Irkutsk | |
523fdcf2 MW |
127 | 'Asia/Kuala_Lumpur', // (UTC+08:00) Kuala Lumpur, Singapore |
128 | 'Asia/Chongqing', // (UTC+08:00) Beijing, Chongqing, Hong Kong | |
129 | 'Australia/Perth', // (UTC+08:00) Perth | |
130 | 'Asia/Taipei', // (UTC+08:00) Taipei | |
131 | 'Asia/Ulaanbaatar', // (UTC+08:00) Ulaan Bataar | |
bfdd4392 | 132 | 'Asia/Yakutsk', // (UTC+09:00) Yakutsk |
523fdcf2 MW |
133 | 'Asia/Tokyo', // (UTC+09:00) Tokyo |
134 | 'Asia/Seoul', // (UTC+09:00) Seoul | |
135 | 'Australia/Adelaide', // (UTC+09:30) Adelaide | |
136 | 'Australia/Darwin', // (UTC+09:30) Darwin | |
137 | 'Australia/Brisbane', // (UTC+10:00) Brisbane | |
138 | 'Australia/Sydney', // (UTC+10:00) Canberra, Melbourne, Sydney | |
139 | 'Pacific/Guam', // (UTC+10:00) Guam, Port Moresby | |
140 | 'Australia/Hobart', // (UTC+10:00) Hobart | |
bfdd4392 | 141 | 'Asia/Vladivostok', // (UTC+10:00) Vladivostok |
523fdcf2 | 142 | 'Pacific/Noumea', // (UTC+11:00) New Caledonia |
523fdcf2 MW |
143 | 'Pacific/Auckland', // (UTC+12:00) Auckland |
144 | 'Pacific/Fiji', // (UTC+12:00) Fiji | |
523fdcf2 MW |
145 | 'Pacific/Tongatapu', // (UTC+13:00) Nukualofa |
146 | 'Pacific/Apia', // (UTC+13:00) Samoa | |
058cbd6a | 147 | ]; |
11ade432 | 148 | |
d0b48367 MS |
149 | /** |
150 | * first day of the week | |
151 | * 0=sunday | |
152 | * 1=monday | |
153 | * @var integer | |
154 | */ | |
155 | private static $firstDayOfTheWeek = null; | |
156 | ||
157 | /** | |
158 | * order of the week days | |
7a23a706 | 159 | * @var string[] |
d0b48367 MS |
160 | */ |
161 | private static $weekDays = null; | |
162 | ||
163 | /** | |
164 | * order of the week days (short textual representation) | |
7a23a706 | 165 | * @var string[] |
d0b48367 MS |
166 | */ |
167 | private static $shortWeekDays = null; | |
168 | ||
11ade432 AE |
169 | /** |
170 | * Returns a formatted date. | |
171 | * | |
4e25add7 MS |
172 | * @param \DateTime $time |
173 | * @param string $format | |
174 | * @param Language $language | |
175 | * @param User $user | |
9f959ced | 176 | * @return string |
11ade432 AE |
177 | */ |
178 | public static function format(\DateTime $time = null, $format = null, Language $language = null, User $user = null) { | |
179 | // get default values | |
180 | if ($time === null) $time = new \DateTime(); | |
181 | if ($user === null) $user = WCF::getUser(); | |
182 | if ($language === null) $language = WCF::getLanguage(); | |
183 | if ($format === null) $format = self::DATE_FORMAT; | |
184 | ||
185 | // set time zone | |
186 | $time->setTimezone($user->getTimeZone()); | |
187 | ||
188 | // format date | |
189 | $output = $time->format($language->get($format)); | |
190 | ||
191 | // localize output | |
4d38eef7 | 192 | $output = self::localizeDate($output, $language->get($format), $language); |
11ade432 AE |
193 | |
194 | return $output; | |
195 | } | |
196 | ||
8ee81f2f | 197 | /** |
2246b509 | 198 | * Returns a formatted date interval. |
8ee81f2f | 199 | * |
2246b509 MS |
200 | * @param \DateInterval $interval interval to be formatted |
201 | * @param boolean $fullInterval if `true`, the complete interval is returned, otherwise a rounded interval is used | |
3b007c94 | 202 | * @param integer $formatType format type for the interval, use the class constant FORMAT_DEFAULT, FORMAT_SENTENCE or FORMAT_PLAIN |
8ee81f2f MS |
203 | * @return string |
204 | */ | |
3b007c94 | 205 | public static function formatInterval(\DateInterval $interval, $fullInterval = false, $formatType = self::FORMAT_DEFAULT) { |
8ee81f2f MS |
206 | $years = $interval->format('%y'); |
207 | $months = $interval->format('%m'); | |
208 | $days = $interval->format('%d'); | |
209 | $weeks = floor($days / 7); | |
210 | $hours = $interval->format('%h'); | |
211 | $minutes = $interval->format('%i'); | |
e0d32362 MS |
212 | |
213 | $direction = ''; | |
506966c4 TD |
214 | switch ($interval->format('%R')) { |
215 | case '+': | |
216 | $direction = 'past'; | |
217 | break; | |
218 | case '-': | |
219 | $direction = 'future'; | |
220 | break; | |
221 | } | |
8ee81f2f | 222 | |
3b007c94 JR |
223 | switch ($formatType) { |
224 | case self::FORMAT_DEFAULT: | |
225 | $languageItemSuffix = $direction; | |
226 | break; | |
227 | ||
228 | case self::FORMAT_SENTENCE: | |
229 | $languageItemSuffix = $direction . '.inSentence'; | |
230 | break; | |
7ec10692 | 231 | |
3b007c94 JR |
232 | case self::FORMAT_PLAIN: |
233 | $languageItemSuffix = 'plain'; | |
234 | break; | |
235 | ||
236 | default: | |
237 | throw new \InvalidArgumentException('Invalid $formatType value'); | |
2246b509 MS |
238 | } |
239 | ||
8ee81f2f | 240 | if ($fullInterval) { |
2246b509 | 241 | return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.full.' . $languageItemSuffix, [ |
8ee81f2f MS |
242 | 'days' => $days - 7 * $weeks, |
243 | 'firstElement' => $years ? 'years' : ($months ? 'months' : ($weeks ? 'weeks' : ($days ? 'days' : ($hours ? 'hours' : 'minutes')))), | |
244 | 'hours' => $hours, | |
245 | 'lastElement' => !$minutes ? (!$hours ? (!$days ? (!$weeks ? (!$months ? 'years' : 'months') : 'weeks') : 'days') : 'hours') : 'minutes', | |
246 | 'minutes' => $minutes, | |
247 | 'months' => $months, | |
248 | 'weeks' => $weeks, | |
249 | 'years' => $years | |
058cbd6a | 250 | ]); |
8ee81f2f MS |
251 | } |
252 | ||
253 | if ($years) { | |
2246b509 | 254 | return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.years.' . $languageItemSuffix, [ |
8ee81f2f | 255 | 'years' => $years |
058cbd6a | 256 | ]); |
8ee81f2f MS |
257 | } |
258 | ||
259 | if ($months) { | |
2246b509 | 260 | return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.months.' . $languageItemSuffix, [ |
8ee81f2f | 261 | 'months' => $months |
058cbd6a | 262 | ]); |
8ee81f2f MS |
263 | } |
264 | ||
acfd3874 | 265 | if ($weeks) { |
2246b509 | 266 | return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.weeks.' . $languageItemSuffix, [ |
acfd3874 | 267 | 'weeks' => $weeks |
058cbd6a | 268 | ]); |
acfd3874 MS |
269 | } |
270 | ||
8ee81f2f | 271 | if ($days) { |
2246b509 | 272 | return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.days.' . $languageItemSuffix, [ |
8ee81f2f | 273 | 'days' => $days |
058cbd6a | 274 | ]); |
8ee81f2f MS |
275 | } |
276 | ||
277 | if ($hours) { | |
2246b509 | 278 | return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.hours.' . $languageItemSuffix, [ |
8ee81f2f | 279 | 'hours' => $hours |
058cbd6a | 280 | ]); |
8ee81f2f MS |
281 | } |
282 | ||
2246b509 | 283 | return WCF::getLanguage()->getDynamicVariable('wcf.date.interval.minutes.' . $languageItemSuffix, [ |
8ee81f2f | 284 | 'minutes' => $minutes |
058cbd6a | 285 | ]); |
8ee81f2f MS |
286 | } |
287 | ||
11ade432 AE |
288 | /** |
289 | * Returns a localized date output. | |
290 | * | |
4e25add7 MS |
291 | * @param string $date |
292 | * @param string $format | |
293 | * @param Language $language | |
9f959ced | 294 | * @return string |
11ade432 AE |
295 | */ |
296 | public static function localizeDate($date, $format, Language $language) { | |
297 | if ($language->languageCode != 'en') { | |
298 | // full textual representation of the day of the week (l) | |
4d38eef7 | 299 | if (strpos($format, 'l') !== false) { |
058cbd6a | 300 | $date = str_replace(['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], [ |
e89c399a MW |
301 | $language->get('wcf.date.day.sunday'), |
302 | $language->get('wcf.date.day.monday'), | |
303 | $language->get('wcf.date.day.tuesday'), | |
304 | $language->get('wcf.date.day.wednesday'), | |
305 | $language->get('wcf.date.day.thursday'), | |
306 | $language->get('wcf.date.day.friday'), | |
307 | $language->get('wcf.date.day.saturday') | |
058cbd6a | 308 | ], $date); |
11ade432 AE |
309 | } |
310 | ||
311 | // textual representation of a day, three letters (D) | |
4d38eef7 | 312 | if (strpos($format, 'D') !== false) { |
058cbd6a | 313 | $date = str_replace(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], [ |
e89c399a MW |
314 | $language->get('wcf.date.day.sun'), |
315 | $language->get('wcf.date.day.mon'), | |
316 | $language->get('wcf.date.day.tue'), | |
317 | $language->get('wcf.date.day.wed'), | |
318 | $language->get('wcf.date.day.thu'), | |
319 | $language->get('wcf.date.day.fri'), | |
320 | $language->get('wcf.date.day.sat') | |
058cbd6a | 321 | ], $date); |
11ade432 AE |
322 | } |
323 | ||
324 | // full textual representation of a month (F) | |
4d38eef7 | 325 | if (strpos($format, 'F') !== false) { |
058cbd6a | 326 | $date = str_replace(['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], [ |
e89c399a MW |
327 | $language->get('wcf.date.month.january'), |
328 | $language->get('wcf.date.month.february'), | |
329 | $language->get('wcf.date.month.march'), | |
330 | $language->get('wcf.date.month.april'), | |
331 | $language->get('wcf.date.month.may'), | |
332 | $language->get('wcf.date.month.june'), | |
333 | $language->get('wcf.date.month.july'), | |
334 | $language->get('wcf.date.month.august'), | |
335 | $language->get('wcf.date.month.september'), | |
336 | $language->get('wcf.date.month.october'), | |
337 | $language->get('wcf.date.month.november'), | |
338 | $language->get('wcf.date.month.december') | |
058cbd6a | 339 | ], $date); |
11ade432 AE |
340 | } |
341 | ||
342 | // short textual representation of a month (M) | |
4d38eef7 | 343 | if (strpos($format, 'M') !== false) { |
058cbd6a | 344 | $date = str_replace(['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], [ |
020bf2c7 AE |
345 | $language->get('wcf.date.month.short.jan'), |
346 | $language->get('wcf.date.month.short.feb'), | |
347 | $language->get('wcf.date.month.short.mar'), | |
348 | $language->get('wcf.date.month.short.apr'), | |
349 | $language->get('wcf.date.month.short.may'), | |
350 | $language->get('wcf.date.month.short.jun'), | |
351 | $language->get('wcf.date.month.short.jul'), | |
352 | $language->get('wcf.date.month.short.aug'), | |
353 | $language->get('wcf.date.month.short.sep'), | |
354 | $language->get('wcf.date.month.short.oct'), | |
355 | $language->get('wcf.date.month.short.nov'), | |
356 | $language->get('wcf.date.month.short.dec') | |
058cbd6a | 357 | ], $date); |
11ade432 AE |
358 | } |
359 | } | |
360 | ||
361 | return $date; | |
362 | } | |
363 | ||
364 | /** | |
365 | * Creates a DateTime object with the given unix timestamp. | |
366 | * | |
367 | * @param integer $timestamp | |
042172ff | 368 | * @return \DateTime |
11ade432 AE |
369 | */ |
370 | public static function getDateTimeByTimestamp($timestamp) { | |
371 | return new \DateTime('@'.$timestamp); | |
372 | } | |
373 | ||
374 | /** | |
375 | * Returns a list of available timezones. | |
376 | * | |
7a23a706 | 377 | * @return string[] |
11ade432 AE |
378 | */ |
379 | public static function getAvailableTimezones() { | |
380 | return self::$availableTimezones; | |
381 | } | |
136fba63 MW |
382 | |
383 | /** | |
384 | * Calculates the age of a given date. | |
385 | * | |
386 | * @param string $date format YYYY-MM-DD | |
387 | * @return integer | |
388 | */ | |
389 | public static function getAge($date) { | |
390 | // split date | |
391 | $year = $month = $day = 0; | |
392 | $value = explode('-', $date); | |
393 | if (isset($value[0])) $year = intval($value[0]); | |
394 | if (isset($value[1])) $month = intval($value[1]); | |
395 | if (isset($value[2])) $day = intval($value[2]); | |
87d3a054 | 396 | |
136fba63 MW |
397 | // calc |
398 | if ($year) { | |
399 | $age = self::format(null, 'Y') - $year; | |
8226fe09 | 400 | if (self::format(null, 'n') < $month) $age--; |
7525af28 | 401 | else if (self::format(null, 'n') == $month && self::format(null, 'j') < $day) $age--; |
136fba63 MW |
402 | return $age; |
403 | } | |
404 | ||
405 | return 0; | |
406 | } | |
18284789 | 407 | |
b4cbf821 AE |
408 | /** |
409 | * Validates if given date is valid ISO-8601. | |
410 | * | |
411 | * @param string $date | |
2b770bdd | 412 | * @throws SystemException |
b4cbf821 AE |
413 | */ |
414 | public static function validateDate($date) { | |
afc3ddab | 415 | if (preg_match('~^(?P<year>[0-9]{4})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})~', $date, $matches)) { |
0840ec5a AE |
416 | if (!checkdate($matches['month'], $matches['day'], $matches['year'])) { |
417 | throw new SystemException("Date '".$date."' is invalid"); | |
418 | } | |
b4cbf821 | 419 | } |
0840ec5a AE |
420 | else { |
421 | throw new SystemException("Date '".$date."' is not a valid ISO-8601 date"); | |
b4cbf821 AE |
422 | } |
423 | } | |
424 | ||
d0b48367 MS |
425 | /** |
426 | * Returns the first day of the week. | |
427 | * | |
428 | * @return integer | |
429 | */ | |
430 | public static function getFirstDayOfTheWeek() { | |
431 | if (self::$firstDayOfTheWeek === null) { | |
432 | self::$firstDayOfTheWeek = intval(WCF::getLanguage()->get('wcf.date.firstDayOfTheWeek')); | |
433 | if (self::$firstDayOfTheWeek != 1 && self::$firstDayOfTheWeek != 0) self::$firstDayOfTheWeek = 0; | |
434 | } | |
435 | ||
436 | return self::$firstDayOfTheWeek; | |
437 | } | |
438 | ||
439 | /** | |
440 | * Returns the order of the week days. | |
441 | * | |
7a23a706 | 442 | * @return string[] |
d0b48367 MS |
443 | */ |
444 | public static function getWeekDays() { | |
445 | if (self::$weekDays === null) { | |
446 | if (self::getFirstDayOfTheWeek() == 1) { | |
058cbd6a | 447 | self::$weekDays = [ |
d0b48367 MS |
448 | 1 => 'monday', |
449 | 2 => 'tuesday', | |
450 | 3 => 'wednesday', | |
451 | 4 => 'thursday', | |
452 | 5 => 'friday', | |
453 | 6 => 'saturday', | |
454 | 0 => 'sunday' | |
058cbd6a | 455 | ]; |
d0b48367 MS |
456 | } |
457 | else { | |
058cbd6a | 458 | self::$weekDays = [ |
d0b48367 MS |
459 | 0 => 'sunday', |
460 | 1 => 'monday', | |
461 | 2 => 'tuesday', | |
462 | 3 => 'wednesday', | |
463 | 4 => 'thursday', | |
464 | 5 => 'friday', | |
465 | 6 => 'saturday' | |
058cbd6a | 466 | ]; |
d0b48367 MS |
467 | } |
468 | } | |
469 | ||
470 | return self::$weekDays; | |
471 | } | |
472 | ||
473 | /** | |
474 | * Returns the order of the week days (short textual representation). | |
475 | * | |
7a23a706 | 476 | * @return string[] |
d0b48367 MS |
477 | */ |
478 | public static function getShortWeekDays() { | |
479 | if (self::$shortWeekDays === null) { | |
480 | if (self::getFirstDayOfTheWeek() == 1) { | |
058cbd6a | 481 | self::$shortWeekDays = [ |
d0b48367 MS |
482 | 1 => 'mon', |
483 | 2 => 'tue', | |
484 | 3 => 'wed', | |
485 | 4 => 'thu', | |
486 | 5 => 'fri', | |
487 | 6 => 'sat', | |
488 | 0 => 'sun' | |
058cbd6a | 489 | ]; |
d0b48367 MS |
490 | } |
491 | else { | |
058cbd6a | 492 | self::$shortWeekDays = [ |
d0b48367 MS |
493 | 0 => 'sun', |
494 | 1 => 'mon', | |
495 | 2 => 'tue', | |
496 | 3 => 'wed', | |
497 | 4 => 'thu', | |
498 | 5 => 'fri', | |
499 | 6 => 'sat' | |
058cbd6a | 500 | ]; |
d0b48367 MS |
501 | } |
502 | } | |
503 | ||
504 | return self::$shortWeekDays; | |
505 | } | |
506 | ||
507 | /** | |
508 | * Returns the number of weeks in the given year. | |
509 | * | |
510 | * @param integer $year | |
511 | * @return integer | |
512 | */ | |
513 | public static function getWeeksInYear($year) { | |
514 | $date = new \DateTime(); | |
515 | $date->setISODate($year, 53, self::getFirstDayOfTheWeek()); | |
516 | return ($date->format('W') == 53 ? 53 : 52); | |
517 | } | |
439109fb CW |
518 | |
519 | /** | |
520 | * Returns the relative date time identical to the relative time generated | |
521 | * through JavaScript. | |
522 | * | |
523 | * @param \DateTime $dateTimeObject target date object | |
524 | * @param integer $timestamp target timestamp | |
525 | * @param string $date localized date | |
526 | * @param string $time localized time | |
527 | * @param boolean $isFutureDate true if timestamp is in the future | |
528 | * @return string relative time | |
529 | */ | |
530 | public static function getRelativeTime(\DateTime $dateTimeObject, $timestamp, $date, $time, $isFutureDate) { | |
531 | if ($isFutureDate) { | |
532 | return str_replace('%time%', $time, str_replace('%date%', $date, WCF::getLanguage()->get('wcf.date.dateTimeFormat'))); | |
533 | } | |
534 | ||
535 | // timestamp is less than 60 seconds ago | |
536 | if ($timestamp >= TIME_NOW || TIME_NOW < ($timestamp + 60)) { | |
537 | return WCF::getLanguage()->get('wcf.date.relative.now'); | |
538 | } | |
539 | // timestamp is less than 60 minutes ago (display 1 hour ago rather than 60 minutes ago) | |
540 | else if (TIME_NOW < ($timestamp + 3540)) { | |
541 | $minutes = max(round((TIME_NOW - $timestamp) / 60), 1); | |
542 | ||
543 | return WCF::getLanguage()->getDynamicVariable('wcf.date.relative.minutes', ['minutes' => $minutes]); | |
544 | } | |
545 | // timestamp is less than 24 hours ago | |
546 | else if (TIME_NOW < ($timestamp + 86400)) { | |
547 | $hours = round((TIME_NOW - $timestamp) / 3600); | |
548 | ||
549 | return WCF::getLanguage()->getDynamicVariable('wcf.date.relative.hours', ['hours' => $hours]); | |
550 | } | |
551 | // timestamp is less than 6 days ago | |
552 | else if (TIME_NOW < ($timestamp + 518400)) { | |
553 | $dtoNoTime = clone $dateTimeObject; | |
554 | $dtoNoTime->setTime(0, 0, 0); | |
555 | $currentDateTimeObject = self::getDateTimeByTimestamp(TIME_NOW); | |
b2204e3d | 556 | $currentDateTimeObject->setTimezone(WCF::getUser()->getTimeZone()); |
439109fb CW |
557 | $currentDateTimeObject->setTime(0, 0, 0); |
558 | ||
559 | $days = $dtoNoTime->diff($currentDateTimeObject)->days; | |
560 | $day = self::format($dateTimeObject, 'l'); | |
561 | ||
562 | return WCF::getLanguage()->getDynamicVariable('wcf.date.relative.pastDays', [ | |
563 | 'days' => $days, | |
564 | 'day' => $day, | |
565 | 'time' => $time | |
566 | ]); | |
567 | } | |
568 | ||
569 | // timestamp is between ~700 million years BC and last week | |
570 | $datetime = WCF::getLanguage()->get('wcf.date.shortDateTimeFormat'); | |
571 | $datetime = str_replace('%date%', $date, $datetime); | |
572 | $datetime = str_replace('%time%', $time, $datetime); | |
573 | ||
574 | return $datetime; | |
575 | } | |
d0b48367 | 576 | |
1d5f9363 MS |
577 | /** |
578 | * Forbid creation of DateUtil objects. | |
579 | */ | |
580 | private function __construct() { | |
581 | // does nothing | |
582 | } | |
11ade432 | 583 | } |