Sturm

Software engineering

WooCommerce Bookings buffer periods not working? Here's what to do

Ben Sturmfels, 27 Feb 2019

WooCommerce Bookings is a brilliant tool for allowing self-service bookings of your facilities, equipment or time. This post describes how the buffer periods feature didn't work as we expected, and how we resolved this.

Our client today is the Brown Hill Community Hall Advisory Committee; a group of business, sporting and community representatives who make sure the public hall is used and maintained in the best interests of the community. It's a wonderful asset, and regularly hosts events like monthly farmers markets, rock 'n roll dance groups, meetings, Taekwondo lessons, birthday parties and seniors' fitness classes to name but a few.

Following some major renovations and investment, the Committee are keen to see the venue well utilised. Up until now, the limiting factor has been the slow response times to availability enquiries and the cumbersome paper-based booking system. It was simply too much trouble, both for the hirer and the volunteer committee, to bother with ad-hoc bookings of 2 hours or less or hire the smaller rooms individually. And so the hall goes underused.

The booking system we recommended and have been implementing is based on WooCommerce and the WooCommerce Bookings extension. This system manages all bookings and availability. Hirers may view the availability of the meeting rooms and main hall, answer some questions and place a tentative booking. The committee can then approve a booking if appropriate or request more information. The hirer is then prompted to confirm the booking by paying, including a refundable security bond. Payments can still be made by cash, cheque and bank transfer and then manually confirmed, but payments made credit card are instantly confirmed.

Confirmation email sent by WooCoommerce Bookings

One key requirement from the committee was to enforce a 1-hour buffer between bookings to avoid conflict. Although set-up and pack-up time is meant to occur within your booking time, it typically takes people longer than they expect, which can cause conflicts with the next hirer who has to wait. So to be specific, bookings can be any time and any length, but must be separated by an hour. Sounds simple?

Enabling the buffer period feature

How we expected buffer periods to work

Assuming an existing 12-2pm booking and a 1-hour buffer period, we were expecting the the "require a buffer period" feature to allow us to book any time until 11am and then any time from 3pm. Something like this:

Video: Expected behaviour for choosing the start and end time with buffer periods, assuming existing booking from 12-2pm

How buffer periods actually worked

The problem was that after enabling buffer periods, we actually saw half the day's slots disappear, leaving us with 6am, 8am, 10am etc, like this:

woocommerce-bookings-actual-buffer-period.png
What we didn't expect — only even hours available

After some enquiries, we were able to learn that the feature was indeed working as the author had intended it, enforcing a 1-hour buffer or sometimes more. The only problem is it did it by simply blocking out every second hour of the day, rather than just enforcing the buffer between bookings. It was clear that the existing approach would unnecessarily restrict the hours the hall could be used.

The solution

We were initially hoping to find a general solution and submit this for inclusion into the upstream WooCommerce Bookings software. Unfortunately, calendaring is a surprisingly complicated problem, especially when dealing with bookable resources and recurrence rules. After some investigation, it became clear that a general solution would require much more work than our relatively small budget allows.

Instead we made the smallest change possible that would enforce 1-hour buffers in the way we wanted. Small is important here, since we may need to maintain this modification through regular security updates for years to come, and the smaller the change, the less chance of conflicting with a security update.

It turns out that the feature has separate components to determine the possible start and the possible end times for your booking.

Note: We'll dive into some of the technical details below, so please feel free to skip over these.

Fixing the start times

When you select a date on the calendar, an AJAX request runs to determine the possible start times. This is done in two passes. The first pass generates all possible start times. In our case, we allow bookings in blocks of 1 hour or more from 6am in the morning to 12am in the evening, so the first pass runs the get_bookable_minute_blocks_for_date function to generate the start times. The second pass compares these times against all existing bookings in the wc_bookings_get_time_slots function, eliminating those that conflict. We have an existing event from 12-2pm. With 1 hour buffer periods enabled, the first pass instead generates as follows:

Without buffer periods With buffer periods
First pass Second pass First pass Second pass

6:00am
7:00am
8:00am
9:00am
10:00am
11:00am
12:00pm
1:00pm
2:00pm
3:00pm
4:00pm
5:00pm
6:00pm
7:00pm
8:00pm
9:00pm
10:00pm
11:00pm

6:00am
7:00am
8:00am
9:00am
10:00am
11:00am
-
-
2:00pm
3:00pm
4:00pm
5:00pm
6:00pm
7:00pm
8:00pm
9:00pm
10:00pm
11:00pm

6:00am
-
8:00am
-
10:00am
-
12:00pm
-
2:00pm
-
4:00pm
-
6:00pm
-
8:00pm
-
10:00pm
-

6:00am
-
8:00am
-
10:00am
-
-
-
-
-
4:00pm
-
6:00pm
-
8:00pm
-
10:00pm
-

But what if someone wants 9-10am? This is obviously a problem for us.

The approach we took was to switch off the buffer periods feature and amend the second pass to check 1 hour (or 3600 seconds) either side of existing bookings when looking for conflicts. Here's the code change to the wc_bookings_get_time_slots function:

--- a/woocommerce-bookings/includes/wc-bookings-functions.php
+++ b/woocommerce-bookings/includes/wc-bookings-functions.php
@@ -680,7 +686,13 @@ function wc_bookings_get_time_slots( $bookable_product, $blocks, $intervals = ar
                $qty_booked_in_block = 0;
 
                foreach ( $existing_bookings as $existing_booking ) {
-                       if ( $existing_booking->is_within_block( $block, strtotime( "+{$interval} minutes", $block ) ) ) {
+                       if ( $existing_booking->is_within_block( $block - 3600, strtotime( "+{$interval} minutes", $block + 3600 ) ) ) {
                                $qty_to_add = $bookable_product->has_person_qty_multiplier() ? max( 1, array_sum( $existing_booking->get_persons() ) ) : 1;
                                if ( $bookable_product->has_resources() ) {
                                        if ( $existing_booking->get_resource_id() === absint( $resource_id ) ) {

With this change, we get the expected result of being able to book up until 11am and from 3pm.

Fixing the end times

When you select the desired start time from the drop-down box, another AJAX request is made for the available end times. This component works quite differently. It starts at the requested start time and steps forward one bookable block at a time until it hits a time that's not available. The smallest change we could make here was to add 1 second to the time being checked to force a conflict with anything in the following hour. Adding 3600 seconds did not work, presumably due to the way conflicts are checked. Here's the code change to the wc_bookings_get_total_available_bookings_for_range function:

--- a/woocommerce-bookings/includes/wc-bookings-functions.php
+++ b/woocommerce-bookings/includes/wc-bookings-functions.php
@@ -555,6 +555,12 @@ function wc_bookings_get_min_timestamp_for_day( $date, $offset, $unit ) {
  * @return array|int|boolean|WP_Error False if no places/blocks are available or the dates are invalid.
  */
 function wc_bookings_get_total_available_bookings_for_range( $bookable_product, $start_date, $end_date, $resource_id = null, $qty = 1, $intervals = array() ) {
+       $end_date += 1;
        // alter the end date to limit it to go up to one slot if the setting is enabled
        if ( $bookable_product->get_check_start_block_only() ) {
                $end_date = strtotime( '+ ' . $bookable_product->get_duration() . ' ' . $bookable_product->get_duration_unit(), $start_date );

Similarly, with this change we get the expected end times of 10am and 11am for a start time of 9am.

Why not a general solution?

So with buffer periods disabled and the above changes, the system is now working beautifully and we're able to begin taking public bookings. But given how small the changes ended up being, why couldn't they be easily generalised to suit any buffer period? Unfortunately, whether buffer periods are enabled or not and the buffer length is not in scope where these changes were made. Providing access to this information would require significant changes to the structure of the code. That's entirely possible, but would require a corresponding significant amount of testing to avoid introducing problems. All of which is well out of scope for this job.

If your organisation is looking for support with WooCommerce Bookings buffer periods, please get in touch about our professional services.