Skip to main content

Timezone Architecture

WooCommerce Appointments features a comprehensive timezone system that handles time zone conversions, local time display, and scheduling across different time zones. This architecture ensures accurate appointment scheduling regardless of the user's location.

Timezone Components

WordPress Timezone Integration

The system builds on WordPress's native timezone handling:

// Get WordPress configured timezone
$wp_timezone = wp_timezone(); // Returns DateTimeZone object

// Get current time in WordPress timezone
$local_time = new DateTime('now', $wp_timezone);

Appointment Time Storage

All appointments are stored in UTC in the database for consistency:

-- Database storage (always UTC)
appointment_start: 2026-01-20 15:00:00 UTC
appointment_end: 2026-01-20 16:00:00 UTC

Timezone Conversion Methods

Local Time Methods

The system uses WordPress local time functions for consistent display:

// Convert UTC timestamp to local time
$local_start = get_date_from_gmt(gmdate('Y-m-d H:i:s', $utc_timestamp));

// Format local time for display
$formatted_time = date_i18n(get_option('time_format'), $utc_timestamp);

Timezone Detection

Automatic timezone detection for customers and staff:

// Customer timezone detection
$customer_timezone = WC_Appointments_Timezone::detect_customer_timezone();

// Staff timezone detection
$staff_timezone = WC_Appointments_Timezone::get_staff_timezone($staff_id);

Timezone Architecture Flow

1. Appointment Creation

Customer selects time → Convert to UTC → Store in database

2. Display Logic

UTC from database → Convert to user timezone → Display locally

3. Availability Checking

User request time → Convert to UTC → Check availability → Return results

Timezone Classes

WC_Appointments_Timezone_Helper

Main timezone helper class (introduced in 5.2.0):

class WC_Appointments_Timezone_Helper {

// Get site timezone string (e.g., 'America/New_York')
public static function get_site_timezone_string(): string;

// Get user's effective timezone (falls back to site timezone if using default)
public static function get_user_timezone( int $user_id = 0 ): string;

// Get user's raw stored timezone (empty string = using default)
public static function get_user_timezone_raw( int $user_id = 0 ): string;

// Check if user is using the default (site) timezone
public static function is_using_default_timezone( int $user_id = 0 ): bool;

// Save user timezone preference (empty string = use default)
public static function save_user_timezone( int $user_id, string $timezone ): bool;

// Get staff member's timezone
public static function get_staff_timezone( int $staff_id ): string;

// Get timezone indicator for admin display
public static function get_timezone_indicator( string $timezone ): string;
}

Default Timezone Handling

The helper class treats empty string as "use default (site) timezone":

// Save user preference - empty = default
WC_Appointments_Timezone_Helper::save_user_timezone( $user_id, '' ); // Uses site TZ

// Check if using default
if ( WC_Appointments_Timezone_Helper::is_using_default_timezone( $user_id ) ) {
// User will follow site timezone changes automatically
}

// Get raw value to check explicit preference
$raw_tz = WC_Appointments_Timezone_Helper::get_user_timezone_raw( $user_id );
if ( empty( $raw_tz ) ) {
// User hasn't set explicit preference
}

Timezone Selection Function

The wc_appointments_wp_timezone_choice() function generates timezone dropdowns:

// Generate timezone select options with Default option
$options = wc_appointments_wp_timezone_choice( $selected_value );

// Without Default option (for specific timezone requirement)
$options = wc_appointments_wp_timezone_choice( $selected_value, '', false );

The Default option:

  • Has an empty value ("")
  • Displays as Default (City Name) with current site timezone
  • Is automatically selected when no value is stored

Frontend Timezone Handling

JavaScript Timezone Detection

// Browser timezone detection
const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;

// Send to server for appointment calculations
fetch('/wp-json/wc-appointments/v2/slots', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Timezone': browserTimezone
},
body: JSON.stringify({
product_id: 123,
date: '2026-01-20'
})
});

Local Time Display

// Convert UTC timestamps to local time for display
function formatLocalTime(utcTimestamp) {
const date = new Date(utcTimestamp * 1000);
return date.toLocaleString();
}

Backend Timezone Processing

Slot Generation with Timezones

class WC_Appointments_Slot_Generator {

public function generate_slots($product_id, $date_range, $timezone = null) {
$timezone = $timezone ?: WC_Appointments_Timezone::get_wp_timezone();

// Convert date range to UTC for processing
$utc_start = WC_Appointments_Timezone::local_to_utc($date_range['start'], $timezone);
$utc_end = WC_Appointments_Timezone::local_to_utc($date_range['end'], $timezone);

// Generate slots in UTC
$slots = $this->generate_utc_slots($product_id, $utc_start, $utc_end);

// Convert back to requested timezone for display
return $this->convert_slots_to_timezone($slots, $timezone);
}
}

Availability Rule Processing

class WC_Appointments_Availability_Rule {

public function is_available($utc_timestamp, $timezone = null) {
$timezone = $timezone ?: WC_Appointments_Timezone::get_wp_timezone();

// Convert UTC timestamp to rule's timezone for evaluation
$local_time = WC_Appointments_Timezone::utc_to_local($utc_timestamp, $this->timezone);

// Check rule in local time context
return $this->evaluate_rule($local_time);
}
}

Timezone Configuration

WordPress Settings

Configure timezone in WordPress admin: Settings → General → Timezone

Staff Timezone Settings

Staff can set their personal timezone: Users → Profile → Timezone

Product Timezone Settings

Products can have specific timezone requirements:

  • Service Location Timezone: For location-based services
  • Staff Timezone Override: Force staff timezone for display
  • Customer Timezone Detection: Auto-detect customer timezone

Timezone Best Practices

Database Storage

  • Always store UTC: Store all timestamps in UTC
  • Consistent format: Use Unix timestamps or UTC datetime strings
  • Timezone metadata: Store original timezone if needed for display

Display Logic

  • Convert on display: Convert UTC to local time only when displaying
  • User context: Use user's timezone for personalized display
  • Fallback handling: Always have a fallback timezone (WordPress default)

API Responses

// API response with timezone information
$response = [
'start_utc' => 1738035600, // UTC timestamp
'end_utc' => 1738039200, // UTC timestamp
'timezone' => 'America/New_York', // Display timezone
'start_local' => '2026-01-28 10:00:00', // Local time
'end_local' => '2026-01-28 11:00:00' // Local time
];

Troubleshooting Timezone Issues

Common Problems

Incorrect Time Display

Symptoms: Appointments show wrong time for users

Solutions:

  1. Verify WordPress timezone setting
  2. Check user profile timezone settings
  3. Ensure JavaScript timezone detection is working
  4. Validate UTC storage in database

Daylight Saving Time Issues

Symptoms: Appointments shift by 1 hour during DST changes

Solutions:

  1. Use PHP's DateTimeZone with DST handling
  2. Test DST transitions thoroughly
  3. Update timezone database regularly
  4. Use WordPress built-in DST handling

Multi-timezone Scheduling

Symptoms: Confusion when staff and customers in different timezones

Solutions:

  1. Always display timezone labels
  2. Show both local and UTC times for clarity
  3. Implement timezone confirmation during booking
  4. Use timezone conversion tools for verification

Debug Tools

// Debug timezone conversions
function debug_timezone_conversion($utc_timestamp, $target_timezone) {
$utc_time = new DateTime('@' . $utc_timestamp);
$local_time = clone $utc_time;
$local_time->setTimezone(new DateTimeZone($target_timezone));

error_log("UTC: " . $utc_time->format('Y-m-d H:i:s'));
error_log("Local ($target_timezone): " . $local_time->format('Y-m-d H:i:s'));
}

Timezone Migration

Upgrading from Pre-5.0

Older versions stored times in WordPress local time. Migration process:

// Migration script for existing appointments
function migrate_appointment_times() {
$appointments = get_posts([
'post_type' => 'wc_appointment',
'posts_per_page' => -1
]);

foreach ($appointments as $appointment) {
$start_time = get_post_meta($appointment->ID, '_appointment_start', true);
$end_time = get_post_meta($appointment->ID, '_appointment_end', true);

// Convert local time to UTC
$utc_start = strtotime(get_gmt_from_date(date('Y-m-d H:i:s', $start_time)));
$utc_end = strtotime(get_gmt_from_date(date('Y-m-d H:i:s', $end_time)));

// Update with UTC timestamps
update_post_meta($appointment->ID, '_appointment_start', $utc_start);
update_post_meta($appointment->ID, '_appointment_end', $utc_end);
}
}

Performance Considerations

Timezone Conversion Caching

// Cache timezone conversions for performance
$cache_key = "timezone_convert_{$utc_timestamp}_{$timezone}";
$cached_result = wp_cache_get($cache_key, 'wc_appointments_timezones');

if (false === $cached_result) {
$result = WC_Appointments_Timezone::utc_to_local($utc_timestamp, $timezone);
wp_cache_set($cache_key, $result, 'wc_appointments_timezones', 3600);
}

Database Optimization

  • Index UTC timestamps: Ensure proper indexing on UTC time columns
  • Avoid functions in WHERE: Don't use timezone conversion functions in database queries
  • Pre-compute when possible: Cache frequently used timezone conversions

Disabling timezone display

For single-timezone or local-only sites, you can hide all timezone UI (pickers, columns, labels, abbreviations) using the woocommerce_appointments_show_timezone filter. When the filter returns false for a given context and user, the plugin uses the site timezone only and does not show timezone selectors or abbreviations anywhere (frontend, admin, emails).

Edge cases

This filter affects only display. Google Calendar sync always uses the site timezone when pushing events; it does not check this filter. If the connected Google Calendar is set to a different timezone, events will sync to the correct moment in time but may show a different clock time in Google. For predictable behaviour when timezones are disabled, keep the Google Calendar timezone aligned with the site. See Hooks – woocommerce_appointments_show_timezone for full notes and other edge cases.

// Disable timezone display everywhere (local-only site)
add_filter( 'woocommerce_appointments_show_timezone', '__return_false', 10, 3 );

// Disable only on frontend (admin still sees timezones)
add_filter( 'woocommerce_appointments_show_timezone', function( $show, $context ) {
return $context !== 'frontend';
}, 10, 2 );

// Disable only in admin
add_filter( 'woocommerce_appointments_show_timezone', function( $show, $context ) {
return $context !== 'admin';
}, 10, 2 );

// Disable for a specific user ID
add_filter( 'woocommerce_appointments_show_timezone', function( $show, $context, $user_id ) {
return $user_id !== 42;
}, 10, 3 );

Filter parameters: $show (bool), $context ('frontend' or 'admin'), $user_id (int; 0 for guests). Return true to show timezone, false to hide. Full reference and more examples: Hooks & Filters – woocommerce_appointments_show_timezone.