Timezones: The Biggest Engineering Challenge in UTasks

Before building UTasks, we set ourselves an ambitious goal: create a tool that is simultaneously simple, convenient, and reliable. But of all the technical problems we had to solve, timezone handling turned out to be one of the hardest.

How we learned that timezones are genuinely hard

From almost the very beginning, people from different countries started using our task manager, and we immediately faced the challenge of supporting multiple timezones.

Timezones aren't just the time difference between regions. They hide a remarkable amount of complexity: seasonal time changes (daylight saving and standard time), historical rule changes, different transition dates across countries and even regions within the same country. Add to that user preferences, task scheduling for specific dates, and automatic sync across devices — and the scale of the problem becomes clear.

Terms. Because you can't avoid them.

Atimezonehas two core properties: a name (e.g., Moscow) and anoffset— the time difference from the zero timezone, measured in minutes.

Example: Moscow timezone — name: Moscow, offset: +3 hours from UTC, equivalent to −180 minutes.

UTC (Coordinated Universal Time)— the accepted global reference point for time on Earth.

GMT (Greenwich Mean Time)— the zero timezone, with an offset of 0. Although GMT and UTC represent the same offset, GMT appears more often in interfaces because it's the more familiar term. In UTasks, for example, Moscow time is displayed as GMT+3.

All timezone logic in UTasks operates relative to UTC, but the interface shows offsets relative to GMT for readability.

How UTasks handles timezones

Timezone selection

UTasks manages time with two inputs in mind:

  1. The user's device timezone

  2. The timezone selected in UTasks settings

When the user relies on the device timezone

If the user hasn't set a custom timezone, UTasks automatically syncs with the system timezone on the device.

Example: a user in Moscow uses Moscow time (GMT+3). If they travel to Novosibirsk, UTasks picks up the new timezone from the phone's system settings.

When the user sets a timezone manually in UTasks

A user can pin a specific timezone in UTasks settings. That timezone stays fixed regardless of where the device is physically located.

Example: a user works on Moscow time but is on a business trip in Vladivostok (GMT+10). They set Moscow (GMT+3) in the app so tasks display in their home timezone. They'll need to keep both time zones in their head to avoid confusion — but we won't envy them.

Task time

On top of the system vs. app timezone distinction, there's a layer of complexity tied to how task time works.

If a task has only a date and no specific time, it appears on the same day — say, Monday, October 28 — for users in Moscow, Vladivostok, and Lisbon alike.

But if a task has a specific time, users in Moscow and Vladivostok will see it at different clock times, since the cities are in different time zones. A user in Lisbon will also deal with a daylight saving transition on top of that.

The simplest case

Let's imagine (purely hypothetically) that Denis lives in Kazan, which runs on a fixed UTC+3 with no daylight saving.

Denis opens UTasks, and the system:

  • reads his timezone from the device

  • records the offset for that timezone:−180 minutes(to convert local time to UTC, subtract 180 minutes — this may look counterintuitive compared to the familiar "GMT+3" format, but in programming it's standard to work with local time and convert to UTC for storage)

Denis creates a task for November 10 at 10:00 AM local time. To save it, UTasks reads the stored offset (−180 min) and converts local time (10:00) to UTC by subtracting the offset.

Result: the task is saved asNovember 10, 07:00 UTC.

Why store everything in UTC?

Storing all time data in UTC simplifies processing in apps where users interact from different time zones:

  1. Notifications are sent correctly for all participants' timezones

  2. The same time slot is displayed correctly for users in different regions

  3. No repeated conversions on the server — all transformation happens on the client side

When Denis comes back to check his task, the server returns the time in UTC (November 10, 07:00). UTasks adds the offset (−180 min) back, and Denis sees the task atNovember 10, 10:00— his local time.

How Denis's teammates see the same task:

Denis creates a task for the whole team: November 10, 10:00 AM Moscow time.

The task is saved as07:00 UTC.

  • **Lyosha (Kaliningrad, GMT+2):**07:00 UTC + 2h =09:00 Kaliningrad time

  • **Kamil (Astana, GMT+6):**07:00 UTC + 6h =13:00 Astana time

  • **Leysan (Maldives, GMT+5):**07:00 UTC + 5h =12:00 Maldives time(peak heat)

This simple algorithm works correctly as long as the user's system timezone matches their actual location and doesn't change.

A more complex case

Things get complicated when the user selects a timezone in the app that differs from the device's system timezone.

Let's say Denis has moved to Lisbon — breakfasting on pastel de nata, winding down with port, taking a self-defense class — but mentally he's still in Kazan: selling his garage and fifty empty glass jars. In UTasks settings, he manually selects Moscow time (UTC+3) to keep all his tasks on the familiar schedule.

Denis is physically in Lisbon but creates a task for "next week at 10:00," expecting that time to mean Moscow time.

The problem: all date operations in the code run in the context of the device's system timezone — in this case, Lisbon (UTC+1). If UTasks used a naive algorithm, it would process the date using the system timezone (Lisbon), not Moscow.

What goes wrong with the naive approach:

Denis sets a task for 10:00 Moscow time → Lisbon offset (+1 in summer) is applied → task is sent to the server as 09:00 UTC (which is 12:00 Moscow) instead of the intended 07:00 UTC (10:00 Moscow) → the task is saved with the wrong time, even though the app shows what looks like a correct 10:00 → notifications arrive two hours late.

Our fix:

  1. UTasks calculates the difference between the device's system timezone (Lisbon) and the user's selected timezone in settings (Moscow). In this example:2 hours(UTC+3 − UTC+1).

  2. Before saving, UTasks adjusts the local time by that difference: Denis entered 10:00 Moscow time → device local time (Lisbon) is corrected: 10:00 − 2h =08:00 Lisbon time.

  3. UTasks converts the adjusted time to UTC:07:00 UTC— which is exactly 10:00 Moscow, matching Denis's expectation.

The hardest case: offset changes within a timezone

The logic gets even trickier when the offset within a timezone changes due to daylight saving transitions.

Take Lisbon, where the clocks go back on October 27 at 02:00 local summer time.

On October 10 (while Lisbon is still on summer time), Denis buys tickets to a Beyoncé concert on November 10 (when Lisbon will already be on winter time) and creates a UTasks reminder:November 10, 10:00 — "Buy flowers for Beyoncé."

This raises a few questions:

  1. Does Denis mean 10:00 winter time (UTC+0) or 10:00 summer time (UTC+1)?

  2. How should UTasks account for the offset change to save the correct time?

  3. Is a €20 bouquet appropriate for Beyoncé, or should he skip the port the night before and splurge on €25?

Offset changes depend on the current timezone rules — which governments can and do alter (suspending daylight saving, changing the transition date, etc.). This makes it impossible to predict exact behavior without up-to-date information.

Solution: use the device's system time

The operating system knows the current daylight saving rules. Instead of maintaining its own timezone database, UTasks delegates to the OS:

  1. **Request the formatted time for the target timezone.**When Denis selects a date in the calendar (say, November 10, 10:00), UTasks asks the system:"Format this date for the Lisbon timezone."

    • For October 10 (summer time): system returns10:00 October 10, UTC+1

    • For November 10 (winter time): system returns10:00 November 10, UTC+0The OS automatically applies the correct offset for the requested date.

  2. **Extract the offset from the system response.**The formatted result includes the offset (+1 for summer, 0 for winter). UTasks reads and uses it.

  3. **Adjust the time before sending to the server.**UTasks calculates the difference between the device system timezone (Moscow, UTC+3) and the user's selected timezone (Lisbon). It adjusts accordingly: Denis selects November 10, 10:00. Device timezone: Moscow (UTC+3). Calculation: 10:00 − 3 + 0 =07:00 UTCThe server receives the time already corrected for the user's timezone and its current offset.

To keep offsets current, UTasks regularly checks whether the system timezone offset has changed (for example, after the October 28 switchover). If it has, the app updates and uses the new values going forward.

So when Denis creates a task on October 10 for November 10 at 10:00, UTasks assumes he knows about the upcoming time change and intends the winter offset (UTC+0). The correct offset for November 10 is applied at save time, ensuring the time stays accurate.

Takeaways

Timezone handling turned out to be one of the most demanding engineering challenges in building UTasks — and one of the most instructive.

The core principle: storing everything in UTC is the reliable foundation that keeps users across regions in sync.

But building a truly comfortable interface requires handling many layers of nuance: correctly displaying time based on both local and user-selected timezone, sending notifications on time, and respecting seasonal offset rules.

Our solution balances automation (syncing with device system time) and flexibility (manual timezone override).

One more thing: our developer Lyosha regularly exercises this muscle by manually switching timezones in the app and verifying the results by hand. It's how we catch the subtle edge cases that no amount of abstract reasoning can fully anticipate.