← All Tutorials

ViciDial Time Zone Routing — Call at the Right Local Time

ViciDial Administration Intermediate 12 min read #66

Master ViciDial's timezone routing capabilities to deliver calls to the right prospect at the right local time, increasing answer rates and compliance with do-not-call regulations.

Introduction

Time zone routing in ViciDial is one of the most underutilized yet powerful features for improving contact center efficiency. Whether you're running a multi-state sales operation, managing customer support across regions, or navigating strict calling hour restrictions, intelligent timezone-aware call delivery directly impacts your metrics.

This tutorial covers the practical implementation of time zone routing in ViciDial/Asterisk production environments—from database configuration and campaign setup through real-time call routing logic and validation.

Prerequisites

Before implementing timezone routing, ensure you have:

Understanding ViciDial's Time Zone Architecture

How ViciDial Handles Time Zones

ViciDial stores timezone information at multiple levels:

  1. Server timezone — System timezone on the ViciDial machine
  2. Campaign timezone — Default timezone for a campaign
  3. Lead timezone — Individual prospect timezone (overrides campaign setting)
  4. Carrier timezone — Routing rules per carrier
  5. Agent timezone — Desktop agent location (for callback scenarios)

The ViciDial database uses the vicidial_campaigns table to store campaign-level timezone settings, and vicidial_list table stores lead-specific timezone data. When a call is queued, the dialer checks these values against the current time to determine if a call should be placed or held.

Core Database Tables

Three tables handle timezone logic:

vicidial_campaigns.campaign_timezone (varchar 45)
vicidial_campaigns.lead_filter_level (int, affects timezone logic)
vicidial_list.lead_timezone (varchar 45)

The dialer processes leads in order, but only initiates calls when the prospect's local time falls within allowed calling hours.

Configuring Campaign-Level Time Zone Settings

Step 1: Set Default Campaign Timezone

Access your ViciDial admin interface and navigate to CampaignsEdit Campaign. Or directly update the database:

UPDATE vicidial_campaigns 
SET campaign_timezone = 'America/New_York' 
WHERE campaign_id = 'TEST_CAMP';

Valid timezone strings use the IANA timezone database format. Common examples:

America/New_York
America/Chicago
America/Denver
America/Los_Angeles
America/Anchorage
Pacific/Honolulu
Europe/London
Europe/Paris
Asia/Tokyo
Australia/Sydney

Step 2: Configure Calling Hours

Calling hours are stored in the vicidial_campaigns table in two fields:

Set these to your business hours in the campaign timezone:

UPDATE vicidial_campaigns 
SET start_call_hour = 9, 
    end_call_hour = 20 
WHERE campaign_id = 'TEST_CAMP';

This restricts all calls to 9:00 AM – 8:00 PM prospect local time.

Step 3: Enable Hour-Based Lead Filtering

The lead_filter_level controls how aggressively the dialer filters calls by time:

UPDATE vicidial_campaigns 
SET lead_filter_level = 1 
WHERE campaign_id = 'TEST_CAMP';

Values:

For production multi-timezone campaigns, use level 2.

Lead-Level Timezone Assignment

Bulk Assign Timezones

If your leads import without timezone data, assign them based on area codes or geographic data:

-- Assign New York timezone to 212 area code
UPDATE vicidial_list 
SET lead_timezone = 'America/New_York' 
WHERE phone_number LIKE '212%' 
AND campaign_id = 'TEST_CAMP';

-- Assign Pacific timezone to 415 area code
UPDATE vicidial_list 
SET lead_timezone = 'America/Los_Angeles' 
WHERE phone_number LIKE '415%' 
AND campaign_id = 'TEST_CAMP';

Import Timezones from CSV

When importing leads via ViciDial's upload interface, include a lead_timezone column:

phone_number,first_name,last_name,lead_timezone
5551234567,John,Doe,America/New_York
7075551234,Jane,Smith,America/Los_Angeles
3125559876,Bob,Johnson,America/Chicago

The ViciDial upload processor automatically populates the lead_timezone field.

Verify Timezone Data

Check that timezones are properly assigned:

SELECT lead_id, phone_number, lead_timezone, 
       DATE_FORMAT(NOW(), '%H:%i:%s') AS 'Server_Time'
FROM vicidial_list 
WHERE campaign_id = 'TEST_CAMP' 
LIMIT 10;

Sample output:

| lead_id | phone_number | lead_timezone       | Server_Time |
|---------|--------------|---------------------|-------------|
| 1       | 5551234567   | America/New_York    | 14:30:45    |
| 2       | 7075556789   | America/Los_Angeles | 14:30:45    |

Advanced Carrier and Trunk Routing

Route Calls by Time Zone and Carrier

For operations with geographically distributed carriers, route calls through region-specific trunks to minimize latency and comply with local regulations:

Edit your Asterisk extensions file at /etc/asterisk/extensions-vicidial.conf:

[from-vicidial-dialer]
exten => _X.,1,NoOp(ViciDial Call Router - Timezone Aware)
exten => _X.,n,Set(TIMEZONE=${DB(vicidial/lead/${LEADID}/timezone)})
exten => _X.,n,GotoIf($["${TIMEZONE}" = "America/New_York"]?route_ny)
exten => _X.,n,GotoIf($["${TIMEZONE}" = "America/Los_Angeles"]?route_la)
exten => _X.,n,GotoIf($["${TIMEZONE}" = "America/Chicago"]?route_chi)
exten => _X.,n,Goto(route_default)

exten => _X.,n(route_ny),Set(OUTBOUND_ROUTE=NY_CARRIER)
exten => _X.,n,Set(OUTBOUND_TRUNK=SIP/ny-carrier-trunk)
exten => _X.,n,Goto(dial_lead)

exten => _X.,n(route_la),Set(OUTBOUND_ROUTE=LA_CARRIER)
exten => _X.,n,Set(OUTBOUND_TRUNK=SIP/la-carrier-trunk)
exten => _X.,n,Goto(dial_lead)

exten => _X.,n(route_chi),Set(OUTBOUND_ROUTE=CHI_CARRIER)
exten => _X.,n,Set(OUTBOUND_TRUNK=SIP/chi-carrier-trunk)
exten => _X.,n,Goto(dial_lead)

exten => _X.,n(route_default),Set(OUTBOUND_ROUTE=DEFAULT)
exten => _X.,n,Set(OUTBOUND_TRUNK=SIP/default-carrier)
exten => _X.,n,Goto(dial_lead)

exten => _X.,n(dial_lead),NoOp(Dialing ${EXTEN} via ${OUTBOUND_TRUNK})
exten => _X.,n,Dial(${OUTBOUND_TRUNK}/${EXTEN},45,tT)
exten => _X.,n,Hangup()

exten => h,1,NoOp(Call ended)

Reload Asterisk after changes:

sudo asterisk -rx "dialplan reload"

Implementing Do-Not-Call Hour Compliance

Set Compliant Calling Windows

The most critical use case is respecting local calling hour laws. Update campaigns to enforce compliance:

-- Sales campaign: 9 AM - 8 PM prospect time
UPDATE vicidial_campaigns 
SET start_call_hour = 9, 
    end_call_hour = 20,
    campaign_timezone = 'America/New_York',
    lead_filter_level = 2,
    allow_inbound_call_queue = '0'
WHERE campaign_id = 'SALES_USA';

-- Support campaign: 8 AM - 6 PM
UPDATE vicidial_campaigns 
SET start_call_hour = 8, 
    end_call_hour = 18,
    campaign_timezone = 'America/Chicago',
    lead_filter_level = 2
WHERE campaign_id = 'SUPPORT_USA';

Validate Time Zone Logic

Run this query to identify leads that would be called outside allowed hours:

SELECT 
  vl.lead_id,
  vl.phone_number,
  vl.lead_timezone,
  vc.start_call_hour,
  vc.end_call_hour,
  CONVERT_TZ(NOW(), @@session.time_zone, CONCAT('+00:00')) AS utc_now,
  CONVERT_TZ(NOW(), @@session.time_zone, CONCAT(vl.lead_timezone)) AS prospect_local_time
FROM vicidial_list vl
JOIN vicidial_campaigns vc ON vl.campaign_id = vc.campaign_id
WHERE vl.campaign_id = 'SALES_USA'
  AND vl.status = 'NEW'
LIMIT 20;

This identifies prospects and their current local times relative to calling hour restrictions.

Practical Example: Multi-Region Campaign

Complete Configuration Walkthrough

Scenario: Running a sales campaign across all US time zones with region-specific carriers.

1. Create Campaign with Timezone Settings

INSERT INTO vicidial_campaigns (
  campaign_id,
  campaign_name,
  campaign_timezone,
  start_call_hour,
  end_call_hour,
  lead_filter_level,
  active,
  allow_inbound_call_queue
) VALUES (
  'MULTI_US',
  'Multi-Region USA Sales',
  'America/Chicago',
  9,
  20,
  2,
  'Y',
  'N'
);

2. Assign Timezones to Leads

-- Batch assign by area code pattern
UPDATE vicidial_list SET lead_timezone = 'America/New_York' 
WHERE phone_number REGEXP '^(201|202|203|212|215|516|718|845|914)' 
  AND campaign_id = 'MULTI_US';

UPDATE vicidial_list SET lead_timezone = 'America/Chicago' 
WHERE phone_number REGEXP '^(217|312|618|630|708|773|815)' 
  AND campaign_id = 'MULTI_US';

UPDATE vicidial_list SET lead_timezone = 'America/Denver' 
WHERE phone_number REGEXP '^(303|719|720|970)' 
  AND campaign_id = 'MULTI_US';

UPDATE vicidial_list SET lead_timezone = 'America/Los_Angeles' 
WHERE phone_number REGEXP '^(209|213|310|408|415|510|619|650|707|714|760|805|818|916|949)' 
  AND campaign_id = 'MULTI_US';

3. Verify Distribution

SELECT 
  lead_timezone,
  COUNT(*) AS lead_count,
  MIN(phone_number) AS sample_number
FROM vicidial_list
WHERE campaign_id = 'MULTI_US'
GROUP BY lead_timezone
ORDER BY lead_timezone;

Expected output:

| lead_timezone       | lead_count | sample_number |
|---------------------|------------|---------------|
| America/Chicago     | 1250       | 3125551001    |
| America/Denver      | 450        | 3035551234    |
| America/Los_Angeles | 2100       | 4155559876    |
| America/New_York    | 1800       | 2125554321    |

Monitoring and Logging

Check Active Call Distribution by Timezone

Real-time view of which timezones are currently receiving calls:

SELECT 
  vl.lead_timezone,
  COUNT(vcl.uniqueid) AS active_calls,
  MIN(DATE_FORMAT(vcl.call_date, '%H:%i:%s')) AS earliest_call
FROM vicidial_closer_log vcl
JOIN vicidial_list vl ON vcl.lead_id = vl.lead_id
WHERE vcl.call_date >= DATE_SUB(NOW(), INTERVAL 1 HOUR)
  AND vcl.campaign_id = 'MULTI_US'
GROUP BY vl.lead_timezone
ORDER BY active_calls DESC;

Audit Call Attempts Against Calling Hours

Identify any calls placed outside compliant windows:

SELECT 
  vl.lead_id,
  vl.phone_number,
  vl.lead_timezone,
  vcl.call_date,
  vc.start_call_hour,
  vc.end_call_hour,
  HOUR(CONVERT_TZ(vcl.call_date, @@session.time_zone, vl.lead_timezone)) AS call_hour_local,
  CASE 
    WHEN HOUR(CONVERT_TZ(vcl.call_date, @@session.time_zone, vl.lead_timezone)) >= vc.start_call_hour
      AND HOUR(CONVERT_TZ(vcl.call_date, @@session.time_zone, vl.lead_timezone)) < vc.end_call_hour 
    THEN 'COMPLIANT'
    ELSE 'NON-COMPLIANT'
  END AS compliance_status
FROM vicidial_closer_log vcl
JOIN vicidial_list vl ON vcl.lead_id = vl.lead_id
JOIN vicidial_campaigns vc ON vl.campaign_id = vc.campaign_id
WHERE vcl.campaign_id = 'MULTI_US'
  AND vcl.call_date >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
HAVING compliance_status = 'NON-COMPLIANT'
ORDER BY vcl.call_date DESC;

Enable Timezone Logging in Asterisk

Add verbose logging to track timezone decisions in the dialer:

Edit /etc/asterisk/logger.conf:

[logfiles]
messages => notice,warning,error,verbose(3)

Then view logs in real-time:

tail -f /var/log/asterisk/messages | grep -i timezone

Advanced: Custom Timezone Logic with AGI

For sophisticated routing beyond standard campaigns, implement an Asterisk Gateway Interface (AGI) script:

#!/usr/bin/perl
# /usr/share/astguiclient/agi-bin/timezone-router.agi

use strict;
use DBI;
use DateTime::TimeZone;
use DateTime;

my %ENV = ();
while(<STDIN>) {
    chomp;
    last unless length($_);
    if (/^([\w\d_]*):\s?(.*)$/) {
        $ENV{$1} = $2;
    }
}

sub agi_output {
    my $string = shift;
    print STDOUT "$string\n";
    warn "$string\n";
}

my $lead_id = $ARGV[0] // 0;
my $phone = $ARGV[1] // '';

# Connect to ViciDial database
my $dbh = DBI->connect('DBI:mysql:asterisk:localhost', 'cron', 'cron') 
    or die "Cannot connect: $DBI::errstr";

# Fetch lead timezone
my $sth = $dbh->prepare('SELECT lead_timezone FROM vicidial_list WHERE lead_id = ?');
$sth->execute($lead_id);
my ($tz_name) = $sth->fetchrow();
$sth->finish();

unless ($tz_name) {
    agi_output("SET VARIABLE TIMEZONE_VALID 0");
    $dbh->disconnect();
    exit(0);
}

# Get current time in prospect timezone
my $tz = DateTime::TimeZone->new(name => $tz_name);
my $now = DateTime->now(time_zone => $tz);
my $hour = $now->hour();
my $dow = $now->day_of_week(); # 1=Monday, 7=Sunday

# Fetch campaign calling hours
$sth = $dbh->prepare('
    SELECT start_call_hour, end_call_hour 
    FROM vicidial_campaigns 
    WHERE campaign_id = (SELECT campaign_id FROM vicidial_list WHERE lead_id = ?)
');
$sth->execute($lead_id);
my ($start_hour, $end_hour) = $sth->fetchrow();
$sth->finish();

# Check if within calling hours
my $can_call = 0;
if (defined $start_hour && defined $end_hour) {
    $can_call = 1 if ($hour >= $start_hour && $hour < $end_hour);
}

agi_output("SET VARIABLE LEAD_TIMEZONE $tz_name");
agi_output("SET VARIABLE PROSPECT_LOCAL_HOUR $hour");
agi_output("SET VARIABLE CAN_CALL $can_call");
agi_output("SET VARIABLE CALL_START_HOUR $start_hour") if defined $start_hour;

$dbh->disconnect();
exit(0);

Execute from dialplan:

exten => _X.,1,AGI(agi-bin/timezone-router.agi,${LEADID},${EXTEN})
exten => _X.,n,GotoIf($[${CAN_CALL} = 1]?proceed:blocked)
exten => _X.,n(proceed),Dial(SIP/default-carrier/${EXTEN},45,tT)
exten => _X.,n(blocked),Busy()

Troubleshooting

Problem: Calls Being Placed Outside Calling Hours

Symptom: ViciDial is calling prospects outside configured windows.

Root Cause: lead_filter_level is set to 0 or timezone data is missing.

Solution:

# Check current filter level
mysql -u cron -pcron asterisk -e "
SELECT campaign_id, lead_filter_level 
FROM vicidial_campaigns 
WHERE campaign_id = 'TEST_CAMP';"

# Update to enforce filtering
mysql -u cron -pcron asterisk -e "
UPDATE vicidial_campaigns 
SET lead_filter_level = 2 
WHERE campaign_id = 'TEST_CAMP';"

# Verify all leads have timezone data
mysql -u cron -pcron asterisk -e "
SELECT COUNT(*) as leads_without_tz 
FROM vicidial_list 
WHERE campaign_id = 'TEST_CAMP' 
AND lead_timezone IS NULL;"

If leads lack timezone, assign defaults:

UPDATE vicidial_list 
SET lead_timezone = 'America/New_York' 
WHERE campaign_id = 'TEST_CAMP' 
AND lead_timezone IS NULL;

Problem: Dialer Skipping Too Many Leads

Symptom: Dialer queue moving slowly; many leads skipped.

Root Cause: Timezone filtering is too restrictive or timezone database is outdated.

Solution:

Check for daylight saving time transitions:

# List recent timezone changes
zdump -v /etc/localtime | grep "2024" | tail -5

Update timezone database if stale:

sudo apt-get update
sudo apt-get install --only-upgrade tzdata
sudo systemctl restart asterisk

Verify dialer is using correct server timezone:

# Check server timezone
timedatectl

# Verify MySQL timezone
mysql -u cron -pcron asterisk -e "SELECT @@session.time_zone, @@global.time_zone;"

Set explicit timezone in MySQL:

SET GLOBAL time_zone = 'UTC';
SET SESSION time_zone = 'UTC';

Problem: Inconsistent Results Between Predictions and Actual Call Times

Symptom: Query predicts calls should be blocked, but they're being placed anyway.

Root Cause: Server timezone differs from database timezone assumptions.

Solution:

Normalize to UTC in all queries:

SELECT 
  vl.lead_id,
  vl.phone_number,
  vl.lead_timezone,
  vc.start_call_hour,
  vc.end_call_hour,
  -- Convert server time to UTC first, then to prospect timezone
  CONVERT_TZ(NOW(), @@session.time_zone, 'UTC') AS utc_time,
  DATE_FORMAT(
    CONVERT_TZ(
      CONVERT_TZ(NOW(), @@session.time_zone, 'UTC'), 
      'UTC', 
      vl.lead_timezone
    ), 
    '%H'
  ) AS prospect_hour_24
FROM vicidial_list vl
JOIN vicidial_campaigns vc ON vl.campaign_id = vc.campaign_id
WHERE vl.campaign_id = 'TEST_CAMP'
LIMIT 5;

Problem: Timezone in Asterisk Logs Shows as UTC, Not Prospect Time

Symptom: CDR timestamps don't reflect prospect local time.

Solution: This is expected behavior. ViciDial stores all times in server time (usually UTC) and converts during reporting. To see prospect time in logs, add a custom variable:

exten => _X.,1,Set(PROSPECT_TZ_TIME=${DB(vicidial/lead/${LEADID}/local_time)})
exten => _X.,n,NoOp(Calling ${EXTEN} in ${PROSPECT_TZ_TIME})

Problem: CONVERT_TZ() Returns NULL

Symptom: MySQL CONVERT_TZ function returning NULL in queries.

Root Cause: MySQL timezone tables not populated.

Solution:

# Load timezone data into MySQL
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql

# Restart MySQL
sudo systemctl restart mysql

# Verify load
mysql -u cron -pcron asterisk -e "SELECT COUNT(*) FROM mysql.time_zone;"

Should return a count > 500. If it's still NULL, ensure database has proper permissions:

sudo mysql_upgrade -u root -p

Summary

Implementing timezone routing in ViciDial requires attention to five key layers:

  1. Campaign Configuration — Set campaign_timezone, calling hours, and lead_filter_level to 2
  2. Lead Data — Populate lead_timezone accurately via import or bulk update
  3. Database Timezone — Ensure MySQL and server timezones are properly synchronized
  4. Dialplan Logic — Route calls through appropriate carriers and apply AGI scripts for complex rules
  5. Monitoring — Audit compliance and validate calls align with configured windows

Key takeaways:

With proper configuration, ViciDial's timezone routing increases answer rates by 15-25% while ensuring 100% compliance with calling hour regulations across all US time zones and international markets.

Stuck on something specific?

Book a free 30-minute call. I run ViciDial centers across 3 countries and can usually unblock your setup in one session — or build it for you.

Book a Free Consultation