This article applies to Zoom Virtual Agent voice agent. If you are using other Zoom Virtual Agent types, such as chat agent or classic chatbot, refer to their respective documentation for setup and channel deployment instructions.
Customers use Zoom Virtual Agent (ZVA) as a voice agent integrated with Zoom Phone to handle inbound consumer inquiries. When end consumers call the phone number listed on the customer’s website, the call is automatically routed from Zoom Phone to Zoom Virtual Agent.
These calls can occur at any time during regular business hours, after hours, or on holidays. Customers can configure ZVA to recognize business hours and greet callers appropriately based on the time of day.
End consumers can also ask about business hours for specific days of the week, such as Are you open on Tuesday? or What are your hours for Friday?.
function formatTime(timeStr) {
if (!timeStr || typeof timeStr !== "string") return "";
var parts = timeStr.split(":");
var hour = parseInt(parts[0], 10);
var minute = parts[1] || "00";
if (isNaN(hour)) return timeStr;
if (hour === 24) return "midnight";
var ampm = hour >= 12 ? "PM" : "AM";
hour = hour % 12;
if (hour === 0) hour = 12;
// Use natural words for noon and midnight
if (hour === 12 && minute === "00") {
return ampm === "PM" ? "noon" : "midnight";
}
// Drop :00 for whole hours
if (minute === "00") return hour + " " + ampm;
return hour + ":" + minute + " " + ampm;
}
function timeToMinutes(timeStr) {
if (!timeStr || typeof timeStr !== "string") return null;
var parts = timeStr.split(":");
var hour = parseInt(parts[0], 10);
var minute = parseInt(parts[1], 10);
if (isNaN(hour) || isNaN(minute)) return null;
return (hour * 60) + minute;
}
function parseBusinessHoursConfig(raw) {
if (!raw) {
log.debug("It is null");
return null;
}
if (typeof raw === "object") {
log.debug("Object");
return raw;
}
if (typeof raw === "string") {
log.debug("It is of the type string")
try { return JSON.parse(raw); } catch (e) {
log.error("Failed to parse businessHoursConfig: " + e.message + " | raw: " + raw);
return null;
}
}
return null;
}
function getCurrentStoreDateTime(timezone) {
var now = new Date();
var dateTimeString = now.toLocaleString("en-US", {
timeZone: timezone,
weekday: "long",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false
});
var parts = dateTimeString.split(" ");
var weekdayName = parts[0];
var timePart = parts[1] || "00:00:00";
var timeParts = timePart.split(":");
return {
weekdayName: weekdayName,
hour: parseInt(timeParts[0], 10),
minute: parseInt(timeParts[1], 10),
second: parseInt(timeParts[2], 10)
};
}
function getTodaySchedule(weeklySchedule, weekdayName) {
if (!Array.isArray(weeklySchedule)) return null;
for (var i = 0; i < weeklySchedule.length; i++) {
if (weeklySchedule[i].day_of_week === weekdayName) {
return weeklySchedule[i];
}
}
return null;
}
function isStoreOpenNow(todaySchedule, currentMinutes) {
if (!todaySchedule || !todaySchedule.is_open) return false;
if (todaySchedule.is_24_hours) return true;
var openMinutes = timeToMinutes(todaySchedule.open_time);
var closeMinutes = timeToMinutes(todaySchedule.close_time);
if (openMinutes === null || closeMinutes === null) return false;
if (closeMinutes >= 1440) return currentMinutes >= openMinutes;
if (closeMinutes > openMinutes) return currentMinutes >= openMinutes && currentMinutes < closeMinutes;
// Overnight hours (e.g. 22:00 – 02:00)
return currentMinutes >= openMinutes || currentMinutes < closeMinutes;
}
async function getStoreStatus(businessHoursConfig) {
var timezone = businessHoursConfig.timezone;
if (!timezone) {
return { storeStatus: "Closed", currentStoreDateTime: null, todaySchedule: null, reason: "Timezone not provided" };
}
var currentStoreDateTime = getCurrentStoreDateTime(timezone);
if (businessHoursConfig.business_hours_type === "24_7") {
return { storeStatus: "Open", currentStoreDateTime: currentStoreDateTime, todaySchedule: null, reason: "Store is open 24/7" };
}
var todaySchedule = getTodaySchedule(businessHoursConfig.weekly_schedule, currentStoreDateTime.weekdayName);
if (!todaySchedule) {
return { storeStatus: "Closed", currentStoreDateTime: currentStoreDateTime, todaySchedule: null, reason: "No schedule found for today" };
}
var currentMinutes = (currentStoreDateTime.hour * 60) + currentStoreDateTime.minute;
var open = isStoreOpenNow(todaySchedule, currentMinutes);
return {
storeStatus: open ? "Open" : "Closed",
currentStoreDateTime: currentStoreDateTime,
todaySchedule: todaySchedule,
reason: open ? "Within business hours" : "Outside business hours"
};
}
function buildGreeting(storeStatusDetails) {
var intro = "Hi I am Zoom Virtual Agent.";
var todaySchedule = storeStatusDetails.todaySchedule;
var openTime = todaySchedule && !todaySchedule.is_24_hours ? formatTime(todaySchedule.open_time) : "";
var closeTime = todaySchedule && !todaySchedule.is_24_hours ? formatTime(todaySchedule.close_time) : "";
var hasHours = openTime && closeTime;
if (storeStatusDetails.storeStatus === "Open") {
if (!hasHours) return intro + "We are open 24 hours a day, 7 days a week. How can I help you?";
return intro + "We are open from " + openTime + " to " + closeTime + ". How can I help you?";
}
if (!hasHours) return intro + "We are currently closed. How can I help you?";
return intro + "We are currently closed. Our hours are " + openTime + " to " + closeTime + ". How can I help you?";
}
async function main() {
var rawConfig = var_get()["business_hours_string"];
var timezone = var_get()["timezone"];
var businessHoursConfig = parseBusinessHoursConfig(rawConfig);
try {
if (!businessHoursConfig) {
throw new Error("Invalid or missing business hours configuration");
}
var storeStatusDetails = await getStoreStatus(businessHoursConfig);
var currentStoreDateTime = storeStatusDetails.currentStoreDateTime;
var todaySchedule = storeStatusDetails.todaySchedule;
// 1. Current day and time
var currentDay = currentStoreDateTime ? currentStoreDateTime.weekdayName : "";
var currentTime = "";
if (currentStoreDateTime) {
var h = currentStoreDateTime.hour < 10 ? "0" + currentStoreDateTime.hour : "" + currentStoreDateTime.hour;
var m = currentStoreDateTime.minute < 10 ? "0" + currentStoreDateTime.minute : "" + currentStoreDateTime.minute;
currentTime = formatTime(h + ":" + m);
}
// 2. Today's open/close hours in user-friendly format ("" when 24-hour or closed all day)
var openHours = "";
var closeHours = "";
if (todaySchedule && !todaySchedule.is_24_hours && todaySchedule.is_open) {
openHours = formatTime(todaySchedule.open_time);
closeHours = formatTime(todaySchedule.close_time);
}
// 3. Store status
var storeStatus = storeStatusDetails.storeStatus;
var greetingMessage = buildGreeting(storeStatusDetails);
log.info("Greeting message built successfully");
return {
currentDay: currentDay,
currentTime: currentTime,
openHours: openHours,
closeHours: closeHours,
storeStatus: storeStatus,
greetingMessage: greetingMessage
};
} catch (error) {
var errorMessage = error && error.message ? error.message : String(error);
log.error("Error building greeting message: " + errorMessage);
return {
currentDay: "",
currentTime: "",
openHours: "",
closeHours: "",
storeStatus: "Closed",
greetingMessage: "Hi, I am Zoom Virtual Agent, How can I help you?",
error: errorMessage
};
}
}
## Goal ##
Answer consumer questions about store open and close hours using global_system.zp_system.businessHours
## Data ##
- Business hours JSON: global_system.zp_system.businessHours
- Today's weekday: global_custom.<>weekdayName (e.g., "Monday")
The JSON includes:
- business_hours_type: "24_7" or "custom"
- business_hours_summary
- weekly_schedule[] with fields: day_of_week, is_open, is_24_hours, open_time, close_time, display_text
## Determine the Target Day ##
Today
Examples: "Are you open today?" / "How long are you open?"
Target day: global_custom.<>.weekdayName
Specific day
Examples: "What are your hours on Friday?" / "Are you open on Wednesday?"
Target day: Day named by the consumer
Next <day>
Examples: "Are you open next Monday?" / "What are your hours next Friday?"
Target day: Day named after "next"
For "next <day>": the weekly schedule repeats, so hours are identical to the matching day_of_week entry. If the named day equals today (e.g., "next Monday" asked on Monday), respond with the hours and note they are the same every week.
## Execution ##
1. Before looking up hours, always respond first with: "Let me check the hours for you!". This gives the consumer immediate acknowledgment while the data is being retrieved.
2. Check business_hours_type.
3. If "24_7": the store is open 24 hours every day. Answer immediately — do not check weekly_schedule.
4. If "custom": find the weekly_schedule entry where day_of_week matches the target day (case-insensitive).
5. Evaluate the entry:
- is_open is false → store is closed that day
- is_open is true AND is_24_hours is true → store is open 24 hours
- is_open is true AND is_24_hours is false → store is open from open_time to close_time
6. Use display_text as your answer source. You may rephrase it naturally but do not alter times or status.
7. Convert times to 12-hour format in your response (e.g., "09:00" → "9:00 AM", "18:00" → "6:00 PM").
8. Do not guess or infer hours. Only use what is in the JSON.
## Response Style
Be brief and natural. Never expose field names, variable names, or JSON structure.
Examples:
- "We're open today from 9:00 AM to 6:00 PM."
- "On Tuesday we're open 24 hours."
- "We're open 24 hours every day."
- "We're closed on Sundays. Would you like to know hours for another day?"