Feature spec
BeginnerCategory
personalization-ux
Build with AI
1–3 hours with FlutterFlow or Lovable
Custom build
1–3 days custom dev
Running cost
$0/mo for most apps
Works on
Everything it takes to ship a Time Zone Converter — parts, prompts, and real costs.
A time zone converter needs the timezone package (Flutter) or Intl.DateTimeFormat (web) for DST-aware conversion, a Timer.periodic live clock ticking every second, a searchable IANA time zone picker, and a pinned zones list stored to Supabase or device shared_preferences. With FlutterFlow or Lovable you can ship a working world clock in 1–3 hours for $0/month. All time math runs on the client — no external APIs required.
What a Time Zone Converter Actually Is
A time zone converter lets users see what time it is in multiple locations simultaneously — a live world clock with a pinnable list of cities. It is a utility feature for apps serving international users: scheduling tools, remote team dashboards, travel apps, and global marketplaces all benefit from it. The feature is entirely client-side: the timezone package (Flutter) or the browser's Intl.DateTimeFormat API (web) handles all DST-aware conversion without any external API calls. The product decisions are how many zones users can pin, how the IANA tz database is searched by city name, and whether pinned preferences are saved to Supabase or to device-local shared_preferences for an offline-first experience. The critical failure mode to avoid: manual UTC offset math that silently breaks during daylight saving time transitions.
What users consider table stakes in 2026
- Search and select any IANA time zone by city name or UTC offset — not by raw IANA identifier like 'America/New_York' which non-technical users do not recognize
- Live clock display updating every second so the time feels alive rather than static
- Date rollover badge showing '+1 day' or '-1 day' clearly when the target zone is on a different calendar date than the device's local time
- Users can pin at least 5 time zones for quick reference visible without searching
- Local device time zone auto-detected on first load with no manual setup required
- UTC offset displayed next to each city name (e.g. 'New York GMT-5') so users can reason about the difference at a glance
Anatomy of the Feature
Six components build a complete time zone converter. The timezone package initialization and DST-aware conversion are where first builds most often break silently.
Time Zone Search Picker
UIA searchable bottom sheet or dropdown backed by a static map of city display names to IANA timezone identifiers. In Flutter: a searchable ListView of city names using the timezone package's tz.getLocation() to validate identifiers; the map covers the 100 most searched cities (London, New York, Tokyo, Sydney, Paris, Dubai, etc.) with their IANA identifiers. In web apps: Intl.supportedValuesOf('timeZone') provides the raw IANA list; a display-name map makes results human-readable. The picker filters by city name as the user types and shows UTC offset alongside each result.
Note: The IANA database uses region/city format (Europe/London, Asia/Tokyo, America/New_York). A static city-to-IANA map is required because searching raw IANA identifiers produces zero results for 'London', 'Tokyo', or 'Paris' — users type city names, not region codes.
Live Clock Display
UIA text widget that updates every second via Timer.periodic(Duration(seconds: 1), (timer) => setState(() {})) in Flutter, or setInterval(update, 1000) in React. Converts DateTime.now() to the target timezone using TZDateTime.now(tz.getLocation(ianaId)) (Flutter) or new Intl.DateTimeFormat('en-US', {timeZone: ianaId, hour: '2-digit', minute: '2-digit', second: '2-digit'}) (web). Formats as HH:mm:ss for precision.
Note: In Flutter, the clock widget must cancel the Timer in its dispose() method to prevent a memory leak and the 'setState() called after dispose()' error when the user navigates away from the converter screen.
Pinned Time Zone List
UIA scrollable column of time zone cards. Each card shows the city display name, current time (HH:mm:ss), UTC offset badge, day-of-week, and date. Cards are ordered by the user's saved pin order. A delete/unpin swipe action removes the zone from the list. An 'Add' button at the bottom opens the Time Zone Search Picker.
Note: Display the current local date on each card (e.g. 'Tuesday, Jul 11') alongside the time — users need to know if a city is already in tomorrow when scheduling across dates.
Date Rollover Indicator
UIA conditional badge on each time zone card. Compares the calendar date of the target timezone against the user's local date: if the target's day-of-month is greater than local, show '+1 day'; if lesser, show '-1 day'. Calculated purely from TZDateTime.now() in the target zone vs DateTime.now().toLocal() — no hardcoded offset math.
Note: The rollover badge must use DST-aware conversion, not manual UTC offset arithmetic. If New York is at 11 PM and the local timezone is Japan, the badge correctly shows '-1 day' even during daylight saving time transitions when the offset temporarily shifts.
Overlap Detector
UIAn optional feature allowing the user to select two pinned zones and a time range to find the overlap window where both are in business hours (9 AM–5 PM local). Highlights the overlap window in green on a simple timeline. Pure client-side calculation: convert the business hours range of each zone to UTC, find the intersection of the two UTC intervals, display the result in each zone's local time. No API calls required.
Note: The overlap detector is a delighter, not table stakes. Include it in the prompt if the target use case is meeting scheduling rather than simple world clock display.
User Preferences Store
DataTwo storage options depending on connectivity requirements. Option A (Supabase): user_timezone_prefs table (id, user_id, pinned_zones text[], home_zone text, updated_at) with RLS. Syncs pinned zones across devices. Option B (device-local): Flutter shared_preferences package storing pinned_zones as a JSON string under a stable key constant. Offline-first, no Supabase required. The brief recommends Supabase for cross-device sync and shared_preferences for offline-only apps.
Note: Stable key naming is critical for shared_preferences. If the key name changes between app versions, the stored pinned zones return null and appear as if the user has no saved zones. Use a constant like kPinnedZonesKey = 'user_pinned_zones_v1' and add migration logic on first launch to copy old key values to the new key.
The data model
One table covers Supabase-backed user timezone preferences for cross-device sync. Run this in the Supabase SQL Editor if using Supabase for persistence — skip if using device-local shared_preferences.
1create table public.user_timezone_prefs (2 id uuid primary key default gen_random_uuid(),3 user_id uuid references auth.users(id) on delete cascade not null unique,4 pinned_zones text[] not null default '{}',5 home_zone text,6 updated_at timestamptz not null default now()7);89alter table public.user_timezone_prefs enable row level security;1011create policy "Users can view own timezone prefs"12 on public.user_timezone_prefs for select13 using (auth.uid() = user_id);1415create policy "Users can insert own timezone prefs"16 on public.user_timezone_prefs for insert17 with check (auth.uid() = user_id);1819create policy "Users can update own timezone prefs"20 on public.user_timezone_prefs for update21 using (auth.uid() = user_id);Heads up: The pinned_zones text[] column stores IANA timezone identifiers (e.g. ['America/New_York', 'Europe/London', 'Asia/Tokyo']). The UNIQUE constraint on user_id ensures one preferences row per user, enabling INSERT ... ON CONFLICT (user_id) DO UPDATE for upsert-style saves. home_zone stores the user's detected home timezone as an IANA identifier.
Build it — pick your path
Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.
Flutter's timezone and intl packages handle all IANA timezone math natively including DST. Timer.periodic drives the live clock. FlutterFlow Custom Widget for the live display. The fastest path for a native mobile world clock.
Step by step
- 1Add the timezone (^0.9.0) and intl packages to FlutterFlow under Settings → Custom Packages; also add shared_preferences if using device-local storage
- 2Create a Custom Code init function that calls initializeTimeZones() — this must run before any widget tree is built; place it in the app's main initialization Custom Code block
- 3Build a Custom Widget for the live clock: use Timer.periodic(Duration(seconds: 1), (_) => setState(() {})) and TZDateTime.now(tz.getLocation(ianaId)) for time conversion; cancel the timer in dispose()
- 4Build the Time Zone Search Picker as a FlutterFlow bottom sheet with a TextField and a filtered ListView using a static Dart Map<String, String> of city display names to IANA identifiers
- 5Store pinned zones in shared_preferences using a constant key name; on load, read the list and pass each IANA identifier to the live clock widget
Where this path bites
- initializeTimeZones() must be called before any tz conversion — FlutterFlow Custom Code execution order can place it after the first frame; test on a real device to confirm no null reference errors on app launch
- The full IANA database via the timezone package adds about 1MB to the app bundle — acceptable for most apps but worth noting for bundle-size-sensitive use cases
- FlutterFlow Custom Widgets require Dart knowledge to write the Timer.periodic live clock; the built-in widget palette has no live-updating clock component
Third-party services you'll need
The time zone converter uses no paid third-party services. All timezone math is handled by free packages and browser-native APIs.
| Service | What it does | Free tier | Paid from |
|---|---|---|---|
| timezone (pub.dev) | IANA timezone database and DST-aware time conversion for Flutter | Open-source, pub.dev | Free |
| intl (pub.dev) | Date/time formatting with locale awareness for Flutter | Open-source, pub.dev | Free |
| Intl.DateTimeFormat | Browser-native DST-aware timezone conversion and formatting for web apps | Browser-native Web API | Free |
| Supabase | User timezone preferences storage for cross-device sync (optional) | Free tier covers preferences storage at any realistic scale | Pro $25/mo only if preferences storage exceeds free limits at 10K+ users |
Swipe the table sideways to see pricing.
What it costs to run
Drag through the tiers to see how your monthly bill scales with users — no surprises later.
Estimated monthly running cost
All timezone math is client-side using free packages. Supabase free tier covers preferences storage for 100 users with trivial storage footprint.
Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.
What breaks when AI tools build this
The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.
Displayed times are consistently wrong during daylight saving time transitions
Symptom: This is the defining bug of time zone converter builds. AI tools almost always implement time conversion with manual UTC offset arithmetic: DateTime.now().add(Duration(hours: -5)) for New York. This works in winter but silently shows the wrong time after DST changes — UTC-5 becomes UTC-4, but the hardcoded offset never updates. The bug is invisible until users in affected regions report that times are off by exactly 1 hour.
Fix: Replace all manual offset math with the timezone package. Call TZDateTime.now(tz.getLocation('America/New_York')) — the package reads the IANA tz database which includes DST rules for every zone. Call initializeTimeZones() once before any conversion. Include 'DST-aware: the timezone package handles DST automatically via TZDateTime — do not use DateTime.add(Duration(hours: offset)) anywhere' in your prompt to explicitly block the manual approach.
initializeTimeZones() throws a null reference error on app launch
Symptom: FlutterFlow's Custom Code initialization runs in a specific order that may place initializeTimeZones() after the first widget frame is built. If any widget calls tz.getLocation() before initializeTimeZones() completes, the tz database returns null and the app crashes with 'Null check operator used on a null value' or 'TimeZoneNotFoundError'.
Fix: Call initializeTimeZones() synchronously in the FlutterFlow app's main Custom Code block — the one that runs at app startup before the widget tree is built. Verify by adding a tz.getLocation('America/New_York') call immediately after initializeTimeZones() in the same block and confirming it returns a non-null Location object. If the error persists, wrap the widget tree in a FutureBuilder that awaits initializeTimeZones() before rendering.
Live clock stops updating after app is backgrounded and resumed
Symptom: Timer.periodic stops executing when the Flutter app is backgrounded on iOS — iOS suspends background Dart isolates after a few seconds. When the user returns to the app, the displayed time is frozen at the moment the app was backgrounded. The timer is still 'running' in the Dart sense but no ticks fired while backgrounded.
Fix: Listen to AppLifecycleState via WidgetsBindingObserver. In didChangeAppLifecycleState: cancel the timer when the state is AppLifecycleState.paused and restart it when the state is AppLifecycleState.resumed. Restarting the timer on resume also forces an immediate clock update so the displayed time jumps to the correct current time instantly.
IANA timezone search returns no results for common city names
Symptom: The IANA database uses region/city format: Europe/London, Asia/Tokyo, America/New_York. If the search picker runs against the raw IANA identifier list, users typing 'London', 'Tokyo', or 'New York' get zero results. The app appears broken when it is simply missing a display-name translation layer.
Fix: Maintain a static Map<String, String> of popular city display names to IANA identifiers and search against the display names. Store the IANA identifier internally for all conversions. A list of 50–100 cities covers the vast majority of real searches. Fall back to showing raw IANA identifiers (filtered by the search term) only if no display-name match is found.
Pinned zones list resets to empty after an app update
Symptom: shared_preferences keys are stored as strings. If the AI generates a key name that differs between app versions (e.g. 'pinnedZones' in v1 and 'pinned_zones' in v2), the v2 app reads null from shared_preferences on first launch and treats it as an empty list — all the user's saved zones appear to have been lost, even though the data is still in storage under the old key.
Fix: Define the key as a constant: const kPinnedZonesKey = 'user_pinned_zones_v1'. On first launch after an update, check for the presence of any known old key names and migrate their values to the new constant key before reading. Increment the version suffix in the constant only intentionally as part of a deliberate migration — never casually rename it.
Best practices
Call initializeTimeZones() before any widget is built — it is a synchronous call that loads the IANA tz database into memory; skip it and every timezone conversion will crash or return wrong times
Never use manual UTC offset arithmetic for DST-affected zones — always use TZDateTime.now(tz.getLocation(ianaId)) in Flutter or Intl.DateTimeFormat with the timeZone option on web
Cancel Timer.periodic in the widget's dispose() method to prevent 'setState() called after dispose()' errors and memory leaks on navigation
Maintain a static city-display-name-to-IANA-identifier map for the search picker — users search for 'Tokyo', not 'Asia/Tokyo'
Use a stable constant for the shared_preferences key and add migration logic between app versions to copy values from old keys to new ones before reading
Show the UTC offset next to each city name (GMT+X or GMT-X) so users can quickly verify the math rather than trusting the displayed time blindly
Listen to AppLifecycleState and restart the live clock timer on AppLifecycleState.resumed to ensure the displayed time is correct immediately after the user returns to the app
When You Need Custom Development
A standalone time zone converter is one of the rare features that almost never needs custom work — the AI path covers it completely. Custom development is only warranted for these specific extensions:
- Meeting scheduler that shows mutual availability across time zones with Google Calendar API integration — requires OAuth for each user's calendar and real event data
- Business hours indicator per pinned zone showing whether each city is currently within office hours, including local public holidays per country
- Travel itinerary feature that auto-converts all event times to the local time at each destination as the user's trip progresses
- International team directory where each team member's current local time is shown on their profile card, requiring team_members table integration with the timezone preferences system
RapidDev builds these features for production
Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.
Frequently asked questions
Does this work without internet connection?
Yes — all timezone conversion math runs locally using the timezone package's bundled IANA database. The timezone package includes DST rules for all zones compiled into the app bundle at build time. No network call is needed for time conversion. If you use shared_preferences for pinned zones storage, the pinned list is also available offline. Supabase sync for cross-device preferences requires connectivity but gracefully falls back to the locally cached list when offline.
How does daylight saving time affect the displayed times?
The timezone package and Intl.DateTimeFormat both use the IANA tz database which contains the complete DST transition history and future rules for every timezone. When DST changes, the displayed time automatically reflects the correct offset without any code change. The bug to avoid is manual offset arithmetic (e.g. 'New York is always UTC-5') — that breaks twice a year. Always use the package, never hardcode offsets.
Can I add any city in the world?
You can add any IANA timezone identifier — there are about 600 in the full database. For the search picker to work by city name, the city must be in your display-name map. The prompt requests a 50-city map covering the most common searches. For less common cities, users can search by UTC offset (e.g. 'UTC+9' surfaces Asia/Tokyo and Asia/Seoul) or browse the full list. You can expand the map with more cities after the initial build.
How many time zones can I pin?
The schema allows any number — pinned_zones is a text[] array with no database constraint. The prompt sets a soft limit of 10 pinned zones with an error message when the user tries to exceed it. The UI becomes unwieldy past about 7–8 cards on a mobile screen. You can adjust this limit in the prompt — just specify the max you want enforced.
What happens when two pinned cities are in the same time zone?
Both appear as separate cards showing identical times. The prompt includes an edge case handler for this: if two IANA identifiers in the pinned list resolve to the same current UTC offset and DST state, they are collapsed into one card showing both city names. Specify this behavior in the prompt if you want it — otherwise both cards render independently.
How do I detect my current local time zone automatically?
In Flutter: DateTime.now().timeZoneName gives the device's IANA timezone name on most platforms. In web: Intl.DateTimeFormat().resolvedOptions().timeZone returns the IANA identifier from the browser's timezone setting. If the device timezone is unavailable (returns null or empty string), the app falls back to UTC and shows a 'Using UTC as default' banner. Auto-detection runs on first app launch and sets the home_zone field.
Can this feature help me schedule meetings across time zones?
The optional Overlap Detector component finds windows where two pinned zones are both in business hours (9 AM–5 PM local). For multi-person scheduling with actual calendar availability — checking who is free at a given time based on real calendar events — you would need Google Calendar API or Calendly integration. That is a custom development requirement separate from the converter itself.
Is UTC the same as GMT?
For display purposes, yes — UTC and GMT are the same offset (zero hours from Coordinated Universal Time). The technical difference is that GMT is a timezone (it observes BST in summer), while UTC is a standard that never changes. In the IANA database, 'Etc/UTC' and 'GMT' are both valid identifiers. Convention in app UIs is to display UTC offsets as 'GMT+5' or 'GMT-3' rather than 'UTC+5' because users recognize GMT more readily.
Need this feature production-ready?
RapidDev builds a time zone converter into real apps — auth, database, payments — at $13K–$25K.
Book a free consultation30-min call. No commitment.