The Third Bear

Just Right.

Customizing ActionKit events: adding "duration" and end times

egj actionkit , events , actionkit data manager

By default, ActionKit events have a start time that's specified by their hosts, but they don't have any concept of an end time or a duration.  Knowing how long an event will last can be important for both organizing reasons -- staff want to know how big a commitment the hosts are making; potential attendees want to know how long they'll be showing up for -- and technical reasons -- an event that lasts ten hours should remain posted online for a lot longer than one that lasts thirty minutes.

ActionKit's `events_event` database table actually has columns for `ends_at` and `ends_at_utc`.  They're never directly exposed in the user interface.  But the existence of these columns is great, because they automatically handle timezone conversions for you (just like `starts_at` / `starts_at_utc`) so they can be used all your in template code, both to display user-friendly local times, and to calculate whether an event has already ended for technical requirements like displaying "time remaining" or hiding them from the user interface.

For example, we can update `event_host_details.html` and `event_search_results.html` to display every event's local end time, if it has one:

<!-- event_host_details.html -->
<td class="text-left">
  {{ event.get_starts_at_display }}
  {% if event.ends_at %}- {{ event.ends_at|date:"P" }}{% endif %}
  {% if event.is_in_past %} <span class="event-over-notice">(event is now over)</span>{% endif %}
</td>
<!-- event_search_results.html -->
<div class="event-time">
  {{ event.starts_at }}
  {% if event.obj.ends_at %} - {{ event.obj.ends_at|date:"P" }}{% endif %}
</div>

But, how do we enter data into these columns?  

As far as I know, you can't set `ends_at` directly from the "event create" page form, even if you customize your templates. Trying to add an "event_ends_at" field, or separate "event_ends_at_date" + "event_ends_at_time" + "event_ends_at_ampm" fields, will result in errors when submitting the form.

However, this isn't a big setback. From an end user's perspective, a better interface is a "duration" field for choosing hours and minutes in freeform numeric fields, or from a dropdown menu.  It's probably more natural to think about when planning your event, and choosing a start date+time and a duration is generally less error-prone than having to set two dates, two times, and two "am/pm"s.

So, we can add "duration" as a simple custom field on our event create page:

<div id="id_action_event_duration_in_hours">
  <label for="id_action_event_duration_in_hours">How long do you expect your event to be?</label>
  <select name="action_event_duration_in_hours" id="id_action_event_duration_in_hours">
    <option value="0.5">30 minutes</option>
    <option value="1">1 hour</option>
    <option value="1.5">90 minutes</option>
    <option value="2" selected>2 hours</option>
    <option value="2.5">2.5 hours</option>
    <option value="3">3 hours</option>
    <option value="4">4 hours</option>
    <option value="6">6 hours</option>
    <option value="12">12 hours</option>
  </select> 
</div>
event duration

Next, we need to calculate each event's `ends_at` timestamp from its start time and duration, if a duration has been specified.  We'll need to make sure we're doing this for events as they're created, and also updating it when hosts change either the start time or the duration.  We'll then need to use ActionKit's REST API to patch every event with its correct `ends_at`.

If you're using our ActionKit Data Management platform, this is very easy to set up on a recurring job.  You'll just need to create an Event Modification Job which:

  1. Looks at every event in the database that has a duration specified
  2. Selects each matching event's ID, duration, start time, and end time (which may be NULL)
  3. Calculates each event's "correct" end time by adding its current duration to its current start time
  4. Compares each event's "correct" end time to its current end time in the database
  5. Filters out all events whose "correct" end time is identical to its current end time in the database -- we don't need to modify those again
  6. Uses the API to set the end time equal to the "correct" end time

Here's the SQL code that performs these steps:

select * from (
  select 
    f.value as duration, 
    e.id as event_id, 
    starts_at, 
    starts_at_utc, 
    ends_at_utc, 
    ends_at, 
    date_format(starts_at + interval (f.value * 60) minute, "%Y-%m-%d %H:%i") as new_data_ends_at 
  from 
    events_event e 
    join events_eventfield f on f.parent_id=e.id and f.name="event_duration_in_hours" 
) foo 
where (
  (new_data_ends_at <> ends_at) 
  or 
  (new_data_ends_at is not null and ends_at is null) 
)

Just set that to run on a recurring job with a frequency depending on the volume of your event creation and update activity.  (Or just run it every minute; it's a fast query and most of the time it won't return any results so it will exit quickly too.)

And note that if you're allowing hosts to "unset" the duration field, you'll also need a second job.  This second job should find events that have an end time but no duration field, and then unset the end time.