diff --git a/np_datetime/lib/np_datetime.dart b/np_datetime/lib/np_datetime.dart index 85e69541..69bd00c9 100644 --- a/np_datetime/lib/np_datetime.dart +++ b/np_datetime/lib/np_datetime.dart @@ -1,5 +1,6 @@ library np_datetime; export 'src/date.dart'; +export 'src/date_range.dart'; export 'src/time_range.dart'; export 'src/util.dart'; diff --git a/np_datetime/lib/src/date.dart b/np_datetime/lib/src/date.dart index 9b0f7ec6..41ca2fde 100644 --- a/np_datetime/lib/src/date.dart +++ b/np_datetime/lib/src/date.dart @@ -76,8 +76,12 @@ extension DateExtension on Date { return day < other.day; } + bool operator <(Date other) => isBefore(other); + bool isBeforeOrAt(Date other) => !isAfter(other); + bool operator <=(Date other) => isBeforeOrAt(other); + bool isAfter(Date other) { if (year < other.year) { return false; @@ -92,7 +96,17 @@ extension DateExtension on Date { return day > other.day; } + bool operator >(Date other) => isAfter(other); + bool isAfterOrAt(Date other) => !isBefore(other); + + bool operator >=(Date other) => isAfterOrAt(other); + + /// Return the earlier date + Date min(Date other) => isBefore(other) ? this : other; + + /// Return the later date + Date max(Date other) => isAfter(other) ? this : other; } extension DateTimeDateExtension on DateTime { diff --git a/np_datetime/lib/src/date_range.dart b/np_datetime/lib/src/date_range.dart new file mode 100644 index 00000000..9498e644 --- /dev/null +++ b/np_datetime/lib/src/date_range.dart @@ -0,0 +1,93 @@ +import 'package:np_datetime/src/date.dart'; +import 'package:np_datetime/src/time_range.dart'; + +class DateRange { + const DateRange({ + this.from, + this.fromBound = TimeRangeBound.inclusive, + this.to, + this.toBound = TimeRangeBound.exclusive, + }); + + @override + String toString() { + return "${fromBound == TimeRangeBound.inclusive ? "[" : "("}" + "$from, $to" + "${toBound == TimeRangeBound.inclusive ? "]" : ")"}"; + } + + final Date? from; + final TimeRangeBound fromBound; + final Date? to; + final TimeRangeBound toBound; +} + +extension DateRangeExtension on DateRange { + /// Return if an arbitrary time [a] is inside this range + /// + /// The comparison is independent of whether the time is in UTC or in the + /// local time zone + bool contains(Date a) { + if (from != null) { + if (a.isBefore(from!)) { + return false; + } + if (fromBound == TimeRangeBound.exclusive) { + if (a == from) { + return false; + } + } + } + if (to != null) { + if (a.isAfter(to!)) { + return false; + } + if (toBound == TimeRangeBound.exclusive) { + if (a == to!) { + return false; + } + } + } + return true; + } + + bool isOverlapped(DateRange other) { + final aFrom = _inclusiveFrom; + final aTo = _inclusiveTo; + final bFrom = other._inclusiveFrom; + final bTo = other._inclusiveTo; + return (aFrom == null || bTo == null || aFrom <= bTo) && + (bFrom == null || aTo == null || bFrom <= aTo); + } + + /// Return the union of two DateRanges + /// + /// Warning: this function always assume the two DateRanges being overlapped, + /// you may want to call [isOverlapped] first + DateRange union(DateRange other) { + assert(isOverlapped(other)); + return DateRange( + from: from == null || other.from == null + ? null + : _inclusiveFrom!.min(other._inclusiveFrom!), + fromBound: TimeRangeBound.inclusive, + to: to == null || other.to == null + ? null + : _inclusiveTo!.max(other._inclusiveTo!), + toBound: TimeRangeBound.inclusive, + ); + } + + TimeRange toLocalTimeRange() => TimeRange( + from: from?.toLocalDateTime(), + fromBound: fromBound, + to: (toBound == TimeRangeBound.inclusive ? to?.add(day: 1) : to) + ?.toLocalDateTime(), + toBound: TimeRangeBound.exclusive, + ); + + Date? get _inclusiveFrom => + fromBound == TimeRangeBound.inclusive ? from : from?.add(day: -1); + Date? get _inclusiveTo => + toBound == TimeRangeBound.inclusive ? to : to?.add(day: -1); +}