Completed
Branch BUG/11252/payment-method-regis... (ef7b02)
by
unknown
52:18 queued 38:58
created

EEH_DTT_Helper::validate_timezone()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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