jama5262 / jiffy

Jiffy is a Flutter (Android, IOS and Web) date time package for parsing, manipulating, querying and formatting dates
https://pub.dev/packages/jiffy
MIT License
576 stars 124 forks source link

isBefore with Unit.day incorrect behaviour #247

Open tricasthu opened 1 year ago

tricasthu commented 1 year ago

Describe the bug

When two following dates are compared using the bool isBefore(Jiffy jiffy, {Unit unit = Unit.microsecond}){...} method, an incorrect 'false' will be the result instead of 'true';

How to reproduce the bug

DateTime eventEnd = DateTime(2023, 6, 15);
DateTime weekStart = DateTime(2023, 6, 16);

Jiffy jEventEnd = Jiffy.parseFromDateTime(eventEnd);
Jiffy jWeekStart = Jiffy.parseFromDateTime(weekStart);

jEventEnd.isBefore(jWeekStart, unit: Unit.day) // results in false

What is the expected behavior

Result of true; clearly, 15th is earlier than 16th.

Working example

import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    DateTime eventEnd = DateTime(2023, 6, 15);
    DateTime weekStart = DateTime(2023, 6, 16);

    Jiffy jEventEnd = Jiffy.parseFromDateTime(eventEnd);
    Jiffy jWeekStart = Jiffy.parseFromDateTime(weekStart);

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'event end before start: ${isDateBefore(eventEnd, weekStart)}',
            ),
            Text(
              'jiffy event end before start: ${jEventEnd.isBefore(jWeekStart, unit: Unit.day)}',
            ),
          ],
        ),
      ),
    );
  }
}

bool isDateBefore(DateTime first, DateTime other) {
  return _compare(first, other) < 0;
}

bool isDateAfter(DateTime first, DateTime other) {
  return _compare(first, other) > 0;
}

int _compare(DateTime first, DateTime other) {
  int cmp = (first.year - other.year);
  if (cmp == 0) {
    cmp = (first.month - other.month);
    if (cmp == 0) {
      cmp = (first.day - other.day);
    }
  }
  return cmp;
}

Additional information

Flutter's DateTime (at least a few months ago, I did not check now) does not have date comparison by day, that is why I included the isDateBefore, isDateAfter, and _compare functions. I use them to compare dates, they are based on the Java implementation of comparing dates by day.

I have traced the issue to the following: when the Unit.day parameter is given, the result is incorrect. The following is the implementation of isBefore in \lib\src\query.dart:

bool isBefore(DateTime firstDateTime, DateTime secondDateTime, Unit unit,
      StartOfWeek startOfWeek) {
    final secondDateTimeMicrosecondsSinceEpoch =
        _getter.microsecondsSinceEpoch(secondDateTime);

    if (unit == Unit.microsecond) {
      return _getter.microsecondsSinceEpoch(firstDateTime) <
          secondDateTimeMicrosecondsSinceEpoch;
    }

    final endOfFirstDateTimeMicrosecondsSinceEpoch =
        _getter.microsecondsSinceEpoch(
            _manipulator.endOf(firstDateTime, unit, startOfWeek));

    return endOfFirstDateTimeMicrosecondsSinceEpoch <
        secondDateTimeMicrosecondsSinceEpoch;
} 

Here, the first parameter is manipulated in a way that its end is considered (endOfFirstDateTimeMicrosecondsSinceEpoch) and then compared with the second parameter. The problem is if the second parameter is eg. 2023. 06. 16. 12:00:00 AM (if I omit the hour part of the date during initialization, that is going to be the default), then compare that date with 2023. 06. 15. will result in comparing the same epoch times, and therefore it will return false.

Flutter doctor

Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 3.10.5, on Microsoft Windows [Version 10.0.19045.3086], locale hu-HU)
[√] Windows Version (Installed version of Windows is version 10 or higher)
[!] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    X cmdline-tools component is missing
      Run `path/to/sdkmanager --install "cmdline-tools;latest"`
      See https://developer.android.com/studio/command-line for more details.
    X Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/docs/get-started/install/windows#android-setup for more details.
[√] Chrome - develop for the web
[X] Visual Studio - develop for Windows
    X Visual Studio not installed; this is necessary for Windows development.
      Download at https://visualstudio.microsoft.com/downloads/.
      Please install the "Desktop development with C++" workload, including all of its default components
[√] Android Studio (version 2022.1)
[√] Android Studio (version 2022.2)
[√] IntelliJ IDEA Community Edition (version 2023.1)
[√] VS Code (version 1.62.3)
[√] Connected device (3 available)
[√] Network resources

Jiffy version: latest.

khoale38 commented 1 year ago

I am having same issue with Unit.hour. Screen Shot 2023-08-04 at 15 03 07 Screen Shot 2023-08-04 at 15 03 42