Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect behaviour of add method when traversing a date in which daylight saving time transition occurs #81

Open
amato-gianluca opened this issue Oct 25, 2020 · 9 comments

Comments

@amato-gianluca
Copy link

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```.

@anisalibegic
Copy link

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

@anisalibegic
Copy link

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...)

@gregoriopalama
Copy link

gregoriopalama commented Nov 1, 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.

@gregoriopalama
Copy link

I've just opened an issue on Dart's SDK repository: dart-lang/sdk#44014

@whesse
Copy link

whesse commented Nov 2, 2020

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.

@wph144
Copy link

wph144 commented Feb 13, 2021

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?

@wph144
Copy link

wph144 commented Feb 18, 2021

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();
  }
}

@jama5262
Copy link
Owner

jama5262 commented Mar 1, 2021

@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

@Magnuti
Copy link

Magnuti commented Oct 11, 2023

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);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants