PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / trunk
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution vtrunk
2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 1.7.2 All 33 releases
fluent-booking / app / Services / DateTimeHelper.php

DateTimeHelper.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution trunk, at app/Services/DateTimeHelper.php

451 lines 17.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBooking\App\Services;
4
5 class DateTimeHelper
6 {
7 public static function getTimeZones($grouped = false)
8 {
9 $tz_identifiers = timezone_identifiers_list();
10
11 if (!$grouped) {
12 return $tz_identifiers;
13 }
14
15 $lists = [];
16
17 $topLevels = [];
18 foreach ($tz_identifiers as $tz) {
19 $parts = explode('/', $tz);
20 if (count($parts) > 1) {
21 $lists[$parts[0]][] = $tz;
22 } else {
23 $topLevels[$tz][] = $tz;
24 }
25 }
26
27 return array_merge($topLevels, $lists);
28 }
29
30 public static function getFlatGroupedTimeZones()
31 {
32 $tz_identifiers = timezone_identifiers_list();
33
34 $lists = [];
35 $topLevels = [];
36 foreach ($tz_identifiers as $tz) {
37 $parts = explode('/', $tz);
38 if (count($parts) > 1) {
39 $lists[] = [
40 'label' => $tz,
41 'value' => $tz,
42 'group' => $parts[0]
43 ];
44 } else {
45 $topLevels[] = [
46 'label' => $tz,
47 'value' => $tz,
48 'group' => $tz
49 ];
50 }
51 }
52
53 return $topLevels + $lists;
54 }
55
56 public static function getTimeZone()
57 {
58 $timeZone = wp_timezone_string();
59 if (!$timeZone || !in_array($timeZone, \DateTimeZone::listIdentifiers())) {
60 $timeZone = 'UTC';
61 }
62
63 return $timeZone;
64 }
65
66 public static function guessTimeZone()
67 {
68 $timeZone = self::getTimeZone();
69
70 if (isset($_COOKIE['fluent_booking_user_timezone'])) {
71 $timeZone = sanitize_text_field(wp_unslash($_COOKIE['fluent_booking_user_timezone']));
72 }
73
74 return $timeZone;
75 }
76
77 public static function getValidatedTimeZone($requestedTimeZone)
78 {
79 static $cached = [];
80 if (isset($cached[$requestedTimeZone])) {
81 return $cached[$requestedTimeZone];
82 }
83
84 if (!in_array($requestedTimeZone, \DateTimeZone::listIdentifiers())) {
85 $requestedTimeZone = apply_filters('fluent_booking/fallback_timezone', self::getTimeZone(), $requestedTimeZone);
86 }
87
88 $cached[$requestedTimeZone] = $requestedTimeZone;
89 return $requestedTimeZone;
90 }
91
92 public static function convertToUtc($dateTime, $timezone, $format = 'Y-m-d H:i:s')
93 {
94 $timezone = self::getValidatedTimeZone($timezone);
95
96 $dateTime = new \DateTime($dateTime, new \DateTimeZone($timezone));
97 $dateTime->setTimezone(new \DateTimeZone('UTC'));
98 return $dateTime->format($format);
99 }
100
101 public static function convertFromUtc($dateTime, $timezone, $format = 'Y-m-d H:i:s')
102 {
103 $dateTime = new \DateTime($dateTime, new \DateTimeZone('UTC'));
104
105 if ($timezone != 'UTC') {
106 $timezone = self::getValidatedTimeZone($timezone);
107 $dateTime->setTimezone(new \DateTimeZone($timezone));
108 }
109
110 return $dateTime->format($format);
111 }
112
113 public static function convertToTimeZone($dateTime, $fromTimeZone, $toTimeZone, $format = 'Y-m-d H:i:s', $date = null)
114 {
115 if ($fromTimeZone == $toTimeZone) {
116 return gmdate($format, strtotime($dateTime)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
117 }
118
119 if ($date) {
120 $dateTime = gmdate('Y-m-d H:i', strtotime($date . ' ' . gmdate('H:i', strtotime($dateTime)))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
121 }
122
123 $fromTimeZone = self::getValidatedTimeZone($fromTimeZone);
124 $toTimeZone = self::getValidatedTimeZone($toTimeZone);
125
126 $dateTime = new \DateTime($dateTime, new \DateTimeZone($fromTimeZone));
127 $dateTime->setTimezone(new \DateTimeZone($toTimeZone));
128 return $dateTime->format($format);
129 }
130
131 public static function getDateWithoutDST($timezone)
132 {
133 if (!self::isDaylightSavingActive('2024-01-03', $timezone)) {
134 return '2024-01-03';
135 }
136 return '2024-06-03';
137 }
138
139 public static function convertToIso($dateTime, $fromTimeZone = 'UTC')
140 {
141 $fromTimeZone = self::getValidatedTimeZone($fromTimeZone);
142
143 $dateTime = new \DateTime($dateTime, new \DateTimeZone($fromTimeZone));
144 return $dateTime->format('Y-m-d\TH:i:s\Z');
145 }
146
147 public static function convertFromIso($dateTime, $toTimeZone = 'UTC')
148 {
149 $toTimeZone = self::getValidatedTimeZone($toTimeZone);
150 $dateTime = new \DateTime($dateTime, new \DateTimeZone('UTC'));
151 $dateTime->setTimezone(new \DateTimeZone($toTimeZone));
152 return $dateTime->format('Y-m-d H:i:s');
153 }
154
155 public static function getIsoDurationInMinutes($duration)
156 {
157 if (!$duration) {
158 return 0;
159 }
160
161 $duration = new \DateInterval($duration);
162
163 return ($duration->d * 24 * 60) + ($duration->h * 60) + ($duration->i) + ($duration->s / 60);
164 }
165
166 public static function getTimestamp($timezone = 'UTC')
167 {
168 $timezone = self::getValidatedTimeZone($timezone);
169 $dateTime = new \DateTime(gmdate('Y-m-d H:i:s'), new \DateTimeZone('UTC')); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
170 $dateTime->setTimezone(new \DateTimeZone($timezone));
171 $date = $dateTime->format('Y-m-d H:i:s');
172 return strtotime($date);
173 }
174
175 public static function formatToLocale($dateTime, $for = 'date')
176 {
177 // $for can be date | date_time | time
178 $dateFormat = (string) get_option('date_format', '');
179
180 if ($for == 'date_time') {
181 $dateFormat .= ' ' . (string) get_option('time_format', '');
182 } elseif ($for == 'time') {
183 $dateFormat = (string) get_option('time_format', '');
184 }
185
186 return date_i18n($dateFormat, strtotime($dateTime)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
187 }
188
189 public static function getFormattedDate($date, $from = '', $to = 'Y-m-d')
190 {
191 if (!$from) {
192 $from = (string) get_option('date_format', '');
193 }
194
195 $date = \DateTime::createFromFormat($from, $date);
196 return $date ? $date->format($to) : '';
197 }
198
199 public static function getAvailableDateFormats()
200 {
201 $dateFormats = apply_filters('fluent_booking/available_date_formats', [
202 'm/d/Y' => 'm/d/Y - (Ex: 05/20/2024)', // USA
203 'd/m/Y' => 'd/m/Y - (Ex: 20/05/2024)', // Canada, UK
204 'd.m.Y' => 'd.m.Y - (Ex: 20.05.2024)', // Germany
205 'n/j/y' => 'n/j/y - (Ex: 5/20/24)',
206 'm/d/y' => 'm/d/y - (Ex: 05/20/24)',
207 'M/d/Y' => 'M/d/Y - (Ex: May/20/2024)',
208 'y/m/d' => 'y/m/d - (Ex: 24/05/20)',
209 'Y-m-d' => 'Y-m-d - (Ex: 2024-05-20)',
210 'd-M-y' => 'd-M-y - (Ex: 20-May-24)',
211 'F j, Y' => 'F j, Y - (Ex: May 20, 2024)'
212 ]);
213
214 $formatted = [];
215 foreach ($dateFormats as $format => $label) {
216 $formatted[] = [
217 'label' => $label,
218 'value' => $format,
219 ];
220 }
221 return $formatted;
222 }
223
224 public static function getFormattedEventTime($eventTime)
225 {
226 $timeZone = self::guessTimeZone();
227
228 $convertedTime = self::convertToTimeZone($eventTime, 'UTC', $timeZone);
229
230 $eventTime = self::formatToLocale($convertedTime, 'time');
231
232 $eventDate = self::formatToLocale($convertedTime, 'date');
233
234 return $eventTime . ', ' . $eventDate . ' (' . $timeZone . ')';
235 }
236
237 public static function convertPhpDateToDayJSFormay($phpFormat)
238 {
239 // Mapping PHP date format characters to Day.js format characters
240 $replacements = [
241 // Day
242 'd' => 'DD', // Day of the month, 2 digits with leading zeros
243 'D' => 'ddd', // A textual representation of a day, three letters
244 'j' => 'D', // Day of the month without leading zeros
245 'l' => 'dddd', // A full textual representation of the day of the week
246 'N' => 'E', // ISO-8601 numeric representation of the day of the week
247 'S' => 'o', // English ordinal suffix for the day of the month, 2 characters
248 'w' => 'd', // Numeric representation of the day of the week
249 'z' => 'DDD', // The day of the year (starting from 0)
250
251 // Week
252 'W' => 'W', // ISO-8601 week number of year, weeks starting on Monday
253
254 // Month
255 'F' => 'MMMM', // A full textual representation of a month
256 'm' => 'MM', // Numeric representation of a month, with leading zeros
257 'M' => 'MMM', // A short textual representation of a month, three letters
258 'n' => 'M', // Numeric representation of a month, without leading zeros
259 't' => '', // Not supported in Day.js (Number of days in the given month)
260
261 // Year
262 'L' => '', // Not supported in Day.js (Whether it's a leap year)
263 'o' => 'GGGG', // ISO-8601 week-numbering year
264 'Y' => 'YYYY', // A full numeric representation of a year, 4 digits
265 'y' => 'YY', // A two digit representation of a year
266
267 // Time
268 'a' => 'a', // Lowercase Ante meridiem and Post meridiem
269 'A' => 'A', // Uppercase Ante meridiem and Post meridiem
270 'B' => '', // Not supported in Day.js (Swatch Internet time)
271 'g' => 'h', // 12-hour format of an hour without leading zeros
272 'G' => 'H', // 24-hour format of an hour without leading zeros
273 'h' => 'hh', // 12-hour format of an hour with leading zeros
274 'H' => 'HH', // 24-hour format of an hour with leading zeros
275 'i' => 'mm', // Minutes with leading zeros
276 's' => 'ss', // Seconds with leading zeros
277 'u' => 'SSS', // Milliseconds (Day.js uses SSS for fractional seconds)
278 'v' => 'SSS', // Milliseconds (Day.js uses SSS for fractional seconds)
279
280 // Timezone
281 'e' => '', // Not supported in Day.js (Timezone identifier)
282 'I' => '', // Not supported in Day.js (Whether or not the date is in daylight saving time)
283 'O' => 'ZZ', // Difference to Greenwich time (GMT) in hours
284 'P' => 'Z', // Difference to Greenwich time (GMT) with colon between hours and minutes
285 'T' => '', // Not supported in Day.js (Timezone abbreviation)
286 'Z' => '', // Not supported in Day.js (Timezone offset in seconds)
287
288 // Full Date/Time
289 'c' => 'YYYY-MM-DDTHH:mm:ssZ', // ISO 8601 date
290 'r' => 'ddd, DD MMM YYYY HH:mm:ss ZZ', // RFC 2822 formatted date
291 'U' => 'X', // Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
292 ];
293
294 // Replace each PHP date format character with Day.js equivalent
295 $dayjsFormat = "";
296
297 for ($i = 0; $i < strlen($phpFormat); $i++) {
298 $char = $phpFormat[$i];
299
300 // Check if the character is escaped
301 if ($char === "\\") {
302 // Day.js escapes literal text with square brackets, not backslashes
303 $i++;
304 if ($i < strlen($phpFormat)) {
305 $dayjsFormat .= "[" . $phpFormat[$i] . "]";
306 }
307 continue;
308 }
309
310 // Add the mapped character or the character itself if not found in the mapping
311 $dayjsFormat .= $replacements[$char] ?? $char;
312 }
313
314 return $dayjsFormat;
315 }
316
317 public static function getDateFormatter($isDayJs = false)
318 {
319 $format = get_option('date_format');
320 if ($isDayJs) {
321 return self::convertPhpDateToDayJSFormay($format);
322 }
323
324 return $format;
325 }
326
327 public static function getTimeFormatter($isDayJs = false)
328 {
329 $format = get_option('time_format');
330 if ($isDayJs) {
331 return self::convertPhpDateToDayJSFormay($format);
332 }
333
334 return $format;
335 }
336
337 public static function getTodayDate($timezone = 'UTC', $format = 'Y-m-d')
338 {
339 $timezone = self::getValidatedTimeZone($timezone);
340 $dateTime = new \DateTime('now', new \DateTimeZone($timezone));
341 return $dateTime->format($format);
342 }
343
344 public static function getDayDifference($dateTime, $fromTimeZone, $toTimeZone, $refDate = 'now')
345 {
346 $fromTimeZone = self::getValidatedTimeZone($fromTimeZone);
347 $toTimeZone = self::getValidatedTimeZone($toTimeZone);
348
349 if ($refDate != 'now') {
350 $dateTime = gmdate('Y-m-d H:i', strtotime($refDate . ' ' . gmdate('H:i', strtotime($dateTime)))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
351 }
352
353 $currentDate = new \DateTime($refDate, new \DateTimeZone($fromTimeZone));
354
355 $originalDateTime = new \DateTime($dateTime, new \DateTimeZone($fromTimeZone));
356
357 $originalDateTime->setTimezone(new \DateTimeZone($toTimeZone));
358
359 return $originalDateTime->format('z') - $currentDate->format('z');
360 }
361
362 public static function getDaylightSavingTime($timezone)
363 {
364 $timezone = self::getValidatedTimeZone($timezone);
365 $timezone = new \DateTimeZone($timezone);
366 $dateTime = new \DateTime('2024-01-01', $timezone);
367
368 $currentOffset = $timezone->getOffset($dateTime);
369
370 $sixMonthsAgo = clone $dateTime;
371 $sixMonthsAgo->modify('+6 month');
372 $offsetBefore = $timezone->getOffset($sixMonthsAgo);
373
374 $dstDifference = $currentOffset - $offsetBefore;
375
376 return abs($dstDifference) / 60;
377 }
378
379 public static function isDaylightSavingActive($dateTime, $timezone)
380 {
381 if (!$dateTime || !$timezone) {
382 return false;
383 }
384
385 $timezone = self::getValidatedTimeZone($timezone);
386
387 $dateTimeObject = new \DateTime($dateTime, new \DateTimeZone($timezone));
388
389 $isDstActive = $dateTimeObject->format('I');
390
391 if ($timezone == 'Europe/Dublin') {
392 return !$isDstActive;
393 }
394
395 return $isDstActive;
396 }
397
398 public static function getI18nDateTimeConfig()
399 {
400 return apply_filters('fluent_booking/i18n_date_time_config', [
401 'weekdays' => array(
402 'sunday' => _x('Sunday', 'calendar day full', 'fluent-booking'),
403 'monday' => _x('Monday', 'calendar day full', 'fluent-booking'),
404 'tuesday' => _x('Tuesday', 'calendar day full', 'fluent-booking'),
405 'wednesday' => _x('Wednesday', 'calendar day full', 'fluent-booking'),
406 'thursday' => _x('Thursday', 'calendar day full', 'fluent-booking'),
407 'friday' => _x('Friday', 'calendar day full', 'fluent-booking'),
408 'saturday' => _x('Saturday', 'calendar day full', 'fluent-booking'),
409 ),
410 'months' => array(
411 'January' => _x('January', 'calendar month name full', 'fluent-booking'),
412 'February' => _x('February', 'calendar month name full', 'fluent-booking'),
413 'March' => _x('March', 'calendar month name full', 'fluent-booking'),
414 'April' => _x('April', 'calendar month name full', 'fluent-booking'),
415 'May' => _x('May', 'calendar month name full', 'fluent-booking'),
416 'June' => _x('June', 'calendar month name full', 'fluent-booking'),
417 'July' => _x('July', 'calendar month name full', 'fluent-booking'),
418 'August' => _x('August', 'calendar month name full', 'fluent-booking'),
419 'September' => _x('September', 'calendar month name full', 'fluent-booking'),
420 'October' => _x('October', 'calendar month name full', 'fluent-booking'),
421 'November' => _x('November', 'calendar month name full', 'fluent-booking'),
422 'December' => _x('December', 'calendar month name full', 'fluent-booking')
423 ),
424 'weekdaysShort' => array(
425 'sun' => _x('Sun', 'calendar day short', 'fluent-booking'),
426 'mon' => _x('Mon', 'calendar day short', 'fluent-booking'),
427 'tue' => _x('Tue', 'calendar day short', 'fluent-booking'),
428 'wed' => _x('Wed', 'calendar day short', 'fluent-booking'),
429 'thu' => _x('Thu', 'calendar day short', 'fluent-booking'),
430 'fri' => _x('Fri', 'calendar day short', 'fluent-booking'),
431 'sat' => _x('Sat', 'calendar day short', 'fluent-booking')
432 ),
433 'monthsShort' => array(
434 'jan' => _x('Jan', 'calendar month name short', 'fluent-booking'),
435 'feb' => _x('Feb', 'calendar month name short', 'fluent-booking'),
436 'mar' => _x('Mar', 'calendar month name short', 'fluent-booking'),
437 'apr' => _x('Apr', 'calendar month name short', 'fluent-booking'),
438 'may' => _x('May', 'calendar month name short', 'fluent-booking'),
439 'jun' => _x('Jun', 'calendar month name short', 'fluent-booking'),
440 'jul' => _x('Jul', 'calendar month name short', 'fluent-booking'),
441 'aug' => _x('Aug', 'calendar month name short', 'fluent-booking'),
442 'sep' => _x('Sep', 'calendar month name short', 'fluent-booking'),
443 'oct' => _x('Oct', 'calendar month name short', 'fluent-booking'),
444 'nov' => _x('Nov', 'calendar month name short', 'fluent-booking'),
445 'dec' => _x('Dec', 'calendar month name short', 'fluent-booking')
446 ),
447 'numericSystem' => _x('0_1_2_3_4_5_6_7_8_9', 'calendar numeric system - Sequence must need to maintained', 'fluent-booking'),
448 ]);
449 }
450 }
451