You can add a filter on posts_clauses to;
- left join additional columns for each meta filter
- add order by clause base on those column values.
Something like below should work, assuming your meta key is start_date
and the value has the actual date format Y-m-d
and your post type is talks
add_filter( 'posts_clauses', function ($clauses, \WP_Query $q) {
// exit if post type not talk, or in admin, or not main query, or not archive
if ( $q->get('post_type') != 'talks' || is_admin() || !$q->is_main_query() || !is_archive() )
return $clauses;
// left join the meta key for future date (fd) and past date (pd)
$clauses['join'] = "
LEFT JOIN wp_postmeta fd ON (wp_posts.ID = fd.post_id)
AND fd.meta_key = 'start_date'
AND CAST(fd.meta_value AS DATE) >= CAST('".date('Y-m-d')."' AS DATE)
LEFT JOIN wp_postmeta pd ON (wp_posts.ID = pd.post_id)
AND pd.meta_key = 'start_date'
AND CAST(pd.meta_value AS DATE) < CAST('".date('Y-m-d')."' AS DATE)";
//order them first if fd value is null, followed by fd ascending order, then pd descending order
$clauses['orderby'] = 'CASE WHEN fd.meta_value IS NULL THEN 1 ELSE 0 END, CAST(fd.meta_value AS DATE) ASC, CAST(pd.meta_value AS DATE) DESC';
return $clauses;
}, 1, 2 );