diff --git a/README.md b/README.md index 8b55e73..3fb90eb 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,35 @@ - -TODO: Put a short description of the package here that helps potential users -know whether this package might be useful for them. +# Date Time Picker + +The date time picker to be able to input a date. ## Features -TODO: List what your package can do. Maybe include images, gifs, or videos. +### Drag down date time picker + +A picker that is placed in the top of the screen. +You are able to select a day at this point. +When it is dragged down you are able to select a day for a given month. + +### Overlay date time picker + +A picker, that when opened using a button, is placed over the screen. +Then you are able to swipe through the month to select a day. +It is possible to add a constraint to de picker to limit the choice of day. + +![Overlay date time picker GIF](overlay_date_time_picker.gif) ## Getting started @@ -25,15 +38,24 @@ start using the package. ## Usage -TODO: Include short and useful examples for package users. Add longer examples -to `/example` folder. - ```dart -const like = 'sample'; +OverlayDateTimePicker( + alignment: Alignment.bottomCenter, + child: const Text("Select Day"), + onTapDay: (date) {}, +), ``` -## Additional information +See the [Example Code](example/lib/main.dart) for more example's on how to use this package. -TODO: Tell users more about the package: where to find more information, how to -contribute to the package, how to file issues, what response they can expect -from the package authors, and more. +## Issues + +Please file any issues, bugs or feature request as an issue on our [GitHub](https://github.com/Iconica-Development/flutter_date_time_picker/pulls) page. Commercial support is available if you need help with integration with your app or services. You can contact us at [support@iconica.nl](mailto:support@iconica.nl). + +## Want to contribute + +If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](../CONTRIBUTING.md) and send us your [pull request](https://github.com/Iconica-Development/flutter_date_time_picker/pulls). + +## Author + +This `flutter-date-time-picker` for Flutter is developed by [Iconica](https://iconica.nl). You can contact us at diff --git a/example/README.md b/example/README.md index 2b3fce4..bf7ffc4 100644 --- a/example/README.md +++ b/example/README.md @@ -1,16 +1,3 @@ # example -A new Flutter project. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +The example Flutter app for the date time picker diff --git a/example/lib/main.dart b/example/lib/main.dart index 327e6c7..7810afd 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -80,6 +80,9 @@ class DatePickerDemo extends StatelessWidget { onPressed: onPressed, child: const Text("Select Day"), ), + dateTimeConstraint: DateTimeConstraint( + min: DateConstraint(date: DateTime.now()), + ), ), OverlayDateTimePicker( theme: dateTimePickerTheme, @@ -91,6 +94,18 @@ class DatePickerDemo extends StatelessWidget { Icons.schedule, ), ), + dateTimeConstraint: DateTimeConstraint( + min: DateConstraint(date: DateTime.now()), + max: DateConstraint( + date: DateTime( + DateTime.now().year, + DateTime.now().month + 4, + DateTime.now().day, + ), + ), + ), + onNextPageButtonChild: const Icon(Icons.add), + onPreviousPageButtonChild: const Icon(Icons.minimize), ) ], ), diff --git a/example/pubspec.lock b/example/pubspec.lock index c47f500..9427b51 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -61,7 +61,7 @@ packages: path: ".." relative: true source: path - version: "0.0.1" + version: "2.0.0" flutter_lints: dependency: "direct dev" description: diff --git a/lib/flutter_date_time_picker.dart b/lib/flutter_date_time_picker.dart index 83adb30..a95132a 100644 --- a/lib/flutter_date_time_picker.dart +++ b/lib/flutter_date_time_picker.dart @@ -6,6 +6,7 @@ library flutter_date_time_picker; export 'src/drag_down_date_time_picker.dart' show DragDownDateTimePicker; export 'src/overlay_date_time_picker.dart' show OverlayDateTimePicker; +export 'src/models/date_constraint.dart'; export 'src/enums/date_box_shape.dart'; export 'src/models/date_box_base_theme.dart'; export 'src/models/date_box_disabled_theme.dart'; diff --git a/lib/src/models/date_constraint.dart b/lib/src/models/date_constraint.dart new file mode 100644 index 0000000..3ca8783 --- /dev/null +++ b/lib/src/models/date_constraint.dart @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2022 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +class DateTimeConstraint { + final DateConstraint min; + final DateConstraint max; + + const DateTimeConstraint({ + this.min = DateConstraint.infinity, + this.max = DateConstraint.infinity, + }); + + bool inRange(DateTime date) { + return _checkDate( + min, + () => !date.isBefore(min.date!), + ) && + _checkDate( + max, + () => !date.isAfter(max.date!), + ); + } + + bool inDateRange(DateTime date) { + return _checkDate( + min, + () => !_stripToDateOnly(date).isBefore(_stripToDateOnly(min.date!)), + ) && + _checkDate( + max, + () => !_stripToDateOnly(date).isAfter(_stripToDateOnly(max.date!)), + ); + } + + bool inMonthRange(DateTime date) { + return _checkDate( + min, + () => + !_stripToMonthsOnly(date).isBefore(_stripToMonthsOnly(min.date!)), + ) && + _checkDate( + max, + () => + !_stripToMonthsOnly(date).isAfter(_stripToMonthsOnly(max.date!)), + ); + } + + bool inYearRange(DateTime date) { + return _checkDate( + min, + () => !_stripToYearsOnly(date).isBefore(_stripToYearsOnly(min.date!)), + ) && + _checkDate( + max, + () => !_stripToYearsOnly(date).isAfter(_stripToYearsOnly(max.date!)), + ); + } + + DateTime _stripToDateOnly(DateTime date) { + return DateTime( + date.year, + date.month, + date.day, + ); + } + + DateTime _stripToMonthsOnly(DateTime date) { + return DateTime( + date.year, + date.month, + 1, + ); + } + + DateTime _stripToYearsOnly(DateTime date) { + return DateTime( + date.year, + 1, + 1, + ); + } + + bool _checkDate(DateConstraint constraint, bool Function() checker) { + if (!constraint.isInfinite) { + return checker(); + } + return constraint.isInfinite; + } +} + +class DateConstraint { + static const DateConstraint infinity = + DateConstraint(date: null, isInfinite: true); + final DateTime? date; + final bool isInfinite; + + const DateConstraint({this.date, this.isInfinite = false}) + : assert( + !(date != null && isInfinite), + 'Can NOT have a limit set and be infinite.', + ), + assert( + date != null || isInfinite, + 'Must set some form of a limit.', + ); +} diff --git a/lib/src/models/date_time_picker_bar_theme.dart b/lib/src/models/date_time_picker_bar_theme.dart index 076b625..f673fca 100644 --- a/lib/src/models/date_time_picker_bar_theme.dart +++ b/lib/src/models/date_time_picker_bar_theme.dart @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2022 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + import 'package:flutter/material.dart'; class DateTimePickerBarTheme { diff --git a/lib/src/overlay_date_time_picker.dart b/lib/src/overlay_date_time_picker.dart index 3ca3edd..ea8bdea 100644 --- a/lib/src/overlay_date_time_picker.dart +++ b/lib/src/overlay_date_time_picker.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_date_time_picker/src/extensions/date_time.dart'; import 'package:flutter_date_time_picker/src/models/date_time_picker_theme.dart'; import 'package:flutter_date_time_picker/src/utils/date_time_picker_controller.dart'; +import 'package:flutter_date_time_picker/src/models/date_constraint.dart'; import 'package:flutter_date_time_picker/src/widgets/overlay_date_time_picker/overlay.dart'; class OverlayDateTimePicker extends StatefulWidget { @@ -14,8 +15,7 @@ class OverlayDateTimePicker extends StatefulWidget { this.textStyle = const TextStyle(), this.alignment = Alignment.bottomRight, this.initialDate, - this.size = const Size(325, 350), - this.wrongTimeDialog, + this.size = const Size(325, 375), this.onTapDay, this.highlightToday = true, this.alwaysUse24HourFormat = true, @@ -28,14 +28,14 @@ class OverlayDateTimePicker extends StatefulWidget { this.buttonBuilder, this.closeOnSelectDate = true, this.showWeekDays = true, + this.dateTimeConstraint = const DateTimeConstraint(), + this.onNextPageButtonChild, + this.onPreviousPageButtonChild, }) : assert(child != null || buttonBuilder != null); /// The child contained by the DatePicker. final Widget? child; - /// A [Widget] to display when the user picks a disabled time in the [TimePickerDialog] - final Widget? wrongTimeDialog; - /// Visual properties for the [OverlayDateTimePicker] final DateTimePickerTheme theme; @@ -51,13 +51,13 @@ class OverlayDateTimePicker extends StatefulWidget { /// a [bool] to set de clock on [TimePickerDialog] to a fixed 24 or 12-hour format. final bool alwaysUse24HourFormat; - /// [pickTime] is a [bool] that determines if the user is able to pick a time after picking a date using the [TimePickerDialog]. + /// is a [bool] that determines if the user is able to pick a time after picking a date using the [TimePickerDialog]. final bool pickTime; /// indicates the starting date. Default is [DateTime.now()] final DateTime? initialDate; - /// [markedDates] contain the dates [DateTime] that will be marked in the [OverlayDateTimePicker] by a small dot. + /// contain the dates [DateTime] that will be marked in the [OverlayDateTimePicker] by a small dot. final List? markedDates; /// a [List] of [DateTime] objects that will be disabled and cannot be interacted with whatsoever. @@ -75,12 +75,21 @@ class OverlayDateTimePicker extends StatefulWidget { /// [buttonBuilder] is a method for building the button that can trigger the overlay to appear final Widget Function(Key key, void Function() onPressed)? buttonBuilder; - /// [closeOnSelectDate] is a bool that indicates if the overlay should be closed if a date has been picked. + /// is a [bool] that indicates if the overlay should be closed if a date has been picked. final bool closeOnSelectDate; /// [showWeekDays] is a [bool] that determines if day in the week indicators should be shown final bool showWeekDays; + /// a [DateTimeConstraint] that dictates the constraints of the dates that can be picked. + final DateTimeConstraint dateTimeConstraint; + + /// a [Widget] that determents the icon of the button for going to the next page + final Widget? onNextPageButtonChild; + + /// a [Widget] that determents the icon of the button for going to the previous page + final Widget? onPreviousPageButtonChild; + @override State createState() => _OverlayDateTimePickerState(); } @@ -222,6 +231,9 @@ class _OverlayDateTimePickerState extends State { showWeekDays: true, onNextDate: nextDate, onPreviousDate: previousDate, + dateTimeConstraint: widget.dateTimeConstraint, + onNextPageButtonChild: widget.onNextPageButtonChild, + onPreviousPageButtonChild: widget.onPreviousPageButtonChild, ), ), ), diff --git a/lib/src/utils/locking_page_scroll_physics.dart b/lib/src/utils/locking_page_scroll_physics.dart new file mode 100644 index 0000000..b21503a --- /dev/null +++ b/lib/src/utils/locking_page_scroll_physics.dart @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2022 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +import 'package:flutter/material.dart'; + +class LockingPageScrollPhysics extends ScrollPhysics { + final bool Function() allowedNextPage; + final bool Function() allowedPreviousPage; + + const LockingPageScrollPhysics({ + required this.allowedNextPage, + required this.allowedPreviousPage, + ScrollPhysics? parent, + }) : super(parent: parent); + + @override + ScrollPhysics applyTo(ScrollPhysics? ancestor) { + return LockingPageScrollPhysics( + allowedNextPage: allowedNextPage, + allowedPreviousPage: allowedPreviousPage, + parent: buildParent(ancestor)); + } + + @override + double applyBoundaryConditions(ScrollMetrics position, double value) { + bool movingLeft = value > position.pixels; + if (movingLeft && allowedNextPage()) { + return super.applyBoundaryConditions(position, value); + } + if (!movingLeft && allowedPreviousPage()) { + return super.applyBoundaryConditions(position, value); + } + return value - position.pixels; + } +} diff --git a/lib/src/widgets/overlay_date_time_picker/date_picker.dart b/lib/src/widgets/overlay_date_time_picker/date_picker.dart index 18667bf..9bb4fbd 100644 --- a/lib/src/widgets/overlay_date_time_picker/date_picker.dart +++ b/lib/src/widgets/overlay_date_time_picker/date_picker.dart @@ -18,6 +18,7 @@ class DatePicker extends StatelessWidget { required this.onSelectDate, required this.date, required this.showWeekDays, + required this.dateTimeConstraint, }); final DateTimePickerController controller; @@ -26,6 +27,7 @@ class DatePicker extends StatelessWidget { final void Function(DateTime date) onSelectDate; final DateTime date; final bool showWeekDays; + final DateTimeConstraint dateTimeConstraint; @override Widget build(BuildContext context) { @@ -85,7 +87,8 @@ class DatePicker extends StatelessWidget { padding: const EdgeInsets.all(2.0), child: PickableDate( isOffMonth: date.month != todayDate.month, - isDisabled: isDisabled(addedIndex + index, daysToSkip), + isDisabled: + isDisabled(addedIndex + index, daysToSkip, todayDate), isSelected: controller.selectedDate == todayDate, isToday: isToday(todayDate) && controller.highlightToday, theme: theme, @@ -108,13 +111,14 @@ class DatePicker extends StatelessWidget { date.day == now.day; } - bool isDisabled(int index, int daysToSkip) { + bool isDisabled(int index, int daysToSkip, DateTime date) { return DateTime( - date.year, - date.month, - index + 1 - daysToSkip, - ).containsAny( - controller.disabledDates ?? [], - ); + date.year, + date.month, + index + 1 - daysToSkip, + ).containsAny( + controller.disabledDates ?? [], + ) || + !dateTimeConstraint.inRange(date); } } diff --git a/lib/src/widgets/overlay_date_time_picker/overlay.dart b/lib/src/widgets/overlay_date_time_picker/overlay.dart index bef923a..7b3a3e6 100644 --- a/lib/src/widgets/overlay_date_time_picker/overlay.dart +++ b/lib/src/widgets/overlay_date_time_picker/overlay.dart @@ -5,7 +5,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_date_time_picker/src/models/date_time_picker_theme.dart'; import 'package:flutter_date_time_picker/src/utils/date_time_picker_controller.dart'; +import 'package:flutter_date_time_picker/src/utils/locking_page_scroll_physics.dart'; import 'package:flutter_date_time_picker/src/widgets/overlay_date_time_picker/date_picker.dart'; +import 'package:flutter_date_time_picker/src/models/date_constraint.dart'; import 'package:intl/intl.dart'; class OverlayDateTimeContent extends StatefulWidget { @@ -18,6 +20,9 @@ class OverlayDateTimeContent extends StatefulWidget { required this.showWeekDays, required this.onNextDate, required this.onPreviousDate, + required this.dateTimeConstraint, + required this.onPreviousPageButtonChild, + required this.onNextPageButtonChild, }); final DateTimePickerTheme theme; @@ -25,6 +30,11 @@ class OverlayDateTimeContent extends StatefulWidget { final Size size; final DateTimePickerController controller; final bool showWeekDays; + final DateTimeConstraint dateTimeConstraint; + + final Widget? onNextPageButtonChild; + final Widget? onPreviousPageButtonChild; + final void Function() onNextDate; final void Function() onPreviousDate; @@ -34,10 +44,22 @@ class OverlayDateTimeContent extends StatefulWidget { class _OverlayDateTimeContentState extends State { bool usesButtons = false; + late DateTime nextDate; + late DateTime previousDate; late final PageController _pageController; @override void initState() { _pageController = PageController(initialPage: 1); + nextDate = DateTime( + widget.controller.browsingDate.year, + widget.controller.browsingDate.month + 1, + 1, + ); + previousDate = DateTime( + widget.controller.browsingDate.year, + widget.controller.browsingDate.month - 1, + 1, + ); super.initState(); } @@ -56,16 +78,25 @@ class _OverlayDateTimeContentState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( - onPressed: goToPreviousPage, - icon: const Icon(Icons.arrow_circle_left_outlined), + onPressed: (widget.dateTimeConstraint.inMonthRange(previousDate)) + ? _goToPreviousPage + : null, + icon: widget.onPreviousPageButtonChild ?? + const Icon(Icons.arrow_circle_left_outlined), color: widget.theme.barTheme.barColor, ), - Text(DateFormat.yMMMM().format( - widget.controller.browsingDate, - )), + Text( + DateFormat.yMMMM().format( + widget.controller.browsingDate, + ), + style: widget.theme.baseTheme.textStyle, + ), IconButton( - onPressed: goToNextPage, - icon: const Icon(Icons.arrow_circle_right_outlined), + onPressed: (widget.dateTimeConstraint.inMonthRange(nextDate)) + ? _goToNextPage + : null, + icon: widget.onNextPageButtonChild ?? + const Icon(Icons.arrow_circle_right_outlined), color: widget.theme.barTheme.barColor, ), ], @@ -78,9 +109,15 @@ class _OverlayDateTimeContentState extends State { ), Expanded( child: PageView( + physics: LockingPageScrollPhysics( + allowedNextPage: () => + widget.dateTimeConstraint.inMonthRange(nextDate), + allowedPreviousPage: () => + widget.dateTimeConstraint.inMonthRange(previousDate), + ), controller: _pageController, onPageChanged: (value) { - if (!usesButtons) movePage(1 - value); + if (!usesButtons) _movePage(1 - value); }, pageSnapping: true, scrollDirection: Axis.horizontal, @@ -88,34 +125,29 @@ class _OverlayDateTimeContentState extends State { children: [ DatePicker( controller: widget.controller, - onSelectDate: onSelectDate, + onSelectDate: _onSelectDate, theme: widget.theme, textStyle: widget.textStyle, - date: DateTime( - widget.controller.browsingDate.year, - widget.controller.browsingDate.month - 1, - 1, - ), + date: previousDate, + dateTimeConstraint: widget.dateTimeConstraint, showWeekDays: widget.showWeekDays, ), DatePicker( controller: widget.controller, - onSelectDate: onSelectDate, + onSelectDate: _onSelectDate, theme: widget.theme, textStyle: widget.textStyle, date: widget.controller.browsingDate, showWeekDays: widget.showWeekDays, + dateTimeConstraint: widget.dateTimeConstraint, ), DatePicker( controller: widget.controller, - onSelectDate: onSelectDate, + onSelectDate: _onSelectDate, theme: widget.theme, textStyle: widget.textStyle, - date: DateTime( - widget.controller.browsingDate.year, - widget.controller.browsingDate.month + 1, - 1, - ), + date: nextDate, + dateTimeConstraint: widget.dateTimeConstraint, showWeekDays: widget.showWeekDays, ), ], @@ -125,7 +157,7 @@ class _OverlayDateTimeContentState extends State { ); } - void goToNextPage() async { + void _goToNextPage() async { setState(() { usesButtons = true; }); @@ -133,10 +165,10 @@ class _OverlayDateTimeContentState extends State { duration: const Duration(milliseconds: 250), curve: Curves.easeInOut, ); - nextPage(); + _nextPage(); } - void goToPreviousPage() async { + void _goToPreviousPage() async { setState(() { usesButtons = true; }); @@ -144,41 +176,59 @@ class _OverlayDateTimeContentState extends State { duration: const Duration(milliseconds: 250), curve: Curves.easeInOut, ); - previousPage(); + _previousPage(); } - void nextPage() { + void _nextPage() { widget.onNextDate.call(); if (!mounted) return; + _pageController.jumpToPage(1); setState(() { usesButtons = false; }); - _pageController.jumpToPage(1); + _setDates(); } - void previousPage() { + void _previousPage() { widget.onPreviousDate.call(); if (!mounted) return; + _pageController.jumpToPage(1); setState(() { usesButtons = false; }); - _pageController.jumpToPage(1); + _setDates(); } - void movePage(int direction) { + void _movePage(int direction) { if (direction < 0) { - nextPage(); + _nextPage(); } else if (direction > 0) { - previousPage(); + _previousPage(); } } - void onSelectDate(DateTime date) { + void _onSelectDate(DateTime date) { if (!mounted) return; setState(() { widget.controller.selectedDate = date; - movePage(widget.controller.browsingDate.month - date.month); + _movePage(widget.controller.browsingDate.month - date.month); widget.controller.onTapDayCallBack?.call(date); }); } + + void _setDates() { + if (!mounted) return; + setState(() { + nextDate = DateTime( + widget.controller.browsingDate.year, + widget.controller.browsingDate.month + 1, + 1, + ); + previousDate = DateTime( + widget.controller.browsingDate.year, + widget.controller.browsingDate.month - 1, + 1, + ); + }); + } } diff --git a/lib/src/widgets/overlay_date_time_picker/pickable_date.dart b/lib/src/widgets/overlay_date_time_picker/pickable_date.dart index b2f40d8..e40a432 100644 --- a/lib/src/widgets/overlay_date_time_picker/pickable_date.dart +++ b/lib/src/widgets/overlay_date_time_picker/pickable_date.dart @@ -77,6 +77,6 @@ class PickableDate extends StatelessWidget { TextStyle? getStyle(bool isToday, bool isSelected) { if (isToday) return theme.highlightTheme.textStyle; if (isSelected) return theme.selectedTheme.textStyle; - return null; + return theme.baseTheme.textStyle; } } diff --git a/overlay_date_time_picker.gif b/overlay_date_time_picker.gif new file mode 100644 index 0000000..3726068 Binary files /dev/null and b/overlay_date_time_picker.gif differ diff --git a/pubspec.yaml b/pubspec.yaml index 54cf0e0..9a93c86 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_date_time_picker description: A new Flutter package project. -version: 0.0.1 +version: 2.0.0 homepage: https://iconica.nl/ environment: @@ -16,40 +16,3 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. -flutter: - - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages diff --git a/test/date_constraint_test.dart b/test/date_constraint_test.dart new file mode 100644 index 0000000..066c4e2 --- /dev/null +++ b/test/date_constraint_test.dart @@ -0,0 +1,219 @@ +// SPDX-FileCopyrightText: 2022 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +import 'package:flutter_date_time_picker/src/models/date_constraint.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Date Range', () { + group('inRange()', () { + test( + 'inRange() should return true when date is between min and max otherwise false', + () { + DateTimeConstraint range = DateTimeConstraint( + min: DateConstraint(date: DateTime(2022, 06, 01)), + max: DateConstraint(date: DateTime(2022, 07, 01)), + ); + expect(range.inRange(DateTime(2022, 06, 20)), true); + expect(range.inRange(DateTime(2022, 07, 20)), false); + expect(range.inRange(DateTime(2022, 05, 20)), false); + }); + + test( + 'inRange() should return true when date is between infinity and max otherwise false', + () { + DateTimeConstraint range = DateTimeConstraint( + min: DateConstraint.infinity, + max: DateConstraint(date: DateTime(2022, 07, 01)), + ); + expect(range.inRange(DateTime(2022, 06, 20)), true); + expect(range.inRange(DateTime(2022, 07, 20)), false); + expect(range.inRange(DateTime(2022, 05, 20)), true); + }); + + test( + 'inRange() should return true when date is between min and infinity otherwise false', + () { + DateTimeConstraint range = DateTimeConstraint( + min: DateConstraint(date: DateTime(2022, 06, 01)), + max: DateConstraint.infinity, + ); + expect(range.inRange(DateTime(2022, 06, 20)), true); + expect(range.inRange(DateTime(2022, 07, 20)), true); + expect(range.inRange(DateTime(2022, 05, 20)), false); + }); + + test('inRange() should return true when date is lower then max', () { + DateTimeConstraint range = DateTimeConstraint( + max: DateConstraint(date: DateTime(2022, 07, 01)), + ); + expect(range.inRange(DateTime(2022, 06, 20)), true); + expect(range.inRange(DateTime(2022, 07, 20)), false); + expect(range.inRange(DateTime(2022, 05, 20)), true); + }); + + test('inRange() should return true when date is higher then min', () { + DateTimeConstraint range = DateTimeConstraint( + min: DateConstraint(date: DateTime(2022, 06, 01)), + ); + expect(range.inRange(DateTime(2022, 06, 20)), true); + expect(range.inRange(DateTime(2022, 07, 20)), true); + expect(range.inRange(DateTime(2022, 05, 20)), false); + }); + + test('inRange() should return true when date is equal to max', () { + DateTimeConstraint range = DateTimeConstraint( + max: DateConstraint(date: DateTime(2022, 06, 01)), + ); + expect(range.inRange(DateTime(2022, 06, 01)), true); + expect(range.inRange(DateTime(2022, 05, 30)), true); + expect(range.inRange(DateTime(2022, 06, 02)), false); + }); + + test('inRange() should return true when date is equal to min', () { + DateTimeConstraint range = DateTimeConstraint( + min: DateConstraint(date: DateTime(2022, 06, 01)), + ); + expect(range.inRange(DateTime(2022, 06, 01)), true); + expect(range.inRange(DateTime(2022, 06, 02)), true); + expect(range.inRange(DateTime(2022, 05, 30)), false); + }); + }); + group('inYearRange()', () { + test( + 'inYearRange() should return true when year is between min and max otherwise false', + () { + DateTimeConstraint range = DateTimeConstraint( + min: DateConstraint(date: DateTime(2022, 1, 1)), + max: DateConstraint(date: DateTime(2024, 1, 1)), + ); + expect(range.inYearRange(DateTime(2023, 1, 1)), true); + expect(range.inYearRange(DateTime(2021, 1, 1)), false); + expect(range.inYearRange(DateTime(2025, 1, 1)), false); + }); + test( + 'inYearRange() should return true when year equals min or max otherwise false', + () { + DateTimeConstraint range = DateTimeConstraint( + min: DateConstraint(date: DateTime(2022, 1, 1)), + max: DateConstraint(date: DateTime(2023, 1, 1)), + ); + expect(range.inYearRange(DateTime(2022, 1, 1)), true); + expect(range.inYearRange(DateTime(2023, 1, 1)), true); + expect(range.inYearRange(DateTime(2021, 1, 1)), false); + expect(range.inYearRange(DateTime(2024, 1, 1)), false); + }); + test( + 'inYearRange() should return true when year is between min and infinity otherwise false', + () { + DateTimeConstraint range = DateTimeConstraint( + min: DateConstraint(date: DateTime(2022, 1, 1)), + ); + expect(range.inYearRange(DateTime(2023, 1, 1)), true); + expect(range.inYearRange(DateTime(2025, 1, 1)), true); + expect(range.inYearRange(DateTime(2021, 1, 1)), false); + }); + test( + 'inYearRange() should return true when year is between infinity and max otherwise false', + () { + DateTimeConstraint range = DateTimeConstraint( + max: DateConstraint(date: DateTime(2024, 1, 1)), + ); + expect(range.inYearRange(DateTime(2023, 1, 1)), true); + expect(range.inYearRange(DateTime(2021, 1, 1)), true); + expect(range.inYearRange(DateTime(2025, 1, 1)), false); + }); + }); + group('inMonthRange()', () { + test( + 'inMonthRange() should return true when year is between min and max otherwise false', + () { + DateTimeConstraint range = DateTimeConstraint( + min: DateConstraint(date: DateTime(2022, 5, 1)), + max: DateConstraint(date: DateTime(2022, 7, 1)), + ); + expect(range.inMonthRange(DateTime(2022, 6, 1)), true); + expect(range.inMonthRange(DateTime(2022, 3, 1)), false); + expect(range.inMonthRange(DateTime(2022, 8, 1)), false); + }); + test( + 'inMonthRange() should return true when year equals min or max otherwise false', + () { + DateTimeConstraint range = DateTimeConstraint( + min: DateConstraint(date: DateTime(2022, 5, 1)), + max: DateConstraint(date: DateTime(2022, 6, 1)), + ); + expect(range.inMonthRange(DateTime(2022, 5, 1)), true); + expect(range.inMonthRange(DateTime(2022, 6, 1)), true); + expect(range.inMonthRange(DateTime(2022, 4, 1)), false); + expect(range.inMonthRange(DateTime(2022, 7, 1)), false); + }); + test( + 'inMonthRange() should return true when year is between min and infinity otherwise false', + () { + DateTimeConstraint range = DateTimeConstraint( + min: DateConstraint(date: DateTime(2022, 5, 1)), + ); + expect(range.inMonthRange(DateTime(2022, 6, 1)), true); + expect(range.inMonthRange(DateTime(2022, 8, 1)), true); + expect(range.inMonthRange(DateTime(2022, 3, 1)), false); + }); + test( + 'inMonthRange() should return true when year is between infinity and max otherwise false', + () { + DateTimeConstraint range = DateTimeConstraint( + max: DateConstraint(date: DateTime(2022, 7, 1)), + ); + expect(range.inMonthRange(DateTime(2022, 6, 1)), true); + expect(range.inMonthRange(DateTime(2022, 3, 1)), true); + expect(range.inMonthRange(DateTime(2022, 8, 1)), false); + }); + }); + group('inDateRange()', () { + test( + 'inDateRange() should return true when year is between min and max otherwise false', + () { + DateTimeConstraint range = DateTimeConstraint( + min: DateConstraint(date: DateTime(2022, 1, 4)), + max: DateConstraint(date: DateTime(2022, 1, 6)), + ); + expect(range.inDateRange(DateTime(2022, 1, 5)), true); + expect(range.inDateRange(DateTime(2022, 1, 3)), false); + expect(range.inDateRange(DateTime(2022, 1, 7)), false); + }); + test( + 'inDateRange() should return true when year equals min or max otherwise false', + () { + DateTimeConstraint range = DateTimeConstraint( + min: DateConstraint(date: DateTime(2022, 1, 4)), + max: DateConstraint(date: DateTime(2022, 1, 5)), + ); + expect(range.inDateRange(DateTime(2022, 1, 4)), true); + expect(range.inDateRange(DateTime(2022, 1, 5)), true); + expect(range.inDateRange(DateTime(2022, 1, 3)), false); + expect(range.inDateRange(DateTime(2022, 1, 6)), false); + }); + test( + 'inDateRange() should return true when year is between min and infinity otherwise false', + () { + DateTimeConstraint range = DateTimeConstraint( + min: DateConstraint(date: DateTime(2022, 1, 4)), + ); + expect(range.inDateRange(DateTime(2022, 1, 5)), true); + expect(range.inDateRange(DateTime(2022, 1, 7)), true); + expect(range.inDateRange(DateTime(2022, 1, 3)), false); + }); + test( + 'inDateRange() should return true when year is between infinity and max otherwise false', + () { + DateTimeConstraint range = DateTimeConstraint( + max: DateConstraint(date: DateTime(2022, 1, 6)), + ); + expect(range.inDateRange(DateTime(2022, 1, 5)), true); + expect(range.inDateRange(DateTime(2022, 1, 3)), true); + expect(range.inDateRange(DateTime(2022, 1, 7)), false); + }); + }); + }); +}