jiffy
jiffy copied to clipboard
Incorrect behaviour of add method when traversing a date in which daylight saving time transition occurs
Hi, I have recently stumbled over this problem. My current timezone is 'Europe/Rome', where DST switches off the last Sunday of October. If I execute
Jiffy('2020-10-19 00:00:00').add(weeks: 1)
due to the DST transition, the result is not 2020-10-26 00:00:00
, as I would expect, but 2020-10-25 23:00:00
. This breaks all the rest of my computation.
I think the behavior of Jiffy is incorrect. Adding a week should return 2020-10-26 00:00:00
, independently from the actual number of hours of difference. As a comparison, the momentjs library with
moment('2020-10-19 00:00:00').add(1, 'weeks')
correctly returns Moment<2020-10-26T00:00:00+01:00>
.
This is not the only instance of this problem. For example,
Jiffy('2020-10-25 00:00:00').add(days: 1)
returns again 2020-10-25 23:00:00
instead of ``2020-10-26 00:00:00```.
I'm having the exact problem when subtracting weeks.
Input:
2020-10-30 00:00:00.000
Output:
2020-10-23 01:00:00.000
Should be:
2020-10-23 00:00:00.000
I believe Jiffy has nothing to do with this. I solved it by using utc()
constructor when creating DateTime
objects.
DateTime.now()
-> DateTime.now().toUtc()
DateTime(2020...)
-> DateTime.utc(2020...)
In fact, what @anisalibegic found is correct: the bug is in Dart SDK itself.
I've replicated this with this Dart code:
//let's start with a date before the DST transition
DateTime dt = DateTime(2020, 10, 25, 16, 10, 00, 999);
print(dt);
//and let's go to the start of week. this code is the same used by Jiffy
DateTime newDate = dt.subtract(Duration(days: dt.weekday - 1));
DateTime dt2 = DateTime(newDate.year, newDate.month, newDate.day);
print(dt2);
//then, let's add a week. again, I'm using the same code that Jiffy uses
dt2 = dt2.add(Duration(days: 7));
print(dt2);
In my timezone, this is going to be 2020-10-25 23:00:00.000, but that's wrong, because I would expect it to go to the end of the day, so to 2020-10-26 00:00:00.000. If we try to change the starting date, assuming it to be, for example, 2020-11-25, we will get a correct behaviour.
I'll try to search in Dart repository to see if there already is any related issue. Otherwise, I'll open one.
I've just opened an issue on Dart's SDK repository: https://github.com/dart-lang/sdk/issues/44014
Jiffy would work correctly (ignoring DST changes) when adding days or weeks if its implementation had special code for it, the way it has special code for adding months and years. It is up to the Jiffy package maintainers if they want to change adding days and weeks to work this way.
It is also solved by using UTC times when working with calendar days, since a calendar day has no time of day associated with it and is independent of time zones.
I am from Android developer. Kotlin(or Java) has 2 class named LocalDateTime and LocalDate. LocalDate is only for Date calculation. It is more simple and difficult to misunderstand design With DateTime or Jiffy in Dart, many people may try first add days, but will realize this issue later. (after publishing app if unlucky) How about creating new class for date calculation?
I made a class in my project only for date calculation. This class is wrapping Jiffy. You can modify and use it if need.
import 'package:jiffy/jiffy.dart';
class UtcDate {
DateTime _dateTime;
UtcDate.now(): this(DateTime.now());
UtcDate(DateTime dateTime) {
_dateTime = DateTime.utc(dateTime.year, dateTime.month, dateTime.day);
}
UtcDate.ymd(int year, int month, int day) {
_dateTime = DateTime.utc(year, month, day);
}
UtcDate.parse(String formattedString): this(DateTime.parse(formattedString));
int get day => _dateTime.day;
int get month => _dateTime.month;
int get year => _dateTime.year;
int get weekday => _dateTime.weekday;
int get daysInMonth => Jiffy(_dateTime).daysInMonth;
int get week => Jiffy(_dateTime).week;
DateTime get dateTime => _dateTime;
static Future<String> setLocale([String locale]) async {
return await Jiffy.locale(locale);
}
UtcDate withDayOfMonth(int dayOfMonth) {
return UtcDate.ymd(this.year, this.month, dayOfMonth);
}
UtcDate addDays(int days) {
return UtcDate(_dateTime.add(Duration(days: days)));
}
UtcDate addMonths(int months) {
return UtcDate(Jiffy(_dateTime).add(months: months));
}
int diffInDays(UtcDate utcDate) {
return Jiffy(_dateTime).diff(utcDate.dateTime, Units.DAY);
}
int diffInWeek(UtcDate utcDate) {
return Jiffy(_dateTime).diff(utcDate.dateTime, Units.WEEK);
}
int diffInMonths(UtcDate utcDate) {
return Jiffy(_dateTime).diff(utcDate.dateTime, Units.MONTH);
}
UtcDate startOfWeek() {
return UtcDate(Jiffy(_dateTime).startOf(Units.WEEK));
}
UtcDate endOfWeek() {
return UtcDate(Jiffy(_dateTime).endOf(Units.WEEK));
}
bool operator ==(dynamic other) {
assert(other is UtcDate);
return year == other.year && month == other.month && day == other.day;
}
bool isBefore(UtcDate other) {
return _dateTime.isBefore(other.dateTime);
}
bool isAfter(UtcDate other) {
return _dateTime.isAfter(other.dateTime);
}
bool isSameDate(UtcDate other) {
return this == other;
}
bool isSameMonth(UtcDate other) {
return this.year == other.year && this.month == other.month;
}
String toDateString() {
return "$year-${month.toString().padLeft(2,'0')}-${day.toString().padLeft(2,'0')}";
}
String toMonthString() {
return '$year.${month.toString().padLeft(2, '0')}';
}
@override
String toString() {
return toDateString();
}
}
@whesse I agree, Jiffy could do its own implementation to correct this, maybe within the subtract and add function we could add utc implementation to manipulate the date and later return its local time. A similar approach to @wonpyohong solution
This is expected behaviour since jiffy's add
method simply calls the Dart DateTime.add
method. The documentation states that whole days are not added, but instead seconds are added.
https://api.flutter.dev/flutter/dart-core/DateTime/add.html
Be careful when working with dates in local time.
This should either be better documented in jiffy's add
method, or jiffy should better handle daylight savings by adding whole days like this
DateTime now = DateTime.now();
DateTime fiveDaysFromNow = DateTime(now.year, now.month, now.day + 5);