PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.6.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.6.0
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 1.6.0, at app/Services/DateTimeHelper.php

386 lines 13.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 = $_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
95 $timezone = self::getValidatedTimeZone($timezone);
96
97 $dateTime = new \DateTime($dateTime, new \DateTimeZone($timezone));
98 $dateTime->setTimezone(new \DateTimeZone('UTC'));
99 return $dateTime->format($format);
100 }
101
102 public static function convertFromUtc($dateTime, $timezone, $format = 'Y-m-d H:i:s')
103 {
104 $dateTime = new \DateTime($dateTime, new \DateTimeZone('UTC'));
105
106 if ($timezone != 'UTC') {
107 $timezone = self::getValidatedTimeZone($timezone);
108 $dateTime->setTimezone(new \DateTimeZone($timezone));
109 }
110
111 return $dateTime->format($format);
112 }
113
114 public static function convertToTimeZone($dateTime, $fromTimeZone, $toTimeZone, $format = 'Y-m-d H:i:s', $date = null)
115 {
116 if ($fromTimeZone == $toTimeZone) {
117 return gmdate($format, strtotime($dateTime)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
118 }
119
120 if ($date) {
121 $dateTime = gmdate('Y-m-d H:i', strtotime($date . ' ' . gmdate('H:i', strtotime($dateTime)))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
122 }
123
124 $fromTimeZone = self::getValidatedTimeZone($fromTimeZone);
125 $toTimeZone = self::getValidatedTimeZone($toTimeZone);
126
127 $dateTime = new \DateTime($dateTime, new \DateTimeZone($fromTimeZone));
128 $dateTime->setTimezone(new \DateTimeZone($toTimeZone));
129 return $dateTime->format($format);
130 }
131
132 public static function getDateWithoutDST($timezone)
133 {
134 if (!self::isDaylightSavingActive('2024-01-03', $timezone)) {
135 return '2024-01-03';
136 }
137 return '2024-06-03';
138 }
139
140 public static function convertToIso($dateTime, $fromTimeZone = 'UTC')
141 {
142 $fromTimeZone = self::getValidatedTimeZone($fromTimeZone);
143
144 $dateTime = new \DateTime($dateTime, new \DateTimeZone($fromTimeZone));
145 return $dateTime->format('Y-m-d\TH:i:s\Z');
146 }
147
148 public static function convertFromIso($dateTime, $toTimeZone = 'UTC')
149 {
150 $toTimeZone = self::getValidatedTimeZone($toTimeZone);
151 $dateTime = new \DateTime($dateTime, new \DateTimeZone('UTC'));
152 $dateTime->setTimezone(new \DateTimeZone($toTimeZone));
153 return $dateTime->format('Y-m-d H:i:s');
154 }
155
156 public static function getTimestamp($timezone = 'UTC')
157 {
158 $timezone = self::getValidatedTimeZone($timezone);
159 $dateTime = new \DateTime(gmdate('Y-m-d H:i:s'), new \DateTimeZone('UTC')); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
160 $dateTime->setTimezone(new \DateTimeZone($timezone));
161 $date = $dateTime->format('Y-m-d H:i:s');
162 return strtotime($date);
163 }
164
165 public static function formatToLocale($dateTime, $for = 'date')
166 {
167 // $for can be date | date_time | time
168 $dateFormat = get_option('date_format');
169
170 if ($for == 'date_time') {
171 $dateFormat .= ' ' . get_option('time_format');
172 } elseif ($for == 'time') {
173 $dateFormat = get_option('time_format');
174 }
175
176 return date_i18n($dateFormat, strtotime($dateTime)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
177 }
178
179 public static function getAvailableDateFormats()
180 {
181 $dateFormats = apply_filters('fluent_booking/available_date_formats', [
182 'm/d/Y' => 'm/d/Y - (Ex: 05/20/2024)', // USA
183 'd/m/Y' => 'd/m/Y - (Ex: 20/05/2024)', // Canada, UK
184 'd.m.Y' => 'd.m.Y - (Ex: 20.05.2024)', // Germany
185 'n/j/y' => 'n/j/y - (Ex: 5/20/24)',
186 'm/d/y' => 'm/d/y - (Ex: 05/20/24)',
187 'M/d/Y' => 'M/d/Y - (Ex: May/20/2024)',
188 'y/m/d' => 'y/m/d - (Ex: 24/05/20)',
189 'Y-m-d' => 'Y-m-d - (Ex: 2024-05-20)',
190 'd-M-y' => 'd-M-y - (Ex: 20-May-24)',
191 'F j, Y' => 'F j, Y - (Ex: May 20, 2024)'
192 ]);
193
194 $formatted = [];
195 foreach ($dateFormats as $format => $label) {
196 $formatted[] = [
197 'label' => $label,
198 'value' => $format,
199 ];
200 }
201 return $formatted;
202 }
203
204 public static function getFormattedEventTime($eventTime)
205 {
206 $timeZone = self::guessTimeZone();
207
208 $convertedTime = self::convertToTimeZone($eventTime, 'UTC', $timeZone);
209
210 $eventTime = self::formatToLocale($convertedTime, 'time');
211
212 $eventDate = self::formatToLocale($convertedTime, 'date');
213
214 return $eventTime . ', ' . $eventDate . ' (' . $timeZone . ')';
215 }
216
217 public static function convertPhpDateToDayJSFormay($phpFormat)
218 {
219 // Mapping PHP date format characters to Day.js format characters
220 $replacements = [
221 // Day
222 'd' => 'DD', // Day of the month, 2 digits with leading zeros
223 'D' => 'ddd', // A textual representation of a day, three letters
224 'j' => 'D', // Day of the month without leading zeros
225 'l' => 'dddd', // A full textual representation of the day of the week
226 'N' => 'E', // ISO-8601 numeric representation of the day of the week
227 'S' => 'o', // English ordinal suffix for the day of the month, 2 characters
228 'w' => 'd', // Numeric representation of the day of the week
229 'z' => 'DDD', // The day of the year (starting from 0)
230
231 // Week
232 'W' => 'W', // ISO-8601 week number of year, weeks starting on Monday
233
234 // Month
235 'F' => 'MMMM', // A full textual representation of a month
236 'm' => 'MM', // Numeric representation of a month, with leading zeros
237 'M' => 'MMM', // A short textual representation of a month, three letters
238 'n' => 'M', // Numeric representation of a month, without leading zeros
239 't' => '', // Not supported in Day.js (Number of days in the given month)
240
241 // Year
242 'L' => '', // Not supported in Day.js (Whether it's a leap year)
243 'o' => 'GGGG', // ISO-8601 week-numbering year
244 'Y' => 'YYYY', // A full numeric representation of a year, 4 digits
245 'y' => 'YY', // A two digit representation of a year
246
247 // Time
248 'a' => 'a', // Lowercase Ante meridiem and Post meridiem
249 'A' => 'A', // Uppercase Ante meridiem and Post meridiem
250 'B' => '', // Not supported in Day.js (Swatch Internet time)
251 'g' => 'h', // 12-hour format of an hour without leading zeros
252 'G' => 'H', // 24-hour format of an hour without leading zeros
253 'h' => 'hh', // 12-hour format of an hour with leading zeros
254 'H' => 'HH', // 24-hour format of an hour with leading zeros
255 'i' => 'mm', // Minutes with leading zeros
256 's' => 'ss', // Seconds with leading zeros
257 'u' => 'SSS', // Milliseconds (Day.js uses SSS for fractional seconds)
258 'v' => 'SSS', // Milliseconds (Day.js uses SSS for fractional seconds)
259
260 // Timezone
261 'e' => '', // Not supported in Day.js (Timezone identifier)
262 'I' => '', // Not supported in Day.js (Whether or not the date is in daylight saving time)
263 'O' => 'ZZ', // Difference to Greenwich time (GMT) in hours
264 'P' => 'Z', // Difference to Greenwich time (GMT) with colon between hours and minutes
265 'T' => '', // Not supported in Day.js (Timezone abbreviation)
266 'Z' => '', // Not supported in Day.js (Timezone offset in seconds)
267
268 // Full Date/Time
269 'c' => 'YYYY-MM-DDTHH:mm:ssZ', // ISO 8601 date
270 'r' => 'ddd, DD MMM YYYY HH:mm:ss ZZ', // RFC 2822 formatted date
271 'U' => 'X', // Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
272 ];
273
274 // Replace each PHP date format character with Day.js equivalent
275 $dayjsFormat = "";
276
277 for ($i = 0; $i < strlen($phpFormat); $i++) {
278 $char = $phpFormat[$i];
279
280 // Special handling for G\hi pattern
281 if ($char === 'G' && $i + 2 < strlen($phpFormat) &&
282 $phpFormat[$i + 1] === '\\' && $phpFormat[$i + 2] === 'h') {
283 $dayjsFormat .= 'H[h]';
284 $i += 2;
285 continue;
286 }
287
288 // Check if the character is escaped
289 if ($char === "\\") {
290 // Add the next character to the result as is, without mapping
291 $i++;
292 if ($i < strlen($phpFormat)) {
293 $dayjsFormat .= "\\" . $phpFormat[$i];
294 }
295 continue;
296 }
297
298 // Add the mapped character or the character itself if not found in the mapping
299 $dayjsFormat .= $replacements[$char] ?? $char;
300 }
301
302 return $dayjsFormat;
303 }
304
305 public static function getDateFormatter($isDayJs = false)
306 {
307 $format = get_option('date_format');
308 if ($isDayJs) {
309 return self::convertPhpDateToDayJSFormay($format);
310 }
311
312 return $format;
313 }
314
315 public static function getTimeFormatter($isDayJs = false)
316 {
317 $format = get_option('time_format');
318 if ($isDayJs) {
319 return self::convertPhpDateToDayJSFormay($format);
320 }
321
322 return $format;
323 }
324
325 public static function getTodayDate($timezone = 'UTC', $format = 'Y-m-d')
326 {
327 $timezone = self::getValidatedTimeZone($timezone);
328 $dateTime = new \DateTime('now', new \DateTimeZone($timezone));
329 return $dateTime->format($format);
330 }
331
332 public static function getDayDifference($dateTime, $fromTimeZone, $toTimeZone, $refDate = 'now')
333 {
334 $fromTimeZone = self::getValidatedTimeZone($fromTimeZone);
335 $toTimeZone = self::getValidatedTimeZone($toTimeZone);
336
337 if ($refDate != 'now') {
338 $dateTime = gmdate('Y-m-d H:i', strtotime($refDate . ' ' . gmdate('H:i', strtotime($dateTime)))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
339 }
340
341 $currentDate = new \DateTime($refDate, new \DateTimeZone($fromTimeZone));
342
343 $originalDateTime = new \DateTime($dateTime, new \DateTimeZone($fromTimeZone));
344
345 $originalDateTime->setTimezone(new \DateTimeZone($toTimeZone));
346
347 return $originalDateTime->format('z') - $currentDate->format('z');
348 }
349
350 public static function getDaylightSavingTime($timezone)
351 {
352 $timezone = self::getValidatedTimeZone($timezone);
353 $timezone = new \DateTimeZone($timezone);
354 $dateTime = new \DateTime('2024-01-01', $timezone);
355
356 $currentOffset = $timezone->getOffset($dateTime);
357
358 $sixMonthsAgo = clone $dateTime;
359 $sixMonthsAgo->modify('+6 month');
360 $offsetBefore = $timezone->getOffset($sixMonthsAgo);
361
362 $dstDifference = $currentOffset - $offsetBefore;
363
364 return abs($dstDifference) / 60;
365 }
366
367 public static function isDaylightSavingActive($dateTime, $timezone)
368 {
369 if (!$dateTime || !$timezone) {
370 return false;
371 }
372
373 $timezone = self::getValidatedTimeZone($timezone);
374
375 $dateTimeObject = new \DateTime($dateTime, new \DateTimeZone($timezone));
376
377 $isDstActive = $dateTimeObject->format('I');
378
379 if ($timezone == 'Europe/Dublin') {
380 return !$isDstActive;
381 }
382
383 return $isDstActive;
384 }
385 }
386