The combination of daylight saving times and DateTime.add()
and DateTime.subtract()
can cause unpredictable problems. Here’s what you can do about it.
Let’s start with an example: in Germany, we have moved our clocks one our forwards on 2021-03-28 at 2 AM. That’s an odd thing if you think about it: that Sunday only had 23 hours because the hour between 2 AM and 3 AM went missing.
An interesting question is: how does the DateTime
API behave, if we have a time before the “magic hour” and add some hours to it? Will it respect the DST or will it ignore it?
1initializeDateFormatting('de');
2DateTime dateTime = DateTime(2021, 3, 28, 1); // Setting the DateTime to 2021-03-28, 1 AM
3print(dateTime.add(Duration(hours: 1)));
What we are doing is setting the time to one hour before the missing hour begins and add one hour using the add()
method of the DateTime
object.
The output is as follows:
12021-03-28 03:00:00.000
So far so good. The hour between 2 and 3 is missing, that’s why it’s skipped.
The issue
What if we are not skipping hours but days? Let’s imagine to have a date picker with two arrows, one incrementing the current day and one decrementing it.
A natural expectation when subtracting a whole day from DateTime
would be that the day of the date is decremented by 1 and the time stays the same.
The output when we subtract a Duration
of 1 day from the day after the day the clock was set one hour forward is as follows:
12021-03-27 23:00:00.000
That does not meet any of the expectations. Neither the day was incremented by 1 nor the time stayed the same. Instead, 24 hours were subtracted which made the resulting DateTime
have a day that is decremented by 2 compared to the original DateTime
value.
This is a tough problem in our fictional date chooser example as the user is unable to choose the date where the clock was set one hour forward. A quick solution could be to normalize every date by settings its time to 00:00. However, then the next problem arises when we look at the day it’s being set backwards:
The output is:
12021-10-31 23:00:00.000
When navigating forward using the date chooser with dates normalized to 00:00, the user would be stuck when reaching this date.
The solution
Actually, the documentation of add and subtract warns us about this circumstance:
Notice that the duration being added is actually 50 * 24 * 60 * 60 seconds. If the resulting DateTime has a different daylight saving offset than this, then the result won’t have the same time-of-day as this, and may not even hit the calendar date 50 days later.
Be careful when working with dates in local time.
So we may not use add()
or subtract()
. But what other options do we have?
Actually, the DateTime
constructor has a useful feature: if you provide an integer that exceeds the current time context such as a 32nd day of the month, it automatically switches to the first day of the next month.
Because of that we can do this:
1DateTime dateTime = DateTime(2021, 10, 31);
2print(dateTime.add(Duration(days: 1)));
3print(DateTime(dateTime.year, dateTime.month, dateTime.day + 1, dateTime.hour, dateTime.minute));
The output is:
So the way of adding it by using the DateTime
constructor with the added days, produces the expected result.
It’s also possible to use the named constructor DateTime.utc
in the first place when working with dates. The documentation says:
When dealing with dates or historic events prefer to use UTC DateTimes, since they are unaffected by daylight-saving changes and are unaffected by the local timezone.
However, if the original time is e. g. based on the current date (DateTime.now()
), the user expects it to be the local time that is affected by daylight saving time.
The same issue arises when working with DateTime.difference()
as this example illustrates:
1print(DateTime(2021, 11, 1).difference(DateTime(2021, 10, 31)));
2print(DateTime.utc(2021, 11, 1).difference(DateTime.utc(2021, 10, 31)));
Conclusion
When working with DateTime
, it’s recommended to always use DateTime.utc
if possible. If not possible, then avoid using the add
and subtract
method as it can lead to confusing results. Instead, use the DateTime
constructor with the added time.
Adnrew King
Marc
In reply to Adnrew King's comment