From 3917fa126c3b5c3e77715a31859704cd3184c738 Mon Sep 17 00:00:00 2001 From: heng Date: Tue, 24 Jan 2023 13:22:30 +1100 Subject: [PATCH 1/3] Added Year, Month, Week feature ADDITION - Added Year class - Added Month class - Added Week class UPDATES - Updated date to support Year, Month, and Week - Updated extensions to support Year, Month, and Week - Added DateTime extension methods to mirror Date UNIT TESTS - added month_test.dart - added week_test.dart - added year_test.dart --- lib/date_time.dart | 3 + lib/src/date.dart | 95 ++++++++- lib/src/extensions.dart | 267 ++++++++++++++++++++++++ lib/src/month.dart | 306 +++++++++++++++++++++++++++ lib/src/time.dart | 26 +++ lib/src/week.dart | 316 ++++++++++++++++++++++++++++ lib/src/year.dart | 320 +++++++++++++++++++++++++++++ pubspec.lock | 9 +- pubspec.yaml | 7 +- test/month_test.dart | 445 ++++++++++++++++++++++++++++++++++++++++ test/week_test.dart | 345 +++++++++++++++++++++++++++++++ test/year_test.dart | 298 +++++++++++++++++++++++++++ 12 files changed, 2432 insertions(+), 5 deletions(-) create mode 100644 lib/src/month.dart create mode 100644 lib/src/week.dart create mode 100644 lib/src/year.dart create mode 100644 test/month_test.dart create mode 100644 test/week_test.dart create mode 100644 test/year_test.dart diff --git a/lib/date_time.dart b/lib/date_time.dart index ae01bc0..1239680 100644 --- a/lib/date_time.dart +++ b/lib/date_time.dart @@ -4,6 +4,9 @@ export 'src/date.dart'; export 'src/date_range.dart'; export 'src/date_time_error.dart'; export 'src/extensions.dart'; +export 'src/month.dart'; export 'src/overflowed_time.dart'; export 'src/time.dart'; export 'src/time_range.dart'; +export 'src/week.dart'; +export 'src/year.dart'; diff --git a/lib/src/date.dart b/lib/src/date.dart index 3f23cab..6802c29 100644 --- a/lib/src/date.dart +++ b/lib/src/date.dart @@ -1,5 +1,8 @@ import 'package:clock/clock.dart'; import 'package:date_time/src/extensions.dart'; +import 'package:date_time/src/month.dart'; +import 'package:date_time/src/week.dart'; +import 'package:date_time/src/year.dart'; import 'package:intl/intl.dart'; import 'package:quiver/core.dart'; @@ -106,6 +109,11 @@ class Date { DateTime get asUtcDateTime => DateTime.utc(year, month, day); + ///////////////////////////////////// OPERATIONS + + /// Return the difference in days + int difference(Date other) => asDateTime.difference(other.asDateTime).inDays; + /// Add a [Duration] to this date Date add(Duration duration) { final t = asUtcDateTime.add(duration); @@ -142,7 +150,9 @@ class Date { /// Add a certain amount of quarters to this date Date subQuarters(int amount) => addQuarters(-amount); - // DateTime subWeeks(amount) + /// Subtracts an amount of weeks from this [Date] + Date subWeeks(int amount) => subDays(amount * 7); + /// Subtracts an amount of years from this [Date] Date subYears(int amount) => addYears(-amount); @@ -254,6 +264,29 @@ class Date { return woy; } + /// Returns the year of this [Date]'s week + int get yearOfWeek => addDays(1).yearOfISOWeek; + + /// Returns the year for this [Date] ISO week + int get yearOfISOWeek { + final woy = (_ordinalDate - weekday + 10) ~/ 7; + + // If the week number equals zero, it means that the given date belongs to the preceding (week-based) year. + if (woy == 0) { + // The 28th of December is always in the last week of the year + return year - 1; + } + + // If the week number equals 53, one must check that the date is not actually in week 1 of the following year + if (woy == 53 && + DateTime(year).weekday != DateTime.thursday && + DateTime(year, 12, 31).weekday != DateTime.thursday) { + return year + 1; + } + + return year; + } + int get _ordinalDate { const offsets = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; return offsets[month - 1] + day + (isLeapYear && month > 2 ? 1 : 0); @@ -296,6 +329,22 @@ class Date { /// Return true if this date [isBefore] [Date.now] bool get isPast => isBefore(Date.today()); + /// Return the start of day in [DateTime] + DateTime get startOfDay => asDateTime.startOfDay; + + /// Return the end of day in [DateTime] + DateTime get endOfDay => asDateTime.endOfDay; + + ///////////////////////////////////// COMPARISON + + int compareTo(Date other) => asDateTime.compareTo(other.asDateTime); + + /// Check if the date is within [start] and [end] + bool isWithinRange(Date start, Date end) => this >= start && this <= end; + + /// Check if the date is out side [start] and [end] + bool isOutsideRange(Date start, Date end) => !isWithinRange(start, end); + /// Check if this date is in the same day than other bool isSameDay(Date other) => this == other; @@ -342,12 +391,16 @@ class Date { bool get isWeekend => weekday == DateTime.saturday || weekday == DateTime.sunday; + /// Greater than bool operator >(Date other) => isAfter(other); + /// Greater than or equal bool operator >=(Date other) => isSameOrAfter(other); + /// Less than bool operator <(Date other) => isBefore(other); + /// Less than or equal bool operator <=(Date other) => isSameOrBefore(other); @override @@ -374,4 +427,44 @@ class Date { day: day ?? this.day, ); } + + /// Convenient method to [DateFormat] + String format([String pattern = 'yMd', String? locale]) { + return DateFormat(pattern, locale).format(asDateTime); + } + + /// Return enum value of day of week + DAY get getDAY => DAY.fromNumber(weekday); + + /// Return a non iso [Week] object for this date + Week get week => Week.week(this); + + /// Return the iso [Week] object for this date + Week get isoWeek => Week.isoWeek(this); + + /// Return the [Month] object for this date. + Month get getMonth => Month.fromDate(this); + + /// Get the enum value of month + MONTH get getMONTH => MONTH.fromNumber(month); + + /// Return the [Year] object for this date. + Year get getYear => Year.fromDate(this); + + /// Const value to signify the start of epoch that can be used as a default value + static const Date epoch = Date(year: 1970); + + /// Const value to signify the start of time that can be used as a default value + static const Date startOfTime = Date(year: 1); + + /// Const value to signify the end of time that can be used as a default value + static const Date endOfTime = Date(year: 9999, month: 12, day: 31); + + ///////////////////////////////////// KEY + + /// Convert [Date] to a unique id + String get key => format('yyyy-MM-dd'); + + /// Convert a unique id to [Date] + static Date? fromKey(String key) => tryParse(key, format: 'yyyy-MM-dd'); } diff --git a/lib/src/extensions.dart b/lib/src/extensions.dart index 75270df..d46e1f1 100644 --- a/lib/src/extensions.dart +++ b/lib/src/extensions.dart @@ -1,5 +1,8 @@ import 'package:date_time/src/date.dart'; +import 'package:date_time/src/month.dart'; import 'package:date_time/src/time.dart'; +import 'package:date_time/src/week.dart'; +import 'package:date_time/src/year.dart'; import 'package:intl/intl.dart'; /// Extensions on [DateTime] @@ -9,6 +12,270 @@ extension DateTimeExtensions on DateTime { /// Create a [Time] instance from a [DateTime]. Time get time => Time(hour: hour, minute: minute, second: second); + + /// Create a [Week] instance from a [DateTime] + Week get week { + return Week.week(date); + } + + /// Create a ISO [Week] instance from [DateTime] + Week get isoWeek { + return Week.isoWeek(date); + } + + /// Create a [Month] instance from a [DateTime] + Month get getMonth { + return Month.fromDateTime(this); + } + + /// Create a [Year] instance from a [DateTime] + Year get getYear { + return Year.fromDateTime(this); + } + + /// Get the number of weeks in an ISO week-numbering year + int get getISOWeeksInYear { + return DateTime(year, 12, 28).getISOWeek; + } + + /// Get the ISO week index + int get getISOWeek { + final woy = (_ordinalDate - weekday + 10) ~/ 7; + + // If the week number equals zero, it means that the given date belongs to the preceding (week-based) year. + if (woy == 0) { + // The 28th of December is always in the last week of the year + return DateTime(year - 1, 12, 28).getISOWeek; + } + + // If the week number equals 53, one must check that the date is not actually in week 1 of the following year + if (woy == 53 && + DateTime(year).weekday != DateTime.thursday && + DateTime(year, 12, 31).weekday != DateTime.thursday) { + return 1; + } + + return woy; + } + + /// Returns the year for this [DateTime] ISO week + int get yearOfISOWeek { + final woy = (_ordinalDate - weekday + 10) ~/ 7; + + // If the week number equals zero, it means that the given date belongs to the preceding (week-based) year. + if (woy == 0) { + // The 28th of December is always in the last week of the year + return year - 1; + } + + // If the week number equals 53, one must check that the date is not actually in week 1 of the following year + if (woy == 53 && + DateTime(year).weekday != DateTime.thursday && + DateTime(year, 12, 31).weekday != DateTime.thursday) { + return year + 1; + } + + return woy; + } + + int get _ordinalDate { + const offsets = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; + return offsets[month - 1] + day + (isLeapYear && month > 2 ? 1 : 0); + } + + ///////////////////////////////////// COMPARISON + + /// Is the given date in the leap year? + bool get isLeapYear => year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + + /// Check if this date is in the same year than other + bool isSameYear(DateTime other) => year == other.year; + + /// Check if the date is within [start] and [end] + bool isWithinRange(DateTime start, DateTime end) => + this >= start && this <= end; + + /// Check if the date is out side [start] and [end] + bool isOutsideRange(DateTime start, DateTime end) => + !isWithinRange(start, end); + + /// Check if a date is [equals] to other + bool isEqual(DateTime other) => this == other; + + /// Return true if other [isEqual] or [isAfter] to this date + bool isSameOrAfter(DateTime other) => this == other || isAfter(other); + + /// Return true if other [isEqual] or [isBefore] to this date + bool isSameOrBefore(DateTime other) => this == other || isBefore(other); + + /// Greater than + bool operator >(DateTime other) => isAfter(other); + + /// Greater than or equal + bool operator >=(DateTime other) => isSameOrAfter(other); + + /// Less than + bool operator <(DateTime other) => isBefore(other); + + /// Less than or equal + bool operator <=(DateTime other) => isSameOrBefore(other); + + ///////////////////////////////////// OPERATION + + /// Adds this DateTime and Duration and returns the sum as a new DateTime object. + DateTime operator +(Duration duration) => add(duration); + + /// Subtracts the Duration from this DateTime returns the difference as a new DateTime object. + DateTime operator -(Duration duration) => subtract(duration); + + /// Add a certain amount of days to this date + DateTime addDays(int amount, {bool ignoreDaylightSavings = false}) => + ignoreDaylightSavings + ? DateTime(year, month, day + amount, hour, minute, second, + millisecond, microsecond) + : add(Duration(days: amount)); + + /// Add a certain amount of hours to this date + DateTime addHours(int amount, {bool ignoreDaylightSavings = false}) => + ignoreDaylightSavings + ? DateTime(year, month, day, hour + amount, minute, second, + millisecond, microsecond) + : add(Duration(hours: amount)); + + /// Add a certain amount of minutes to this date + DateTime addMinutes(int amount, {bool ignoreDaylightSavings = false}) => + ignoreDaylightSavings + ? DateTime(year, month, day, hour, minute + amount, second, + millisecond, microsecond) + : add(Duration(minutes: amount)); + + /// Add a certain amount of milliseconds to this date + DateTime addMilliseconds(int amount) => add(Duration(milliseconds: amount)); + + /// Add a certain amount of microseconds to this date + DateTime addMicroseconds(int amount) => add(Duration(microseconds: amount)); + + /// Add a certain amount of seconds to this date + DateTime addSeconds(int amount, {bool ignoreDaylightSavings = false}) => + ignoreDaylightSavings + ? DateTime(year, month, day, hour, minute, second + amount, + millisecond, microsecond) + : add(Duration(seconds: amount)); + + /// Add a certain amount of months to this date + DateTime addMonths(int amount) => copyWith(month: month + amount); + + /// Add a certain amount of quarters to this date + DateTime addQuarters(int amount) => addMonths(amount * 3); + + /// Add a certain amount of weeks to this date + DateTime addWeeks(int amount) => addDays(amount * 7); + + /// Add a certain amount of years to this date + DateTime addYears(int amount) => copyWith(year: year + amount); + + /// Subtract a certain amount of days from this date + DateTime subDays(int amount, {bool ignoreDaylightSavings = false}) => + ignoreDaylightSavings + ? DateTime(year, month, day - amount, hour, minute, second, + millisecond, microsecond) + : subtract(Duration(days: amount)); + + /// Subtract a certain amount of hours from this date + DateTime subHours(int amount, {bool ignoreDaylightSavings = false}) => + ignoreDaylightSavings + ? DateTime(year, month, day, hour - amount, minute, second, + millisecond, microsecond) + : subtract(Duration(hours: amount)); + + /// Subtract a certain amount of minutes from this date + DateTime subMinutes(int amount, {bool ignoreDaylightSavings = false}) => + ignoreDaylightSavings + ? DateTime(year, month, day, hour, minute - amount, second, + millisecond, microsecond) + : subtract(Duration(minutes: amount)); + + /// Subtract a certain amount of milliseconds to this date + DateTime subMilliseconds(int amount) => + subtract(Duration(milliseconds: amount)); + + /// Subtract a certain amount of microseconds from this date + DateTime subMicroseconds(int amount) => + subtract(Duration(microseconds: amount)); + + /// Subtract a certain amount of seconds from this date + DateTime subSeconds(int amount, {bool ignoreDaylightSavings = false}) => + ignoreDaylightSavings + ? DateTime(year, month, day, hour, minute, second - amount, + millisecond, microsecond) + : subtract(Duration(seconds: amount)); + + /// Subtract a certain amount of months from this date + DateTime subMonths(int amount) => copyWith(month: month - amount); + + /// Subtract a certain amount of quarters from this date + DateTime subQuarters(int amount) => addMonths(-amount * 3); + + /// Subtract a certain amount of weeks from this date + DateTime subWeeks(int amount) => addDays(-amount * 7); + + /// Subtract a certain amount of years from this date + DateTime subYears(int amount) => copyWith(year: year - amount); + + ///////////////////////////////////// KEY + + /// Convert [DateTime] to a unique id + String get key => toIso8601String(); + + /// Convert a unique id to [DateTime] + static DateTime? fromKey(String key) => DateTime.tryParse(key); + + /// Short-hand to DateFormat + String format([String? pattern, String? locale]) { + return DateFormat(pattern, locale).format(this); + } + + /// Get a [DateTime] representing start of Day of this [DateTime] in local time. + DateTime get startOfDay => + copyWith(hour: 0, minute: 0, second: 0, millisecond: 0, microsecond: 0); + + /// Return the end of a day for this date. The result will be in the local timezone. + DateTime get endOfDay => copyWith( + hour: 23, minute: 59, second: 59, millisecond: 999, microsecond: 999); + + /// Convenient copy with method + DateTime copyWith({ + int? year, + int? month, + int? day, + int? hour, + int? minute, + int? second, + int? millisecond, + int? microsecond, + }) { + return isUtc + ? DateTime.utc( + year ?? this.year, + month ?? this.month, + day ?? this.day, + hour ?? this.hour, + minute ?? this.minute, + second ?? this.second, + millisecond ?? this.millisecond, + microsecond ?? this.microsecond, + ) + : DateTime( + year ?? this.year, + month ?? this.month, + day ?? this.day, + hour ?? this.hour, + minute ?? this.minute, + second ?? this.second, + millisecond ?? this.millisecond, + microsecond ?? this.microsecond, + ); + } } /// Extensions on [DateFormat] diff --git a/lib/src/month.dart b/lib/src/month.dart new file mode 100644 index 0000000..26bb13d --- /dev/null +++ b/lib/src/month.dart @@ -0,0 +1,306 @@ +import 'package:date_time/src/date.dart'; +import 'package:date_time/src/extensions.dart'; +import 'package:intl/intl.dart'; +import 'package:quiver/core.dart'; + +/// An enum representation of month +enum MONTH { + /// january + january(number: 1), + + /// february + february(number: 2), + + /// march + march(number: 3), + + /// april + april(number: 4), + + /// may + may(number: 5), + + /// june + june(number: 6), + + /// july + july(number: 7), + + /// august + august(number: 8), + + /// september + september(number: 9), + + /// october + october(number: 10), + + /// november + november(number: 11), + + /// december + december(number: 12); + + /// the month number + final int number; + + /// Constructor + const MONTH({required this.number}); + + /// Convert a number to the enum value + static MONTH fromNumber(int number) { + try { + return values.firstWhere((element) => element.number == number); + } catch (e) { + return january; + } + } + + /// Returns the month title based on locale + String title([String? locale]) { + final datetime = Date.epoch.setMonth(number).asDateTime; + return DateFormat('MMMM', locale).format(datetime); + } + + /// Return the abbreviation + String shortTitle([String? locale]) { + final datetime = Date.epoch.setMonth(number).asDateTime; + return DateFormat('MMM', locale).format(datetime); + } +} + +/// A convenient class to work with only IsoWeek +class Month { + /// A number that represent year + final int year; + + /// A number that represent month + final int month; + + /// Constructs the [Month] object + const Month({required this.year, this.month = 1}) + : assert(month >= 1), + assert(month <= 12); + + /// Const value for the first month of epoch that can be used as a default value + static const Month epoch = Month(year: 1970); + + /// Const value for the first month in year 1 that can be used as a default value + static const Month startOfTime = Month(year: 1); + + /// Const value to signify the end of time that can be used as a default value + static const Month endOfTime = Month(year: 9999, month: 12); + + ///////////////////////////////////// FACTORIES + + /// Create Month from DateTime + factory Month.fromDateTime(DateTime datetime) { + return Month(year: datetime.year, month: datetime.month); + } + + /// Create Month from Date + factory Month.fromDate(Date date) { + return Month(year: date.year, month: date.month); + } + + /// Get the current Month + factory Month.now() => Date.now().getMonth; + + /// Same as [Month.now] + factory Month.thisMonth() => Month.now(); + + /// Get last month + factory Month.lastMonth() => Month.now().subWeeks(1); + + /// Get next month + factory Month.nextMonth() => Month.now().addWeeks(1); + + /// Return the [Date] at the start of month + Date get startOfMonth => Date(year: year, month: month); + + /// Return the [Date] at the end of month + Date get endOfMonth => startOfMonth.endOfMonth; + + /// Get the enum value of month + MONTH get getMONTH => MONTH.fromNumber(month); + + /// Returns the dates of the month + Map get dates { + final count = endOfMonth.day; + final list = List.generate(count, (index) => startOfMonth.addDays(index)); + return {for (var date in list) date.day: date}; + } + + /// Convenient operator to get the date + Date operator [](int day) { + if (day < 1 || day > endOfMonth.day) + throw RangeError.range(day, 1, endOfMonth.day); + return startOfMonth.addDays(day - 1); + } + + ///////////////////////////////////// SETTERS + + /// Create a new copy with updated values + Month copyWith({int? year, int? month}) { + return Month(year: year ?? this.year, month: month ?? this.month); + } + + /// Set the year + Month setYear(int year) => copyWith(year: year); + + /// Set the month by number + Month setMonth(int month) => copyWith(month: month); + + /// Set the month by enum value + Month setMONTH(MONTH month) => setMonth(month.number); + + ///////////////////////////////////// OPERATIONS + + /// Add a [Duration] to this month + Month add(Duration duration) => endOfMonth.asDateTime.add(duration).getMonth; + + /// Subtract a [Duration] to this month + Month subtract(Duration duration) => startOfMonth.subtract(duration).getMonth; + + /// Add weeks + Month addDays(int amount) => endOfMonth.addDays(amount.abs()).getMonth; + + /// Add a certain amount of weeks to this week + Month addWeeks(int amount) => endOfMonth.addWeeks(amount.abs()).getMonth; + + /// Add a certain amount of months to this week + Month addMonths(int amount) => startOfMonth.addMonths(amount.abs()).getMonth; + + /// Add a certain amount of quarters to this week + Month addQuarters(int amount) => + startOfMonth.addQuarters(amount.abs()).getMonth; + + /// Add a certain amount of years to this week + Month addYears(int amount) => startOfMonth.addYears(amount.abs()).getMonth; + + /// Subtracts an amount of days from this week + Month subDays(int amount) => startOfMonth.subDays(amount.abs()).getMonth; + + /// Subtracts an amount of months from this week + Month subWeeks(int amount) => startOfMonth.addWeeks(-amount.abs()).getMonth; + + /// Subtracts an amount of months from this week + Month subMonths(int amount) => startOfMonth.subMonths(amount.abs()).getMonth; + + /// Add a certain amount of quarters to this week + Month subQuarters(int amount) => + startOfMonth.subQuarters(amount.abs()).getMonth; + + /// Subtracts an amount of years from this [Date] + Month subYears(int amount) => startOfMonth.subYears(amount.abs()).getMonth; + + ///////////////////////////////////// COMPARISON + + /// Return true if [month] is after [other], false otherwise. + bool isAfter(Month other) => startOfMonth.isAfter(other.startOfMonth); + + /// Return true if [month] is before [other], false otherwise. + bool isBefore(Month other) => startOfMonth.isBefore(other.startOfMonth); + + /// Return true if other [isEqual] or [isAfter] to this date + bool isSameOrAfter(Month other) => + startOfMonth.isSameOrAfter(other.startOfMonth); + + /// Return true if other [isEqual] or [isBefore] to this date + bool isSameOrBefore(Month other) => + startOfMonth.isSameOrBefore(other.startOfMonth); + + /// Return true if month is this month + bool get isThisMonth => this == Month.thisMonth(); + + /// Return true if month is last month + bool get isLastMonth => this == Month.lastMonth(); + + /// Return true if month is next month + bool get isNextMonth => this == Month.nextMonth(); + + /// Greater than operator + bool operator >(Month other) => isAfter(other); + + /// Greater or equals to operator + bool operator >=(Month other) => isSameOrAfter(other); + + /// Less than operator + bool operator <(Month other) => isBefore(other); + + /// Less than or equals to operator + bool operator <=(Month other) => isSameOrBefore(other); + + ///////////////////////////////////// OBJECT OVERRIDES + + @override + bool operator ==(Object other) => + other is Month && other.year == year && other.month == month; + + @override + int get hashCode => hash2( + year.hashCode, + month.hashCode, + ); + + @override + String toString() => toKey(); + + ///////////////////////////////////// KEY + + /// Convert [Month] to a unique id + String toKey() => format('yyyy-MM'); + + /// Convert a unique id to [Month] + static Month? fromKey(String key) => tryParse(key, format: 'yyyy-MM'); + + ///////////////////////////////////// FORMAT + + /// Constructs a new [DateTime] instance based on [dateStr]. + /// + /// Given user input, attempt to parse the [dateStr] into the anticipated + /// format, treating it as being in the local timezone. + /// + /// If [dateStr] does not match our format, throws a [FormatException]. + /// This will accept dates whose values are not strictly valid, or strings + /// with additional characters (including whitespace) after a valid date. For + /// stricter parsing, use [dateStr]. + static Month parse( + String dateStr, { + String? format, + bool utc = false, + }) { + if (format == null || format == '') { + return DateTime.parse(dateStr).getMonth; + } + + final formatter = DateFormat(format); + final dateTimeFromStr = formatter.parse(dateStr, utc); + return dateTimeFromStr.getMonth; + } + + /// Constructs a new [DateTime] instance based on [dateStr]. + /// + /// Works like [parse] except that this function returns `null` + /// where [parse] would throw a [FormatException]. + static Month? tryParse( + String dateStr, { + String? format, + bool utc = false, + }) { + try { + return parse( + dateStr, + format: format, + utc: utc, + ); + } on FormatException { + return null; + } + } + + /// Short-hand to DateFormat + String format([String? pattern, String? locale]) { + return DateFormat(pattern, locale).format(startOfMonth.asDateTime); + } +} diff --git a/lib/src/time.dart b/lib/src/time.dart index 0de0ef8..3c8c47c 100644 --- a/lib/src/time.dart +++ b/lib/src/time.dart @@ -83,6 +83,14 @@ class Time { return Time(hour: h, minute: m, second: s); } + /// Convert to [DateTime] + DateTime get asDateTime => DateTime(1970).copyWith( + hour: hour, + minute: minute, + second: second, + millisecond: millisecond, + ); + /// Represents hours final int hour; @@ -278,6 +286,16 @@ class Time { String toStringWithSeparator(String separator) => toString().replaceAll(Time.defaultSeparator, separator); + ///////////////////////////////////// KEY + + /// Convert [Time] to a unique id (using dash, most db system won't accept : as id) + String get key => format().replaceAll(':', '-'); + + /// Convert a unique id to [Time] + static Time? fromKey(String key) => fromStr(key.replaceAll('-', ':')); + + ///////////////////////////////////// COMPARISON + /// Is the `Time` after another [time] bool isAfter(Time time, {bool orSame = false}) { return orSame ? this >= time : this > time; @@ -328,6 +346,14 @@ class Time { return Time.fromSeconds(inSeconds + other.inSeconds); } + /// Comparator function used for sorting purpose + int compareTo(Time other) => asDateTime.compareTo(other.asDateTime); + + /// Return true if time is within [start] and [end] + bool isWithinRange(Time start, Time end) => this >= start && this <= end; + + ///////////////////////////////////// OPERATIONS + Time addDuration(Duration dur) { final durationInSeconds = dur.inSeconds; return Time.fromSeconds(inSeconds + durationInSeconds); diff --git a/lib/src/week.dart b/lib/src/week.dart new file mode 100644 index 0000000..85509d1 --- /dev/null +++ b/lib/src/week.dart @@ -0,0 +1,316 @@ +import 'package:date_time/src/date.dart'; +import 'package:date_time/src/extensions.dart'; +import 'package:intl/intl.dart'; + +/// Enum representation of day of week +enum DAY { + /// Monday + monday(number: 1), + + /// Tuesday + tuesday(number: 2), + + /// Wednesday + wednesday(number: 3), + + /// Thursday + thursday(number: 4), + + /// Friday + friday(number: 5), + + /// Saturday + saturday(number: 6), + + /// Sunday + sunday(number: 7); + + /// ISO number of the day of week + final int number; + + /// Constructor + const DAY({required this.number}); + + /// Returns the [DAY] enum value based on day of week iso number + static DAY fromNumber(int number) { + return values.firstWhere((element) => element.number == number); + } + + /// Returns the day of week title based on locale + String title([String? locale]) { + final dayOfWeek = + Week.firstISOWeekOfEpoch.startOfWeek.addDays(number - 1).asDateTime; + return DateFormat('EEEE', locale).format(dayOfWeek); + } + + /// Return the abbreviation of day - useful for calendar views. + String shortTitle([String? locale]) { + final dayOfWeek = + Week.firstISOWeekOfEpoch.startOfWeek.addDays(number - 1).asDateTime; + return DateFormat('E', locale).format(dayOfWeek); + } + + /// Return the first letter of day - useful for alarm or smaller views. + String acronym([String? locale]) { + return shortTitle(locale)[0]; + } +} + +/// A convenient class to work with only Week +class Week { + /// A number representing the year this week belongs to + // final int year; + + /// A number representing the week (currently only iso week is supported) + // final int week; + + /// The start of the week, must be sunday or monday + final Date startOfWeek; + + /// A flag to determine if this week should be treated as iso week (starts monday). + /// By default, iso week is used. To change this, pass a false value to the constructor. + // final bool iso; + + /// Constructs the week + const Week({required this.startOfWeek}); + + /// Const value for the first week of epoch that can be used as a default value + static Week firstISOWeekOfEpoch = Week.startWeek(Date.epoch.startOfISOWeek); + + /// Const value for the first week of year 1 that can be used as a default value + static Week firstISOWeekOfTime = + Week.startWeek(Date.startOfTime.startOfISOWeek); + + /// Const value to signify the end of time that can be used as a default value + static Week lastISOWeekOfTime = Week.startWeek(Date.endOfTime.startOfISOWeek); + + ///////////////////////////////////// FACTORIES + + /// Create Week from start [Date] + factory Week.startWeek(Date date) { + return Week(startOfWeek: date); + } + + /// Create Week from DateTime + factory Week.startDateTime(DateTime datetime) { + return Week.startWeek(datetime.date); + } + + /// Create a sunday Week (week starting on Sunday) from [Date] + factory Week.week(Date date) { + return Week.startWeek(date.startOfWeek); + } + + /// Create the ISO week (week starting Monday) from [date] + factory Week.isoWeek(Date date) { + return Week.startWeek(date.startOfISOWeek); + } + + /// When [iso] is true of absent, will return the current week starting on Monday + /// When false, will return the current week starting on Sunday + factory Week.now({bool iso = true}) => + iso ? Week.isoWeek(Date.now()) : Week.week(Date.now()); + + /// Same as [Week.now] + factory Week.thisWeek({bool iso = true}) => Week.now(iso: iso); + + /// Get last week + factory Week.lastWeek({bool iso = true}) => Week.now(iso: iso).subWeeks(1); + + /// Get next week + factory Week.nextWeek({bool iso = true}) => Week.now(iso: iso).addWeeks(1); + + ///////////////////////////////////// GETTERS + + /// Returns the next week relative to this week + Week get next => addWeeks(1); + + /// Returns the previous week relative to this week + Week get previous => subWeeks(1); + + /// Get the week index of week + int get weekIndex => startOfWeek.getWeek; + + /// Get the week index of ISO week + int get isoWeekIndex => startOfWeek.getISOWeek; + + /// Get the date for the end of week - this is always a saturday or sunday + Date get endOfWeek => startOfWeek.addDays(6); + + /// Returns [true] if week starts monday + bool get isISOWeek => startOfWeek.getDAY == DAY.monday; + + /// Returns the [startOfWeek] as [DAY] + DAY get startDay => startOfWeek.getDAY; + + /// Returns [endOfWeek] as [DAY]; + DAY get endDay => endOfWeek.getDAY; + + /// Returns the dates of the week + Map get dates { + final list = List.generate(7, (index) => startOfWeek.addDays(index)); + return {for (var date in list) date.getDAY: date}; + } + + /// Returns the monday [Date] of the week + Date get monday => dates[DAY.monday]!; + + /// Returns the tuesday [Date] of the week + Date get tuesday => dates[DAY.tuesday]!; + + /// Returns the wednesday [Date] of the week + Date get wednesday => dates[DAY.wednesday]!; + + /// Returns the thursday [Date] of the week + Date get thursday => dates[DAY.thursday]!; + + /// Returns the friday [Date] of the week + Date get friday => dates[DAY.friday]!; + + /// Returns the saturday [Date] of the week + Date get saturday => dates[DAY.saturday]!; + + /// Returns the sunday [Date] of the week + Date get sunday => dates[DAY.sunday]!; + + /// Convenient operator to get the date of a given week day + Date operator [](DAY day) => dates[day]!; + + ///////////////////////////////////// SETTERS + + /// Create a new copy with updated values + Week copyWith({Date? startOfWeek}) { + return Week(startOfWeek: startOfWeek ?? this.startOfWeek); + } + + /// Set the year + Week setStartOfWeek(Date startOfWeek) => copyWith(startOfWeek: startOfWeek); + + ///////////////////////////////////// OPERATIONS + + /// Return the difference in weeks + int difference(Week other) => startOfWeek.difference(other.startOfWeek); + + /// Add a certain amount of weeks to this week + Week addWeeks(int amount) => + Week.startWeek(startOfWeek.addWeeks(amount.abs())); + + /// Add a certain amount of years to this week + Week addYears(int amount) => addWeeks(52); + + /// Subtracts an amount of months from this week + Week subWeeks(int amount) => + Week.startWeek(startOfWeek.subWeeks(amount.abs())); + + /// Subtracts an amount of years from this [Date] + Week subYears(int amount) => subWeeks(52); + + ///////////////////////////////////// COMPARISON + + /// Return true if [startOfWeek] is after [other], false otherwise. + bool isAfter(Week other) => startOfWeek.isAfter(other.startOfWeek); + + /// Return true if [startOfWeek] is before [other], false otherwise. + bool isBefore(Week other) => startOfWeek.isBefore(other.startOfWeek); + + /// Return true if other [isEqual] or [isAfter] to this date + bool isSameOrAfter(Week other) => + startOfWeek.isSameOrAfter(other.startOfWeek); + + /// Return true if other [isEqual] or [isBefore] to this date + bool isSameOrBefore(Week other) => + startOfWeek.isSameOrBefore(other.startOfWeek); + + /// Return true if week is this week + bool get isThisWeek => this == Week.thisWeek(); + + /// Return true if week is last week + bool get isLastWeek => this == Week.lastWeek(); + + /// Return true if week is next week + bool get isNextWeek => this == Week.nextWeek(); + + /// Greater than operator + bool operator >(Week other) => isAfter(other); + + /// Greater or equals to operator + bool operator >=(Week other) => isSameOrAfter(other); + + /// Less than operator + bool operator <(Week other) => isBefore(other); + + /// Less than or equals to operator + bool operator <=(Week other) => isSameOrBefore(other); + + ///////////////////////////////////// OBJECT OVERRIDES + + @override + bool operator ==(Object other) => + other is Week && other.startOfWeek == startOfWeek; + + @override + int get hashCode => startOfWeek.hashCode; + + @override + String toString() => toKey(); + + ///////////////////////////////////// KEY + + String toKey() => startOfWeek.format('yyyy-MM-dd'); + + /// Parse from string in the format yyyy-ww - useful for unique week id + static Week? fromKey(String key) { + final date = Date.tryParse(key, format: 'yyyy-MM-dd'); + return date == null ? null : Week(startOfWeek: date); + } + + ///////////////////////////////////// FORMAT + + /// Constructs a new [DateTime] instance based on [dateStr]. + /// + /// Given user input, attempt to parse the [dateStr] into the anticipated + /// format, treating it as being in the local timezone. + /// + /// If [dateStr] does not match our format, throws a [FormatException]. + /// This will accept dates whose values are not strictly valid, or strings + /// with additional characters (including whitespace) after a valid date. For + /// stricter parsing, use [dateStr]. + static Week parse( + String dateStr, { + String? format, + bool utc = false, + bool iso = true, + }) { + if (format == null || format == '') { + return iso + ? DateTime.parse(dateStr).isoWeek + : DateTime.parse(dateStr).week; + } + + final formatter = DateFormat(format); + final date = formatter.parse(dateStr, utc).date; + return iso ? date.isoWeek : date.week; + } + + /// Constructs a new [DateTime] instance based on [dateStr]. + /// + /// Works like [parse] except that this function returns `null` + /// where [parse] would throw a [FormatException]. + static Week? tryParse( + String dateStr, { + String? format, + bool utc = false, + bool iso = true, + }) { + try { + return parse( + dateStr, + format: format, + utc: utc, + iso: iso, + ); + } on FormatException { + return null; + } + } +} diff --git a/lib/src/year.dart b/lib/src/year.dart new file mode 100644 index 0000000..ba85d91 --- /dev/null +++ b/lib/src/year.dart @@ -0,0 +1,320 @@ +import 'package:date_time/src/date.dart'; +import 'package:date_time/src/extensions.dart'; +import 'package:date_time/src/month.dart'; +import 'package:date_time/src/week.dart'; +import 'package:intl/intl.dart'; + +/// A convenient class to work with only iso year +class Year { + /// A number that represent year + final int year; + + /// Constructs the [Year] object + const Year(this.year); + + ///////////////////////////////////// CONST + + /// Const value for the year of epoch that can be used as a default value + static const Year epoch = Year(1970); + + /// Const value for year 1 that can be used as a default value + static const Year startOfTime = Year(1); + + /// Const value to signify the end of time that can be used as a default value + static const Year endOfTime = Year(9999); + + ///////////////////////////////////// FACTORIES + + /// Create Year from DateTime + factory Year.fromDateTime(DateTime datetime) { + return Year(datetime.year); + } + + /// Create Year from Date + factory Year.fromDate(Date date) { + return Year(date.year); + } + + /// Get the current year + factory Year.now() => Date.now().getYear; + + /// Same as [Year.now] + factory Year.thisYear() => Year.now(); + + /// Get last year + factory Year.lastYear() => Year.now().subYears(1); + + /// Get next year + factory Year.nextYear() => Year.now().addYears(1); + + ///////////////////////////////////// GETTERS + + /// Get the date for the start of year + Date get startOfYear => Date(year: year); + + /// Get the date for the end of year + Date get endOfYear => startOfYear.endOfYear; + + /// Get the first week of the year + Week get firstWeek { + final yearOfWeek = startOfYear.yearOfWeek; + if (yearOfWeek == year) { + // yearOfWeek is the same year, return the start of week for 1st of Jan + return Week.week(startOfYear); + } else { + // yearOfWeek must be previous year, return the next week instead + return Week.week(startOfYear.addWeeks(1)); + } + } + + /// Get the first ISO week of the year + Week get firstISOWeek { + final yearOfWeek = startOfYear.yearOfISOWeek; + if (yearOfWeek == year) { + // yearOfWeek is the same year, return the start of week for 1st of Jan + return Week.isoWeek(startOfYear); + } else { + // yearOfWeek must be previous year, return the next week instead + return Week.isoWeek(startOfYear.addWeeks(1)); + } + } + + /// Get the last week of the year + Week get lastWeek { + final yearOfWeek = endOfYear.yearOfWeek; + if (yearOfWeek == year) { + // yearOfWeek is the same year, return the start of week for 31st of Dec + return Week.week(endOfYear); + } else { + // yearOfWeek must be next year, return the previous week instead + return Week.week(endOfYear.subWeeks(1)); + } + } + + /// Get the last week of the year + Week get lastISOWeek { + final yearOfWeek = endOfYear.yearOfISOWeek; + if (yearOfWeek == year) { + // yearOfWeek is the same year, return the start of week for 31st of Dec + return Week.isoWeek(endOfYear); + } else { + // yearOfWeek must be next year, return the previous week instead + return Week.isoWeek(endOfYear.subWeeks(1)); + } + } + + /// Returns the dates of the year + Map get dates { + final count = startOfYear.isLeapYear ? 366 : 365; + final list = List.generate(count, (index) => startOfYear.addDays(index)); + int index = 1; + return {for (var date in list) index++: date}; + } + + /// Returns the weeks of the year (week starting sunday) + Map get weeks { + final list = List.generate(52, (index) => firstWeek.addWeeks(index)); + return {for (var week in list) week.weekIndex: week}; + } + + /// Returns the iso weeks of the year (week starting monday) + Map get isoWeeks { + final list = List.generate(52, (index) => firstISOWeek.addWeeks(index)); + return {for (var week in list) week.isoWeekIndex: week}; + } + + /// Returns the months of the year + Map get months { + final list = + List.generate(12, (index) => Month(year: year).addMonths(index)); + return {for (var month in list) month.getMONTH: month}; + } + + /// Returns january + Month get january => Month(year: year); + + /// Returns february + Month get february => Month(year: year).addMonths(1); + + /// Returns march + Month get march => Month(year: year).addMonths(2); + + /// Returns april + Month get april => Month(year: year).addMonths(3); + + /// Returns may + Month get may => Month(year: year).addMonths(4); + + /// Returns june + Month get june => Month(year: year).addMonths(5); + + /// Returns july + Month get july => Month(year: year).addMonths(6); + + /// Returns august + Month get august => Month(year: year).addMonths(7); + + /// Returns september + Month get september => Month(year: year).addMonths(8); + + /// Returns october + Month get october => Month(year: year).addMonths(9); + + /// Returns november + Month get november => Month(year: year).addMonths(10); + + /// Returns december + Month get december => Month(year: year).addMonths(11); + + ///////////////////////////////////// SETTERS + + /// Create a new copy with updated values + Year copyWith({int? year}) { + return Year(year ?? this.year); + } + + /// Set the year + Year setYear(int year) => copyWith(year: year); + + ///////////////////////////////////// OPERATIONS + + /// Add a [Duration] to this week + Year add(Duration duration) => endOfYear.add(duration).getYear; + + /// Subtract a [Duration] to this week + Year subtract(Duration duration) => startOfYear.subtract(duration).getYear; + + /// Add a certain amount of days to the end of the year + Year addDays(int amount) => endOfYear.addDays(amount.abs()).getYear; + + /// Add a certain amount of weeks to the end of the year + Year addWeeks(int amount) => endOfYear.addWeeks(amount.abs()).getYear; + + /// Add a certain amount of months to the end of the year + Year addMonths(int amount) => endOfYear.addMonths(amount.abs()).getYear; + + /// Add a certain amount of quarters to the end of the year + Year addQuarters(int amount) => endOfYear.addQuarters(amount.abs()).getYear; + + /// Add a certain amount of years to the end of the year + Year addYears(int amount) => copyWith(year: year + amount.abs()); + + /// Subtracts an amount of days from the start of year + Year subDays(int amount) => startOfYear.subDays(amount.abs()).getYear; + + /// Subtracts an amount of weeks from the start of year + Year subWeeks(int amount) => startOfYear.addWeeks(-amount.abs()).getYear; + + /// Subtracts an amount of months from the start of year + Year subMonths(int amount) => startOfYear.subMonths(amount.abs()).getYear; + + /// Subtracts an amount of quarters from the start of year + Year subQuarters(int amount) => startOfYear.subQuarters(amount.abs()).getYear; + + /// Subtracts an amount of years from this year. + Year subYears(int amount) => copyWith(year: year - amount.abs()); + + ///////////////////////////////////// COMPARISON + + /// Return true if [year] is after [other], false otherwise. + bool isAfter(Year other) => year > other.year; + + /// Return true if [year] is before [other], false otherwise. + bool isBefore(Year other) => year < other.year; + + /// Return true if other [isEqual] or [isAfter] to this date + bool isSameOrAfter(Year other) => year >= other.year; + + /// Return true if other [isEqual] or [isBefore] to this date + bool isSameOrBefore(Year other) => year <= other.year; + + /// Return true if year is this year + bool get isThisYear => this == Year.thisYear(); + + /// Return true if year is last year + bool get isLastYear => this == Year.lastYear(); + + /// Return true if year is next year + bool get isNextYear => this == Year.nextYear(); + + /// Greater than operator + bool operator >(Year other) => isAfter(other); + + /// Greater or equals to operator + bool operator >=(Year other) => isSameOrAfter(other); + + /// Less than operator + bool operator <(Year other) => isBefore(other); + + /// Less than or equals to operator + bool operator <=(Year other) => isSameOrBefore(other); + + ///////////////////////////////////// OBJECT OVERRIDES + + @override + bool operator ==(Object other) => other is Year && other.year == year; + + @override + int get hashCode => year.hashCode; + + @override + String toString() => key; + + ///////////////////////////////////// KEY + + /// Convert [Year] to a unique id + String get key => format('yyyy'); + + /// Convert a unique id to [Year] + static Year? fromKey(String key) => tryParse(key, format: 'yyyy'); + + ///////////////////////////////////// FORMAT + + /// Constructs a new [DateTime] instance based on [dateStr]. + /// + /// Given user input, attempt to parse the [dateStr] into the anticipated + /// format, treating it as being in the local timezone. + /// + /// If [dateStr] does not match our format, throws a [FormatException]. + /// This will accept dates whose values are not strictly valid, or strings + /// with additional characters (including whitespace) after a valid date. For + /// stricter parsing, use [dateStr]. + static Year parse( + String dateStr, { + String? format, + bool utc = false, + }) { + if (format == null || format == '') { + return DateTime.parse(dateStr).getYear; + } + + final formatter = DateFormat(format); + final dateTimeFromStr = formatter.parse(dateStr, utc); + return dateTimeFromStr.getYear; + } + + /// Constructs a new [DateTime] instance based on [dateStr]. + /// + /// Works like [parse] except that this function returns `null` + /// where [parse] would throw a [FormatException]. + static Year? tryParse( + String dateStr, { + String? format, + bool utc = false, + }) { + try { + return parse( + dateStr, + format: format, + utc: utc, + ); + } on FormatException { + return null; + } + } + + /// Short-hand to DateFormat + String format([String? pattern, String? locale]) { + return DateFormat(pattern, locale).format(startOfYear.asDateTime); + } +} diff --git a/pubspec.lock b/pubspec.lock index 0f72b65..5df9dd8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -148,6 +148,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.3" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.8.0" lint: dependency: "direct dev" description: @@ -373,4 +380,4 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.17.0 <3.0.0" + dart: ">=2.18.0 <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index b24832d..0d9d8f6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,12 +4,13 @@ version: 0.8.0 homepage: https://github.com/AndrewPiterov/date_time environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" dependencies: - quiver: ^3.1.0 - intl: ^0.17.0 clock: ^1.1.0 + intl: ^0.17.0 + json_annotation: ^4.8.0 + quiver: ^3.1.0 dev_dependencies: given_when_then_unit_test: ^0.1.0 diff --git a/test/month_test.dart b/test/month_test.dart new file mode 100644 index 0000000..aa9260d --- /dev/null +++ b/test/month_test.dart @@ -0,0 +1,445 @@ +// ignore_for_file: avoid_redundant_argument_values, prefer_const_constructors + +import 'package:date_time/date_time.dart'; +import 'package:shouldly/shouldly.dart'; +import 'package:test/test.dart'; + +void main() { + final now = Month.now(); + final thisMonth = Month.thisMonth(); + final lastMonth = Month.lastMonth(); + final nextMonth = Month.nextMonth(); + const janOnNormalYear = Month(year: 2023); //2023 is a normal year + const janOnLeapYear = Month(year: 2024); // 2024 is a leap year + const febOnNormalYear = Month(year: 2023, month: 2); + const febOnLeapYear = Month(year: 2024, month: 2); + const march = Month(year: 2023, month: 3); + const april = Month(year: 2023, month: 4); + const may = Month(year: 2023, month: 5); + const june = Month(year: 2023, month: 6); + const july = Month(year: 2023, month: 7); + const august = Month(year: 2023, month: 8); + const september = Month(year: 2023, month: 9); + const october = Month(year: 2023, month: 10); + const november = Month(year: 2023, month: 11); + const december = Month(year: 2023, month: 12); + + group('Testing month comparison methods', () { + test('Compare this month and itself (now)', () { + (thisMonth == now).should.beTrue(); + (thisMonth > now).should.beFalse(); + (thisMonth >= now).should.beTrue(); + (thisMonth < now).should.beFalse(); + (thisMonth <= now).should.beTrue(); + + thisMonth.isAfter(now).should.beFalse(); + thisMonth.isSameOrAfter(now).should.beTrue(); + thisMonth.isBefore(now).should.beFalse(); + thisMonth.isSameOrBefore(now).should.beTrue(); + }); + + test('Compare this month and next month', () { + (thisMonth == nextMonth).should.beFalse(); + (thisMonth > nextMonth).should.beFalse(); + (thisMonth >= nextMonth).should.beFalse(); + (thisMonth < nextMonth).should.beTrue(); + (thisMonth <= nextMonth).should.beTrue(); + + thisMonth.isAfter(nextMonth).should.beFalse(); + thisMonth.isSameOrAfter(nextMonth).should.beFalse(); + thisMonth.isBefore(nextMonth).should.beTrue(); + thisMonth.isSameOrBefore(nextMonth).should.beTrue(); + }); + + test('Compare this month and last month', () { + (thisMonth == lastMonth).should.beFalse(); + (thisMonth > lastMonth).should.beTrue(); + (thisMonth >= lastMonth).should.beTrue(); + (thisMonth < lastMonth).should.beFalse(); + (thisMonth <= lastMonth).should.beFalse(); + + thisMonth.isAfter(lastMonth).should.beTrue(); + thisMonth.isSameOrAfter(lastMonth).should.beTrue(); + thisMonth.isBefore(lastMonth).should.beFalse(); + thisMonth.isSameOrBefore(lastMonth).should.beFalse(); + }); + + test('Compare last month and next month', () { + (lastMonth == nextMonth).should.beFalse(); + (lastMonth > nextMonth).should.beFalse(); + (lastMonth >= nextMonth).should.beFalse(); + (lastMonth < nextMonth).should.beTrue(); + (lastMonth <= nextMonth).should.beTrue(); + + thisMonth.isAfter(nextMonth).should.beFalse(); + thisMonth.isSameOrAfter(nextMonth).should.beFalse(); + thisMonth.isBefore(nextMonth).should.beTrue(); + thisMonth.isSameOrBefore(nextMonth).should.beTrue(); + }); + }); + + group('Testing start and end of month', () { + test('Start and end of january', () { + janOnNormalYear.startOfMonth.should + .be(Date(year: 2023, month: 1, day: 1)); + janOnNormalYear.endOfMonth.should.be(Date(year: 2023, month: 1, day: 31)); + }); + + test('Start and end of february', () { + febOnNormalYear.startOfMonth.should + .be(Date(year: 2023, month: 2, day: 1)); + febOnNormalYear.endOfMonth.should.be(Date(year: 2023, month: 2, day: 28)); + febOnLeapYear.startOfMonth.should.be(Date(year: 2024, month: 2, day: 1)); + febOnLeapYear.endOfMonth.should.be(Date(year: 2024, month: 2, day: 29)); + }); + + test('Start and end of march', () { + march.startOfMonth.should.be(Date(year: 2023, month: 3, day: 1)); + march.endOfMonth.should.be(Date(year: 2023, month: 3, day: 31)); + }); + + test('Start and end of april', () { + april.startOfMonth.should.be(Date(year: 2023, month: 4, day: 1)); + april.endOfMonth.should.be(Date(year: 2023, month: 4, day: 30)); + }); + + test('Start and end of may', () { + may.startOfMonth.should.be(Date(year: 2023, month: 5, day: 1)); + may.endOfMonth.should.be(Date(year: 2023, month: 5, day: 31)); + }); + + test('Start and end of june', () { + june.startOfMonth.should.be(Date(year: 2023, month: 6, day: 1)); + june.endOfMonth.should.be(Date(year: 2023, month: 6, day: 30)); + }); + + test('Start and end of july', () { + july.startOfMonth.should.be(Date(year: 2023, month: 7, day: 1)); + july.endOfMonth.should.be(Date(year: 2023, month: 7, day: 31)); + }); + + test('Start and end of august', () { + august.startOfMonth.should.be(Date(year: 2023, month: 8, day: 1)); + august.endOfMonth.should.be(Date(year: 2023, month: 8, day: 31)); + }); + + test('Start and end of september', () { + september.startOfMonth.should.be(Date(year: 2023, month: 9, day: 1)); + september.endOfMonth.should.be(Date(year: 2023, month: 9, day: 30)); + }); + + test('Start and end of october', () { + october.startOfMonth.should.be(Date(year: 2023, month: 10, day: 1)); + october.endOfMonth.should.be(Date(year: 2023, month: 10, day: 31)); + }); + + test('Start and end of november', () { + november.startOfMonth.should.be(Date(year: 2023, month: 11, day: 1)); + november.endOfMonth.should.be(Date(year: 2023, month: 11, day: 30)); + }); + + test('Start and end of december', () { + december.startOfMonth.should.be(Date(year: 2023, month: 12, day: 1)); + december.endOfMonth.should.be(Date(year: 2023, month: 12, day: 31)); + }); + }); + + group('Testing month operations', () { + test('Add/subtract duration operation', () { + // add normal + janOnNormalYear + .add(Duration(days: 1)) + .should + .be(Month(year: 2023, month: 2)); + janOnNormalYear + .add(Duration(days: 28)) + .should + .be(Month(year: 2023, month: 2)); + janOnNormalYear + .add(Duration(days: 29)) + .should + .be(Month(year: 2023, month: 3)); + + // sub normal + janOnNormalYear + .subtract(Duration(days: 1)) + .should + .be(Month(year: 2022, month: 12)); + janOnNormalYear + .subtract(Duration(days: 31)) + .should + .be(Month(year: 2022, month: 12)); + janOnNormalYear + .subtract(Duration(days: 32)) + .should + .be(Month(year: 2022, month: 11)); + + // add leap + janOnLeapYear + .add(Duration(days: 1)) + .should + .be(Month(year: 2024, month: 2)); + janOnLeapYear + .add(Duration(days: 29)) + .should + .be(Month(year: 2024, month: 2)); + janOnLeapYear + .add(Duration(days: 30)) + .should + .be(Month(year: 2024, month: 3)); + }); + + test('Add/subtract days operation', () { + // add normal + janOnNormalYear.addDays(1).should.be(Month(year: 2023, month: 2)); + janOnNormalYear.addDays(28).should.be(Month(year: 2023, month: 2)); + janOnNormalYear.addDays(29).should.be(Month(year: 2023, month: 3)); + + // sub normal + janOnNormalYear.subDays(1).should.be(Month(year: 2022, month: 12)); + janOnNormalYear.subDays(31).should.be(Month(year: 2022, month: 12)); + janOnNormalYear.subDays(32).should.be(Month(year: 2022, month: 11)); + + // add leap + janOnLeapYear.addDays(1).should.be(Month(year: 2024, month: 2)); + janOnLeapYear.addDays(29).should.be(Month(year: 2024, month: 2)); + janOnLeapYear.addDays(30).should.be(Month(year: 2024, month: 3)); + }); + + test('Add/subtract weeks operation', () { + // add + janOnNormalYear.addWeeks(1).should.be(Month(year: 2023, month: 2)); + + // sub + janOnNormalYear.subWeeks(1).should.be(Month(year: 2022, month: 12)); + janOnNormalYear.subWeeks(5).should.be(Month(year: 2022, month: 11)); + }); + + test('Add/subtract months operation', () { + // add + janOnNormalYear.addMonths(1).should.be(Month(year: 2023, month: 2)); + janOnNormalYear.addMonths(11).should.be(Month(year: 2023, month: 12)); + janOnNormalYear.addMonths(12).should.be(Month(year: 2024, month: 1)); + + // sub + janOnNormalYear.subMonths(1).should.be(Month(year: 2022, month: 12)); + janOnNormalYear.subMonths(12).should.be(Month(year: 2022, month: 1)); + janOnNormalYear.subMonths(13).should.be(Month(year: 2021, month: 12)); + }); + + test('Add/subtract quarters operation', () { + // add + janOnNormalYear.addQuarters(1).should.be(Month(year: 2023, month: 4)); + janOnNormalYear.addQuarters(4).should.be(Month(year: 2024, month: 1)); + + // sub + janOnNormalYear.subQuarters(1).should.be(Month(year: 2022, month: 10)); + janOnNormalYear.subQuarters(4).should.be(Month(year: 2022, month: 1)); + }); + + test('Add/subtract years operation', () { + // add + march.addYears(1).should.be(Month(year: 2024, month: 3)); + march.addYears(4).should.be(Month(year: 2027, month: 3)); + + // sub + march.subYears(1).should.be(Month(year: 2022, month: 3)); + march.subYears(4).should.be(Month(year: 2019, month: 3)); + }); + }); + + group('Test parsing', () { + test('parse without formatter', () { + final dates = [ + '2012-02-27 13:27:00', + '2012-02-27 13:27:00.123456z', + '20120227 13:27:00', + '20120227T132700', + '+20120227', + '2012-02-27T14Z', + '2012-02-27T14+00:00', + '2012-02-27T14:00:00-0500', + '20120227', + '2012-02-27', + ]; + + for (final dateStr in dates) { + final month = Month.parse(dateStr); + month.should.be(const Month(year: 2012, month: 2)); + } + }); + + test('parse with formatter', () { + const format = "MMMM dd, yyyy 'at' hh:mm:ss a Z"; + final month = + Month.parse('August 6, 2020 at 5:44:45 PM UTC+7', format: format); + month.should.be(const Month(year: 2020, month: 8)); + }); + + test('try parse invalid date w/o formatter', () { + final month = Month.tryParse('12/31/2021'); + month.should.beNull(); + }); + + test('try parse with format', () { + final month = Month.tryParse('12/31/2021', format: 'MM/dd/yyyy'); + month.should.be(const Month(year: 2021, month: 12)); + }); + + test('format with pattern', () { + const pattern = 'MMMM dd, yyyy'; + Month(year: 2023, month: 8).format(pattern).should.be('August 01, 2023'); + }); + + test('toString', () { + march.toString().should.be('2023-03'); + december.toString().should.be('2023-12'); + Month.epoch.toString().should.be('1970-01'); + Month.startOfTime.toString().should.be('0001-01'); + }); + + test('fromKey', () { + Month.fromKey('2023-03').should.be(march); + Month.fromKey('2023-12').should.be(december); + Month.fromKey('1970-01').should.be(Month.epoch); + Month.fromKey('0001-01').should.be(Month.startOfTime); + }); + }); + + group('Test MONTH enum', () { + test('test fromNumber()', () { + MONTH.fromNumber(1).should.be(MONTH.january); + MONTH.fromNumber(2).should.be(MONTH.february); + MONTH.fromNumber(3).should.be(MONTH.march); + MONTH.fromNumber(4).should.be(MONTH.april); + MONTH.fromNumber(5).should.be(MONTH.may); + MONTH.fromNumber(6).should.be(MONTH.june); + MONTH.fromNumber(7).should.be(MONTH.july); + MONTH.fromNumber(8).should.be(MONTH.august); + MONTH.fromNumber(9).should.be(MONTH.september); + MONTH.fromNumber(10).should.be(MONTH.october); + MONTH.fromNumber(11).should.be(MONTH.november); + MONTH.fromNumber(12).should.be(MONTH.december); + }); + + test('test month number', () { + (janOnNormalYear.month == MONTH.january.number).should.beTrue(); + (febOnNormalYear.month == MONTH.february.number).should.beTrue(); + (march.month == MONTH.march.number).should.beTrue(); + (april.month == MONTH.april.number).should.beTrue(); + (may.month == MONTH.may.number).should.beTrue(); + (june.month == MONTH.june.number).should.beTrue(); + (july.month == MONTH.july.number).should.beTrue(); + (august.month == MONTH.august.number).should.beTrue(); + (september.month == MONTH.september.number).should.beTrue(); + (october.month == MONTH.october.number).should.beTrue(); + (november.month == MONTH.november.number).should.beTrue(); + (december.month == MONTH.december.number).should.beTrue(); + }); + + test('test enum value', () { + (janOnNormalYear.getMONTH == MONTH.january).should.beTrue(); + (febOnNormalYear.getMONTH == MONTH.february).should.beTrue(); + (march.getMONTH == MONTH.march).should.beTrue(); + (april.getMONTH == MONTH.april).should.beTrue(); + (may.getMONTH == MONTH.may).should.beTrue(); + (june.getMONTH == MONTH.june).should.beTrue(); + (july.getMONTH == MONTH.july).should.beTrue(); + (august.getMONTH == MONTH.august).should.beTrue(); + (september.getMONTH == MONTH.september).should.beTrue(); + (october.getMONTH == MONTH.october).should.beTrue(); + (november.getMONTH == MONTH.november).should.beTrue(); + (december.getMONTH == MONTH.december).should.beTrue(); + }); + + test('test enum titles in en_US', () { + MONTH.january.title('en_US').should.be('January'); + MONTH.february.title('en_US').should.be('February'); + MONTH.march.title('en_US').should.be('March'); + MONTH.april.title('en_US').should.be('April'); + MONTH.may.title('en_US').should.be('May'); + MONTH.june.title('en_US').should.be('June'); + MONTH.july.title('en_US').should.be('July'); + MONTH.august.title('en_US').should.be('August'); + MONTH.september.title('en_US').should.be('September'); + MONTH.october.title('en_US').should.be('October'); + MONTH.november.title('en_US').should.be('November'); + MONTH.december.title('en_US').should.be('December'); + }); + + test('test enum short titles in en_US', () { + MONTH.january.shortTitle('en_US').should.be('Jan'); + MONTH.february.shortTitle('en_US').should.be('Feb'); + MONTH.march.shortTitle('en_US').should.be('Mar'); + MONTH.april.shortTitle('en_US').should.be('Apr'); + MONTH.may.shortTitle('en_US').should.be('May'); + MONTH.june.shortTitle('en_US').should.be('Jun'); + MONTH.july.shortTitle('en_US').should.be('Jul'); + MONTH.august.shortTitle('en_US').should.be('Aug'); + MONTH.september.shortTitle('en_US').should.be('Sep'); + MONTH.october.shortTitle('en_US').should.be('Oct'); + MONTH.november.shortTitle('en_US').should.be('Nov'); + MONTH.december.shortTitle('en_US').should.be('Dec'); + }); + }); + + group('Test dates in the month', () { + test('test dates', () { + janOnNormalYear.dates[1].should.be(Date(year: 2023, month: 1, day: 1)); + janOnNormalYear.dates[31].should.be(Date(year: 2023, month: 1, day: 31)); + janOnNormalYear.dates[0].should.beNull(); + janOnNormalYear.dates[32].should.beNull(); + + febOnNormalYear.dates[1].should.be(Date(year: 2023, month: 2, day: 1)); + febOnNormalYear.dates[28].should.be(Date(year: 2023, month: 2, day: 28)); + febOnNormalYear.dates[0].should.beNull(); + febOnNormalYear.dates[29].should.beNull(); + + febOnLeapYear.dates[1].should.be(Date(year: 2024, month: 2, day: 1)); + febOnLeapYear.dates[29].should.be(Date(year: 2024, month: 2, day: 29)); + febOnLeapYear.dates[0].should.beNull(); + febOnLeapYear.dates[30].should.beNull(); + }); + + test('test [] operator', () { + janOnNormalYear[1].should.be(Date(year: 2023, month: 1, day: 1)); + janOnNormalYear[31].should.be(Date(year: 2023, month: 1, day: 31)); + expect(() => janOnNormalYear[0], throwsRangeError); + expect(() => janOnNormalYear[32], throwsRangeError); + + febOnNormalYear[1].should.be(Date(year: 2023, month: 2, day: 1)); + febOnNormalYear[28].should.be(Date(year: 2023, month: 2, day: 28)); + expect(() => febOnNormalYear[0], throwsRangeError); + expect(() => febOnNormalYear[29], throwsRangeError); + + febOnLeapYear[1].should.be(Date(year: 2024, month: 2, day: 1)); + febOnLeapYear[29].should.be(Date(year: 2024, month: 2, day: 29)); + expect(() => febOnLeapYear[0], throwsRangeError); + expect(() => febOnLeapYear[30], throwsRangeError); + }); + }); + + group('test copyWith', () { + const month = Month(year: 2022, month: 5); + + test('year', () { + month.copyWith(year: 2025).should.be(Month(year: 2025, month: 5)); + }); + + test('month', () { + month.copyWith(month: 9).should.be(Month(year: 2022, month: 9)); + }); + + test('setYear', () { + month.setYear(2025).should.be(Month(year: 2025, month: 5)); + }); + + test('setMonth', () { + month.setMonth(9).should.be(Month(year: 2022, month: 9)); + }); + + test('setMONTH', () { + month.setMONTH(MONTH.september).should.be(Month(year: 2022, month: 9)); + }); + }); +} diff --git a/test/week_test.dart b/test/week_test.dart new file mode 100644 index 0000000..d9b387a --- /dev/null +++ b/test/week_test.dart @@ -0,0 +1,345 @@ +// ignore_for_file: avoid_redundant_argument_values, prefer_const_constructors + +import 'package:date_time/date_time.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:shouldly/shouldly.dart'; +import 'package:test/test.dart'; + +void main() { + initializeDateFormatting(); + final now = Week.now(); + final thisWeek = Week.thisWeek(); + final lastWeek = Week.lastWeek(); + final nextWeek = Week.nextWeek(); + final isoFirstWeekOf2023 = Year(2023).firstISOWeek; + final isoLastWeekOf2023 = Year(2023).lastISOWeek; + final firstWeekOf2023 = Year(2023).firstWeek; + final lastWeekOf2023 = Year(2023).lastWeek; + final sunday1 = Date(year: 2023, month: 1, day: 1); + final monday = Date(year: 2023, month: 1, day: 2); + final tuesday = Date(year: 2023, month: 1, day: 3); + final wednesday = Date(year: 2023, month: 1, day: 4); + final thursday = Date(year: 2023, month: 1, day: 5); + final friday = Date(year: 2023, month: 1, day: 6); + final saturday = Date(year: 2023, month: 1, day: 7); + final sunday2 = Date(year: 2023, month: 1, day: 8); + + group('Testing week comparison methods', () { + test('Compare this week and itself (now)', () { + (thisWeek == now).should.beTrue(); + (thisWeek > now).should.beFalse(); + (thisWeek >= now).should.beTrue(); + (thisWeek < now).should.beFalse(); + (thisWeek <= now).should.beTrue(); + + thisWeek.isAfter(now).should.beFalse(); + thisWeek.isSameOrAfter(now).should.beTrue(); + thisWeek.isBefore(now).should.beFalse(); + thisWeek.isSameOrBefore(now).should.beTrue(); + }); + + test('Compare this week and next week', () { + (thisWeek == nextWeek).should.beFalse(); + (thisWeek > nextWeek).should.beFalse(); + (thisWeek >= nextWeek).should.beFalse(); + (thisWeek < nextWeek).should.beTrue(); + (thisWeek <= nextWeek).should.beTrue(); + + thisWeek.isAfter(nextWeek).should.beFalse(); + thisWeek.isSameOrAfter(nextWeek).should.beFalse(); + thisWeek.isBefore(nextWeek).should.beTrue(); + thisWeek.isSameOrBefore(nextWeek).should.beTrue(); + }); + + test('Compare this week and last week', () { + // lastWeek.should.be('lastWeek'); + // thisWeek.should.be('thisWeek'); + // nextWeek.should.be('nextWeek'); + (thisWeek == lastWeek).should.beFalse(); + (thisWeek > lastWeek).should.beTrue(); + (thisWeek >= lastWeek).should.beTrue(); + (thisWeek < lastWeek).should.beFalse(); + (thisWeek <= lastWeek).should.beFalse(); + + thisWeek.isAfter(lastWeek).should.beTrue(); + thisWeek.isSameOrAfter(lastWeek).should.beTrue(); + thisWeek.isBefore(lastWeek).should.beFalse(); + thisWeek.isSameOrBefore(lastWeek).should.beFalse(); + }); + + test('Compare last week and next week', () { + (lastWeek == nextWeek).should.beFalse(); + (lastWeek > nextWeek).should.beFalse(); + (lastWeek >= nextWeek).should.beFalse(); + (lastWeek < nextWeek).should.beTrue(); + (lastWeek <= nextWeek).should.beTrue(); + + thisWeek.isAfter(nextWeek).should.beFalse(); + thisWeek.isSameOrAfter(nextWeek).should.beFalse(); + thisWeek.isBefore(nextWeek).should.beTrue(); + thisWeek.isSameOrBefore(nextWeek).should.beTrue(); + }); + }); + + group('Testing start and end of week', () { + test('Start and end of week of 2023', () { + firstWeekOf2023.startOfWeek.should.be(Date(year: 2023, month: 1, day: 1)); + firstWeekOf2023.endOfWeek.should.be(Date(year: 2023, month: 1, day: 7)); + isoFirstWeekOf2023.startOfWeek.should + .be(Date(year: 2023, month: 1, day: 2)); + isoFirstWeekOf2023.endOfWeek.should + .be(Date(year: 2023, month: 1, day: 8)); + + lastWeekOf2023.startOfWeek.should + .be(Date(year: 2023, month: 12, day: 24)); + lastWeekOf2023.endOfWeek.should.be(Date(year: 2023, month: 12, day: 30)); + isoLastWeekOf2023.startOfWeek.should + .be(Date(year: 2023, month: 12, day: 25)); + isoLastWeekOf2023.endOfWeek.should + .be(Date(year: 2023, month: 12, day: 31)); + }); + }); + + group('Testing week operations', () { + test('Add/subtract weeks operation', () { + // iso + isoFirstWeekOf2023 + .addWeeks(1) + .should + .be(Week(startOfWeek: Date(year: 2023, month: 1, day: 9))); + isoFirstWeekOf2023 + .subWeeks(1) + .should + .be(Week(startOfWeek: Date(year: 2022, month: 12, day: 26))); + isoLastWeekOf2023 + .addWeeks(1) + .should + .be(Week(startOfWeek: Date(year: 2024, month: 1, day: 1))); + isoLastWeekOf2023 + .subWeeks(1) + .should + .be(Week(startOfWeek: Date(year: 2023, month: 12, day: 18))); + + // non iso + firstWeekOf2023 + .addWeeks(1) + .should + .be(Week(startOfWeek: Date(year: 2023, month: 1, day: 8))); + firstWeekOf2023 + .subWeeks(1) + .should + .be(Week(startOfWeek: Date(year: 2022, month: 12, day: 25))); + lastWeekOf2023 + .addWeeks(1) + .should + .be(Week(startOfWeek: Date(year: 2023, month: 12, day: 31))); + lastWeekOf2023 + .subWeeks(1) + .should + .be(Week(startOfWeek: Date(year: 2023, month: 12, day: 17))); + }); + + test('Add/subtract years operation', () { + // iso + isoFirstWeekOf2023 + .addYears(1) + .should + .be(Week(startOfWeek: Date(year: 2024, month: 1, day: 1))); + isoFirstWeekOf2023 + .subYears(1) + .should + .be(Week(startOfWeek: Date(year: 2022, month: 1, day: 3))); + + // non iso + firstWeekOf2023 + .addYears(1) + .should + .be(Week(startOfWeek: Date(year: 2023, month: 12, day: 31))); + firstWeekOf2023 + .subYears(1) + .should + .be(Week(startOfWeek: Date(year: 2022, month: 1, day: 2))); + }); + }); + + group('Test parsing', () { + test('parse without formatter', () { + final dates = [ + '2012-02-27 13:27:00', + '2012-02-27 13:27:00.123456z', + '20120227 13:27:00', + '20120227T132700', + '+20120227', + '2012-02-27T14Z', + '2012-02-27T14+00:00', + '2012-02-27T14:00:00-0500', + '20120227', + '2012-02-27', + ]; + + for (final dateStr in dates) { + final week = Week.parse(dateStr); + week.should.be(Week(startOfWeek: Date(year: 2012, month: 2, day: 27))); + } + }); + + test('parse with formatter', () { + const format = "MMMM dd, yyyy 'at' hh:mm:ss a Z"; + final week = + Week.parse('August 6, 2020 at 5:44:45 PM UTC+7', format: format); + week.should.be(Week(startOfWeek: Date(year: 2020, month: 8, day: 3))); + }); + + test('try parse invalid date w/o formatter', () { + final week = Week.tryParse('12/31/2021'); + week.should.beNull(); + }); + + test('try parse with format', () { + final week = Week.tryParse('12/31/2021', format: 'MM/dd/yyyy'); + week.should.be(Week(startOfWeek: Date(year: 2021, month: 12, day: 27))); + }); + + test('toString', () { + isoFirstWeekOf2023.toString().should.be('2023-01-02'); + isoLastWeekOf2023.toString().should.be('2023-12-25'); + Week.firstISOWeekOfEpoch.toString().should.be('1969-12-29'); + Week.firstISOWeekOfTime.toString().should.be('0001-01-01'); + }); + + test('toKey', () { + isoFirstWeekOf2023.toKey().should.be('2023-01-02'); + isoLastWeekOf2023.toKey().should.be('2023-12-25'); + Week.firstISOWeekOfEpoch.toKey().should.be('1969-12-29'); + Week.firstISOWeekOfTime.toKey().should.be('0001-01-01'); + }); + + test('fromKey', () { + Week.fromKey('2023-01-02').should.be(isoFirstWeekOf2023); + Week.fromKey('2023-12-25').should.be(isoLastWeekOf2023); + Week.fromKey('1969-12-29').should.be(Week.firstISOWeekOfEpoch); + Week.fromKey('0001-01-01').should.be(Week.firstISOWeekOfTime); + }); + }); + + group('Test DAY enum', () { + test('test fromNumber()', () { + DAY.fromNumber(1).should.be(DAY.monday); + DAY.fromNumber(2).should.be(DAY.tuesday); + DAY.fromNumber(3).should.be(DAY.wednesday); + DAY.fromNumber(4).should.be(DAY.thursday); + DAY.fromNumber(5).should.be(DAY.friday); + DAY.fromNumber(6).should.be(DAY.saturday); + DAY.fromNumber(7).should.be(DAY.sunday); + }); + + test('test day number', () { + (monday.weekday == DAY.monday.number).should.beTrue(); + (tuesday.weekday == DAY.tuesday.number).should.beTrue(); + (wednesday.weekday == DAY.wednesday.number).should.beTrue(); + (thursday.weekday == DAY.thursday.number).should.beTrue(); + (friday.weekday == DAY.friday.number).should.beTrue(); + (saturday.weekday == DAY.saturday.number).should.beTrue(); + (sunday2.weekday == DAY.sunday.number).should.beTrue(); + }); + + test('test enum value', () { + (monday.getDAY == DAY.monday).should.beTrue(); + (tuesday.getDAY == DAY.tuesday).should.beTrue(); + (wednesday.getDAY == DAY.wednesday).should.beTrue(); + (thursday.getDAY == DAY.thursday).should.beTrue(); + (friday.getDAY == DAY.friday).should.beTrue(); + (saturday.getDAY == DAY.saturday).should.beTrue(); + (sunday2.getDAY == DAY.sunday).should.beTrue(); + }); + + test('test enum titles in en_US', () { + DAY.monday.title('en_US').should.be('Monday'); + DAY.tuesday.title('en_US').should.be('Tuesday'); + DAY.wednesday.title('en_US').should.be('Wednesday'); + DAY.thursday.title('en_US').should.be('Thursday'); + DAY.friday.title('en_US').should.be('Friday'); + DAY.saturday.title('en_US').should.be('Saturday'); + DAY.sunday.title('en_US').should.be('Sunday'); + }); + + test('test enum short titles in en_US', () { + DAY.monday.shortTitle('en_US').should.be('Mon'); + DAY.tuesday.shortTitle('en_US').should.be('Tue'); + DAY.wednesday.shortTitle('en_US').should.be('Wed'); + DAY.thursday.shortTitle('en_US').should.be('Thu'); + DAY.friday.shortTitle('en_US').should.be('Fri'); + DAY.saturday.shortTitle('en_US').should.be('Sat'); + DAY.sunday.shortTitle('en_US').should.be('Sun'); + }); + + test('test enum acronym in en_US', () { + DAY.monday.acronym('en_US').should.be('M'); + DAY.tuesday.acronym('en_US').should.be('T'); + DAY.wednesday.acronym('en_US').should.be('W'); + DAY.thursday.acronym('en_US').should.be('T'); + DAY.friday.acronym('en_US').should.be('F'); + DAY.saturday.acronym('en_US').should.be('S'); + DAY.sunday.acronym('en_US').should.be('S'); + }); + + test('test enum titles in fr', () { + DAY.monday.title('fr').should.be('lundi'); + DAY.tuesday.title('fr').should.be('mardi'); + DAY.wednesday.title('fr').should.be('mercredi'); + DAY.thursday.title('fr').should.be('jeudi'); + DAY.friday.title('fr').should.be('vendredi'); + DAY.saturday.title('fr').should.be('samedi'); + DAY.sunday.title('fr').should.be('dimanche'); + }); + }); + + group('Test dates', () { + test('test non iso dates', () { + firstWeekOf2023[DAY.sunday].should.be(sunday1); + firstWeekOf2023[DAY.monday].should.be(monday); + firstWeekOf2023[DAY.tuesday].should.be(tuesday); + firstWeekOf2023[DAY.wednesday].should.be(wednesday); + firstWeekOf2023[DAY.thursday].should.be(thursday); + firstWeekOf2023[DAY.friday].should.be(friday); + firstWeekOf2023[DAY.saturday].should.be(saturday); + + firstWeekOf2023.sunday.should.be(sunday1); + firstWeekOf2023.monday.should.be(monday); + firstWeekOf2023.tuesday.should.be(tuesday); + firstWeekOf2023.wednesday.should.be(wednesday); + firstWeekOf2023.thursday.should.be(thursday); + firstWeekOf2023.friday.should.be(friday); + firstWeekOf2023.saturday.should.be(saturday); + }); + + test('test iso dates', () { + isoFirstWeekOf2023[DAY.monday].should.be(monday); + isoFirstWeekOf2023[DAY.tuesday].should.be(tuesday); + isoFirstWeekOf2023[DAY.wednesday].should.be(wednesday); + isoFirstWeekOf2023[DAY.thursday].should.be(thursday); + isoFirstWeekOf2023[DAY.friday].should.be(friday); + isoFirstWeekOf2023[DAY.saturday].should.be(saturday); + isoFirstWeekOf2023[DAY.sunday].should.be(sunday2); + + isoFirstWeekOf2023.monday.should.be(monday); + isoFirstWeekOf2023.tuesday.should.be(tuesday); + isoFirstWeekOf2023.wednesday.should.be(wednesday); + isoFirstWeekOf2023.thursday.should.be(thursday); + isoFirstWeekOf2023.friday.should.be(friday); + isoFirstWeekOf2023.saturday.should.be(saturday); + isoFirstWeekOf2023.sunday.should.be(sunday2); + }); + }); + + group('test copyWith', () { + final week = Week(startOfWeek: Date(year: 2022, month: 1, day: 1)); + + test('startOfWeek', () { + week + .copyWith(startOfWeek: Date(year: 2022, month: 1, day: 1)) + .should + .be(Week(startOfWeek: Date(year: 2022, month: 1, day: 1))); + }); + }); +} diff --git a/test/year_test.dart b/test/year_test.dart new file mode 100644 index 0000000..5a14f40 --- /dev/null +++ b/test/year_test.dart @@ -0,0 +1,298 @@ +// ignore_for_file: avoid_redundant_argument_values, prefer_const_constructors + +import 'package:date_time/date_time.dart'; +import 'package:shouldly/shouldly.dart'; +import 'package:test/test.dart'; + +void main() { + final now = Year.now(); + final thisYear = Year.thisYear(); + final lastYear = Year.lastYear(); + final nextYear = Year.nextYear(); + const normalYear2023 = Year(2023); //2023 is a normal year + const leapYear2024 = Year(2024); // 2024 is a leap year + + group('Testing year comparison methods', () { + test('Compare this year and itself (now)', () { + (thisYear == now).should.beTrue(); + (thisYear > now).should.beFalse(); + (thisYear >= now).should.beTrue(); + (thisYear < now).should.beFalse(); + (thisYear <= now).should.beTrue(); + + thisYear.isAfter(now).should.beFalse(); + thisYear.isSameOrAfter(now).should.beTrue(); + thisYear.isBefore(now).should.beFalse(); + thisYear.isSameOrBefore(now).should.beTrue(); + }); + + test('Compare this year and next year', () { + (thisYear == nextYear).should.beFalse(); + (thisYear > nextYear).should.beFalse(); + (thisYear >= nextYear).should.beFalse(); + (thisYear < nextYear).should.beTrue(); + (thisYear <= nextYear).should.beTrue(); + + thisYear.isAfter(nextYear).should.beFalse(); + thisYear.isSameOrAfter(nextYear).should.beFalse(); + thisYear.isBefore(nextYear).should.beTrue(); + thisYear.isSameOrBefore(nextYear).should.beTrue(); + }); + + test('Compare this year and last year', () { + (thisYear == lastYear).should.beFalse(); + (thisYear > lastYear).should.beTrue(); + (thisYear >= lastYear).should.beTrue(); + (thisYear < lastYear).should.beFalse(); + (thisYear <= lastYear).should.beFalse(); + + thisYear.isAfter(lastYear).should.beTrue(); + thisYear.isSameOrAfter(lastYear).should.beTrue(); + thisYear.isBefore(lastYear).should.beFalse(); + thisYear.isSameOrBefore(lastYear).should.beFalse(); + }); + + test('Compare last year and next year', () { + (lastYear == nextYear).should.beFalse(); + (lastYear > nextYear).should.beFalse(); + (lastYear >= nextYear).should.beFalse(); + (lastYear < nextYear).should.beTrue(); + (lastYear <= nextYear).should.beTrue(); + + thisYear.isAfter(nextYear).should.beFalse(); + thisYear.isSameOrAfter(nextYear).should.beFalse(); + thisYear.isBefore(nextYear).should.beTrue(); + thisYear.isSameOrBefore(nextYear).should.beTrue(); + }); + }); + + group('Testing start and end of year', () { + test('Start and end of year (2023)', () { + normalYear2023.startOfYear.should.be(Date(year: 2023, month: 1, day: 1)); + normalYear2023.endOfYear.should.be(Date(year: 2023, month: 12, day: 31)); + normalYear2023.firstWeek.should + .be(Week(startOfWeek: Date(year: 2023, month: 1, day: 1))); + normalYear2023.lastWeek.should + .be(Week(startOfWeek: Date(year: 2023, month: 12, day: 24))); + normalYear2023.firstISOWeek.should + .be(Week(startOfWeek: Date(year: 2023, month: 1, day: 2))); + normalYear2023.lastISOWeek.should + .be(Week(startOfWeek: Date(year: 2023, month: 12, day: 25))); + }); + + test('Start and end of year (2024)', () { + leapYear2024.startOfYear.should.be(Date(year: 2024, month: 1, day: 1)); + leapYear2024.endOfYear.should.be(Date(year: 2024, month: 12, day: 31)); + leapYear2024.firstWeek.should + .be(Week(startOfWeek: Date(year: 2023, month: 12, day: 31))); + leapYear2024.lastWeek.should + .be(Week(startOfWeek: Date(year: 2024, month: 12, day: 22))); + leapYear2024.firstISOWeek.should + .be(Week(startOfWeek: Date(year: 2024, month: 1, day: 1))); + leapYear2024.lastISOWeek.should + .be(Week(startOfWeek: Date(year: 2024, month: 12, day: 23))); + }); + }); + + group('Testing year operations', () { + test('Add/subtract duration operation', () { + // add + normalYear2023.add(Duration(days: 1)).should.be(Year(2024)); + normalYear2023.add(Duration(days: 366)).should.be(Year(2024)); + normalYear2023.add(Duration(days: 367)).should.be(Year(2025)); + + // sub + normalYear2023.subtract(Duration(days: 1)).should.be(Year(2022)); + }); + + test('Add/subtract days operation', () { + // add + normalYear2023.addDays(1).should.be(Year(2024)); + normalYear2023.addDays(366).should.be(Year(2024)); + normalYear2023.addDays(367).should.be(Year(2025)); + + // sub + normalYear2023.subDays(1).should.be(Year(2022)); + }); + + test('Add/subtract weeks operation', () { + // add + normalYear2023.addWeeks(1).should.be(Year(2024)); + normalYear2023.addWeeks(52).should.be(Year(2024)); + normalYear2023.addWeeks(53).should.be(Year(2025)); + + // sub + normalYear2023.subWeeks(1).should.be(Year(2022)); + normalYear2023.subWeeks(52).should.be(Year(2022)); + normalYear2023.subWeeks(53).should.be(Year(2021)); + }); + + test('Add/subtract months operation', () { + // add + normalYear2023.addMonths(1).should.be(Year(2024)); + normalYear2023.addMonths(12).should.be(Year(2024)); + normalYear2023.addMonths(13).should.be(Year(2025)); + + // sub + normalYear2023.subMonths(1).should.be(Year(2022)); + normalYear2023.subMonths(12).should.be(Year(2022)); + normalYear2023.subMonths(13).should.be(Year(2021)); + }); + + test('Add/subtract quarters operation', () { + // add + normalYear2023.addQuarters(1).should.be(Year(2024)); + normalYear2023.addQuarters(4).should.be(Year(2024)); + normalYear2023.addQuarters(5).should.be(Year(2025)); + + // sub + normalYear2023.subQuarters(1).should.be(Year(2022)); + normalYear2023.subQuarters(4).should.be(Year(2022)); + normalYear2023.subQuarters(5).should.be(Year(2021)); + }); + + test('Add/subtract years operation', () { + // add + normalYear2023.addYears(1).should.be(Year(2024)); + + // sub + normalYear2023.subYears(1).should.be(Year(2022)); + }); + }); + + group('Test parsing', () { + test('parse without formatter', () { + final dates = [ + '2012-02-27 13:27:00', + '2012-02-27 13:27:00.123456z', + '20120227 13:27:00', + '20120227T132700', + '+20120227', + '2012-02-27T14Z', + '2012-02-27T14+00:00', + '2012-02-27T14:00:00-0500', + '20120227', + '2012-02-27', + ]; + + for (final dateStr in dates) { + final year = Year.parse(dateStr); + year.should.be(const Year(2012)); + } + }); + + test('parse with formatter', () { + const format = "MMMM dd, yyyy 'at' hh:mm:ss a Z"; + final year = + Year.parse('August 6, 2020 at 5:44:45 PM UTC+7', format: format); + year.should.be(const Year(2020)); + }); + + test('try parse invalid date w/o formatter', () { + final year = Year.tryParse('12/31/2021'); + year.should.beNull(); + }); + + test('try parse with format', () { + final year = Year.tryParse('12/31/2021', format: 'MM/dd/yyyy'); + year.should.be(const Year(2021)); + }); + + test('format with pattern', () { + const pattern = 'MMMM dd, yyyy'; + Year(2023).format(pattern).should.be('January 01, 2023'); + }); + + test('toString', () { + normalYear2023.toString().should.be('2023'); + leapYear2024.toString().should.be('2024'); + Year.epoch.toString().should.be('1970'); + Year.startOfTime.toString().should.be('0001'); + }); + + test('fromKey', () { + Year.fromKey('2023').should.be(normalYear2023); + Year.fromKey('2024').should.be(leapYear2024); + Year.fromKey('1970').should.be(Year.epoch); + Year.fromKey('0001').should.be(Year.startOfTime); + }); + }); + + group('test dates, weeks and months', () { + test('dates', () { + normalYear2023.dates.length.should.be(365); + normalYear2023.dates[1].should.be(Date(year: 2023, month: 1, day: 1)); + normalYear2023.dates[365].should.be(Date(year: 2023, month: 12, day: 31)); + normalYear2023.dates[366].should.beNull(); + + leapYear2024.dates.length.should.be(366); + leapYear2024.dates[1].should.be(Date(year: 2024, month: 1, day: 1)); + leapYear2024.dates[366].should.be(Date(year: 2024, month: 12, day: 31)); + leapYear2024.dates[367].should.beNull(); + }); + + test('weeks', () { + normalYear2023.weeks.length.should.be(52); + normalYear2023.weeks[1].should + .be(Week(startOfWeek: Date(year: 2023, month: 1, day: 1))); + normalYear2023.weeks[52].should + .be(Week(startOfWeek: Date(year: 2023, month: 12, day: 24))); + }); + + test('iso weeks', () { + normalYear2023.isoWeeks.length.should.be(52); + normalYear2023.isoWeeks[1].should + .be(Week(startOfWeek: Date(year: 2023, month: 1, day: 2))); + normalYear2023.isoWeeks[52].should + .be(Week(startOfWeek: Date(year: 2023, month: 12, day: 25))); + }); + + test('months', () { + normalYear2023.months.length.should.be(12); + normalYear2023.months[MONTH.january].should + .be(Month(year: 2023, month: 1)); + normalYear2023.months[MONTH.february].should + .be(Month(year: 2023, month: 2)); + normalYear2023.months[MONTH.march].should.be(Month(year: 2023, month: 3)); + normalYear2023.months[MONTH.april].should.be(Month(year: 2023, month: 4)); + normalYear2023.months[MONTH.may].should.be(Month(year: 2023, month: 5)); + normalYear2023.months[MONTH.june].should.be(Month(year: 2023, month: 6)); + normalYear2023.months[MONTH.july].should.be(Month(year: 2023, month: 7)); + normalYear2023.months[MONTH.august].should + .be(Month(year: 2023, month: 8)); + normalYear2023.months[MONTH.september].should + .be(Month(year: 2023, month: 9)); + normalYear2023.months[MONTH.october].should + .be(Month(year: 2023, month: 10)); + normalYear2023.months[MONTH.november].should + .be(Month(year: 2023, month: 11)); + normalYear2023.months[MONTH.december].should + .be(Month(year: 2023, month: 12)); + + normalYear2023.january.should.be(Month(year: 2023, month: 1)); + normalYear2023.february.should.be(Month(year: 2023, month: 2)); + normalYear2023.march.should.be(Month(year: 2023, month: 3)); + normalYear2023.april.should.be(Month(year: 2023, month: 4)); + normalYear2023.may.should.be(Month(year: 2023, month: 5)); + normalYear2023.june.should.be(Month(year: 2023, month: 6)); + normalYear2023.july.should.be(Month(year: 2023, month: 7)); + normalYear2023.august.should.be(Month(year: 2023, month: 8)); + normalYear2023.september.should.be(Month(year: 2023, month: 9)); + normalYear2023.october.should.be(Month(year: 2023, month: 10)); + normalYear2023.november.should.be(Month(year: 2023, month: 11)); + normalYear2023.december.should.be(Month(year: 2023, month: 12)); + }); + }); + + group('test copyWith', () { + const year = Year(2022); + + test('year', () { + year.copyWith(year: 2025).should.be(Year(2025)); + }); + + test('setYear', () { + year.setYear(2025).should.be(Year(2025)); + }); + }); +} From 83f43db6bee7d427dcd53e445097fdde91220079 Mon Sep 17 00:00:00 2001 From: heng Date: Sat, 4 Mar 2023 08:15:41 +1100 Subject: [PATCH 2/3] - Updated factory values to const --- lib/src/week.dart | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/lib/src/week.dart b/lib/src/week.dart index 85509d1..23d5c21 100644 --- a/lib/src/week.dart +++ b/lib/src/week.dart @@ -38,15 +38,13 @@ enum DAY { /// Returns the day of week title based on locale String title([String? locale]) { - final dayOfWeek = - Week.firstISOWeekOfEpoch.startOfWeek.addDays(number - 1).asDateTime; + final dayOfWeek = Week.firstISOWeekOfEpoch.startOfWeek.addDays(number - 1).asDateTime; return DateFormat('EEEE', locale).format(dayOfWeek); } /// Return the abbreviation of day - useful for calendar views. String shortTitle([String? locale]) { - final dayOfWeek = - Week.firstISOWeekOfEpoch.startOfWeek.addDays(number - 1).asDateTime; + final dayOfWeek = Week.firstISOWeekOfEpoch.startOfWeek.addDays(number - 1).asDateTime; return DateFormat('E', locale).format(dayOfWeek); } @@ -75,14 +73,13 @@ class Week { const Week({required this.startOfWeek}); /// Const value for the first week of epoch that can be used as a default value - static Week firstISOWeekOfEpoch = Week.startWeek(Date.epoch.startOfISOWeek); + static const Week firstISOWeekOfEpoch = Week(startOfWeek: Date(year: 1969, month: 12, day: 29)); /// Const value for the first week of year 1 that can be used as a default value - static Week firstISOWeekOfTime = - Week.startWeek(Date.startOfTime.startOfISOWeek); + static const Week firstISOWeekOfTime = Week(startOfWeek: Date.startOfTime); /// Const value to signify the end of time that can be used as a default value - static Week lastISOWeekOfTime = Week.startWeek(Date.endOfTime.startOfISOWeek); + static const Week lastISOWeekOfTime = Week(startOfWeek: Date(year: 9999, month: 12, day: 27)); ///////////////////////////////////// FACTORIES @@ -108,8 +105,7 @@ class Week { /// When [iso] is true of absent, will return the current week starting on Monday /// When false, will return the current week starting on Sunday - factory Week.now({bool iso = true}) => - iso ? Week.isoWeek(Date.now()) : Week.week(Date.now()); + factory Week.now({bool iso = true}) => iso ? Week.isoWeek(Date.now()) : Week.week(Date.now()); /// Same as [Week.now] factory Week.thisWeek({bool iso = true}) => Week.now(iso: iso); @@ -192,15 +188,13 @@ class Week { int difference(Week other) => startOfWeek.difference(other.startOfWeek); /// Add a certain amount of weeks to this week - Week addWeeks(int amount) => - Week.startWeek(startOfWeek.addWeeks(amount.abs())); + Week addWeeks(int amount) => Week.startWeek(startOfWeek.addWeeks(amount.abs())); /// Add a certain amount of years to this week Week addYears(int amount) => addWeeks(52); /// Subtracts an amount of months from this week - Week subWeeks(int amount) => - Week.startWeek(startOfWeek.subWeeks(amount.abs())); + Week subWeeks(int amount) => Week.startWeek(startOfWeek.subWeeks(amount.abs())); /// Subtracts an amount of years from this [Date] Week subYears(int amount) => subWeeks(52); @@ -214,12 +208,10 @@ class Week { bool isBefore(Week other) => startOfWeek.isBefore(other.startOfWeek); /// Return true if other [isEqual] or [isAfter] to this date - bool isSameOrAfter(Week other) => - startOfWeek.isSameOrAfter(other.startOfWeek); + bool isSameOrAfter(Week other) => startOfWeek.isSameOrAfter(other.startOfWeek); /// Return true if other [isEqual] or [isBefore] to this date - bool isSameOrBefore(Week other) => - startOfWeek.isSameOrBefore(other.startOfWeek); + bool isSameOrBefore(Week other) => startOfWeek.isSameOrBefore(other.startOfWeek); /// Return true if week is this week bool get isThisWeek => this == Week.thisWeek(); @@ -245,8 +237,7 @@ class Week { ///////////////////////////////////// OBJECT OVERRIDES @override - bool operator ==(Object other) => - other is Week && other.startOfWeek == startOfWeek; + bool operator ==(Object other) => other is Week && other.startOfWeek == startOfWeek; @override int get hashCode => startOfWeek.hashCode; @@ -282,9 +273,7 @@ class Week { bool iso = true, }) { if (format == null || format == '') { - return iso - ? DateTime.parse(dateStr).isoWeek - : DateTime.parse(dateStr).week; + return iso ? DateTime.parse(dateStr).isoWeek : DateTime.parse(dateStr).week; } final formatter = DateFormat(format); From 04c0558f1927df84475c4cb3be5f63b39b553ea8 Mon Sep 17 00:00:00 2001 From: heng Date: Wed, 5 Apr 2023 15:36:25 +1000 Subject: [PATCH 3/3] - Added DateTimeRange - Included additional test to meet coverage --- lib/date_time.dart | 1 + lib/src/date_time_range.dart | 62 +++++++++ lib/src/extensions.dart | 142 ++++++++++--------- lib/src/time.dart | 10 +- lib/src/week.dart | 44 +++--- test/date_range_test.dart | 1 + test/date_test.dart | 52 +++++++ test/date_time_range_test.dart | 109 +++++++++++++++ test/extensions_test.dart | 240 +++++++++++++++++++++++++++++++++ test/time_range_test.dart | 46 +++++++ test/time_test.dart | 50 ++++++- test/week_test.dart | 54 ++++++++ test/year_test.dart | 14 ++ 13 files changed, 729 insertions(+), 96 deletions(-) create mode 100644 lib/src/date_time_range.dart create mode 100644 test/date_time_range_test.dart create mode 100644 test/extensions_test.dart diff --git a/lib/date_time.dart b/lib/date_time.dart index 1239680..02d2c96 100644 --- a/lib/date_time.dart +++ b/lib/date_time.dart @@ -3,6 +3,7 @@ library date_time; export 'src/date.dart'; export 'src/date_range.dart'; export 'src/date_time_error.dart'; +export 'src/date_time_range.dart'; export 'src/extensions.dart'; export 'src/month.dart'; export 'src/overflowed_time.dart'; diff --git a/lib/src/date_time_range.dart b/lib/src/date_time_range.dart new file mode 100644 index 0000000..8ad4fcf --- /dev/null +++ b/lib/src/date_time_range.dart @@ -0,0 +1,62 @@ +import 'package:date_time/src/extensions.dart'; + +/// +class DateTimeRange { + /// + DateTimeRange({ + required this.start, + required this.end, + }) : assert(!start.isAfter(end)); + + /// + final DateTime start; + + /// + final DateTime end; + + /// + bool get isValid => start <= end; + + /// + Duration get duration => end.difference(start); + + @override + bool operator ==(Object other) => + other is DateTimeRange && other.start == start && other.end == end; + + @override + int get hashCode => Object.hash(start, end); + + @override + String toString() => '$start - $end'; + + /// + DateTimeRange expand(DateTime datetime) { + if (datetime.isBefore(start)) { + return copyWith(start: datetime); + } else if (datetime.isAfter(end)) { + return copyWith(end: datetime); + } + return this; + } + + /// + DateTimeRange expandOther(DateTimeRange other) { + if (other.start.isBefore(start) && other.end.isAfter(end)) { + return copyWith(start: other.start, end: other.end); + } else if (other.start.isBefore(start)) { + return copyWith(start: other.start); + } else if (other.end.isAfter(end)) { + return copyWith(end: other.end); + } + return this; + } + + /// + DateTimeRange copyWith({DateTime? start, DateTime? end}) { + return DateTimeRange( + start: start ?? this.start, + end: end ?? this.end, + ); + } +} diff --git a/lib/src/extensions.dart b/lib/src/extensions.dart index d46e1f1..4f41e37 100644 --- a/lib/src/extensions.dart +++ b/lib/src/extensions.dart @@ -1,4 +1,5 @@ import 'package:date_time/src/date.dart'; +import 'package:date_time/src/date_time_range.dart'; import 'package:date_time/src/month.dart'; import 'package:date_time/src/time.dart'; import 'package:date_time/src/week.dart'; @@ -75,7 +76,7 @@ extension DateTimeExtensions on DateTime { return year + 1; } - return woy; + return year; } int get _ordinalDate { @@ -99,6 +100,12 @@ extension DateTimeExtensions on DateTime { bool isOutsideRange(DateTime start, DateTime end) => !isWithinRange(start, end); + /// Check if the date is within [range] + bool isWithin(DateTimeRange range) => isWithinRange(range.start, range.end); + + /// Check if the date is out side [range] + bool isOutside(DateTimeRange range) => !isWithin(range); + /// Check if a date is [equals] to other bool isEqual(DateTime other) => this == other; @@ -131,84 +138,77 @@ extension DateTimeExtensions on DateTime { /// Add a certain amount of days to this date DateTime addDays(int amount, {bool ignoreDaylightSavings = false}) => ignoreDaylightSavings - ? DateTime(year, month, day + amount, hour, minute, second, - millisecond, microsecond) + ? copyWith(day: day + amount) : add(Duration(days: amount)); /// Add a certain amount of hours to this date DateTime addHours(int amount, {bool ignoreDaylightSavings = false}) => ignoreDaylightSavings - ? DateTime(year, month, day, hour + amount, minute, second, - millisecond, microsecond) + ? copyWith(hour: hour + amount) : add(Duration(hours: amount)); /// Add a certain amount of minutes to this date DateTime addMinutes(int amount, {bool ignoreDaylightSavings = false}) => ignoreDaylightSavings - ? DateTime(year, month, day, hour, minute + amount, second, - millisecond, microsecond) + ? copyWith(minute: minute + amount) : add(Duration(minutes: amount)); - /// Add a certain amount of milliseconds to this date - DateTime addMilliseconds(int amount) => add(Duration(milliseconds: amount)); - - /// Add a certain amount of microseconds to this date - DateTime addMicroseconds(int amount) => add(Duration(microseconds: amount)); - /// Add a certain amount of seconds to this date DateTime addSeconds(int amount, {bool ignoreDaylightSavings = false}) => ignoreDaylightSavings - ? DateTime(year, month, day, hour, minute, second + amount, - millisecond, microsecond) + ? copyWith(second: second + amount) : add(Duration(seconds: amount)); + /// Add a certain amount of milliseconds to this date + DateTime addMilliseconds(int amount, {bool ignoreDaylightSavings = false}) => + ignoreDaylightSavings + ? copyWith(millisecond: millisecond + amount) + : add(Duration(milliseconds: amount)); + + /// Add a certain amount of microseconds to this date + DateTime addMicroseconds(int amount, {bool ignoreDaylightSavings = false}) => + ignoreDaylightSavings + ? copyWith(microsecond: microsecond + amount) + : add(Duration(microseconds: amount)); + + /// Add a certain amount of weeks to this date + DateTime addWeeks(int amount) => addDays(amount * 7); + /// Add a certain amount of months to this date DateTime addMonths(int amount) => copyWith(month: month + amount); /// Add a certain amount of quarters to this date DateTime addQuarters(int amount) => addMonths(amount * 3); - /// Add a certain amount of weeks to this date - DateTime addWeeks(int amount) => addDays(amount * 7); - /// Add a certain amount of years to this date DateTime addYears(int amount) => copyWith(year: year + amount); /// Subtract a certain amount of days from this date DateTime subDays(int amount, {bool ignoreDaylightSavings = false}) => - ignoreDaylightSavings - ? DateTime(year, month, day - amount, hour, minute, second, - millisecond, microsecond) - : subtract(Duration(days: amount)); + addDays(-amount, ignoreDaylightSavings: ignoreDaylightSavings); /// Subtract a certain amount of hours from this date DateTime subHours(int amount, {bool ignoreDaylightSavings = false}) => - ignoreDaylightSavings - ? DateTime(year, month, day, hour - amount, minute, second, - millisecond, microsecond) - : subtract(Duration(hours: amount)); + addHours(-amount, ignoreDaylightSavings: ignoreDaylightSavings); /// Subtract a certain amount of minutes from this date DateTime subMinutes(int amount, {bool ignoreDaylightSavings = false}) => - ignoreDaylightSavings - ? DateTime(year, month, day, hour, minute - amount, second, - millisecond, microsecond) - : subtract(Duration(minutes: amount)); + addMinutes(-amount, ignoreDaylightSavings: ignoreDaylightSavings); + + /// Subtract a certain amount of seconds from this date + DateTime subSeconds(int amount, {bool ignoreDaylightSavings = false}) => + addSeconds(-amount, ignoreDaylightSavings: ignoreDaylightSavings); /// Subtract a certain amount of milliseconds to this date - DateTime subMilliseconds(int amount) => - subtract(Duration(milliseconds: amount)); + DateTime subMilliseconds(int amount, {bool ignoreDaylightSavings = false}) => + addMilliseconds(-amount, ignoreDaylightSavings: ignoreDaylightSavings); /// Subtract a certain amount of microseconds from this date - DateTime subMicroseconds(int amount) => - subtract(Duration(microseconds: amount)); + DateTime subMicroseconds(int amount, {bool ignoreDaylightSavings = false}) => + addMicroseconds(-amount, ignoreDaylightSavings: ignoreDaylightSavings); - /// Subtract a certain amount of seconds from this date - DateTime subSeconds(int amount, {bool ignoreDaylightSavings = false}) => - ignoreDaylightSavings - ? DateTime(year, month, day, hour, minute, second - amount, - millisecond, microsecond) - : subtract(Duration(seconds: amount)); + /// Subtract a certain amount of weeks from this date + DateTime subWeeks(int amount) => addDays(-amount * 7); /// Subtract a certain amount of months from this date DateTime subMonths(int amount) => copyWith(month: month - amount); @@ -216,20 +216,9 @@ extension DateTimeExtensions on DateTime { /// Subtract a certain amount of quarters from this date DateTime subQuarters(int amount) => addMonths(-amount * 3); - /// Subtract a certain amount of weeks from this date - DateTime subWeeks(int amount) => addDays(-amount * 7); - /// Subtract a certain amount of years from this date DateTime subYears(int amount) => copyWith(year: year - amount); - ///////////////////////////////////// KEY - - /// Convert [DateTime] to a unique id - String get key => toIso8601String(); - - /// Convert a unique id to [DateTime] - static DateTime? fromKey(String key) => DateTime.tryParse(key); - /// Short-hand to DateFormat String format([String? pattern, String? locale]) { return DateFormat(pattern, locale).format(this); @@ -241,7 +230,12 @@ extension DateTimeExtensions on DateTime { /// Return the end of a day for this date. The result will be in the local timezone. DateTime get endOfDay => copyWith( - hour: 23, minute: 59, second: 59, millisecond: 999, microsecond: 999); + hour: 23, + minute: 59, + second: 59, + millisecond: 999, + microsecond: 999, + ); /// Convenient copy with method DateTime copyWith({ @@ -254,27 +248,29 @@ extension DateTimeExtensions on DateTime { int? millisecond, int? microsecond, }) { - return isUtc - ? DateTime.utc( - year ?? this.year, - month ?? this.month, - day ?? this.day, - hour ?? this.hour, - minute ?? this.minute, - second ?? this.second, - millisecond ?? this.millisecond, - microsecond ?? this.microsecond, - ) - : DateTime( - year ?? this.year, - month ?? this.month, - day ?? this.day, - hour ?? this.hour, - minute ?? this.minute, - second ?? this.second, - millisecond ?? this.millisecond, - microsecond ?? this.microsecond, - ); + if (isUtc) { + return DateTime.utc( + year ?? this.year, + month ?? this.month, + day ?? this.day, + hour ?? this.hour, + minute ?? this.minute, + second ?? this.second, + millisecond ?? this.millisecond, + microsecond ?? this.microsecond, + ); + } else { + return DateTime( + year ?? this.year, + month ?? this.month, + day ?? this.day, + hour ?? this.hour, + minute ?? this.minute, + second ?? this.second, + millisecond ?? this.millisecond, + microsecond ?? this.microsecond, + ); + } } } diff --git a/lib/src/time.dart b/lib/src/time.dart index 3c8c47c..1c6b994 100644 --- a/lib/src/time.dart +++ b/lib/src/time.dart @@ -84,7 +84,15 @@ class Time { } /// Convert to [DateTime] - DateTime get asDateTime => DateTime(1970).copyWith( + DateTime get asDateTime => DateTime.fromMillisecondsSinceEpoch(0).copyWith( + hour: hour, + minute: minute, + second: second, + millisecond: millisecond, + ); + + /// Convert to [DateTime] with the given [date] + DateTime withDate(Date date) => date.asDateTime.copyWith( hour: hour, minute: minute, second: second, diff --git a/lib/src/week.dart b/lib/src/week.dart index 23d5c21..e2e121d 100644 --- a/lib/src/week.dart +++ b/lib/src/week.dart @@ -38,13 +38,15 @@ enum DAY { /// Returns the day of week title based on locale String title([String? locale]) { - final dayOfWeek = Week.firstISOWeekOfEpoch.startOfWeek.addDays(number - 1).asDateTime; + final dayOfWeek = + Week.firstISOWeekOfEpoch.startOfWeek.addDays(number - 1).asDateTime; return DateFormat('EEEE', locale).format(dayOfWeek); } /// Return the abbreviation of day - useful for calendar views. String shortTitle([String? locale]) { - final dayOfWeek = Week.firstISOWeekOfEpoch.startOfWeek.addDays(number - 1).asDateTime; + final dayOfWeek = + Week.firstISOWeekOfEpoch.startOfWeek.addDays(number - 1).asDateTime; return DateFormat('E', locale).format(dayOfWeek); } @@ -56,30 +58,22 @@ enum DAY { /// A convenient class to work with only Week class Week { - /// A number representing the year this week belongs to - // final int year; - - /// A number representing the week (currently only iso week is supported) - // final int week; - /// The start of the week, must be sunday or monday final Date startOfWeek; - /// A flag to determine if this week should be treated as iso week (starts monday). - /// By default, iso week is used. To change this, pass a false value to the constructor. - // final bool iso; - /// Constructs the week const Week({required this.startOfWeek}); /// Const value for the first week of epoch that can be used as a default value - static const Week firstISOWeekOfEpoch = Week(startOfWeek: Date(year: 1969, month: 12, day: 29)); + static const Week firstISOWeekOfEpoch = + Week(startOfWeek: Date(year: 1969, month: 12, day: 29)); /// Const value for the first week of year 1 that can be used as a default value static const Week firstISOWeekOfTime = Week(startOfWeek: Date.startOfTime); /// Const value to signify the end of time that can be used as a default value - static const Week lastISOWeekOfTime = Week(startOfWeek: Date(year: 9999, month: 12, day: 27)); + static const Week lastISOWeekOfTime = + Week(startOfWeek: Date(year: 9999, month: 12, day: 27)); ///////////////////////////////////// FACTORIES @@ -105,7 +99,8 @@ class Week { /// When [iso] is true of absent, will return the current week starting on Monday /// When false, will return the current week starting on Sunday - factory Week.now({bool iso = true}) => iso ? Week.isoWeek(Date.now()) : Week.week(Date.now()); + factory Week.now({bool iso = true}) => + iso ? Week.isoWeek(Date.now()) : Week.week(Date.now()); /// Same as [Week.now] factory Week.thisWeek({bool iso = true}) => Week.now(iso: iso); @@ -188,13 +183,15 @@ class Week { int difference(Week other) => startOfWeek.difference(other.startOfWeek); /// Add a certain amount of weeks to this week - Week addWeeks(int amount) => Week.startWeek(startOfWeek.addWeeks(amount.abs())); + Week addWeeks(int amount) => + Week.startWeek(startOfWeek.addWeeks(amount.abs())); /// Add a certain amount of years to this week Week addYears(int amount) => addWeeks(52); /// Subtracts an amount of months from this week - Week subWeeks(int amount) => Week.startWeek(startOfWeek.subWeeks(amount.abs())); + Week subWeeks(int amount) => + Week.startWeek(startOfWeek.subWeeks(amount.abs())); /// Subtracts an amount of years from this [Date] Week subYears(int amount) => subWeeks(52); @@ -208,10 +205,12 @@ class Week { bool isBefore(Week other) => startOfWeek.isBefore(other.startOfWeek); /// Return true if other [isEqual] or [isAfter] to this date - bool isSameOrAfter(Week other) => startOfWeek.isSameOrAfter(other.startOfWeek); + bool isSameOrAfter(Week other) => + startOfWeek.isSameOrAfter(other.startOfWeek); /// Return true if other [isEqual] or [isBefore] to this date - bool isSameOrBefore(Week other) => startOfWeek.isSameOrBefore(other.startOfWeek); + bool isSameOrBefore(Week other) => + startOfWeek.isSameOrBefore(other.startOfWeek); /// Return true if week is this week bool get isThisWeek => this == Week.thisWeek(); @@ -237,7 +236,8 @@ class Week { ///////////////////////////////////// OBJECT OVERRIDES @override - bool operator ==(Object other) => other is Week && other.startOfWeek == startOfWeek; + bool operator ==(Object other) => + other is Week && other.startOfWeek == startOfWeek; @override int get hashCode => startOfWeek.hashCode; @@ -273,7 +273,9 @@ class Week { bool iso = true, }) { if (format == null || format == '') { - return iso ? DateTime.parse(dateStr).isoWeek : DateTime.parse(dateStr).week; + return iso + ? DateTime.parse(dateStr).isoWeek + : DateTime.parse(dateStr).week; } final formatter = DateFormat(format); diff --git a/test/date_range_test.dart b/test/date_range_test.dart index c1b67ab..0f7cff4 100644 --- a/test/date_range_test.dart +++ b/test/date_range_test.dart @@ -38,5 +38,6 @@ void main() { ); dateRange2.should.be(dateRange1); + dateRange2.hashCode.should.be(dateRange1.hashCode); }); } diff --git a/test/date_test.dart b/test/date_test.dart index 9d76290..e05bc33 100644 --- a/test/date_test.dart +++ b/test/date_test.dart @@ -698,4 +698,56 @@ void main() { date.copyWith(day: 13).should.be(Date(year: 2022, month: 5, day: 13)); }); }); + + group('convenience methods', () { + const start = Date(year: 2023, month: 1, day: 1); + const end = Date(year: 2023, month: 1, day: 10); + const outsideDate = Date(year: 2023, month: 1, day: 11); + + test('difference', () { + start.difference(end).should.be(-9); + end.difference(start).should.be(9); + }); + + test('startOfDay', () { + start.startOfDay.should.be(DateTime(2023, 1, 1).startOfDay); + }); + + test('endOfDay', () { + start.endOfDay.should.be(DateTime(2023, 1, 1).endOfDay); + }); + + test('compareTo', () { + start.compareTo(start).should.be(0); + start.compareTo(end).should.be(-1); + end.compareTo(start).should.be(1); + }); + + test('isWithinRange', () { + start.isWithinRange(start, end).should.be(true); + outsideDate.isWithinRange(start, end).should.be(false); + }); + + test('isOutsideRange', () { + start.isOutsideRange(start, end).should.be(false); + outsideDate.isOutsideRange(start, end).should.be(true); + }); + + test('getWeek, getMONTH', () { + // objects + start.week.should.be(Week(startOfWeek: start.startOfWeek)); + start.isoWeek.should.be(Week(startOfWeek: start.startOfISOWeek)); + start.getMonth.should.be(Month(year: 2023, month: 1)); + start.getYear.should.be(Year(2023)); + + // enums + start.getDAY.should.be(DAY.sunday); + start.getMONTH.should.be(MONTH.january); + }); + + test('key parse', () { + start.key.should.be('2023-01-01'); + Date.fromKey('2023-01-01').should.be(start); + }); + }); } diff --git a/test/date_time_range_test.dart b/test/date_time_range_test.dart new file mode 100644 index 0000000..32822dc --- /dev/null +++ b/test/date_time_range_test.dart @@ -0,0 +1,109 @@ +import 'package:date_time/date_time.dart'; +import 'package:given_when_then_unit_test/given_when_then_unit_test.dart'; +import 'package:shouldly/shouldly.dart'; +import 'package:test/scaffolding.dart'; + +void main() { + given('DateTimeRange', () { + final range = DateTimeRange( + start: DateTime(2023, 1, 2), + end: DateTime(2023, 1, 3), + ); + + then('should be valid', () { + range.isValid.should.beTrue(); + }); + + then('toString() should return datetime', () { + range + .toString() + .should + .be('2023-01-02 00:00:00.000 - 2023-01-03 00:00:00.000'); + }); + }); + + test('Two date time ranges should be equal', () { + final range1 = DateTimeRange( + start: DateTime(2023, 1, 2), + end: DateTime(2023, 1, 3), + ); + + final range2 = DateTimeRange( + start: DateTime(2023, 1, 2), + end: DateTime(2023, 1, 3), + ); + + range1.should.be(range2); + range1.hashCode.should.be(range2.hashCode); + }); + + group('copyWith', () { + final range1 = DateTimeRange( + start: DateTime(2023, 1, 2), + end: DateTime(2023, 1, 3), + ); + + test('copy start', () { + range1.copyWith(start: DateTime(2023, 1, 3)).should.be( + DateTimeRange( + start: DateTime(2023, 1, 3), + end: DateTime(2023, 1, 3), + ), + ); + }); + + test('copy end', () { + range1.copyWith(end: DateTime(2023, 1, 7)).should.be( + DateTimeRange( + start: DateTime(2023, 1, 2), + end: DateTime(2023, 1, 7), + ), + ); + }); + }); + + group('expand / expandOther', () { + final range = DateTimeRange( + start: DateTime(2023, 1, 2), + end: DateTime(2023, 1, 3), + ); + + final expandedRange = DateTimeRange( + start: DateTime(2023), + end: DateTime(2023, 1, 4), + ); + + final expandedRangeStart = DateTimeRange( + start: DateTime(2023), + end: DateTime(2023, 1, 3), + ); + + final expandedRangeEnd = DateTimeRange( + start: DateTime(2023, 1, 2), + end: DateTime(2023, 1, 4), + ); + + test('expandOther', () { + range.expandOther(expandedRange).should.be(expandedRange); + range.expandOther(expandedRangeStart).should.be(expandedRangeStart); + range.expandOther(expandedRangeEnd).should.be(expandedRangeEnd); + }); + + test('expand', () { + range.expand(DateTime(2023)).should.be(expandedRangeStart); + range.expand(DateTime(2023, 1, 4)).should.be(expandedRangeEnd); + }); + }); + + group('duration', () { + final duration = DateTime(2023, 1, 3).difference(DateTime(2023, 1, 2)); + final range = DateTimeRange( + start: DateTime(2023, 1, 2), + end: DateTime(2023, 1, 3), + ); + + test('test duration', () { + range.duration.should.be(duration); + }); + }); +} diff --git a/test/extensions_test.dart b/test/extensions_test.dart new file mode 100644 index 0000000..ca66a96 --- /dev/null +++ b/test/extensions_test.dart @@ -0,0 +1,240 @@ +import 'package:date_time/date_time.dart'; +import 'package:shouldly/shouldly.dart'; +import 'package:test/scaffolding.dart'; + +void main() { + group('DateTime extensions', () { + final datetime = DateTime(2023, 2, 2); + + test('getISOWeek', () { + datetime.getISOWeek.should.be(5); + }); + + test('getIsoWeeksInYear', () { + datetime.getISOWeeksInYear.should.be(52); + }); + + test('yearOfISOWeek', () { + datetime.yearOfISOWeek.should.be(2023); + DateTime(2023).yearOfISOWeek.should.be(2022); + }); + + test('yearOfISOWeek', () { + datetime.yearOfISOWeek.should.be(2023); + DateTime(2023).yearOfISOWeek.should.be(2022); + }); + + test('isLeapYear', () { + DateTime(2020).isLeapYear.should.be(true); + DateTime(2024).isLeapYear.should.be(true); + }); + + test('isLeapYear', () { + DateTime(2020).isLeapYear.should.be(true); + DateTime(2024).isLeapYear.should.be(true); + }); + + test('isSameYear', () { + DateTime(2020, 2, 2).isSameYear(DateTime(2020, 3, 2)).should.be(true); + DateTime(2020, 2, 2).isSameYear(DateTime(2021, 3, 2)).should.be(false); + }); + + test('isWithinRange', () { + DateTime(2020, 2, 2) + .isWithinRange(DateTime(2020), DateTime(2020, 3, 2)) + .should + .be(true); + DateTime(2021, 2, 2) + .isWithinRange(DateTime(2020), DateTime(2020, 3, 2)) + .should + .be(false); + }); + + test('isOutsideRange', () { + DateTime(2020, 2, 2) + .isOutsideRange(DateTime(2020), DateTime(2020, 3, 2)) + .should + .be(false); + DateTime(2021, 2, 2) + .isOutsideRange(DateTime(2020), DateTime(2020, 3, 2)) + .should + .be(true); + }); + }); + + group('Testing date time extension operations', () { + const oneDay = Duration(days: 1); + test('Add/subtract operation', () { + (DateTime(2023) + oneDay).should.be(DateTime(2023, 1, 2)); + (DateTime(2023) - oneDay).should.be(DateTime(2022, 12, 31)); + }); + + test('Add/sub days', () { + DateTime(2023) + .addDays(1, ignoreDaylightSavings: true) + .should + .be(DateTime(2023, 1, 2)); + DateTime(2023).subDays(1).should.be(DateTime(2022, 12, 31)); + }); + + test('Add/sub hours', () { + DateTime(2023) + .addHours(1, ignoreDaylightSavings: true) + .should + .be(DateTime(2023, 1, 1, 1)); + DateTime(2023, 1, 1, 1).subHours(1).should.be(DateTime(2023)); + }); + + test('Add/sub minutes', () { + DateTime(2023) + .addMinutes(1, ignoreDaylightSavings: true) + .should + .be(DateTime(2023, 1, 1, 0, 1)); + DateTime(2023, 1, 1, 0, 1).subMinutes(1).should.be(DateTime(2023)); + }); + + test('Add/sub seconds', () { + DateTime(2023) + .addSeconds(1, ignoreDaylightSavings: true) + .should + .be(DateTime(2023, 1, 1, 0, 0, 1)); + DateTime(2023, 1, 1, 0, 0, 1).subSeconds(1).should.be(DateTime(2023)); + }); + + test('Add/sub milliseconds', () { + DateTime(2023) + .addMilliseconds(1, ignoreDaylightSavings: true) + .should + .be(DateTime(2023, 1, 1, 0, 0, 0, 1)); + DateTime(2023, 1, 1, 0, 0, 0, 1) + .subMilliseconds(1) + .should + .be(DateTime(2023)); + }); + + test('Add/sub microseconds', () { + DateTime(2023) + .addMicroseconds(1, ignoreDaylightSavings: true) + .should + .be(DateTime(2023, 1, 1, 0, 0, 0, 0, 1)); + DateTime(2023, 1, 1, 0, 0, 0, 0, 1) + .subMicroseconds(1) + .should + .be(DateTime(2023)); + }); + + test('Add/sub months', () { + DateTime(2023).addMonths(1).should.be(DateTime(2023, 2)); + DateTime(2023, 2).subMonths(1).should.be(DateTime(2023)); + }); + + test('Add/sub quarters', () { + DateTime(2023).addQuarters(1).should.be(DateTime(2023, 4)); + DateTime(2023, 4).subQuarters(1).should.be(DateTime(2023)); + }); + + test('Add/sub weeks', () { + DateTime(2023).addWeeks(1).should.be(DateTime(2023, 1, 8)); + DateTime(2023, 1, 8).subWeeks(1).should.be(DateTime(2023)); + }); + + test('Add/sub years', () { + DateTime(2023).addYears(1).should.be(DateTime(2024)); + DateTime(2023).subYears(1).should.be(DateTime(2022)); + }); + }); + + group('DateTime copyWith', () { + final datetime = DateTime(2023); + test('copy all by 1s', () { + datetime + .copyWith( + year: 2022, + month: 1, + day: 1, + hour: 1, + minute: 1, + second: 1, + millisecond: 1, + microsecond: 1, + ) + .should + .be(DateTime(2022, 1, 1, 1, 1, 1, 1, 1)); + }); + + test('copy all by 1s in utc time', () { + DateTime.utc(2023) + .copyWith( + year: 2022, + month: 1, + day: 1, + hour: 1, + minute: 1, + second: 1, + millisecond: 1, + microsecond: 1, + ) + .should + .be(DateTime.utc(2022, 1, 1, 1, 1, 1, 1, 1)); + }); + }); + + group('convenience methods', () { + final start = DateTime(2023); + final end = DateTime(2023, 1, 10); + final outsideRange = DateTime(2023, 1, 11); + + test('get week', () { + start.week.should + .be(Week(startOfWeek: const Date(year: 2023).startOfWeek)); + }); + + test('isWithin', () { + start.isWithin(DateTimeRange(start: start, end: end)).should.be(true); + outsideRange + .isWithin(DateTimeRange(start: start, end: end)) + .should + .be(false); + }); + + test('isOutside', () { + start.isOutside(DateTimeRange(start: start, end: end)).should.be(false); + outsideRange + .isOutside(DateTimeRange(start: start, end: end)) + .should + .be(true); + }); + + test('isWithinRange', () { + start.isWithinRange(start, end).should.be(true); + outsideRange.isWithinRange(start, end).should.be(false); + }); + + test('isOutsideRange', () { + start.isOutsideRange(start, end).should.be(false); + outsideRange.isOutsideRange(start, end).should.be(true); + }); + + test('isEqual', () { + start.isEqual(DateTime(2023)).should.be(true); + start.isEqual(end).should.be(false); + }); + + test('comparison operators', () { + (start < end).should.be(true); + (start > end).should.be(false); + (start >= end).should.be(false); + (start >= end).should.be(false); + }); + }); + + group('Formatter', () { + // Not necessary since part of dart core but added for complete coverage + test('with formatter', () { + DateTime(2023, 2, 21, 6) + .format('yyyy-MM-dd HH:mm') + .should + .be('2023-02-21 06:00'); + }); + }); +} diff --git a/test/time_range_test.dart b/test/time_range_test.dart index 71270ab..d40f4cf 100644 --- a/test/time_range_test.dart +++ b/test/time_range_test.dart @@ -2,6 +2,7 @@ import 'package:date_time/date_time.dart'; import 'package:given_when_then_unit_test/given_when_then_unit_test.dart'; import 'package:shouldly/shouldly.dart'; import 'package:test/scaffolding.dart'; +import 'package:test/test.dart'; void main() { given('DateTime', () { @@ -45,4 +46,49 @@ void main() { .toString(); string.should.be('[01:00:00:000-13:00:00:123]'); }); + + test('durationInMinutes', () { + const range = TimeRange(Time(hour: 1), Time(hour: 2)); + range.durationInMinutes.should.be(60); + }); + + group('comparisons', () { + final start = DateTime.now().time; + final end = DateTime.now().add(const Duration(days: 1, minutes: 1)).time; + final range1 = TimeRange(start, end); + final range2 = TimeRange(start, end); + + test('==', () { + (range1 == range2).should.be(true); + (range1 != range2).should.be(false); + (range1.hashCode == range2.hashCode).should.be(true); + (range1.hashCode != range2.hashCode).should.be(false); + }); + + test('earlyOrSameAs', () { + range1.earlyOrSameTime(range2).should.be(true); + }); + + test('upTo', () { + range1.upTo(end).should.be(range1); + Should.throwError(() => range1.upTo(start)); + }); + + test('contains', () { + range1.contains(start).should.be(true); + range1.contains(end).should.be(true); + }); + + test('isCross', () { + range1.isCross(range2).should.be(true); + }); + + test('>=', () { + (range1 >= range2).should.be(true); + }); + + test('<=', () { + (range1 <= range2).should.be(true); + }); + }); } diff --git a/test/time_test.dart b/test/time_test.dart index 3c78876..ff276ce 100644 --- a/test/time_test.dart +++ b/test/time_test.dart @@ -8,10 +8,18 @@ void main() { test('compare time', () { const time = Time(hour: 21, minute: 01); const time2 = Time(hour: 21, minute: 01); - time.should.be(time2); }); + test('compareTo', () { + const time1 = Time(hour: 21, minute: 01); + const time2 = Time(hour: 21, minute: 01); + const time3 = Time(hour: 22, minute: 01); + time1.compareTo(time2).should.be(0); + time1.compareTo(time3).should.be(-1); + time3.compareTo(time1).should.be(1); + }); + test('compare with `isAfter`', () { final time = Time(hour: 21, minute: 01); final time2 = Time(hour: 22, minute: 01); @@ -183,8 +191,15 @@ void main() { const time = Time(hour: 1); const time1 = Time(hour: 1); const time2 = Time(hour: 2); + + test('equal', () { + (time == time1).should.beTrue(); + (time.hashCode == time1.hashCode).should.beTrue(); + }); + test('not equal', () { (time != time2).should.beTrue(); + (time.hashCode != time2.hashCode).should.beTrue(); }); test('before', () { @@ -438,4 +453,37 @@ void main() { Time.fromStr('20:33:7').should.be(Time(hour: 20, minute: 33, second: 7)); }); }); + + group('database key', () { + test('to key', () { + Time(hour: 20, minute: 33).key.should.be('20-33-00-000'); + }); + + test('from key', () { + Time.fromKey('20-33-07').should.be(Time(hour: 20, minute: 33, second: 7)); + }); + }); + + test('isWithinRange', () { + const start = Time(hour: 5); + const end = Time(hour: 10); + const outsideHour = Time(hour: 12); + start.isWithinRange(start, end).should.be(true); + outsideHour.isWithinRange(start, end).should.be(false); + }); + + test('asDateTime', () { + Time(hour: 5).asDateTime.should.be(DateTime(1970).copyWith(hour: 5)); + }); + + test('withDate', () { + Time(hour: 5) + .withDate(Date(year: 2023)) + .should + .be(DateTime(2023).copyWith(hour: 5)); + }); + + test('duration', () { + Time(hour: 5).duration.should.be(Duration(hours: 5)); + }); } diff --git a/test/week_test.dart b/test/week_test.dart index d9b387a..80c6248 100644 --- a/test/week_test.dart +++ b/test/week_test.dart @@ -25,6 +25,16 @@ void main() { final sunday2 = Date(year: 2023, month: 1, day: 8); group('Testing week comparison methods', () { + test('equals', () { + (thisWeek == Week.thisWeek()).should.beTrue(); + (lastWeek == Week.lastWeek()).should.beTrue(); + (nextWeek == Week.nextWeek()).should.beTrue(); + (thisWeek == nextWeek).should.beFalse(); + (thisWeek.hashCode == nextWeek.hashCode).should.beFalse(); + (thisWeek != nextWeek).should.beTrue(); + (thisWeek.hashCode != nextWeek.hashCode).should.beTrue(); + }); + test('Compare this week and itself (now)', () { (thisWeek == now).should.beTrue(); (thisWeek > now).should.beFalse(); @@ -79,6 +89,20 @@ void main() { thisWeek.isBefore(nextWeek).should.beTrue(); thisWeek.isSameOrBefore(nextWeek).should.beTrue(); }); + + test('boolean flags', () { + lastWeek.isThisWeek.should.beFalse(); + lastWeek.isLastWeek.should.beTrue(); + lastWeek.isNextWeek.should.beFalse(); + + thisWeek.isThisWeek.should.beTrue(); + thisWeek.isLastWeek.should.beFalse(); + thisWeek.isNextWeek.should.beFalse(); + + nextWeek.isThisWeek.should.beFalse(); + nextWeek.isLastWeek.should.beFalse(); + nextWeek.isNextWeek.should.beTrue(); + }); }); group('Testing start and end of week', () { @@ -342,4 +366,34 @@ void main() { .be(Week(startOfWeek: Date(year: 2022, month: 1, day: 1))); }); }); + + group('next / previous', () { + final week = Week(startOfWeek: Date(year: 2022, month: 1, day: 1)); + test('test next', () { + week.next.should + .be(Week(startOfWeek: Date(year: 2022, month: 1, day: 8))); + }); + + test('test previous', () { + Week(startOfWeek: Date(year: 2022, month: 1, day: 8)) + .previous + .should + .be(week); + }); + }); + + group('convenient methods', () { + final week = Week.startDateTime(DateTime(2023, 1, 2)); + test('test isISOWeek', () { + week.isISOWeek.should.be(true); + }); + + test('test startDay', () { + week.startDay.should.be(DAY.monday); + }); + + test('test endDay', () { + week.endDay.should.be(DAY.sunday); + }); + }); } diff --git a/test/year_test.dart b/test/year_test.dart index 5a14f40..434866f 100644 --- a/test/year_test.dart +++ b/test/year_test.dart @@ -64,6 +64,20 @@ void main() { thisYear.isBefore(nextYear).should.beTrue(); thisYear.isSameOrBefore(nextYear).should.beTrue(); }); + + test('Test bool for this, last and next year', () { + lastYear.isLastYear.should.beTrue(); + lastYear.isThisYear.should.beFalse(); + lastYear.isNextYear.should.beFalse(); + + thisYear.isLastYear.should.beFalse(); + thisYear.isThisYear.should.beTrue(); + thisYear.isNextYear.should.beFalse(); + + nextYear.isLastYear.should.beFalse(); + nextYear.isThisYear.should.beFalse(); + nextYear.isNextYear.should.beTrue(); + }); }); group('Testing start and end of year', () {