PluginProbe ʕ •ᴥ•ʔ
Pods – Custom Content Types and Fields / 2.9.19.4
Pods – Custom Content Types and Fields v2.9.19.4
2.7.31.4 2.8.23.5 2.9.19.5 3.0.10.5 3.1.4.3 3.2.8.4 3.3.9.2 2.8.23.4 2.9.19.4 3.0.10.4 3.1.4.2 3.2.8.3 3.3.9.1 trunk 1.14.8 2.7.31.3 2.8.23.3 2.9.19.3 3.0.10.3 3.1.4.1 3.2.0 3.2.1 3.2.1.1 3.2.2 3.2.4 3.2.5 3.2.6 3.2.7 3.2.7.1 3.2.8 3.2.8.1 3.2.8.2 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9
pods / tribe-common / src / Tribe / Date_Utils.php
pods / tribe-common / src / Tribe Last commit date
Admin 2 weeks ago Ajax 2 weeks ago Asset 2 weeks ago Context 2 weeks ago Customizer 2 weeks ago Debug_Bar 2 weeks ago Dialog 2 weeks ago Documentation 2 weeks ago Duplicate 2 weeks ago Editor 2 weeks ago Image 2 weeks ago JSON_LD 2 weeks ago Languages 2 weeks ago Log 2 weeks ago Meta 2 weeks ago Models 2 weeks ago PUE 2 weeks ago Process 2 weeks ago Promoter 2 weeks ago REST 2 weeks ago Repository 2 weeks ago Service_Providers 2 weeks ago Shortcode 2 weeks ago Support 2 weeks ago Tabbed_View 2 weeks ago Tooltip 2 weeks ago Traits 2 weeks ago Utils 2 weeks ago Validator 2 weeks ago Widget 2 weeks ago Abstract_Deactivation.php 2 weeks ago Abstract_Plugin_Register.php 2 weeks ago App_Shop.php 2 weeks ago Assets.php 2 weeks ago Assets_Pipeline.php 2 weeks ago Autoloader.php 2 weeks ago Cache.php 2 weeks ago Cache_Listener.php 2 weeks ago Changelog_Reader.php 2 weeks ago Container.php 2 weeks ago Context.php 2 weeks ago Cost_Utils.php 2 weeks ago Credits.php 2 weeks ago Customizer.php 2 weeks ago DB_Lock.php 2 weeks ago Data.php 2 weeks ago Date_Utils.php 2 weeks ago Db.php 2 weeks ago Debug.php 2 weeks ago Dependency.php 2 weeks ago Deprecation.php 2 weeks ago Editor.php 2 weeks ago Error.php 2 weeks ago Exception.php 2 weeks ago Extension.php 2 weeks ago Extension_Loader.php 2 weeks ago Feature_Detection.php 2 weeks ago Field.php 2 weeks ago Field_Conditional.php 2 weeks ago Freemius.php 2 weeks ago Log.php 2 weeks ago Main.php 2 weeks ago Notices.php 2 weeks ago Plugin_Meta_Links.php 2 weeks ago Plugins.php 2 weeks ago Plugins_API.php 2 weeks ago Post_History.php 2 weeks ago Post_Transient.php 2 weeks ago Promise.php 2 weeks ago Repository.php 2 weeks ago Rewrite.php 2 weeks ago Settings.php 2 weeks ago Settings_Manager.php 2 weeks ago Settings_Tab.php 2 weeks ago Simple_Table.php 2 weeks ago Support.php 2 weeks ago Tabbed_View.php 2 weeks ago Template.php 2 weeks ago Template_Factory.php 2 weeks ago Template_Part_Cache.php 2 weeks ago Templates.php 2 weeks ago Terms.php 2 weeks ago Timezones.php 2 weeks ago Tracker.php 2 weeks ago Updater.php 2 weeks ago Validate.php 2 weeks ago View_Helpers.php 2 weeks ago
Date_Utils.php
1599 lines
1 <?php
2 /**
3 * Date utility functions used throughout TEC + Addons
4 */
5
6 use Tribe\Utils\Date_I18n;
7 use Tribe\Utils\Date_I18n_Immutable;
8
9 // Don't load directly
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 die( '-1' );
13 }
14
15 if ( ! class_exists( 'Tribe__Date_Utils' ) ) {
16 class Tribe__Date_Utils {
17 // Default formats, they are overridden by WP options or by arguments to date methods
18 const DATEONLYFORMAT = 'F j, Y';
19 const TIMEFORMAT = 'g:i A';
20 const HOURFORMAT = 'g';
21 const MINUTEFORMAT = 'i';
22 const MERIDIANFORMAT = 'A';
23 const DBDATEFORMAT = 'Y-m-d';
24 const DBDATETIMEFORMAT = 'Y-m-d H:i:s';
25 const DBTZDATETIMEFORMAT = 'Y-m-d H:i:s O';
26 const DBTIMEFORMAT = 'H:i:s';
27 const DBYEARMONTHTIMEFORMAT = 'Y-m';
28
29 /**
30 * Default datepicker format index.
31 *
32 * @since 4.11.0.1
33 *
34 * @var int
35 */
36 private static $default_datepicker_format_index = 1;
37
38 private static $localized_months_full = [];
39 private static $localized_months_short = [];
40 private static $localized_weekdays = [];
41 private static $localized_months = [];
42
43 /**
44 * Get the datepickerFormat index.
45 *
46 * @since 4.11.0.1
47 *
48 * @return int
49 */
50 public static function get_datepicker_format_index() {
51 /**
52 * Filter the datepickerFormat index.
53 *
54 * @since 4.11.0.1
55 *
56 * @param int $format_index Index of datepickerFormat.
57 */
58 return apply_filters( 'tribe_datepicker_format_index', tribe_get_option( 'datepickerFormat', static::$default_datepicker_format_index ) );
59 }
60
61 /**
62 * Try to format a Date to the Default Datepicker format
63 *
64 * @since 4.5.12
65 *
66 * @param string $date Original Date that came from a datepicker
67 * @param string|int $datepicker Datepicker format
68 * @return string
69 */
70 public static function maybe_format_from_datepicker( $date, $datepicker = null ) {
71 if ( ! is_numeric( $datepicker ) ) {
72 $datepicker = self::get_datepicker_format_index();
73 }
74
75 if ( is_numeric( $datepicker ) ) {
76 $datepicker = self::datepicker_formats( $datepicker );
77 }
78
79 $default_datepicker = self::datepicker_formats( 1 );
80
81 // If the current datepicker is the default we don't care
82 if ( $datepicker === $default_datepicker ) {
83 return $date;
84 }
85
86 return self::datetime_from_format( $datepicker, $date );
87 }
88
89 /**
90 * Get the datepicker format, that is used to translate the option from the DB to a string
91 *
92 * @param int $translate The db Option from datepickerFormat
93 * @return string|array If $translate is not set returns the full array, if not returns the `Y-m-d`
94 */
95 public static function datepicker_formats( $translate = null ) {
96
97 // The datepicker has issues when a period separator and no leading zero is used. Those formats are purposefully omitted.
98 $formats = [
99 0 => 'Y-m-d',
100 1 => 'n/j/Y',
101 2 => 'm/d/Y',
102 3 => 'j/n/Y',
103 4 => 'd/m/Y',
104 5 => 'n-j-Y',
105 6 => 'm-d-Y',
106 7 => 'j-n-Y',
107 8 => 'd-m-Y',
108 9 => 'Y.m.d',
109 10 => 'm.d.Y',
110 11 => 'd.m.Y',
111 'm0' => 'Y-m',
112 'm1' => 'n/Y',
113 'm2' => 'm/Y',
114 'm3' => 'n/Y',
115 'm4' => 'm/Y',
116 'm5' => 'n-Y',
117 'm6' => 'm-Y',
118 'm7' => 'n-Y',
119 'm8' => 'm-Y',
120 'm9' => 'Y.m',
121 'm10' => 'm.Y',
122 'm11' => 'm.Y',
123 ];
124
125 if ( is_null( $translate ) ) {
126 return $formats;
127 }
128
129 return isset( $formats[ $translate ] ) ? $formats[ $translate ] : $formats[ static::get_datepicker_format_index() ];
130 }
131
132 /**
133 * As PHP 5.2 doesn't have a good version of `date_parse_from_format`, this is how we deal with
134 * possible weird datepicker formats not working
135 *
136 * @param string $format The weird format you are using
137 * @param string $date The date string to parse
138 *
139 * @return string A DB formated Date, includes time if possible
140 */
141 public static function datetime_from_format( $format, $date ) {
142 // Reverse engineer the relevant date formats
143 $keys = [
144 // Year with 4 Digits
145 'Y' => [ 'year', '\d{4}' ],
146
147 // Year with 2 Digits
148 'y' => [ 'year', '\d{2}' ],
149
150 // Month with leading 0
151 'm' => [ 'month', '\d{2}' ],
152
153 // Month without the leading 0
154 'n' => [ 'month', '\d{1,2}' ],
155
156 // Month ABBR 3 letters
157 'M' => [ 'month', '[A-Z][a-z]{2}' ],
158
159 // Month Name
160 'F' => [ 'month', '[A-Z][a-z]{2,8}' ],
161
162 // Day with leading 0
163 'd' => [ 'day', '\d{2}' ],
164
165 // Day without leading 0
166 'j' => [ 'day', '\d{1,2}' ],
167
168 // Day ABBR 3 Letters
169 'D' => [ 'day', '[A-Z][a-z]{2}' ],
170
171 // Day Name
172 'l' => [ 'day', '[A-Z][a-z]{5,8}' ],
173
174 // Hour 12h formatted, with leading 0
175 'h' => [ 'hour', '\d{2}' ],
176
177 // Hour 24h formatted, with leading 0
178 'H' => [ 'hour', '\d{2}' ],
179
180 // Hour 12h formatted, without leading 0
181 'g' => [ 'hour', '\d{1,2}' ],
182
183 // Hour 24h formatted, without leading 0
184 'G' => [ 'hour', '\d{1,2}' ],
185
186 // Minutes with leading 0
187 'i' => [ 'minute', '\d{2}' ],
188
189 // Seconds with leading 0
190 's' => [ 'second', '\d{2}' ],
191 ];
192
193 $date_regex = "/{$keys['Y'][1]}-{$keys['m'][1]}-{$keys['d'][1]}( {$keys['H'][1]}:{$keys['i'][1]}:{$keys['s'][1]})?$/";
194
195 // if the date is already in Y-m-d or Y-m-d H:i:s, just return it
196 if ( preg_match( $date_regex, $date ) ) {
197 return $date;
198 }
199
200
201 // Convert format string to regex
202 $regex = '';
203 $chars = str_split( $format );
204 foreach ( $chars as $n => $char ) {
205 $last_char = isset( $chars[ $n - 1 ] ) ? $chars[ $n - 1 ] : '';
206 $skip_current = '\\' == $last_char;
207 if ( ! $skip_current && isset( $keys[ $char ] ) ) {
208 $regex .= '(?P<' . $keys[ $char ][0] . '>' . $keys[ $char ][1] . ')';
209 } elseif ( '\\' == $char ) {
210 $regex .= $char;
211 } else {
212 $regex .= preg_quote( $char );
213 }
214 }
215
216 $dt = [];
217
218 // Now try to match it
219 if ( preg_match( '#^' . $regex . '$#', $date, $dt ) ) {
220 // Remove unwanted Indexes
221 foreach ( $dt as $k => $v ) {
222 if ( is_int( $k ) ) {
223 unset( $dt[ $k ] );
224 }
225 }
226
227 // We need at least Month + Day + Year to work with
228 if ( ! checkdate( $dt['month'], $dt['day'], $dt['year'] ) ) {
229 return false;
230 }
231 } else {
232 return false;
233 }
234
235 $dt['month'] = str_pad( $dt['month'], 2, '0', STR_PAD_LEFT );
236 $dt['day'] = str_pad( $dt['day'], 2, '0', STR_PAD_LEFT );
237
238 $formatted = '{year}-{month}-{day}' . ( isset( $dt['hour'], $dt['minute'], $dt['second'] ) ? ' {hour}:{minute}:{second}' : '' );
239 foreach ( $dt as $key => $value ) {
240 $formatted = str_replace( '{' . $key . '}', $value, $formatted );
241 }
242
243 return $formatted;
244 }
245
246 /**
247 * Returns the date only.
248 *
249 * @param int|string $date The date (timestamp or string).
250 * @param bool $isTimestamp Is $date in timestamp format?
251 * @param string|null $format The format used
252 *
253 * @return string The date only in DB format.
254 */
255 public static function date_only( $date, $isTimestamp = false, $format = null ) {
256 $date = $isTimestamp ? $date : strtotime( $date );
257
258 if ( is_null( $format ) ) {
259 $format = self::DBDATEFORMAT;
260 }
261
262 return date( $format, $date );
263 }
264
265 /**
266 * Returns as string the nearest half a hour for a given valid string datetime.
267 *
268 * @since 4.10.2
269 *
270 * @param string $date Valid DateTime string.
271 *
272 * @return string Rounded datetime string
273 */
274 public static function round_nearest_half_hour( $date ) {
275 $date_object = static::build_date_object( $date );
276 $rounded_minutes = floor( $date_object->format( 'i' ) / 30 ) * 30;
277
278 return $date_object->format( 'Y-m-d H:' ) . $rounded_minutes . ':00';
279 }
280
281 /**
282 * Returns the time only.
283 *
284 * @param string $date The date.
285 *
286 * @return string The time only in DB format.
287 */
288 public static function time_only( $date ) {
289 $date = is_numeric( $date ) ? $date : strtotime( $date );
290 return date( self::DBTIMEFORMAT, $date );
291 }
292
293 /**
294 * Returns the hour only.
295 *
296 * @param string $date The date.
297 *
298 * @return string The hour only.
299 */
300 public static function hour_only( $date ) {
301 $date = is_numeric( $date ) ? $date : strtotime( $date );
302 return date( self::HOURFORMAT, $date );
303 }
304
305 /**
306 * Returns the minute only.
307 *
308 * @param string $date The date.
309 *
310 * @return string The minute only.
311 */
312 public static function minutes_only( $date ) {
313 $date = is_numeric( $date ) ? $date : strtotime( $date );
314 return date( self::MINUTEFORMAT, $date );
315 }
316
317 /**
318 * Returns the meridian (am or pm) only.
319 *
320 * @param string $date The date.
321 *
322 * @return string The meridian only in DB format.
323 */
324 public static function meridian_only( $date ) {
325 $date = is_numeric( $date ) ? $date : strtotime( $date );
326 return date( self::MERIDIANFORMAT, $date );
327 }
328
329 /**
330 * Returns the number of seconds (absolute value) between two dates/times.
331 *
332 * @param string $date1 The first date.
333 * @param string $date2 The second date.
334 *
335 * @return int The number of seconds between the dates.
336 */
337 public static function time_between( $date1, $date2 ) {
338 return abs( strtotime( $date1 ) - strtotime( $date2 ) );
339 }
340
341 /**
342 * The number of days between two arbitrary dates.
343 *
344 * @param string $date1 The first date.
345 * @param string $date2 The second date.
346 *
347 * @return int The number of days between two dates.
348 */
349 public static function date_diff( $date1, $date2 ) {
350 // Get number of days between by finding seconds between and dividing by # of seconds in a day
351 $days = self::time_between( $date1, $date2 ) / ( 60 * 60 * 24 );
352
353 return $days;
354 }
355
356 /**
357 * Returns the last day of the month given a php date.
358 *
359 * @param int $timestamp THe timestamp.
360 *
361 * @return string The last day of the month.
362 */
363 public static function get_last_day_of_month( $timestamp ) {
364 $curmonth = date( 'n', $timestamp );
365 $curYear = date( 'Y', $timestamp );
366 $nextmonth = mktime( 0, 0, 0, $curmonth + 1, 1, $curYear );
367 $lastDay = strtotime( date( self::DBDATETIMEFORMAT, $nextmonth ) . ' - 1 day' );
368
369 return date( 'j', $lastDay );
370 }
371
372 /**
373 * Returns true if the timestamp is a weekday.
374 *
375 * @param int $curDate A timestamp.
376 *
377 * @return bool If the timestamp is a weekday.
378 */
379 public static function is_weekday( $curdate ) {
380 return in_array( date( 'N', $curdate ), [ 1, 2, 3, 4, 5 ] );
381 }
382
383 /**
384 * Returns true if the timestamp is a weekend.
385 *
386 * @param int $curDate A timestamp.
387 *
388 * @return bool If the timestamp is a weekend.
389 */
390 public static function is_weekend( $curdate ) {
391 return in_array( date( 'N', $curdate ), [ 6, 7 ] );
392 }
393
394 /**
395 * Gets the last day of the week in a month (ie the last Tuesday). Passing in -1 gives you the last day in the month.
396 *
397 * @param int $curdate A timestamp.
398 * @param int $day_of_week The index of the day of the week.
399 *
400 * @return int The timestamp of the date that fits the qualifications.
401 */
402 public static function get_last_day_of_week_in_month( $curdate, $day_of_week ) {
403 $nextdate = mktime( date( 'H', $curdate ), date( 'i', $curdate ), date( 's', $curdate ), date( 'n', $curdate ), self::get_last_day_of_month( $curdate ), date( 'Y', $curdate ) );;
404
405 while ( date( 'N', $nextdate ) != $day_of_week && $day_of_week != - 1 ) {
406 $nextdate = strtotime( date( self::DBDATETIMEFORMAT, $nextdate ) . ' - 1 day' );
407 }
408
409 return $nextdate;
410 }
411
412 /**
413 * Gets the first day of the week in a month (ie the first Tuesday).
414 *
415 * @param int $curdate A timestamp.
416 * @param int $day_of_week The index of the day of the week.
417 *
418 * @return int The timestamp of the date that fits the qualifications.
419 */
420 public static function get_first_day_of_week_in_month( $curdate, $day_of_week ) {
421 $nextdate = mktime( 0, 0, 0, date( 'n', $curdate ), 1, date( 'Y', $curdate ) );
422
423 while ( ! ( $day_of_week > 0 && date( 'N', $nextdate ) == $day_of_week ) &&
424 ! ( $day_of_week == - 1 && self::is_weekday( $nextdate ) ) &&
425 ! ( $day_of_week == - 2 && self::is_weekend( $nextdate ) ) ) {
426 $nextdate = strtotime( date( self::DBDATETIMEFORMAT, $nextdate ) . ' + 1 day' );
427 }
428
429 return $nextdate;
430 }
431
432 /**
433 * From http://php.net/manual/en/function.date.php
434 *
435 * @param int $number A number.
436 *
437 * @return string The ordinal for that number.
438 */
439 public static function number_to_ordinal( $number ) {
440 $output = $number . ( ( ( strlen( $number ) > 1 ) && ( substr( $number, - 2, 1 ) == '1' ) ) ?
441 'th' : date( 'S', mktime( 0, 0, 0, 0, substr( $number, - 1 ), 0 ) ) );
442
443 return apply_filters( 'tribe_events_number_to_ordinal', $output, $number );
444 }
445
446 /**
447 * check if a given string is a timestamp
448 *
449 * @param $timestamp
450 *
451 * @return bool
452 */
453 public static function is_timestamp( $timestamp ) {
454 if ( is_numeric( $timestamp ) && (int) $timestamp == $timestamp && date( 'U', $timestamp ) == $timestamp ) {
455 return true;
456 }
457
458 return false;
459 }
460
461 /**
462 * Accepts a string representing a date/time and attempts to convert it to
463 * the specified format, returning an empty string if this is not possible.
464 *
465 * @param $dt_string
466 * @param $new_format
467 *
468 * @return string
469 */
470 public static function reformat( $dt_string, $new_format ) {
471 $timestamp = self::is_timestamp( $dt_string ) ? $dt_string : strtotime( $dt_string );
472 $revised = date( $new_format, $timestamp );
473
474 return $revised ? $revised : '';
475 }
476
477 /**
478 * Accepts a numeric offset (such as "4" or "-6" as stored in the gmt_offset
479 * option) and converts it to a strtotime() style modifier that can be used
480 * to adjust a DateTime object, etc.
481 *
482 * @param $offset
483 *
484 * @return string
485 */
486 public static function get_modifier_from_offset( $offset ) {
487 $modifier = '';
488 $offset = (float) $offset;
489
490 // Separate out hours, minutes, polarity
491 $hours = (int) $offset;
492 $minutes = (int) ( ( $offset - $hours ) * 60 );
493 $polarity = ( $offset >= 0 ) ? '+' : '-';
494
495 // Correct hours and minutes to positive values
496 if ( $hours < 0 ) $hours *= -1;
497 if ( $minutes < 0 ) $minutes *= -1;
498
499 // Form the modifier string
500 if ( $hours >= 0 ) $modifier = "$polarity $hours hours ";
501 if ( $minutes > 0 ) $modifier .= "$minutes minutes";
502
503 return $modifier;
504 }
505
506 /**
507 * Returns the weekday of the 1st day of the month in
508 * "w" format (ie, Sunday is 0 and Saturday is 6) or
509 * false if this cannot be established.
510 *
511 * @param mixed $month
512 * @return int|bool
513 */
514 public static function first_day_in_month( $month ) {
515 try {
516 $date = new DateTime( $month );
517 $day_1 = new DateTime( $date->format( 'Y-m-01 ' ) );
518 return $day_1->format( 'w' );
519 }
520 catch ( Exception $e ) {
521 return false;
522 }
523 }
524
525 /**
526 * Returns the weekday of the last day of the month in
527 * "w" format (ie, Sunday is 0 and Saturday is 6) or
528 * false if this cannot be established.
529 *
530 * @param mixed $month
531 * @return int|bool
532 */
533 public static function last_day_in_month( $month ) {
534 try {
535 $date = new DateTime( $month );
536 $day_1 = new DateTime( $date->format( 'Y-m-t' ) );
537 return $day_1->format( 'w' );
538 }
539 catch ( Exception $e ) {
540 return false;
541 }
542 }
543
544 /**
545 * Returns the day of the week the week ends on, expressed as a "w" value
546 * (ie, Sunday is 0 and Saturday is 6).
547 *
548 * @param int $week_starts_on
549 *
550 * @return int
551 */
552 public static function week_ends_on( $week_starts_on ) {
553 if ( --$week_starts_on < 0 ) $week_starts_on = 6;
554 return $week_starts_on;
555 }
556
557 /**
558 * Helper method to convert EventAllDay values to a boolean
559 *
560 * @param mixed $all_day_value Value to check for "all day" status. All day values: (true, 'true', 'TRUE', 'yes')
561 *
562 * @return boolean Is value considered "All Day"?
563 */
564 public static function is_all_day( $all_day_value ) {
565 $all_day_value = trim( $all_day_value );
566
567 return (
568 'true' === strtolower( $all_day_value )
569 || 'yes' === strtolower( $all_day_value )
570 || true === $all_day_value
571 || 1 == $all_day_value
572 );
573 }
574
575 /**
576 * Given 2 datetime ranges, return whether the 2nd one occurs during the 1st one
577 * Note: all params should be unix timestamps
578 *
579 * @param integer $range_1_start timestamp for start of the first range
580 * @param integer $range_1_end timestamp for end of the first range
581 * @param integer $range_2_start timestamp for start of the second range
582 * @param integer $range_2_end timestamp for end of the second range
583 *
584 * @return bool
585 */
586 public static function range_coincides( $range_1_start, $range_1_end, $range_2_start, $range_2_end ) {
587
588 // Initialize the return value
589 $range_coincides = false;
590
591 /**
592 * conditions:
593 * range 2 starts during range 1 (range 2 start time is between start and end of range 1 )
594 * range 2 ends during range 1 (range 2 end time is between start and end of range 1 )
595 * range 2 encloses range 1 (range 2 starts before range 1 and ends after range 1)
596 */
597
598 $range_2_starts_during_range_1 = $range_2_start >= $range_1_start && $range_2_start < $range_1_end;
599 $range_2_ends_during_range_1 = $range_2_end > $range_1_start && $range_2_end <= $range_1_end;
600 $range_2_encloses_range_1 = $range_2_start < $range_1_start && $range_2_end > $range_1_end;
601
602 if ( $range_2_starts_during_range_1 || $range_2_ends_during_range_1 || $range_2_encloses_range_1 ) {
603 $range_coincides = true;
604 }
605
606 return $range_coincides;
607
608 }
609
610 /**
611 * Converts a locally-formatted date to a unix timestamp. This is a drop-in
612 * replacement for `strtotime()`, except that where strtotime assumes GMT, this
613 * assumes local time (as described below). If a timezone is specified, this
614 * function defers to strtotime().
615 *
616 * If there is a timezone_string available, the date is assumed to be in that
617 * timezone, otherwise it simply subtracts the value of the 'gmt_offset'
618 * option.
619 *
620 * @see strtotime()
621 * @uses get_option() to retrieve the value of 'gmt_offset'
622 *
623 * @param string $string A date/time string. See `strtotime` for valid formats
624 *
625 * @return int UNIX timestamp.
626 */
627 public static function wp_strtotime( $string ) {
628 // If there's a timezone specified, we shouldn't convert it
629 try {
630 $test_date = new DateTime( $string );
631 if ( 'UTC' != $test_date->getTimezone()->getName() ) {
632 return strtotime( $string );
633 }
634 } catch ( Exception $e ) {
635 return strtotime( $string );
636 }
637
638 $tz = get_option( 'timezone_string' );
639 if ( ! empty( $tz ) ) {
640 $date = date_create( $string, new DateTimeZone( $tz ) );
641 if ( ! $date ) {
642 return strtotime( $string );
643 }
644 $date->setTimezone( new DateTimeZone( 'UTC' ) );
645 return $date->format( 'U' );
646 } else {
647 $offset = (float) get_option( 'gmt_offset' );
648 $seconds = intval( $offset * HOUR_IN_SECONDS );
649 $timestamp = strtotime( $string ) - $seconds;
650 return $timestamp;
651 }
652 }
653
654 /**
655 * Returns an array of localized full month names.
656 *
657 * @return array
658 */
659 public static function get_localized_months_full() {
660 global $wp_locale;
661
662 if ( empty( self::$localized_months ) ) {
663 self::build_localized_months();
664 }
665
666 if ( empty( self::$localized_months_full ) ) {
667 self::$localized_months_full = [
668 'January' => self::$localized_months['full']['01'],
669 'February' => self::$localized_months['full']['02'],
670 'March' => self::$localized_months['full']['03'],
671 'April' => self::$localized_months['full']['04'],
672 'May' => self::$localized_months['full']['05'],
673 'June' => self::$localized_months['full']['06'],
674 'July' => self::$localized_months['full']['07'],
675 'August' => self::$localized_months['full']['08'],
676 'September' => self::$localized_months['full']['09'],
677 'October' => self::$localized_months['full']['10'],
678 'November' => self::$localized_months['full']['11'],
679 'December' => self::$localized_months['full']['12'],
680 ];
681 }
682
683 return self::$localized_months_full;
684 }
685
686 /**
687 * Returns an array of localized short month names.
688 *
689 * @return array
690 */
691 public static function get_localized_months_short() {
692 global $wp_locale;
693
694 if ( empty( self::$localized_months ) ) {
695 self::build_localized_months();
696 }
697
698 if ( empty( self::$localized_months_short ) ) {
699 self::$localized_months_short = [
700 'Jan' => self::$localized_months['short']['01'],
701 'Feb' => self::$localized_months['short']['02'],
702 'Mar' => self::$localized_months['short']['03'],
703 'Apr' => self::$localized_months['short']['04'],
704 'May' => self::$localized_months['short']['05'],
705 'Jun' => self::$localized_months['short']['06'],
706 'Jul' => self::$localized_months['short']['07'],
707 'Aug' => self::$localized_months['short']['08'],
708 'Sep' => self::$localized_months['short']['09'],
709 'Oct' => self::$localized_months['short']['10'],
710 'Nov' => self::$localized_months['short']['11'],
711 'Dec' => self::$localized_months['short']['12'],
712 ];
713 }
714
715 return self::$localized_months_short;
716 }
717
718 /**
719 * Returns an array of localized full week day names.
720 *
721 * @return array
722 */
723 public static function get_localized_weekdays_full() {
724 if ( empty( self::$localized_weekdays ) ) {
725 self::build_localized_weekdays();
726 }
727
728 return self::$localized_weekdays['full'];
729 }
730
731 /**
732 * Returns an array of localized short week day names.
733 *
734 * @return array
735 */
736 public static function get_localized_weekdays_short() {
737 if ( empty( self::$localized_weekdays ) ) {
738 self::build_localized_weekdays();
739 }
740
741 return self::$localized_weekdays['short'];
742 }
743
744 /**
745 * Returns an array of localized week day initials.
746 *
747 * @return array
748 */
749 public static function get_localized_weekdays_initial() {
750 if ( empty( self::$localized_weekdays ) ) {
751 self::build_localized_weekdays();
752 }
753
754 return self::$localized_weekdays['initial'];
755 }
756
757 /**
758 * Builds arrays of localized full, short and initialized weekdays.
759 */
760 private static function build_localized_weekdays() {
761 global $wp_locale;
762
763 for ( $i = 0; $i <= 6; $i++ ) {
764 $day = $wp_locale->get_weekday( $i );
765 self::$localized_weekdays['full'][ $i ] = $day;
766 self::$localized_weekdays['short'][ $i ] = $wp_locale->get_weekday_abbrev( $day );
767 self::$localized_weekdays['initial'][ $i ] = $wp_locale->get_weekday_initial( $day );
768 }
769 }
770
771 /**
772 * Builds arrays of localized full and short months.
773 *
774 * @since 4.4.3
775 */
776 private static function build_localized_months() {
777 global $wp_locale;
778
779 for ( $i = 1; $i <= 12; $i++ ) {
780 $month_number = str_pad( $i, 2, '0', STR_PAD_LEFT );
781 $month = $wp_locale->get_month( $month_number );
782 self::$localized_months['full'][ $month_number ] = $month;
783 self::$localized_months['short'][ $month_number ] = $wp_locale->get_month_abbrev( $month );
784 }
785 }
786
787 /**
788 * Return a WP Locale weekday in the specified format
789 *
790 * @since 4.4.3
791 *
792 * @param int|string $weekday Day of week
793 * @param string $format Weekday format: full, weekday, initial, abbreviation, abbrev, abbr, short
794 *
795 * @return string
796 */
797 public static function wp_locale_weekday( $weekday, $format = 'weekday' ) {
798 $weekday = trim( $weekday );
799
800 $valid_formats = [
801 'full',
802 'weekday',
803 'initial',
804 'abbreviation',
805 'abbrev',
806 'abbr',
807 'short',
808 ];
809
810 // if there isn't a valid format, bail without providing a localized string
811 if ( ! in_array( $format, $valid_formats ) ) {
812 return $weekday;
813 }
814
815 if ( empty( self::$localized_weekdays ) ) {
816 self::build_localized_weekdays();
817 }
818
819 // if the weekday isn't numeric, we need to convert to numeric in order to
820 // leverage self::localized_weekdays
821 if ( ! is_numeric( $weekday ) ) {
822 $days_of_week = [
823 'Sun',
824 'Mon',
825 'Tue',
826 'Wed',
827 'Thu',
828 'Fri',
829 'Sat',
830 ];
831
832 $day_index = array_search( ucwords( substr( $weekday, 0, 3 ) ), $days_of_week );
833
834 if ( false === $day_index ) {
835 return $weekday;
836 }
837
838 $weekday = $day_index;
839 }
840
841 switch ( $format ) {
842 case 'initial':
843 $type = 'initial';
844 break;
845 case 'abbreviation':
846 case 'abbrev':
847 case 'abbr':
848 case 'short':
849 $type = 'short';
850 break;
851 case 'weekday':
852 case 'full':
853 default:
854 $type = 'full';
855 break;
856 }
857
858 return self::$localized_weekdays[ $type ][ $weekday ];
859 }
860
861 /**
862 * Return a WP Locale month in the specified format
863 *
864 * @since 4.4.3
865 *
866 * @param int|string $month Month of year
867 * @param string $format Month format: full, month, abbreviation, abbrev, abbr, short
868 *
869 * @return string
870 */
871 public static function wp_locale_month( $month, $format = 'month' ) {
872 $month = trim( $month );
873
874 $valid_formats = [
875 'full',
876 'month',
877 'abbreviation',
878 'abbrev',
879 'abbr',
880 'short',
881 ];
882
883 // if there isn't a valid format, bail without providing a localized string
884 if ( ! in_array( $format, $valid_formats ) ) {
885 return $month;
886 }
887
888 if ( empty( self::$localized_months ) ) {
889 self::build_localized_months();
890 }
891
892 // make sure numeric months are valid
893 if ( is_numeric( $month ) ) {
894 $month_num = (int) $month;
895
896 // if the month num falls out of range, bail without localizing
897 if ( 0 > $month_num || 12 < $month_num ) {
898 return $month;
899 }
900 } else {
901 $months = [
902 'Jan',
903 'Feb',
904 'Mar',
905 'Apr',
906 'May',
907 'Jun',
908 'Jul',
909 'Aug',
910 'Sep',
911 'Oct',
912 'Nov',
913 'Dec',
914 ];
915
916 // convert the provided month to a 3-character month and find it in the months array so we
917 // can build an appropriate month number
918 $month_num = array_search( ucwords( substr( $month, 0, 3 ) ), $months );
919
920 // if we can't find the provided month in our month list, bail without localizing
921 if ( false === $month_num ) {
922 return $month;
923 }
924
925 // let's increment the num because months start at 01 rather than 00
926 $month_num++;
927 }
928
929 $month_num = str_pad( $month_num, 2, '0', STR_PAD_LEFT );
930
931 $type = ( 'full' === $format || 'month' === $format ) ? 'full' : 'short';
932
933 return self::$localized_months[ $type ][ $month_num ];
934 }
935
936 // DEPRECATED METHODS
937 // @codingStandardsIgnoreStart
938 /**
939 * Deprecated camelCase version of self::date_only
940 *
941 * @param int|string $date The date (timestamp or string).
942 * @param bool $isTimestamp Is $date in timestamp format?
943 *
944 * @return string The date only in DB format.
945 */
946 public static function dateOnly( $date, $isTimestamp = false ) {
947 _deprecated_function( __METHOD__, '3.11', __CLASS__ . '::date_only' );
948 return self::date_only( $date, $isTimestamp );
949 }
950
951 /**
952 * Deprecated camelCase version of self::time_only
953 *
954 * @param string $date The date.
955 *
956 * @return string The time only in DB format.
957 */
958 public static function timeOnly( $date ) {
959 _deprecated_function( __METHOD__, '3.11', __CLASS__ . '::time_only' );
960 return self::time_only( $date );
961 }
962
963 /**
964 * Deprecated camelCase version of self::hour_only
965 *
966 * @param string $date The date.
967 *
968 * @return string The hour only.
969 */
970 public static function hourOnly( $date ) {
971 _deprecated_function( __METHOD__, '3.11', __CLASS__ . '::hour_only' );
972 return self::hour_only( $date );
973 }
974
975 /**
976 * Deprecated camelCase version of self::minutes_only
977 *
978 * @param string $date The date.
979 *
980 * @return string The minute only.
981 */
982 public static function minutesOnly( $date ) {
983 _deprecated_function( __METHOD__, '3.11', __CLASS__ . '::minutes_only' );
984 return self::minutes_only( $date );
985 }
986
987 /**
988 * Deprecated camelCase version of self::meridian_only
989 *
990 * @param string $date The date.
991 *
992 * @return string The meridian only in DB format.
993 */
994 public static function meridianOnly( $date ) {
995 _deprecated_function( __METHOD__, '3.11', __CLASS__ . '::meridian_only' );
996 return self::meridian_only( $date );
997 }
998
999 /**
1000 * Returns the end of a given day.
1001 *
1002 * @deprecated since 3.10 - use tribe_event_end_of_day()
1003 * @todo remove in 4.1
1004 *
1005 * @param int|string $date The date (timestamp or string).
1006 * @param bool $isTimestamp Is $date in timestamp format?
1007 *
1008 * @return string The date and time of the end of a given day
1009 */
1010 public static function endOfDay( $date, $isTimestamp = false ) {
1011 _deprecated_function( __METHOD__, '3.10', 'tribe_event_end_of_day' );
1012
1013 if ( $isTimestamp ) {
1014 $date = date( self::DBDATEFORMAT, $date );
1015 }
1016
1017 return tribe_event_end_of_day( $date, self::DBDATETIMEFORMAT );
1018 }
1019
1020 /**
1021 * Returns the beginning of a given day.
1022 *
1023 * @deprecated since 3.10
1024 * @todo remove in 4.1
1025 *
1026 * @param int|string $date The date (timestamp or string).
1027 * @param bool $isTimestamp Is $date in timestamp format?
1028 *
1029 * @return string The date and time of the beginning of a given day.
1030 */
1031 public static function beginningOfDay( $date, $isTimestamp = false ) {
1032 _deprecated_function( __METHOD__, '3.10', 'tribe_event_beginning_of_day' );
1033
1034 if ( $isTimestamp ) {
1035 $date = date( self::DBDATEFORMAT, $date );
1036 }
1037
1038 return tribe_event_beginning_of_day( $date, self::DBDATETIMEFORMAT );
1039 }
1040
1041 /**
1042 * Deprecated camelCase version of self::time_between
1043 *
1044 * @param string $date1 The first date.
1045 * @param string $date2 The second date.
1046 *
1047 * @return int The number of seconds between the dates.
1048 */
1049 public static function timeBetween( $date1, $date2 ) {
1050 _deprecated_function( __METHOD__, '3.11', __CLASS__ . '::time_between' );
1051 return self::time_between( $date1, $date2 );
1052 }
1053
1054 /**
1055 * Deprecated camelCase version of self::date_diff
1056 *
1057 * @param string $date1 The first date.
1058 * @param string $date2 The second date.
1059 *
1060 * @return int The number of days between two dates.
1061 */
1062 public static function dateDiff( $date1, $date2 ) {
1063 _deprecated_function( __METHOD__, '3.11', __CLASS__ . '::date_diff' );
1064 return self::date_diff( $date1, $date2 );
1065 }
1066
1067 /**
1068 * Deprecated camelCase version of self::get_last_day_of_month
1069 *
1070 * @param int $timestamp THe timestamp.
1071 *
1072 * @return string The last day of the month.
1073 */
1074 public static function getLastDayOfMonth( $timestamp ) {
1075 _deprecated_function( __METHOD__, '3.11', __CLASS__ . '::get_last_day_of_month' );
1076 return self::get_last_day_of_month( $timestamp );
1077 }
1078
1079 /**
1080 * Deprecated camelCase version of self::is_weekday
1081 *
1082 * @param int $curDate A timestamp.
1083 *
1084 * @return bool If the timestamp is a weekday.
1085 */
1086 public static function isWeekday( $curdate ) {
1087 _deprecated_function( __METHOD__, '3.11', __CLASS__ . '::is_weekday' );
1088 return self::is_weekday( $curdate );
1089 }
1090
1091 /**
1092 * Deprecated camelCase version of self::is_weekend
1093 *
1094 * @param int $curDate A timestamp.
1095 *
1096 * @return bool If the timestamp is a weekend.
1097 */
1098 public static function isWeekend( $curdate ) {
1099 _deprecated_function( __METHOD__, '3.11', __CLASS__ . '::is_weekend' );
1100 return self::is_weekend( $curdate );
1101 }
1102
1103 /**
1104 * Deprecated camelCase version of self::get_last_day_of_week_in_month
1105 *
1106 * @param int $curdate A timestamp.
1107 * @param int $day_of_week The index of the day of the week.
1108 *
1109 * @return int The timestamp of the date that fits the qualifications.
1110 */
1111 public static function getLastDayOfWeekInMonth( $curdate, $day_of_week ) {
1112 _deprecated_function( __METHOD__, '3.11', __CLASS__ . '::get_last_day_of_week_in_month' );
1113 return self::get_last_day_of_week_in_month( $curdate, $day_of_week );
1114 }
1115
1116 /**
1117 * Deprecated camelCase version of self::get_first_day_of_week_in_month
1118 *
1119 * @param int $curdate A timestamp.
1120 * @param int $day_of_week The index of the day of the week.
1121 *
1122 * @return int The timestamp of the date that fits the qualifications.
1123 */
1124 public static function getFirstDayOfWeekInMonth( $curdate, $day_of_week ) {
1125 _deprecated_function( __METHOD__, '3.11', __CLASS__ . '::get_fist_day_of_week_in_month' );
1126 return self::get_first_day_of_week_in_month( $curdate, $day_of_week );
1127 }
1128
1129 /**
1130 * Deprecated camelCase version of self::number_to_ordinal
1131 *
1132 * @param int $number A number.
1133 *
1134 * @return string The ordinal for that number.
1135 */
1136 public static function numberToOrdinal( $number ) {
1137 _deprecated_function( __METHOD__, '3.11', __CLASS__ . '::number_to_ordinal' );
1138 return self::number_to_ordinal( $number );
1139 }
1140
1141 /**
1142 * Deprecated camelCase version of self::is_timestamp
1143 *
1144 * @param $timestamp
1145 *
1146 * @return bool
1147 */
1148 public static function isTimestamp( $timestamp ) {
1149 _deprecated_function( __METHOD__, '3.11', __CLASS__ . '::is_timestamp' );
1150 return self::is_timestamp( $timestamp );
1151 }
1152
1153 /**
1154 * Gets the timestamp of a day in week, month and year context.
1155 *
1156 * Kudos to [icedwater StackOverflow user](http://stackoverflow.com/users/1091386/icedwater) in
1157 * [his answer](http://stackoverflow.com/questions/924246/get-the-first-or-last-friday-in-a-month).
1158 *
1159 * Usage examples:
1160 * "The second Wednesday of March 2015" - `get_day_timestamp( 3, 2, 3, 2015, 1)`
1161 * "The last Friday of December 2015" - `get_day_timestamp( 5, 1, 12, 2015, -1)`
1162 * "The first Monday of April 2016 - `get_day_timestamp( 1, 1, 4, 2016, 1)`
1163 * "The penultimate Thursday of January 2012" - `get_day_timestamp( 4, 2, 1, 2012, -1)`
1164 *
1165 * @param int $day_of_week The day representing the number in the week, Monday is `1`, Tuesday is `2`, Sunday is `7`
1166 * @param int $week_in_month The week number in the month; first week is `1`, second week is `2`; when direction is reverse
1167 * then `1` is last week of the month, `2` is penultimate week of the month and so on.
1168 * @param int $month The month number in the year, January is `1`
1169 * @param int $year The year number, e.g. "2015"
1170 * @param int $week_direction Either `1` or `-1`; the direction for the search referring to the week, defaults to `1`
1171 * to specify weeks in natural order so:
1172 * $week_direction `1` and $week_in_month `1` means "first week of the month"
1173 * $week_direction `1` and $week_in_month `3` means "third week of the month"
1174 * $week_direction `-1` and $week_in_month `1` means "last week of the month"
1175 * $week_direction `-1` and $week_in_month `2` means "penultimmate week of the month"
1176 *
1177 * @return int The day timestamp
1178 */
1179 public static function get_weekday_timestamp( $day_of_week, $week_in_month, $month, $year, $week_direction = 1 ) {
1180 if (
1181 ! (
1182 is_numeric( $day_of_week )
1183 && is_numeric( $week_in_month )
1184 && is_numeric( $month )
1185 && is_numeric( $year )
1186 && is_numeric( $week_direction )
1187 && in_array( $week_direction, [ -1, 1 ] )
1188 )
1189 ) {
1190 return false;
1191 }
1192
1193 if ( $week_direction > 0 ) {
1194 $startday = 1;
1195 } else {
1196 $startday = date( 't', mktime( 0, 0, 0, $month, 1, $year ) );
1197 }
1198
1199 $start = mktime( 0, 0, 0, $month, $startday, $year );
1200 $weekday = date( 'N', $start );
1201
1202 if ( $week_direction * $day_of_week >= $week_direction * $weekday ) {
1203 $offset = - $week_direction * 7;
1204 } else {
1205 $offset = 0;
1206 }
1207
1208 $offset += $week_direction * ( $week_in_month * 7 ) + ( $day_of_week - $weekday );
1209
1210 return mktime( 0, 0, 0, $month, $startday + $offset, $year );
1211 }
1212
1213 /**
1214 * Unescapes date format strings to be used in functions like `date`.
1215 *
1216 * Double escaping happens when storing a date format in the database.
1217 *
1218 * @param mixed $date_format A date format string.
1219 *
1220 * @return mixed Either the original input or an unescaped date format string.
1221 */
1222 public static function unescape_date_format( $date_format ) {
1223 if ( ! is_string( $date_format ) ) {
1224 return $date_format;
1225 }
1226
1227 // Why so simple? Let's handle other cases as those come up. We have tests in place!
1228 return str_replace( '\\\\', '\\', $date_format );
1229 }
1230
1231 /**
1232 * Builds a date object from a given datetime and timezone.
1233 *
1234 * @since 4.9.5
1235 *
1236 * @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or
1237 * a timestamp; defaults to `now`.
1238 * @param string|DateTimeZone|null $timezone A timezone string, UTC offset or DateTimeZone object;
1239 * defaults to the site timezone; this parameter is ignored
1240 * if the `$datetime` parameter is a DatTime object.
1241 * @param bool $with_fallback Whether to return a DateTime object even when the date data is
1242 * invalid or not; defaults to `true`.
1243 *
1244 * @return DateTime|false A DateTime object built using the specified date, time and timezone; if `$with_fallback`
1245 * is set to `false` then `false` will be returned if a DateTime object could not be built.
1246 */
1247 public static function build_date_object( $datetime = 'now', $timezone = null, $with_fallback = true ) {
1248 if ( $datetime instanceof DateTime ) {
1249 return clone $datetime;
1250 }
1251
1252 if ( class_exists( 'DateTimeImmutable' ) && $datetime instanceof DateTimeImmutable ) {
1253 // Return the mutable version of the date.
1254 return Date_I18n::createFromImmutable( $datetime );
1255 }
1256
1257 $timezone_object = null;
1258 $datetime = empty( $datetime ) ? 'now' : $datetime;
1259
1260 try {
1261 // PHP 5.2 will not throw an exception but will generate an error.
1262 $utc = new DateTimeZone( 'UTC' );
1263 $timezone_object = Tribe__Timezones::build_timezone_object( $timezone );
1264
1265 if ( self::is_timestamp( $datetime ) ) {
1266 $timestamp_timezone = $timezone ? $timezone_object : $utc;
1267
1268 return new Date_I18n( '@' . $datetime, $timestamp_timezone );
1269 }
1270
1271 set_error_handler( 'tribe_catch_and_throw' );
1272 $date = new Date_I18n( $datetime, $timezone_object );
1273 restore_error_handler();
1274 } catch ( Exception $e ) {
1275 // If we encounter an error, we need to restore after catching.
1276 restore_error_handler();
1277
1278 if ( $timezone_object === null ) {
1279 $timezone_object = Tribe__Timezones::build_timezone_object( $timezone );
1280 }
1281
1282 return $with_fallback
1283 ? new Date_I18n( 'now', $timezone_object )
1284 : false;
1285 }
1286
1287 return $date;
1288 }
1289
1290 /**
1291 * Validates a date string to make sure it can be used to build DateTime objects.
1292 *
1293 * @since 4.9.5
1294 *
1295 * @param string $date The date string that should validated.
1296 *
1297 * @return bool Whether the date string can be used to build DateTime objects, and is thus parse-able by functions
1298 * like `strtotime`, or not.
1299 */
1300 public static function is_valid_date( $date ) {
1301 static $cache_var_name = __FUNCTION__;
1302
1303 $cache_date_check = tribe_get_var( $cache_var_name, [] );
1304
1305 if ( isset( $cache_date_check[ $date ] ) ) {
1306 return $cache_date_check[ $date ];
1307 }
1308
1309 $cache_date_check[ $date ] = self::build_date_object( $date, null, false ) instanceof DateTimeInterface;
1310
1311 tribe_set_var( $cache_var_name, $cache_date_check );
1312
1313 return $cache_date_check[ $date ];
1314 }
1315
1316 /**
1317 * Returns the DateTime object representing the start of the week for a date.
1318 *
1319 * @since 4.9.21
1320 *
1321 * @throws Exception
1322 *
1323 * @param string|int|\DateTime $date The date string, timestamp or object.
1324 * @param int|null $start_of_week The number representing the start of week day as handled by
1325 * WordPress: `0` (for Sunday) through `6` (for Saturday).
1326 *
1327 * @return array An array of objects representing the week start and end days, or `false` if the
1328 * supplied date is invalid. The timezone of the returned object is set to the site one.
1329 * The week start has its time set to `00:00:00`, the week end will have its time set
1330 * `23:59:59`.
1331 */
1332 public static function get_week_start_end( $date, $start_of_week = null ) {
1333 static $cache_var_name = __FUNCTION__;
1334
1335 $cache_week_start_end = tribe_get_var( $cache_var_name, [] );
1336
1337 $date_obj = static::build_date_object( $date );
1338 $date_obj->setTime( 0, 0, 0 );
1339
1340 $date_string = $date_obj->format( static::DBDATEFORMAT );
1341
1342 // `0` (for Sunday) through `6` (for Saturday), the way WP handles the `start_of_week` option.
1343 $week_start_day = null !== $start_of_week
1344 ? (int) $start_of_week
1345 : (int) get_option( 'start_of_week', 0 );
1346
1347 $memory_cache_key = "{$date_string}:{$week_start_day}";
1348
1349 if ( isset( $cache_week_start_end[ $memory_cache_key ] ) ) {
1350 return $cache_week_start_end[ $memory_cache_key ];
1351 }
1352
1353 $cache_key = md5(
1354 __METHOD__ . serialize( [ $date_obj->format( static::DBDATEFORMAT ), $week_start_day ] )
1355 );
1356 $cache = tribe( 'cache' );
1357
1358 if ( false !== $cached = $cache[ $cache_key ] ) {
1359 return $cached;
1360 }
1361
1362 // `0` (for Sunday) through `6` (for Saturday), the way WP handles the `start_of_week` option.
1363 $date_day = (int) $date_obj->format( 'w' );
1364
1365 $week_offset = 0;
1366 if ( 0 === $date_day && 0 !== $week_start_day ) {
1367 $week_offset = 0;
1368 } elseif ( $date_day < $week_start_day ) {
1369 // If the current date of the week is before the start of the week, move back a week.
1370 $week_offset = -1;
1371 } elseif ( 0 === $date_day ) {
1372 // When start of the week is on a sunday we add a week.
1373 $week_offset = 1;
1374 }
1375
1376 $week_start = clone $date_obj;
1377
1378 /*
1379 * From the PHP docs, the `W` format stands for:
1380 * - ISO-8601 week number of year, weeks starting on Monday
1381 */
1382 $week_start->setISODate(
1383 (int) $week_start->format( 'o' ),
1384 (int) $week_start->format( 'W' ) + $week_offset,
1385 $week_start_day
1386 );
1387
1388 $week_end = clone $week_start;
1389 // Add 6 days, then move at the end of the day.
1390 $week_end->add( new DateInterval( 'P6D' ) );
1391 $week_end->setTime( 23, 59, 59 );
1392
1393 $week_start = static::immutable( $week_start );
1394 $week_end = static::immutable( $week_end );
1395
1396 $cache[ $cache_key ] = [ $week_start, $week_end ];
1397 $cache_week_start_end[ $memory_cache_key ] = [ $week_start, $week_end ];
1398
1399 tribe_set_var( $cache_var_name, $cache_week_start_end );
1400
1401 return [ $week_start, $week_end ];
1402 }
1403
1404 /**
1405 * Given a specific DateTime we determine the end of that day based on our Internal End of Day Cut-off.
1406 *
1407 * @since 4.11.2
1408 *
1409 * @param string|DateTimeInterface $date Date that we are getting the end of day from.
1410 * @param null|string $cutoff Which cutoff to use.
1411 *
1412 * @return DateTimeInterface|false Returns a DateTimeInterface when a valid date is given or false.
1413 */
1414 public static function get_shifted_end_of_day( $date, $cutoff = null ) {
1415 $date_obj = static::build_date_object( $date );
1416
1417 if ( ! $date_obj ) {
1418 return false;
1419 }
1420
1421 $start_of_day = clone $date_obj;
1422 $end_of_day = clone $date_obj;
1423
1424 if ( empty( $cutoff ) || ! is_string( $cutoff ) || false === strpos( $cutoff, ':' ) ) {
1425 $cutoff = tribe_get_option( 'multiDayCutoff', '00:00' );
1426 }
1427
1428 list( $hours_to_add, $minutes_to_add ) = array_map( 'absint', explode( ':', $cutoff ) );
1429
1430 $seconds_to_add = ( $hours_to_add * HOUR_IN_SECONDS ) + ( $minutes_to_add * MINUTE_IN_SECONDS );
1431 if ( 0 !== $seconds_to_add ) {
1432 $interval = static::interval( "PT{$seconds_to_add}S" );
1433 }
1434
1435 $start_of_day->setTime( '0', '0', '0' );
1436 $end_of_day->setTime( '23', '59', '59' );
1437
1438 if ( 0 !== $seconds_to_add ) {
1439 $start_of_day->add( $interval );
1440 $end_of_day->add( $interval );
1441 }
1442
1443 if ( $end_of_day >= $date_obj && $date_obj >= $start_of_day ) {
1444 return $end_of_day;
1445 }
1446
1447 $start_of_day->sub( static::interval( 'P1D' ) );
1448
1449 if ( $start_of_day < $date_obj ) {
1450 $end_of_day->sub( static::interval( 'P1D' ) );
1451 }
1452
1453 return $end_of_day;
1454 }
1455
1456 /**
1457 * Given a specific DateTime we determine the start of that day based on our Internal End of Day Cut-off.
1458 *
1459 * @since 4.11.2
1460 *
1461 * @param string|DateTimeInterface $date Date that we are getting the start of day from.
1462 * @param null|string $cutoff Which cutoff to use.
1463 *
1464 * @return DateTimeInterface|false Returns a DateTimeInterface when a valid date is given or false.
1465 */
1466 public static function get_shifted_start_of_day( $date, $cutoff = null ) {
1467 $date_obj = static::build_date_object( $date );
1468
1469 if ( ! $date_obj ) {
1470 return false;
1471 }
1472
1473 $start_of_day = clone $date_obj;
1474 $end_of_day = clone $date_obj;
1475
1476 if ( empty( $cutoff ) || ! is_string( $cutoff ) || false === strpos( $cutoff, ':' ) ) {
1477 $cutoff = tribe_get_option( 'multiDayCutoff', '00:00' );
1478 }
1479
1480 list( $hours_to_add, $minutes_to_add ) = array_map( 'absint', explode( ':', $cutoff ) );
1481
1482 $seconds_to_add = ( $hours_to_add * HOUR_IN_SECONDS ) + ( $minutes_to_add * MINUTE_IN_SECONDS );
1483 if ( 0 !== $seconds_to_add ) {
1484 $interval = static::interval( "PT{$seconds_to_add}S" );
1485 }
1486
1487 $start_of_day->setTime( '0', '0', '0' );
1488 $end_of_day->setTime( '23', '59', '59' );
1489
1490 if ( 0 !== $seconds_to_add ) {
1491 $start_of_day->add( $interval );
1492 $end_of_day->add( $interval );
1493 }
1494
1495 if ( $end_of_day <= $date_obj && $date_obj >= $start_of_day ) {
1496 return $start_of_day;
1497 }
1498
1499 $end_of_day->sub( static::interval( 'P1D' ) );
1500
1501 if ( $end_of_day > $date_obj ) {
1502 $start_of_day->sub( static::interval( 'P1D' ) );
1503 }
1504
1505 return $start_of_day;
1506 }
1507
1508 /**
1509 * Builds and returns a `DateInterval` object from the interval specification.
1510 *
1511 * For performance purposes the use of `DateInterval` specifications is preferred, so `P1D` is better than
1512 * `1 day`.
1513 *
1514 * @since 4.10.2
1515 *
1516 * @return DateInterval The built date interval object.
1517 */
1518 public static function interval( $interval_spec ) {
1519 try {
1520 $interval = new \DateInterval( $interval_spec );
1521 } catch ( \Exception $e ) {
1522 $interval = DateInterval::createFromDateString( $interval_spec );
1523 }
1524
1525 return $interval;
1526 }
1527
1528 /**
1529 * Builds the immutable version of a date from a string, integer (timestamp) or \DateTime object.
1530 *
1531 * It's the immutable version of the `Tribe__Date_Utils::build_date_object` method.
1532 *
1533 * @since 4.10.2
1534 *
1535 * @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or
1536 * a timestamp; defaults to `now`.
1537 * @param string|DateTimeZone|null $timezone A timezone string, UTC offset or DateTimeZone object;
1538 * defaults to the site timezone; this parameter is ignored
1539 * if the `$datetime` parameter is a DatTime object.
1540 * @param bool $with_fallback Whether to return a DateTime object even when the date data is
1541 * invalid or not; defaults to `true`.
1542 *
1543 * @return DateTimeImmutable|false A DateTime object built using the specified date, time and timezone; if
1544 * `$with_fallback` is set to `false` then `false` will be returned if a
1545 * DateTime object could not be built.
1546 */
1547 static function immutable( $datetime = 'now', $timezone = null, $with_fallback = true ) {
1548 if ( $datetime instanceof DateTimeImmutable ) {
1549 return $datetime;
1550 }
1551
1552 if ( $datetime instanceof DateTime ) {
1553 return Date_I18n_Immutable::createFromMutable( $datetime );
1554 }
1555
1556 $mutable = static::build_date_object( $datetime, $timezone, $with_fallback );
1557
1558 if ( false === $mutable ) {
1559 return false;
1560 }
1561
1562 $cache_key = md5( ( __METHOD__ . $mutable->getTimezone()->getName() . $mutable->getTimestamp() ) );
1563 $cache = tribe( 'cache' );
1564
1565 if ( false !== $cached = $cache[ $cache_key ] ) {
1566 return $cached;
1567 }
1568
1569 $immutable = Date_I18n_Immutable::createFromMutable( $mutable );
1570
1571 $cache[ $cache_key ] = $immutable;
1572
1573 return $immutable;
1574 }
1575
1576 /**
1577 * Builds a date object from a given datetime and timezone.
1578 *
1579 * An alias of the `Tribe__Date_Utils::build_date_object` function.
1580 *
1581 * @since 4.10.2
1582 *
1583 * @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or
1584 * a timestamp; defaults to `now`.
1585 * @param string|DateTimeZone|null $timezone A timezone string, UTC offset or DateTimeZone object;
1586 * defaults to the site timezone; this parameter is ignored
1587 * if the `$datetime` parameter is a DatTime object.
1588 * @param bool $with_fallback Whether to return a DateTime object even when the date data is
1589 * invalid or not; defaults to `true`.
1590 *
1591 * @return DateTime|false A DateTime object built using the specified date, time and timezone; if `$with_fallback`
1592 * is set to `false` then `false` will be returned if a DateTime object could not be built.
1593 */
1594 public static function mutable( $datetime = 'now', $timezone = null, $with_fallback = true ) {
1595 return static::build_date_object( $datetime, $timezone, $with_fallback );
1596 }
1597 }
1598 }
1599