Querying ongoing and single events separately

Let's assume you're working with code along these lines:

$events = tribe_get_events();
foreach ( $events as $event ):
    // ... Code to print out event details ...
endforeach;

It may be however that you have a number of – perhaps long-running – multiday events that you wish to ignore. In very simple cases you can handle this by making use of the tribe_is_multiday() function as this example shows:

$events = tribe_get_events();
foreach ( $events as $event ):
    // Skip any multiday events!
    if (  tribe_event_is_multiday( $event->ID ) ) continue;
        // ... Code to print out event details ...
    endif;
endforeach;

In many cases this will be perfectly fine, but the downside is if you want to show the next 10 upcoming events there is no way to guarantee that some of those results won't themselves be multiday events ... of course that opens up the possibility that some will be discarded and less than 10 events will ultimately be shown to the visitor.

An easy "hack" to solve this is simply to increase the number of results you are querying for:

$events = tribe_get_events( array(
    'posts_per_page' => 50
) );

This will pull upto 50 events rather than the default number, which is normally much lower. Now, this isn't necessarily the most efficient way to go, but often it is the quickest in terms of putting a solution together.

If you only want to list 10 events and not 50 (in case there are no upcoming multiday events at the point in time when the query runs) you can simply use a counter:

$counter = 0;
$desired = 10;

$events = tribe_get_events( array(
    'posts_per_page' => 50
) );

foreach ( $events as $event ):
    if ( tribe_event_is_multiday( $event->ID ) ) continue;
    if ( ++counter > $desired ) break;
        // ... Code to print out event details ...
    endif;
endforeach;

Advanced techniques

The experienced coder may scorn such a hack as we've outlined above: surely we can craft a query that handles this task for us?

That is indeed possible and, if we simplistically define a multiday event as one where the duration exceeds 24hrs (86,400 seconds) we can form a wrapper around tribe_get_events() like this:

class SingleDayEvents {
	public $results;

	public static function fetch( $args = array() ) {
		$query = new self( $args );
		return $query->results;
	}

	public function __construct( $args = array() ) {
		$this->setup();
		$this->query( $args );
		$this->teardown();
	}

	protected function setup() {
		add_filter( 'tribe_events_query_posts_joins',  array( $this, 'posts_join' ) );
		add_filter( 'tribe_events_query_posts_fields', array( $this, 'posts_fields' ) );
		add_filter( 'posts_where', array( $this, 'posts_where' ), 100 );
	}

	protected function teardown() {
		remove_filter( 'tribe_events_query_posts_joins',  array( $this, 'posts_join' ) );
		remove_filter( 'tribe_events_query_posts_fields', array( $this, 'posts_fields' ) );
		remove_filter( 'posts_where', array( $this, 'posts_where' ), 100 );
	}

	public function query( $args ) {
		$this->results = tribe_get_events( $args );
	}

	/**
	 * It may be that PRO implements some of this for us, if so - great! If not - they joins will be
	 * made even if we are only using core.
	 *
	 * @param  array $fields
	 * @return array
	 **/
	public function posts_fields( $fields ) {
		$fields['event_start_date'] = "tribe_event_start_date.meta_value as EventEndDate";
		$fields['event_end_date']   = "tribe_event_end_date.meta_value as EventEndDate";
		return $fields;
	}

	/**
	 * Counterpart to $this->posts_fields().
	 *
	 * @param  array $joins
	 * @return array
	 **/
	public function posts_join( $joins ) {
		global $wpdb;
		$joins['event_start_date'] = " LEFT JOIN $wpdb->postmeta as tribe_event_start_date ON ( $wpdb->posts.ID = tribe_event_start_date.post_id AND tribe_event_start_date.meta_key = '_EventStartDate' ) ";
		$joins['event_end_date']   = " LEFT JOIN $wpdb->postmeta as tribe_event_end_date ON ( $wpdb->posts.ID = tribe_event_end_date.post_id AND tribe_event_end_date.meta_key = '_EventEndDate' ) ";
		return $joins;
	}

	/**
	 * Adds logic to reject multiday events from the result set.
	 *
	 * The logic is still relatively crude - it treats any event longer than 24hrs in duration as "multiday"
	 * whereas in practice definitions may be more elastic than this but it could of course be modified
	 * further to meet specific needs.
	 *
	 * @param  string $where_sql
	 * @return string
	 */
	public function posts_where( $where_sql ) {
		return " $where_sql AND UNIX_TIMESTAMP( tribe_event_end_date.meta_value ) - UNIX_TIMESTAMP( tribe_event_start_date.meta_value ) < 86400 ";
	}
}

Usage is as follows:

$single_day_events = SingleDayEvents::fetch();

The normal range of arguments can be passed. For instance, if you are only interested in upcoming events you might do this:

$events = SingleDayEvents::fetch( array( 'eventDisplay' => 'list' ) );

The key difference, of course, being that no multiday events should be included in the result set. We hope this helps - and do feel free to adjust and modify to better suit your needs!