WC_Data_Store_WP::parse_date_for_wp_query │ public │ WC 3.2.0
Map a valid date query var to WP_Query arguments. Valid date formats: YYYY-MM-DD or timestamp, possibly combined with an operator from $valid_operators. Also accepts a WC_DateTime object.
Method of the class: WC_Data_Store_WP{}
No Hooks.
Returns
array. Modified $wp_query_args
Usage
$WC_Data_Store_WP = new WC_Data_Store_WP(); $WC_Data_Store_WP->parse_date_for_wp_query( $query_var, $key, $wp_query_args );
- $query_var(mixed) (required)
- A valid date format.
- $key(string) (required)
- meta or db column key.
- $wp_query_args(array)
- WP_Query args.
Default:array()
Changelog
| Since 3.2.0 | Introduced. |
WC_Data_Store_WP::parse_date_for_wp_query() WC Data Store WP::parse date for wp query code WC 11.2.2
public function parse_date_for_wp_query( $query_var, $key, $wp_query_args = array() ) {
$query_parse_regex = '/([^.<>]*)(>=|<=|>|<|\.\.\.)([^.<>]+)/';
$valid_operators = array( '>', '>=', '=', '<=', '<', '...' );
// YYYY-MM-DD queries have 'day' precision. Timestamp/WC_DateTime queries have 'second' precision.
$precision = 'second';
$dates = array();
$operator = '=';
// The raw strings $dates[0] and $dates[1] came from. Only day precision reads them.
$raw_start = '';
$raw_end = '';
try {
// Specific time query with a WC_DateTime.
if ( is_a( $query_var, 'WC_DateTime' ) ) {
$dates[] = $query_var;
} elseif ( is_numeric( $query_var ) ) { // Specific time query with a timestamp.
$dates[] = new WC_DateTime( "@{$query_var}", new DateTimeZone( 'UTC' ) );
} elseif ( preg_match( $query_parse_regex, $query_var, $sections ) ) { // Query with operators and possible range of dates.
if ( ! empty( $sections[1] ) ) {
$dates[] = is_numeric( $sections[1] ) ? new WC_DateTime( "@{$sections[1]}", new DateTimeZone( 'UTC' ) ) : wc_string_to_datetime( $sections[1] );
$raw_start = $sections[1];
$raw_end = $sections[3];
} else {
$raw_start = $sections[3];
}
$operator = in_array( $sections[2], $valid_operators, true ) ? $sections[2] : '';
$dates[] = is_numeric( $sections[3] ) ? new WC_DateTime( "@{$sections[3]}", new DateTimeZone( 'UTC' ) ) : wc_string_to_datetime( $sections[3] );
if ( ! is_numeric( $sections[1] ) && ! is_numeric( $sections[3] ) ) {
$precision = 'day';
}
} else { // Specific time query with a string.
$dates[] = wc_string_to_datetime( $query_var );
$raw_start = $query_var;
$precision = 'day';
}
} catch ( Exception $e ) {
return $wp_query_args;
}
// Check for valid inputs.
if ( ! $operator || empty( $dates ) || ( '...' === $operator && count( $dates ) < 2 ) ) {
return $wp_query_args;
}
// Build date query for 'post_date' or 'post_modified' keys.
if ( 'post_date' === $key || 'post_modified' === $key ) {
if ( ! isset( $wp_query_args['date_query'] ) ) {
$wp_query_args['date_query'] = array();
}
$query_arg = array(
'column' => 'day' === $precision ? $key : $key . '_gmt',
'inclusive' => '>' !== $operator && '<' !== $operator,
);
// Add 'before'/'after' query args.
$comparisons = array();
if ( '>' === $operator || '>=' === $operator || '...' === $operator ) {
$comparisons[] = 'after';
}
if ( '<' === $operator || '<=' === $operator || '...' === $operator ) {
$comparisons[] = 'before';
}
foreach ( $comparisons as $index => $comparison ) {
if ( 'day' === $precision ) {
/**
* WordPress doesn't generate the correct SQL for inclusive day queries with both a 'before' and
* 'after' string query, so we have to use the array format in 'day' precision.
*
* @see https://core.trac.wordpress.org/ticket/29908
*/
$query_arg[ $comparison ]['year'] = $dates[ $index ]->date( 'Y' );
$query_arg[ $comparison ]['month'] = $dates[ $index ]->date( 'n' );
$query_arg[ $comparison ]['day'] = $dates[ $index ]->date( 'j' );
} else {
/**
* WordPress doesn't support 'hour'/'second'/'minute' in array format 'before'/'after' queries,
* so we have to use a string query.
*/
$query_arg[ $comparison ] = gmdate( 'm/d/Y H:i:s', $dates[ $index ]->getTimestamp() );
}
}
if ( empty( $comparisons ) ) {
$query_arg['year'] = $dates[0]->date( 'Y' );
$query_arg['month'] = $dates[0]->date( 'n' );
$query_arg['day'] = $dates[0]->date( 'j' );
if ( 'second' === $precision ) {
$query_arg['hour'] = $dates[0]->date( 'H' );
$query_arg['minute'] = $dates[0]->date( 'i' );
$query_arg['second'] = $dates[0]->date( 's' );
}
}
$wp_query_args['date_query'][] = $query_arg;
return $wp_query_args;
}
// Build meta query for unrecognized keys.
if ( ! isset( $wp_query_args['meta_query'] ) ) {
$wp_query_args['meta_query'] = array(); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
}
// Meta dates are stored as timestamps in the db.
// Check against beginning/end-of-day timestamps when using 'day' precision.
if ( 'day' === $precision ) {
/*
* Name the day from the raw string the way OrdersTableQuery::local_time_to_gmt_date_query()
* does, never from the timezone-aware $dates, or posts and HPOS resolve one query var to
* different days.
*/
try {
$timezone = wp_timezone();
$start = new DateTime( gmdate( 'Y-m-d', (int) wc_string_to_timestamp( $raw_start ) ) . ' 00:00:00', $timezone );
$end = '...' === $operator
? new DateTime( gmdate( 'Y-m-d', (int) wc_string_to_timestamp( $raw_end ) ) . ' 00:00:00', $timezone )
: clone $start;
} catch ( Exception $e ) {
/*
* The date could not be constructed, which a year past 9999 does. Match nothing, by
* asking for a value that is both at least 1 and at most 0.
*
* Returning without a clause would not do that: the callers have already dropped
* their own date clause, so the query would come back unfiltered. Nor would a single
* bound like "below 0", since these keys hold a negative timestamp for a date before
* 1970.
*/
$wp_query_args['meta_query'][] = array(
'key' => $key,
'value' => array( 1, 0 ),
'type' => 'NUMERIC',
'compare' => 'BETWEEN',
);
return $wp_query_args;
}
/*
* The end of the day is the next local midnight, not the start plus 24 hours: a local day
* runs 23 or 25 hours across a DST transition. '+1 day' would not do either, because it
* keeps the start's time of day, and that is not midnight in a timezone where DST begins
* at 00:00 and the constructor above had to move the start forward.
*/
$end->modify( 'tomorrow' );
$start_timestamp = $start->getTimestamp();
$end_timestamp = $end->getTimestamp();
// The bounds are half-open (>= start, < end) so that a day is matched in full and exactly once.
switch ( $operator ) {
case '>':
$wp_query_args['meta_query'][] = array(
'key' => $key,
'value' => $end_timestamp,
'compare' => '>=',
);
break;
case '<=':
$wp_query_args['meta_query'][] = array(
'key' => $key,
'value' => $end_timestamp,
'compare' => '<',
);
break;
case '<':
case '>=':
$wp_query_args['meta_query'][] = array(
'key' => $key,
'value' => $start_timestamp,
'compare' => $operator,
);
break;
default:
$wp_query_args['meta_query'][] = array(
'key' => $key,
'value' => $start_timestamp,
'compare' => '>=',
);
$wp_query_args['meta_query'][] = array(
'key' => $key,
'value' => $end_timestamp,
'compare' => '<',
);
}
} elseif ( '...' !== $operator ) {
$wp_query_args['meta_query'][] = array(
'key' => $key,
'value' => $dates[0]->getTimestamp(),
'compare' => $operator,
);
} else {
$wp_query_args['meta_query'][] = array(
'key' => $key,
'value' => $dates[0]->getTimestamp(),
'compare' => '>=',
);
$wp_query_args['meta_query'][] = array(
'key' => $key,
'value' => $dates[1]->getTimestamp(),
'compare' => '<=',
);
}
return $wp_query_args;
}