Timezone Support
Loquent stores all timestamps in UTC and converts them to the organization’s configured timezone before display. This page covers the resolution pipeline, formatting utilities, and how timezone-awareness affects reports and analytics.
Configuration
Section titled “Configuration”Organizations set their timezone in Settings → Organization Profile → Languages. The UI presents a searchable dropdown of 25 curated timezones (one per UTC offset). The server accepts any valid IANA timezone string — the UI list is a UX convenience, not a validation constraint.
PUT /api/settings/organization-profileValidation: The API parses the timezone field against chrono_tz::Tz. Invalid values return 400 Bad Request. The field is Option<String> — if unset, the system defaults to US/Eastern.
Contacts also have a timezone field (PUT /api/contacts/{id}), but it’s informational only. All timestamps use the organization timezone.
Resolution
Section titled “Resolution”resolve_org_timezone() is the single entry point for timezone resolution across the app.
pub async fn resolve_org_timezone( organization_id: Uuid,) -> Result<chrono_tz::Tz, AppError>Behavior:
- Queries
organization_profile.timezonefor the given org - Parses the stored string into
chrono_tz::Tz - Falls back to
chrono_tz::US::Easternif the value is missing or invalid (logs a warning on invalid)
Every API endpoint that returns timestamps calls this function early and threads the Tz value through its formatting logic.
Formatting Utilities
Section titled “Formatting Utilities”Two helpers convert UTC timestamps to org-local display strings:
/// Formats a NaiveDateTime (assumed UTC) in the given timezone./// Output: "2026-03-12 14:30:00 -05:00"pub fn format_in_tz(dt: &NaiveDateTime, tz: &chrono_tz::Tz) -> String
/// Formats a DateTime<FixedOffset> in the given timezone./// Used where timestamps already carry offset info (e.g., Twilio message dates).pub fn format_in_tz_fixed(dt: &DateTime<FixedOffset>, tz: &chrono_tz::Tz) -> StringAffected endpoints: Every API that returns user-facing timestamps — calls, contacts, plans, plan templates, todos, messages, notes, and dashboard stats. The pattern is consistent:
let tz = resolve_org_timezone(session.organization.id).await?;// ... later, when building response:formatted_date: format_in_tz(&record.created_at, &tz),Dashboard Analytics
Section titled “Dashboard Analytics”The dashboard (GET /api/dashboard/stats) buckets data by org-local date and hour, not UTC. This ensures “today” and hourly heatmaps align with the organization’s actual business day.
let tz = resolve_org_timezone(session.organization.id).await?;let to_local = |dt: &NaiveDateTime| -> DateTime<chrono_tz::Tz> { dt.and_utc().with_timezone(&tz)};Hourly heatmap slots and daily chart boundaries use the converted local time.
Daily Report Scheduling
Section titled “Daily Report Scheduling”The SendDailyReportJob runs every hour (0 0 * * * * *). Instead of one cron per timezone, it checks all orgs each hour and sends reports when the org’s local time is 5 AM.
for org in orgs_with_daily_reports_enabled { let tz = resolve_org_timezone(org.id).await?; let local_now = Utc::now().with_timezone(&tz); if local_now.hour() == 5 { send_org_report(org.id, TimeInterval::Last24Hours, tz).await?; }}The resolved timezone propagates through the entire report chain (send_org_report → generate_org_report → generate_phone_report → generate_call_report) so all timestamps in the email body display in org-local time.
Timezone Constants
Section titled “Timezone Constants”The curated UI list lives in src/shared/constants/timezones_constant.rs:
pub struct TimezoneEntry { pub value: &'static str, // "America/New_York" pub label: &'static str, // "Eastern Time (New York)" pub utc_offset: &'static str, // "-05:00"}
pub const TIMEZONES: &[TimezoneEntry] = &[ // 25 entries, one per UTC offset from -12:00 to +13:00];The SearchableSelect component (src/ui/searchable_select_ui.rs) renders these as a filterable dropdown. Users can search by city name, IANA identifier, or UTC offset.
Key Files
Section titled “Key Files”| File | Purpose |
|---|---|
src/shared/constants/timezones_constant.rs | TIMEZONES list and validate_timezone() |
src/shared/utils/resolve_org_timezone.rs | DB lookup with fallback |
src/shared/utils/format_in_tz.rs | format_in_tz() and format_in_tz_fixed() |
src/ui/searchable_select_ui.rs | Reusable searchable dropdown component |
src/mods/report/services/send_timezone_aware_daily_reports_service.rs | Hourly cron → per-org local-time check |
src/mods/settings/components/organization_profile/languages_section_component.rs | Timezone selector UI |