Mike Eng User Experience Designer 401-234-4611
me on github

Developing a Dynamic Schedule in WordPress

In my post, Open Source Web Development Software, I discussed some of the open source tools we used in the process of developing a custom WordPress theme for the Better World by Design website. This post dives into the nitty-gritty of some of the theme development involved.

Better World by Design - Dynamic Schedule

Dynamic Schedule page

Here’s an example of how we pushed WordPress to the limit – achieving some of the power of Drupal while maintaining the top-notch usability of WordPress. One of the more involved pieces of the theme was a dynamic schedule of conference activities.


  1. The schedule needed to show items across multiple content types (or “custom post types” in WordPress parlance).
  2. The conference spanned three days, so items needed to be grouped by day, then ordered by start time.
  3. Content entry should follow the principle of DRY (don’t repeat yourself). Data will appear in various views, but it should only be entered once.

Plugins Used

Charles and Ray Eames famously instructed readers to “innovate as a last resort“. In that spirit, this feature stood on the shoulders of some solid WordPress plugins and innovated only where necessary.

  1. Custom Post Type UI: Admin UI for creating custom post types and taxonomies.
  2. Simple Fields: Allows the addition of custom fields such as textareas, checkboxes, radio buttons, drop-downs, or file uploads to specified post types.

Content Setup

Items that appear on the schedule page are comprised of the following custom post types:

  1. Speakers
  2. Panels
  3. Workshops & Tours (they are grouped together because they occurred in the same time slot)
  4. Events (such as social mixers)
  5. Schedule Items (opening, closing, breaks, meals)

Each of those custom post types listed above had the following custom fields:

  1. Day
  2. Start Time
    1. Hour
    2. Minute
    3. AM / PM
  3. End Time
    1. Hour
    2. Minute
    3. AM / PM
A separate custom post type called “locations” stored locations at the building level (as opposed to the room level). We used this with MapPress Pro to generate the mashup map of all conference locations, pulling points from all “locations” posts.
Items on the schedule page had a custom taxonomy called room (which specified the room level of location).


The basic approach consisted of lots of array manipulation before some fairly simple output.

We grabbed all these items from the database with a single query.

//query for all schedule content
$schedule_query = new WP_Query(array('posts_per_page' => -1, 'post_type'=>array('speakers','panels','workshops-tours','events','schedule-items'));
Then we started a while loop to iterate through each item and defined some variables (most of which are calling custom fields) for each one retrieved.

while ($schedule_query->have_posts()) : $schedule_query->the_post();
//defining variables for each post
$id = get_the_id();
$day = simple_fields_get_post_value(get_the_id(), 'Day', true);
$start_am_pm = simple_fields_get_post_value(get_the_id(), 'Start - AM/PM', true);
$start_hour = simple_fields_get_post_value(get_the_id(), 'Start - Hour', true);
$start_minute = simple_fields_get_post_value(get_the_id(), 'Start - Minute', true);
$end_am_pm = simple_fields_get_post_value(get_the_id(), 'End - AM/PM', true);
$end_hour = simple_fields_get_post_value(get_the_id(), 'End - Hour', true);
$end_minute = simple_fields_get_post_value(get_the_id(), 'End - Minute', true);
Now we had all the fields indicating time stored in the variables. The next step was sorting these items by their start time, which wasn’t as straightforward as it may sound. We had to take this human-readable description of time and translate it to be ordered by logic.
We couldn’t order by just one of those variables (hour, minute, am/pm) – we had to concatenate them in order to get the full time. We were treating this new variable as a string, so with the hours, in order to keep the number of digits consistent between “1” and “11”, we transformed single-digit hours such as “1” into “01”. We stored the result in a variable called $full_time.

// if start hour and end hour are both single-digit values, concatenates a 0 before both start hour and end hour
if (($start_hour == 1 || $start_hour == 2 || $start_hour == 3 || $start_hour == 4 || $start_hour == 5 || $start_hour == 6 || $start_hour == 7 || $start_hour == 8 || $start_hour == 9) && ($end_hour == 1 || $end_hour == 2 || $end_hour == 3 || $end_hour == 4 || $end_hour == 5 || $end_hour == 6 || $end_hour == 7 || $end_hour == 8 || $end_hour == 9)){
$full_time = $start_am_pm.'0'.$start_hour.$start_minute.$end_am_pm.'0'.$end_hour.$end_minute;
}// if start hour is single-digit value, concatenates a 0 before start hour
elseif ($start_hour == 1 || $start_hour == 2 || $start_hour == 3 || $start_hour == 4 || $start_hour == 5 || $start_hour == 6 || $start_hour == 7 || $start_hour == 8 || $start_hour == 9){
$full_time = $start_am_pm.'0'.$start_hour.$start_minute.$end_am_pm.$end_hour.$end_minute;

// if end hour is single-digit value, concatenates a 0 before end hour
elseif ($end_hour == 1 || $end_hour == 2 || $end_hour == 3 || $end_hour == 4 || $end_hour == 5 || $end_hour == 6 || $end_hour == 7 || $end_hour == 8 || $end_hour == 9){
$full_time = $start_am_pm.$start_hour.$start_minute.$end_am_pm.'0'.$end_hour.$end_minute;

// otherwise concatenates all time as is
else {
$full_time = $start_am_pm.$start_hour.$start_minute.$end_am_pm.$end_hour.$end_minute;
This mostly worked, but it wasn’t quite enough. At this point, a full time of 12:01 PM would be logically ordered after 11:01 PM. Sounds crazy until one thinks about it like a computer. Next step was to change “12” to “00”.

// replaces 12 with 00 for correct ordering by hour
$full_time = str_replace('12' , '00' , $full_time);
It was playing a little loose to replace all instances (not just hours) of “12” with “00”, but it was safe here, since the data wouldn’t have any “12”s in the minute field – the custom fields were drop-downs with 15-minute increments as options.
Now that we had the variable we needed in order to sort, we placed each query result into one of three numerically-indexed arrays, depending on the day.

$counter_friday = 0;
$counter_saturday = 0;
$counter_sunday = 0;if ($day == 'Friday'){
$friday_arr[$counter_friday] = array('post_type'=>get_post_type(), 'title'=>get_the_title(), 'permalink'=>get_permalink(), 'start_am_pm'=>$start_am_pm, 'start_hour'=>$start_hour, 'start_minute'=>$start_minute, 'end_am_pm'=>$end_am_pm, 'end_hour'=>$end_hour, 'end_minute'=>$end_minute, 'location'=>$location, 'room'=>$room, 'full_time'=>$full_time);

} elseif($day == 'Saturday'){
$saturday_arr[$counter_saturday] = array('post_type'=>get_post_type(), 'title'=>get_the_title(), 'permalink'=>get_permalink(), 'start_am_pm'=>$start_am_pm, 'start_hour'=>$start_hour, 'start_minute'=>$start_minute, 'end_am_pm'=>$end_am_pm, 'end_hour'=>$end_hour, 'end_minute'=>$end_minute, 'location'=>$location, 'room'=>$room, 'full_time'=>$full_time);

} elseif($day == 'Sunday'){
$sunday_arr[$counter_sunday] = array('post_type'=>get_post_type(), 'title'=>get_the_title(), 'permalink'=>get_permalink(), 'start_am_pm'=>$start_am_pm, 'start_hour'=>$start_hour, 'start_minute'=>$start_minute, 'end_am_pm'=>$end_am_pm, 'end_hour'=>$end_hour, 'end_minute'=>$end_minute, 'location'=>$location, 'room'=>$room, 'full_time'=>$full_time);
The rest involved manipulating each array (containing all items for a day) to order its contents by $full_time and then echoing the variables in the proper places.


Thinking through all the logical implications of a 12-hour clock suddenly makes the 24-hour clock much more appealing. It’s amazing the gymnastics we are hardwired to do subconsciously in our heads.

As another example of odd human terminology, the word “biweekly” can mean either twice a week or once every two weeks.