We have been struggling to design a robust flow of date and time values in our app. It is a shift scheduling app with a Flutter frontend and Node.js backend with PostgreSQL database.
Business logic
The app allows business owners to post shifts and employees to sign up for shifts.
All businesses are currently in one time zone – "Europe/Berlin". This time zone respects winter (CET – UTC+1) and summer times (CEST – UTC+2).
All dates and times displayed to and entered by users should be assumed to be in the local business’ time zone irrespective of device’s local time.
Current solution
Node.js presents date/time values in UTC ("2023-03-30T09:00:00.000Z") from timestamptz column in PostgreSQL.
Flutter app converts those values on display using an injected formatter, like so:
import 'package:intl/intl.dart';
import 'package:timezone/timezone.dart' as tz;
/// Display functions:
DateTime toEuropeBerlinTime(DateTime utcDate) {
final europeBerlin = tz.getLocation('Europe/Berlin');
return tz.TZDateTime.from(utcDate, europeBerlin);
}
String display(String format, DateTime? utcDateTime) {
if (utcDateTime == null) {
return '';
} else {
final convertedToEuropeBerlin = toEuropeBerlinTime(utcDateTime);
final dateFormatter = DateFormat(
format,
'de_DE',
);
final convertedAndFormatted =
dateFormatter.format(convertedToEuropeBerlin);
return convertedAndFormatted;
}
}
/// Example usage:
final exampleUtcTime = DateTime.parse('2023-03-30T09:00:00.000Z');
final displayValue = display('d MMM y hh:mm', exampleUtcTime);
// displays 30 Marz 2023 11:00 - correct CEST
final exampleUtcTimeWinter = DateTime.parse('2023-01-30T09:00:00.000Z');
final displayValue = display('d MMM y hh:mm', exampleUtcTimeWinter);
// displays 30 Jan 2023 10:00 - correct CET
With a similar reversed solution for user input.
Current issues
We are not sure if this is the best solution.
- There is a lot of converting going on – say, user enters a date and time in the app, it is converted to UTC, sent to the API, stored in the database. On display it is converted back to Europe/Berlin time zone and displayed to the user. In the meantime, UX has to accept user input, store it in UTC and – if we want to display the value to the user before API call – convert it back to Europe/Berlin time zone.
- Therefore, it is difficult to keep track of what time zone a value is in. We have to be careful to convert values to UTC before sending them to the API and back to Europe/Berlin time zone before displaying them to the user. This leads to bugs that are difficult to track.
- Injecting the converter to UX widgets is not ideal as building multiple widgets – each doing the same conversion – leads to noticeable performance delay when e.g. refreshing a list of shifts.
- We are yet to figure out a solution for cases where a shift in summer time is displayed in January.
I’m not even thinking of future-proofing now for any sort of expansion outside of this time zone.
Potential solutions we’ve considered
- Converting all UTC date/time values to business localised values and vice-versa in API service, hence keeping Flutter DateTime completely timezone-unaware.
- Ditching time zone aware date/time altogether and using only unaware objects with timestamp Postgres column -> this would mean we would need to consider timezones only when calculating shift length for overnight shifts that took place on the day of timezone change.
- Pushing down the conversion in Flutter to the data repository level, so that the conversion would be tied to fromJson / toJson methods.
As we are both self-taught and each of those solutions is pretty significant for the codebase, we would really appreciate any advice on the matter from the community.
With many thanks!
2
Answers
In terms of organization, it is better to do the conversions in fromJson/toJson, as your conversion would be in just one place, thus reducing the complexity of the code and facilitating its future maintenance.
I hope I helped
IMO storing everything in UTC is the right way, so it is ok how you are handling this. You can do the UTC conversion on the back-end side(this will leave the front-end away from handling this). When getting the data from the back-end you can apply the conversion on the model layer of the app when doing the parsing of the decoded JSON response(since you are going to receive all dates in UTC format). This way you divide the load between the front-end and the back-end.