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:
- Verify WordPress timezone setting
- Check user profile timezone settings
- Ensure JavaScript timezone detection is working
- Validate UTC storage in database
Daylight Saving Time Issues
Symptoms: Appointments shift by 1 hour during DST changes
Solutions:
- Use PHP's DateTimeZone with DST handling
- Test DST transitions thoroughly
- Update timezone database regularly
- Use WordPress built-in DST handling
Multi-timezone Scheduling
Symptoms: Confusion when staff and customers in different timezones
Solutions:
- Always display timezone labels
- Show both local and UTC times for clarity
- Implement timezone confirmation during booking
- 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).
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.
Related Documentation
- Hooks & Filters – woocommerce_appointments_show_timezone - Disable timezone display
- Setup: Timezones - Store, customer, and staff timezone configuration
- REST API Timezone Handling - API timezone parameters
- Calendar Timezone Display - Calendar timezone features