Handling Time Zones in a Global App: A Comprehensive Guide

Overview

Users can access an app from various locations across the world, and this brings a significant challenge: handling different local times. Fortunately, databases store dates in UTC format, which provides a consistent reference point. However, managing reports and notifications based on date ranges, such as daily or weekly reports, requires careful handling of dates and time zones. In this blog, we will dive into the problem and the solution we implemented in our Flutter and Node.js app using flutter_timezone and moment-timezone. For JavaScript frontends, moment-timezone can be used instead of flutter_timezone.

What Are We Solving?

A.K.A Problem Statement

Let's illustrate the problem with an example:

Imagine it's 1st July, 1 am IST (Indian Standard Time) in your local time. The corresponding UTC time is 30th June, 7:30 PM UTC. When generating a particular report for the current day, the start of the day and the end of the day date and time are required. If we directly call moment().startOf('d') and moment().endOf('d') in the backend, we'll get:

  • startDate = June 30th 00:00 UTC
  • endDate = June 30th 23:59 UTC

The above behavior happens because the backend will probably be set to a default timezone of UTC (or it can be any timezone depending on the region where the server is located; in our case, let's say it is UTC. The solution works for any timezone).

This approach generates and shows the previous day's reports instead of the current day. As a result, users won’t be able to see the new charts until UTC also crosses over to the next day—in our example, 1st July.

The same issue occurs with PST (Pacific Standard Time), which is 8 hours behind UTC. For example:

  • June 30th, 5 PM PST is July 1st, 1 AM UTC. In our scenario, since the local time is PST, the start and end date should be:

  • startDate = June 30th, 00:00 PST

  • endDate = June 30th, 23:59 PST

but the backend would be considering July 1st as the current date.

Solution

The solution is to always know the users’ timezone when generating reports or sending notifications. In Flutter, the issue can be resolved using the flutter_timezone package in the frontend and moment-timezone in the backend. Let's break down the solution.

Frontend

For all API requests to the backend, we attach the device's timezone to the request headers. We obtain the timezone using the flutter_timezone package. Here’s how we add the timezone to all requests using Dio's request interceptor:

import 'package:dio/dio.dart';
import 'package:flutter_timezone/flutter_timezone.dart';

class TimezoneInterceptor extends Interceptor {
  
  Future<void> onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
    final timezone = await FlutterTimezone.getLocalTimezone();
    options.headers['timezone'] = timezone;
    return handler.next(options);
  }
}

final dio = Dio();
dio.interceptors.add(TimezoneInterceptor());

The timezone can also be cached once the app starts and can be periodically updated to avoid an async operation for every API call. It is not a costly operation, so I decided to leave it as it is.

Backend

On the backend, we use the moment-timezone package to convert dates into the given timezone. Any ODM/ORM, such as Mongoose, Sequelize, or Prisma, is capable of understanding the timezone of the given date and retrieving the data accordingly.

Here’s how we convert the dates using moment-timezone:

const moment = require('moment-timezone');

// An express controller function
const reportsController = async (req, res) => {
  const { timezone } = req.headers;
  
  const start = moment.tz(timezone).startOf('D');
  const end = moment.tz(timezone).endOf('D');
  
  // Use start and end as needed
  const reports = await generateReports(start, end);
  
  return res.status(200).send({ success: true, data: reports });
}

The timezone is included in the request headers for all requests from the frontend. We can access it using:

const timezone = req.headers.timezone;

This conversion is primarily used for report generation or any activity that is local time-sensitive, such as personalizing the user experience, notifications, and so on.

JavaScript Frontend

This solution can be implemented in a JavaScript frontend using moment-timezone:

const tz = moment.tz.guess(); // Example output: Asia/Calcutta

tz can then be attached as a header to all the requests sent to the backend. Almost all the frequently used JavaScript request libraries like axios and fetch have interceptor implementations which can be used to attach the header for all API requests.

Conclusion

Handling time zones correctly is crucial for providing accurate and relevant data to users across different regions. By leveraging flutter_timezone in the frontend and moment-timezone in the backend, we can ensure that our app provides the correct reports and notifications based on the user's local time.

This approach not only improves the user experience but also ensures that our reports and analytics are accurate and reliable.

Happy Coding!!