Completed
Branch FET/10339/exit-modal-for-ee-de... (81712e)
by
unknown
29:29 queued 13:19
created

EEH_DTT_Helper::setTimezone()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
use EventEspresso\core\services\helpers\datetime\HelperInterface;
4
use EventEspresso\core\exceptions\InvalidDataTypeException;
5
use EventEspresso\core\exceptions\InvalidInterfaceException;
6
use EventEspresso\core\services\loaders\LoaderFactory;
7
8
defined('EVENT_ESPRESSO_VERSION') || exit('NO direct script access allowed');
9
10
11
12
/**
13
 * EEH_DTT_Helper
14
 * This is a helper utility class containing a variety for date time formatting helpers for Event Espresso.
15
 *
16
 * @package         Event Espresso
17
 * @subpackage      /helpers/EEH_DTT_Helper.helper.php
18
 * @author          Darren Ethier
19
 */
20
class EEH_DTT_Helper
21
{
22
23
24
    /**
25
     * return the timezone set for the WP install
26
     *
27
     * @return string valid timezone string for PHP DateTimeZone() class
28
     * @throws InvalidArgumentException
29
     * @throws InvalidDataTypeException
30
     * @throws InvalidInterfaceException
31
     */
32
    public static function get_timezone()
33
    {
34
        return EEH_DTT_Helper::get_valid_timezone_string();
35
    }
36
37
38
    /**
39
     * get_valid_timezone_string
40
     *    ensures that a valid timezone string is returned
41
     *
42
     * @param string $timezone_string
43
     * @return string
44
     * @throws InvalidArgumentException
45
     * @throws InvalidDataTypeException
46
     * @throws InvalidInterfaceException
47
     */
48
    public static function get_valid_timezone_string($timezone_string = '')
49
    {
50
        return self::getHelperAdapter()->getValidTimezoneString($timezone_string);
51
    }
52
53
54
    /**
55
     * This only purpose for this static method is to validate that the incoming timezone is a valid php timezone.
56
     *
57
     * @static
58
     * @param  string $timezone_string Timezone string to check
59
     * @param bool    $throw_error
60
     * @return bool
61
     * @throws InvalidArgumentException
62
     * @throws InvalidDataTypeException
63
     * @throws InvalidInterfaceException
64
     */
65
    public static function validate_timezone($timezone_string, $throw_error = true)
66
    {
67
        return self::getHelperAdapter()->validateTimezone($timezone_string, $throw_error);
68
    }
69
70
71
    /**
72
     * This returns a string that can represent the provided gmt offset in format that can be passed into
73
     * DateTimeZone.  This is NOT a string that can be passed as a value on the WordPress timezone_string option.
74
     *
75
     * @param float|string $gmt_offset
76
     * @return string
77
     * @throws InvalidArgumentException
78
     * @throws InvalidDataTypeException
79
     * @throws InvalidInterfaceException
80
     */
81
    public static function get_timezone_string_from_gmt_offset($gmt_offset = '')
82
    {
83
        return self::getHelperAdapter()->getTimezoneStringFromGmtOffset($gmt_offset);
84
    }
85
86
87
    /**
88
     * Gets the site's GMT offset based on either the timezone string
89
     * (in which case teh gmt offset will vary depending on the location's
90
     * observance of daylight savings time) or the gmt_offset wp option
91
     *
92
     * @return int seconds offset
93
     * @throws InvalidArgumentException
94
     * @throws InvalidDataTypeException
95
     * @throws InvalidInterfaceException
96
     */
97
    public static function get_site_timezone_gmt_offset()
98
    {
99
        return self::getHelperAdapter()->getSiteTimezoneGmtOffset();
100
    }
101
102
103
    /**
104
     * Depending on PHP version,
105
     * there might not be valid current timezone strings to match these gmt_offsets in its timezone tables.
106
     * To get around that, for these fringe timezones we bump them to a known valid offset.
107
     * This method should ONLY be called after first verifying an timezone_string cannot be retrieved for the offset.
108
     *
109
     * @deprecated 4.9.54.rc    Developers this was always meant to only be an internally used method.  This will be
110
     *                          removed in a future version of EE.
111
     * @param int $gmt_offset
112
     * @return int
113
     * @throws InvalidArgumentException
114
     * @throws InvalidDataTypeException
115
     * @throws InvalidInterfaceException
116
     */
117
    public static function adjust_invalid_gmt_offsets($gmt_offset = 0)
118
    {
119
        return self::getHelperAdapter()->adjustInvalidGmtOffsets($gmt_offset);
120
    }
121
122
123
    /**
124
     * get_timezone_string_from_abbreviations_list
125
     *
126
     * @deprecated 4.9.54.rc  Developers, this was never intended to be public.  This is a soft deprecation for now.
127
     *                        If you are using this, you'll want to work out an alternate way of getting the value.
128
     * @param int  $gmt_offset
129
     * @param bool $coerce If true, we attempt to coerce with our adjustment table @see self::adjust_invalid_gmt_offset.
130
     * @return string
131
     * @throws EE_Error
132
     * @throws InvalidArgumentException
133
     * @throws InvalidDataTypeException
134
     * @throws InvalidInterfaceException
135
     */
136
    public static function get_timezone_string_from_abbreviations_list($gmt_offset = 0, $coerce = true)
137
    {
138
        $gmt_offset =  (int) $gmt_offset;
139
        /** @var array[] $abbreviations */
140
        $abbreviations = DateTimeZone::listAbbreviations();
141 View Code Duplication
        foreach ($abbreviations as $abbreviation) {
142
            foreach ($abbreviation as $timezone) {
143
                if ((int) $timezone['offset'] === $gmt_offset && (bool) $timezone['dst'] === false) {
144
                    try {
145
                        $offset = self::get_timezone_offset(new DateTimeZone($timezone['timezone_id']));
146
                        if ($offset !== $gmt_offset) {
147
                            continue;
148
                        }
149
                        return $timezone['timezone_id'];
150
                    } catch (Exception $e) {
151
                        continue;
152
                    }
153
                }
154
            }
155
        }
156
        //if $coerce is true, let's see if we can get a timezone string after the offset is adjusted
157
        if ($coerce === true) {
158
            $timezone_string = self::get_timezone_string_from_abbreviations_list(
0 ignored issues
show
Deprecated Code introduced by
The method EEH_DTT_Helper::get_time...om_abbreviations_list() has been deprecated with message: 4.9.54.rc Developers, this was never intended to be public. This is a soft deprecation for now. If you are using this, you'll want to work out an alternate way of getting the value.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
159
                self::adjust_invalid_gmt_offsets($gmt_offset),
0 ignored issues
show
Deprecated Code introduced by
The method EEH_DTT_Helper::adjust_invalid_gmt_offsets() has been deprecated with message: 4.9.54.rc Developers this was always meant to only be an internally used method. This will be removed in a future version of EE.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
160
                false
161
            );
162
            if ($timezone_string) {
163
                return $timezone_string;
164
            }
165
        }
166
        throw new EE_Error(
167
            sprintf(
168
                esc_html__(
169
                    'The provided GMT offset (%1$s), is invalid, please check with %2$sthis list%3$s for what valid timezones can be used',
170
                    'event_espresso'
171
                ),
172
                $gmt_offset / HOUR_IN_SECONDS,
173
                '<a href="http://www.php.net/manual/en/timezones.php">',
174
                '</a>'
175
            )
176
        );
177
    }
178
179
180
    /**
181
     * Get Timezone Transitions
182
     *
183
     * @param DateTimeZone $date_time_zone
184
     * @param int|null     $time
185
     * @param bool         $first_only
186
     * @return array
187
     * @throws InvalidArgumentException
188
     * @throws InvalidDataTypeException
189
     * @throws InvalidInterfaceException
190
     */
191
    public static function get_timezone_transitions(DateTimeZone $date_time_zone, $time = null, $first_only = true)
192
    {
193
        return self::getHelperAdapter()->getTimezoneTransitions($date_time_zone, $time, $first_only);
194
    }
195
196
197
    /**
198
     * Get Timezone Offset for given timezone object.
199
     *
200
     * @param DateTimeZone $date_time_zone
201
     * @param null         $time
202
     * @return mixed
203
     * @throws InvalidArgumentException
204
     * @throws InvalidDataTypeException
205
     * @throws InvalidInterfaceException
206
     */
207
    public static function get_timezone_offset(DateTimeZone $date_time_zone, $time = null)
208
    {
209
        return self::getHelperAdapter()->getTimezoneOffset($date_time_zone, $time);
210
    }
211
212
213
    /**
214
     * Prints a select input for the given timezone string.
215
     * @param string $timezone_string
216
     * @deprecatd 4.9.54.rc   Soft deprecation.  Consider using \EEH_DTT_Helper::wp_timezone_choice instead.
217
     * @throws InvalidArgumentException
218
     * @throws InvalidDataTypeException
219
     * @throws InvalidInterfaceException
220
     */
221
    public static function timezone_select_input($timezone_string = '')
222
    {
223
        self::getHelperAdapter()->timezoneSelectInput($timezone_string);
224
    }
225
226
227
    /**
228
     * This method will take an incoming unix timestamp and add the offset to it for the given timezone_string.
229
     * If no unix timestamp is given then time() is used.  If no timezone is given then the set timezone string for
230
     * the site is used.
231
     * This is used typically when using a Unix timestamp any core WP functions that expect their specially
232
     * computed timestamp (i.e. date_i18n() )
233
     *
234
     * @param int    $unix_timestamp                  if 0, then time() will be used.
235
     * @param string $timezone_string                 timezone_string. If empty, then the current set timezone for the
236
     *                                                site will be used.
237
     * @return int $unix_timestamp with the offset applied for the given timezone.
238
     * @throws InvalidArgumentException
239
     * @throws InvalidDataTypeException
240
     * @throws InvalidInterfaceException
241
     */
242
    public static function get_timestamp_with_offset($unix_timestamp = 0, $timezone_string = '')
243
    {
244
        return self::getHelperAdapter()->getTimestampWithOffset($unix_timestamp, $timezone_string);
245
    }
246
247
248
    /**
249
     *    _set_date_time_field
250
     *    modifies EE_Base_Class EE_Datetime_Field objects
251
     *
252
     * @param  EE_Base_Class $obj                 EE_Base_Class object
253
     * @param    DateTime    $DateTime            PHP DateTime object
254
     * @param  string        $datetime_field_name the datetime fieldname to be manipulated
255
     * @return EE_Base_Class
256
     * @throws EE_Error
257
     */
258
    protected static function _set_date_time_field(EE_Base_Class $obj, DateTime $DateTime, $datetime_field_name)
259
    {
260
        // grab current datetime format
261
        $current_format = $obj->get_format();
262
        // set new full timestamp format
263
        $obj->set_date_format(EE_Datetime_Field::mysql_date_format);
264
        $obj->set_time_format(EE_Datetime_Field::mysql_time_format);
265
        // set the new date value using a full timestamp format so that no data is lost
266
        $obj->set($datetime_field_name, $DateTime->format(EE_Datetime_Field::mysql_timestamp_format));
267
        // reset datetime formats
268
        $obj->set_date_format($current_format[0]);
269
        $obj->set_time_format($current_format[1]);
270
        return $obj;
271
    }
272
273
274
    /**
275
     *    date_time_add
276
     *    helper for doing simple datetime calculations on a given datetime from EE_Base_Class
277
     *    and modifying it IN the EE_Base_Class so you don't have to do anything else.
278
     *
279
     * @param  EE_Base_Class $obj                 EE_Base_Class object
280
     * @param  string        $datetime_field_name name of the EE_Datetime_Filed datatype db column to be manipulated
281
     * @param  string        $period              what you are adding. The options are (years, months, days, hours,
282
     *                                            minutes, seconds) defaults to years
283
     * @param  integer       $value               what you want to increment the time by
284
     * @return EE_Base_Class return the EE_Base_Class object so right away you can do something with it
285
     *                                            (chaining)
286
     * @throws EE_Error
287
     * @throws Exception
288
     */
289 View Code Duplication
    public static function date_time_add(EE_Base_Class $obj, $datetime_field_name, $period = 'years', $value = 1)
290
    {
291
        //get the raw UTC date.
292
        $DateTime = $obj->get_DateTime_object($datetime_field_name);
293
        $DateTime = EEH_DTT_Helper::calc_date($DateTime, $period, $value);
294
        return EEH_DTT_Helper::_set_date_time_field($obj, $DateTime, $datetime_field_name);
295
    }
296
297
298
    /**
299
     *    date_time_subtract
300
     *    same as date_time_add except subtracting value instead of adding.
301
     *
302
     * @param EE_Base_Class $obj
303
     * @param  string       $datetime_field_name name of the EE_Datetime_Filed datatype db column to be manipulated
304
     * @param string        $period
305
     * @param int           $value
306
     * @return EE_Base_Class
307
     * @throws EE_Error
308
     * @throws Exception
309
     */
310 View Code Duplication
    public static function date_time_subtract(EE_Base_Class $obj, $datetime_field_name, $period = 'years', $value = 1)
311
    {
312
        //get the raw UTC date
313
        $DateTime = $obj->get_DateTime_object($datetime_field_name);
314
        $DateTime = EEH_DTT_Helper::calc_date($DateTime, $period, $value, '-');
315
        return EEH_DTT_Helper::_set_date_time_field($obj, $DateTime, $datetime_field_name);
316
    }
317
318
319
    /**
320
     * Simply takes an incoming DateTime object and does calculations on it based on the incoming parameters
321
     *
322
     * @param  DateTime   $DateTime DateTime object
323
     * @param  string     $period   a value to indicate what interval is being used in the calculation. The options are
324
     *                              'years', 'months', 'days', 'hours', 'minutes', 'seconds'. Defaults to years.
325
     * @param  int|string $value    What you want to increment the date by
326
     * @param  string     $operand  What operand you wish to use for the calculation
327
     * @return DateTime return whatever type came in.
328
     * @throws Exception
329
     * @throws EE_Error
330
     */
331
    protected static function _modify_datetime_object(DateTime $DateTime, $period = 'years', $value = 1, $operand = '+')
332
    {
333
        if (! $DateTime instanceof DateTime) {
334
            throw new EE_Error(
335
                sprintf(
336
                    esc_html__('Expected a PHP DateTime object, but instead received %1$s', 'event_espresso'),
337
                    print_r($DateTime, true)
338
                )
339
            );
340
        }
341
        switch ($period) {
342
            case 'years' :
343
                $value = 'P' . $value . 'Y';
344
                break;
345
            case 'months' :
346
                $value = 'P' . $value . 'M';
347
                break;
348
            case 'weeks' :
349
                $value = 'P' . $value . 'W';
350
                break;
351
            case 'days' :
352
                $value = 'P' . $value . 'D';
353
                break;
354
            case 'hours' :
355
                $value = 'PT' . $value . 'H';
356
                break;
357
            case 'minutes' :
358
                $value = 'PT' . $value . 'M';
359
                break;
360
            case 'seconds' :
361
                $value = 'PT' . $value . 'S';
362
                break;
363
        }
364
        switch ($operand) {
365
            case '+':
366
                $DateTime->add(new DateInterval($value));
367
                break;
368
            case '-':
369
                $DateTime->sub(new DateInterval($value));
370
                break;
371
        }
372
        return $DateTime;
373
    }
374
375
376
    /**
377
     * Simply takes an incoming Unix timestamp and does calculations on it based on the incoming parameters
378
     *
379
     * @param  int     $timestamp Unix timestamp
380
     * @param  string  $period    a value to indicate what interval is being used in the calculation. The options are
381
     *                            'years', 'months', 'days', 'hours', 'minutes', 'seconds'. Defaults to years.
382
     * @param  integer $value     What you want to increment the date by
383
     * @param  string  $operand   What operand you wish to use for the calculation
384
     * @return int
385
     * @throws EE_Error
386
     */
387
    protected static function _modify_timestamp($timestamp, $period = 'years', $value = 1, $operand = '+')
388
    {
389
        if (! preg_match(EE_Datetime_Field::unix_timestamp_regex, $timestamp)) {
390
            throw new EE_Error(
391
                sprintf(
392
                    esc_html__('Expected a Unix timestamp, but instead received %1$s', 'event_espresso'),
393
                    print_r($timestamp, true)
394
                )
395
            );
396
        }
397
        switch ($period) {
398
            case 'years' :
399
                $value = YEAR_IN_SECONDS * $value;
400
                break;
401
            case 'months' :
402
                $value = YEAR_IN_SECONDS / 12 * $value;
403
                break;
404
            case 'weeks' :
405
                $value = WEEK_IN_SECONDS * $value;
406
                break;
407
            case 'days' :
408
                $value = DAY_IN_SECONDS * $value;
409
                break;
410
            case 'hours' :
411
                $value = HOUR_IN_SECONDS * $value;
412
                break;
413
            case 'minutes' :
414
                $value = MINUTE_IN_SECONDS * $value;
415
                break;
416
        }
417
        switch ($operand) {
418
            case '+':
419
                $timestamp += $value;
420
                break;
421
            case '-':
422
                $timestamp -= $value;
423
                break;
424
        }
425
        return $timestamp;
426
    }
427
428
429
    /**
430
     * Simply takes an incoming UTC timestamp or DateTime object and does calculations on it based on the incoming
431
     * parameters and returns the new timestamp or DateTime.
432
     *
433
     * @param  int | DateTime $DateTime_or_timestamp DateTime object or Unix timestamp
434
     * @param  string         $period                a value to indicate what interval is being used in the
435
     *                                               calculation. The options are 'years', 'months', 'days', 'hours',
436
     *                                               'minutes', 'seconds'. Defaults to years.
437
     * @param  integer        $value                 What you want to increment the date by
438
     * @param  string         $operand               What operand you wish to use for the calculation
439
     * @return mixed string|DateTime          return whatever type came in.
440
     * @throws Exception
441
     * @throws EE_Error
442
     */
443
    public static function calc_date($DateTime_or_timestamp, $period = 'years', $value = 1, $operand = '+')
444
    {
445
        if ($DateTime_or_timestamp instanceof DateTime) {
446
            return EEH_DTT_Helper::_modify_datetime_object(
447
                $DateTime_or_timestamp,
448
                $period,
449
                $value,
450
                $operand
451
            );
452
        }
453
        if (preg_match(EE_Datetime_Field::unix_timestamp_regex, $DateTime_or_timestamp)) {
454
            return EEH_DTT_Helper::_modify_timestamp(
455
                $DateTime_or_timestamp,
456
                $period,
457
                $value,
458
                $operand
459
            );
460
        }
461
        //error
462
        return $DateTime_or_timestamp;
463
    }
464
465
466
    /**
467
     * The purpose of this helper method is to receive an incoming format string in php date/time format
468
     * and spit out the js and moment.js equivalent formats.
469
     * Note, if no format string is given, then it is assumed the user wants what is set for WP.
470
     * Note, js date and time formats are those used by the jquery-ui datepicker and the jquery-ui date-
471
     * time picker.
472
     *
473
     * @see http://stackoverflow.com/posts/16725290/ for the code inspiration.
474
     * @param string $date_format_string
475
     * @param string $time_format_string
476
     * @return array
477
     *              array(
478
     *              'js' => array (
479
     *              'date' => //date format
480
     *              'time' => //time format
481
     *              ),
482
     *              'moment' => //date and time format.
483
     *              )
484
     */
485
    public static function convert_php_to_js_and_moment_date_formats(
486
        $date_format_string = null,
487
        $time_format_string = null
488
    ) {
489
        if ($date_format_string === null) {
490
            $date_format_string = (string) get_option('date_format');
491
        }
492
        if ($time_format_string === null) {
493
            $time_format_string = (string) get_option('time_format');
494
        }
495
        $date_format = self::_php_to_js_moment_converter($date_format_string);
496
        $time_format = self::_php_to_js_moment_converter($time_format_string);
497
        return array(
498
            'js'     => array(
499
                'date' => $date_format['js'],
500
                'time' => $time_format['js'],
501
            ),
502
            'moment' => $date_format['moment'] . ' ' . $time_format['moment'],
503
        );
504
    }
505
506
507
    /**
508
     * This converts incoming format string into js and moment variations.
509
     *
510
     * @param string $format_string incoming php format string
511
     * @return array js and moment formats.
512
     */
513
    protected static function _php_to_js_moment_converter($format_string)
514
    {
515
        /**
516
         * This is a map of symbols for formats.
517
         * The index is the php symbol, the equivalent values are in the array.
518
         *
519
         * @var array
520
         */
521
        $symbols_map          = array(
522
            // Day
523
            //01
524
            'd' => array(
525
                'js'     => 'dd',
526
                'moment' => 'DD',
527
            ),
528
            //Mon
529
            'D' => array(
530
                'js'     => 'D',
531
                'moment' => 'ddd',
532
            ),
533
            //1,2,...31
534
            'j' => array(
535
                'js'     => 'd',
536
                'moment' => 'D',
537
            ),
538
            //Monday
539
            'l' => array(
540
                'js'     => 'DD',
541
                'moment' => 'dddd',
542
            ),
543
            //ISO numeric representation of the day of the week (1-6)
544
            'N' => array(
545
                'js'     => '',
546
                'moment' => 'E',
547
            ),
548
            //st,nd.rd
549
            'S' => array(
550
                'js'     => '',
551
                'moment' => 'o',
552
            ),
553
            //numeric representation of day of week (0-6)
554
            'w' => array(
555
                'js'     => '',
556
                'moment' => 'd',
557
            ),
558
            //day of year starting from 0 (0-365)
559
            'z' => array(
560
                'js'     => 'o',
561
                'moment' => 'DDD' //note moment does not start with 0 so will need to modify by subtracting 1
562
            ),
563
            // Week
564
            //ISO-8601 week number of year (weeks starting on monday)
565
            'W' => array(
566
                'js'     => '',
567
                'moment' => 'w',
568
            ),
569
            // Month
570
            // January...December
571
            'F' => array(
572
                'js'     => 'MM',
573
                'moment' => 'MMMM',
574
            ),
575
            //01...12
576
            'm' => array(
577
                'js'     => 'mm',
578
                'moment' => 'MM',
579
            ),
580
            //Jan...Dec
581
            'M' => array(
582
                'js'     => 'M',
583
                'moment' => 'MMM',
584
            ),
585
            //1-12
586
            'n' => array(
587
                'js'     => 'm',
588
                'moment' => 'M',
589
            ),
590
            //number of days in given month
591
            't' => array(
592
                'js'     => '',
593
                'moment' => '',
594
            ),
595
            // Year
596
            //whether leap year or not 1/0
597
            'L' => array(
598
                'js'     => '',
599
                'moment' => '',
600
            ),
601
            //ISO-8601 year number
602
            'o' => array(
603
                'js'     => '',
604
                'moment' => 'GGGG',
605
            ),
606
            //1999...2003
607
            'Y' => array(
608
                'js'     => 'yy',
609
                'moment' => 'YYYY',
610
            ),
611
            //99...03
612
            'y' => array(
613
                'js'     => 'y',
614
                'moment' => 'YY',
615
            ),
616
            // Time
617
            // am/pm
618
            'a' => array(
619
                'js'     => 'tt',
620
                'moment' => 'a',
621
            ),
622
            // AM/PM
623
            'A' => array(
624
                'js'     => 'TT',
625
                'moment' => 'A',
626
            ),
627
            // Swatch Internet Time?!?
628
            'B' => array(
629
                'js'     => '',
630
                'moment' => '',
631
            ),
632
            //1...12
633
            'g' => array(
634
                'js'     => 'h',
635
                'moment' => 'h',
636
            ),
637
            //0...23
638
            'G' => array(
639
                'js'     => 'H',
640
                'moment' => 'H',
641
            ),
642
            //01...12
643
            'h' => array(
644
                'js'     => 'hh',
645
                'moment' => 'hh',
646
            ),
647
            //00...23
648
            'H' => array(
649
                'js'     => 'HH',
650
                'moment' => 'HH',
651
            ),
652
            //00..59
653
            'i' => array(
654
                'js'     => 'mm',
655
                'moment' => 'mm',
656
            ),
657
            //seconds... 00...59
658
            's' => array(
659
                'js'     => 'ss',
660
                'moment' => 'ss',
661
            ),
662
            //microseconds
663
            'u' => array(
664
                'js'     => '',
665
                'moment' => '',
666
            ),
667
        );
668
        $jquery_ui_format     = '';
669
        $moment_format        = '';
670
        $escaping             = false;
671
        $format_string_length = strlen($format_string);
672
        for ($i = 0; $i < $format_string_length; $i++) {
673
            $char = $format_string[ $i ];
674
            if ($char === '\\') { // PHP date format escaping character
675
                $i++;
676
                if ($escaping) {
677
                    $jquery_ui_format .= $format_string[ $i ];
678
                    $moment_format    .= $format_string[ $i ];
679
                } else {
680
                    $jquery_ui_format .= '\'' . $format_string[ $i ];
681
                    $moment_format    .= $format_string[ $i ];
682
                }
683
                $escaping = true;
684
            } else {
685
                if ($escaping) {
686
                    $jquery_ui_format .= "'";
687
                    $moment_format    .= "'";
688
                    $escaping         = false;
689
                }
690
                if (isset($symbols_map[ $char ])) {
691
                    $jquery_ui_format .= $symbols_map[ $char ]['js'];
692
                    $moment_format    .= $symbols_map[ $char ]['moment'];
693
                } else {
694
                    $jquery_ui_format .= $char;
695
                    $moment_format    .= $char;
696
                }
697
            }
698
        }
699
        return array('js' => $jquery_ui_format, 'moment' => $moment_format);
700
    }
701
702
703
    /**
704
     * This takes an incoming format string and validates it to ensure it will work fine with PHP.
705
     *
706
     * @param string $format_string   Incoming format string for php date().
707
     * @return mixed bool|array  If all is okay then TRUE is returned.  Otherwise an array of validation
708
     *                                errors is returned.  So for client code calling, check for is_array() to
709
     *                                indicate failed validations.
710
     */
711
    public static function validate_format_string($format_string)
712
    {
713
        $error_msg = array();
714
        //time format checks
715
        switch (true) {
716
            case   strpos($format_string, 'h') !== false  :
717
            case   strpos($format_string, 'g') !== false :
718
                /**
719
                 * if the time string has a lowercase 'h' which == 12 hour time format and there
720
                 * is not any ante meridiem format ('a' or 'A').  Then throw an error because its
721
                 * too ambiguous and PHP won't be able to figure out whether 1 = 1pm or 1am.
722
                 */
723
                if (stripos($format_string, 'A') === false) {
724
                    $error_msg[] = esc_html__(
725
                        'There is a  time format for 12 hour time but no  "a" or "A" to indicate am/pm.  Without this distinction, PHP is unable to determine if a "1" for the hour value equals "1pm" or "1am".',
726
                        'event_espresso'
727
                    );
728
                }
729
                break;
730
        }
731
        return empty($error_msg) ? true : $error_msg;
732
    }
733
734
735
    /**
736
     *     If the the first date starts at midnight on one day, and the next date ends at midnight on the
737
     *     very next day then this method will return true.
738
     *    If $date_1 = 2015-12-15 00:00:00 and $date_2 = 2015-12-16 00:00:00 then this function will return true.
739
     *    If $date_1 = 2015-12-15 03:00:00 and $date_2 = 2015-12_16 03:00:00 then this function will return false.
740
     *    If $date_1 = 2015-12-15 00:00:00 and $date_2 = 2015-12-15 00:00:00 then this function will return true.
741
     *
742
     * @param mixed $date_1
743
     * @param mixed $date_2
744
     * @return bool
745
     */
746
    public static function dates_represent_one_24_hour_date($date_1, $date_2)
747
    {
748
749
        if (
750
            (! $date_1 instanceof DateTime || ! $date_2 instanceof DateTime)
751
            || ($date_1->format(EE_Datetime_Field::mysql_time_format) !== '00:00:00'
752
                || $date_2->format(
753
                    EE_Datetime_Field::mysql_time_format
754
                ) !== '00:00:00')
755
        ) {
756
            return false;
757
        }
758
        return $date_2->format('U') - $date_1->format('U') === 86400;
759
    }
760
761
762
    /**
763
     * This returns the appropriate query interval string that can be used in sql queries involving mysql Date
764
     * Functions.
765
     *
766
     * @param string $timezone_string    A timezone string in a valid format to instantiate a DateTimeZone object.
767
     * @param string $field_for_interval The Database field that is the interval is applied to in the query.
768
     * @return string
769
     */
770
    public static function get_sql_query_interval_for_offset($timezone_string, $field_for_interval)
771
    {
772
        try {
773
            /** need to account for timezone offset on the selects */
774
            $DateTimeZone = new DateTimeZone($timezone_string);
775
        } catch (Exception $e) {
776
            $DateTimeZone = null;
777
        }
778
        /**
779
         * Note get_option( 'gmt_offset') returns a value in hours, whereas DateTimeZone::getOffset returns values in seconds.
780
         * Hence we do the calc for DateTimeZone::getOffset.
781
         */
782
        $offset         = $DateTimeZone instanceof DateTimeZone
783
            ? $DateTimeZone->getOffset(new DateTime('now')) / HOUR_IN_SECONDS
784
            : (float) get_option('gmt_offset');
785
        $query_interval = $offset < 0
786
            ? 'DATE_SUB(' . $field_for_interval . ', INTERVAL ' . $offset * -1 . ' HOUR)'
787
            : 'DATE_ADD(' . $field_for_interval . ', INTERVAL ' . $offset . ' HOUR)';
788
        return $query_interval;
789
    }
790
791
792
    /**
793
     * Retrieves the site's default timezone and returns it formatted so it's ready for display
794
     * to users. If you want to customize how its displayed feel free to fetch the 'timezone_string'
795
     * and 'gmt_offset' WordPress options directly; or use the filter
796
     * FHEE__EEH_DTT_Helper__get_timezone_string_for_display
797
     * (although note that we remove any HTML that may be added)
798
     *
799
     * @return string
800
     */
801
    public static function get_timezone_string_for_display()
802
    {
803
        $pretty_timezone = apply_filters('FHEE__EEH_DTT_Helper__get_timezone_string_for_display', '');
804
        if (! empty($pretty_timezone)) {
805
            return esc_html($pretty_timezone);
806
        }
807
        $timezone_string = get_option('timezone_string');
808
        if ($timezone_string) {
809
            static $mo_loaded = false;
810
            // Load translations for continents and cities just like wp_timezone_choice does
811
            if (! $mo_loaded) {
812
                $locale = get_locale();
813
                $mofile = WP_LANG_DIR . '/continents-cities-' . $locale . '.mo';
814
                load_textdomain('continents-cities', $mofile);
815
                $mo_loaded = true;
816
            }
817
            //well that was easy.
818
            $parts = explode('/', $timezone_string);
819
            //remove the continent
820
            unset($parts[0]);
821
            $t_parts = array();
822
            foreach ($parts as $part) {
823
                $t_parts[] = translate(str_replace('_', ' ', $part), 'continents-cities');
824
            }
825
            return implode(' - ', $t_parts);
826
        }
827
        //they haven't set the timezone string, so let's return a string like "UTC+1"
828
        $gmt_offset = get_option('gmt_offset');
829
        $prefix     = (int) $gmt_offset >= 0 ? '+' : '';
830
        $parts      = explode('.', (string) $gmt_offset);
831
        if (count($parts) === 1) {
832
            $parts[1] = '00';
833
        } else {
834
            //convert the part after the decimal, eg "5" (from x.5) or "25" (from x.25)
835
            //to minutes, eg 30 or 15, respectively
836
            $hour_fraction = (float) ('0.' . $parts[1]);
837
            $parts[1]      = (string) $hour_fraction * 60;
838
        }
839
        return sprintf(__('UTC%1$s', 'event_espresso'), $prefix . implode(':', $parts));
840
    }
841
842
843
844
    /**
845
     * So PHP does this awesome thing where if you are trying to get a timestamp
846
     * for a month using a string like "February" or "February 2017",
847
     * and you don't specify a day as part of your string,
848
     * then PHP will use whatever the current day of the month is.
849
     * IF the current day of the month happens to be the 30th or 31st,
850
     * then PHP gets really confused by a date like February 30,
851
     * so instead of saying
852
     *      "Hey February only has 28 days (this year)...
853
     *      ...you must have meant the last day of the month!"
854
     * PHP does the next most logical thing, and bumps the date up to March 2nd,
855
     * because someone requesting February 30th obviously meant March 1st!
856
     * The way around this is to always set the day to the first,
857
     * so that the month will stay on the month you wanted.
858
     * this method will add that "1" into your date regardless of the format.
859
     *
860
     * @param string $month
861
     * @return string
862
     */
863
    public static function first_of_month_timestamp($month = '')
864
    {
865
        $month = (string) $month;
866
        $year  = '';
867
        // check if the incoming string has a year in it or not
868
        if (preg_match('/\b\d{4}\b/', $month, $matches)) {
869
            $year = $matches[0];
870
            // ten remove that from the month string as well as any spaces
871
            $month = trim(str_replace($year, '', $month));
872
            // add a space before the year
873
            $year = " {$year}";
874
        }
875
        // return timestamp for something like "February 1 2017"
876
        return strtotime("{$month} 1{$year}");
877
    }
878
879
880
    /**
881
     * This simply returns the timestamp for tomorrow (midnight next day) in this sites timezone.  So it may be midnight
882
     * for this sites timezone, but the timestamp could be some other time GMT.
883
     */
884
    public static function tomorrow()
885
    {
886
        //The multiplication of -1 ensures that we switch positive offsets to negative and negative offsets to positive
887
        //before adding to the timestamp.  Why? Because we want tomorrow to be for midnight the next day in THIS timezone
888
        //not an offset from midnight in UTC.  So if we're starting with UTC 00:00:00, then we want to make sure the
889
        //final timestamp is equivalent to midnight in this timezone as represented in GMT.
890
        return strtotime('tomorrow') + (self::get_site_timezone_gmt_offset() * -1);
891
    }
892
893
894
    /**
895
     * **
896
     * Gives a nicely-formatted list of timezone strings.
897
     * Copied from the core wp function by the same name so we could customize to remove UTC offsets.
898
     *
899
     * @since     4.9.40.rc.008
900
     * @staticvar bool $mo_loaded
901
     * @staticvar string $locale_loaded
902
     * @param string $selected_zone Selected timezone.
903
     * @param string $locale        Optional. Locale to load the timezones in. Default current site locale.
904
     * @return string
905
     */
906
    public static function wp_timezone_choice($selected_zone, $locale = null)
907
    {
908
        static $mo_loaded = false, $locale_loaded = null;
909
        $continents = array(
910
            'Africa',
911
            'America',
912
            'Antarctica',
913
            'Arctic',
914
            'Asia',
915
            'Atlantic',
916
            'Australia',
917
            'Europe',
918
            'Indian',
919
            'Pacific',
920
        );
921
        // Load translations for continents and cities.
922
        if (! $mo_loaded || $locale !== $locale_loaded) {
923
            $locale_loaded = $locale ? $locale : get_locale();
924
            $mofile        = WP_LANG_DIR . '/continents-cities-' . $locale_loaded . '.mo';
925
            unload_textdomain('continents-cities');
926
            load_textdomain('continents-cities', $mofile);
927
            $mo_loaded = true;
928
        }
929
        $zone_data = array();
930
        foreach (timezone_identifiers_list() as $zone) {
931
            $zone = explode('/', $zone);
932
            if (! in_array($zone[0], $continents, true)) {
933
                continue;
934
            }
935
            // This determines what gets set and translated - we don't translate Etc/* strings here, they are done later
936
            $exists      = array(
937
                0 => isset($zone[0]) && $zone[0],
938
                1 => isset($zone[1]) && $zone[1],
939
                2 => isset($zone[2]) && $zone[2],
940
            );
941
            $exists[3]   = $exists[0] && $zone[0] !== 'Etc';
942
            $exists[4]   = $exists[1] && $exists[3];
943
            $exists[5]   = $exists[2] && $exists[3];
944
            $zone_data[] = array(
945
                'continent'   => $exists[0] ? $zone[0] : '',
946
                'city'        => $exists[1] ? $zone[1] : '',
947
                'subcity'     => $exists[2] ? $zone[2] : '',
948
                't_continent' => $exists[3]
949
                    ? translate(str_replace('_', ' ', $zone[0]), 'continents-cities')
950
                    : '',
951
                't_city'      => $exists[4]
952
                    ? translate(str_replace('_', ' ', $zone[1]), 'continents-cities')
953
                    : '',
954
                't_subcity'   => $exists[5]
955
                    ? translate(str_replace('_', ' ', $zone[2]), 'continents-cities')
956
                    : '',
957
            );
958
        }
959
        usort($zone_data, '_wp_timezone_choice_usort_callback');
960
        $structure = array();
961
        if (empty($selected_zone)) {
962
            $structure[] = '<option selected="selected" value="">' . __('Select a city') . '</option>';
963
        }
964
        foreach ($zone_data as $key => $zone) {
965
            // Build value in an array to join later
966
            $value = array($zone['continent']);
967
            if (empty($zone['city'])) {
968
                // It's at the continent level (generally won't happen)
969
                $display = $zone['t_continent'];
970
            } else {
971
                // It's inside a continent group
972
                // Continent optgroup
973
                if (! isset($zone_data[ $key - 1 ]) || $zone_data[ $key - 1 ]['continent'] !== $zone['continent']) {
974
                    $label       = $zone['t_continent'];
975
                    $structure[] = '<optgroup label="' . esc_attr($label) . '">';
976
                }
977
                // Add the city to the value
978
                $value[] = $zone['city'];
979
                $display = $zone['t_city'];
980
                if (! empty($zone['subcity'])) {
981
                    // Add the subcity to the value
982
                    $value[] = $zone['subcity'];
983
                    $display .= ' - ' . $zone['t_subcity'];
984
                }
985
            }
986
            // Build the value
987
            $value       = implode('/', $value);
988
            $selected    = $value === $selected_zone ? ' selected="selected"' : '';
989
            $structure[] = '<option value="' . esc_attr($value) . '"' . $selected . '>'
990
                           . esc_html($display)
991
                           . '</option>';
992
            // Close continent optgroup
993
            if (! empty($zone['city'])
994
                && (
995
                    ! isset($zone_data[ $key + 1 ])
996
                    || (isset($zone_data[ $key + 1 ]) && $zone_data[ $key + 1 ]['continent'] !== $zone['continent'])
997
                )
998
            ) {
999
                $structure[] = '</optgroup>';
1000
            }
1001
        }
1002
        return implode("\n", $structure);
1003
    }
1004
1005
1006
    /**
1007
     * Shim for the WP function `get_user_locale` that was added in WordPress 4.7.0
1008
     *
1009
     * @param int|WP_User $user_id
1010
     * @return string
1011
     */
1012
    public static function get_user_locale($user_id = 0)
1013
    {
1014
        if (function_exists('get_user_locale')) {
1015
            return get_user_locale($user_id);
1016
        }
1017
        return get_locale();
1018
    }
1019
1020
1021
    /**
1022
     * Return the appropriate helper adapter for DTT related things.
1023
     *
1024
     * @return HelperInterface
1025
     * @throws InvalidArgumentException
1026
     * @throws InvalidDataTypeException
1027
     * @throws InvalidInterfaceException
1028
     */
1029
    private static function getHelperAdapter() {
1030
        $dtt_helper_fqcn = PHP_VERSION_ID < 50600
1031
            ? 'EventEspresso\core\services\helpers\datetime\PhpCompatLessFiveSixHelper'
1032
            : 'EventEspresso\core\services\helpers\datetime\PhpCompatGreaterFiveSixHelper';
1033
        return LoaderFactory::getLoader()->getShared($dtt_helper_fqcn);
1034
    }
1035
1036
1037
    /**
1038
     * Helper function for setting the timezone on a DateTime object.
1039
     * This is implemented to standardize a workaround for a PHP bug outlined in
1040
     * https://events.codebasehq.com/projects/event-espresso/tickets/11407 and
1041
     * https://events.codebasehq.com/projects/event-espresso/tickets/11233
1042
     *
1043
     * @param DateTime     $datetime
1044
     * @param DateTimeZone $timezone
1045
     */
1046
    public static function setTimezone(DateTime $datetime, DateTimeZone $timezone)
1047
    {
1048
        $datetime->setTimezone($timezone);
1049
        $datetime->getTimestamp();
1050
    }
1051
}