Clamp dates when going from long months to short months
diff --git a/pkgs/clock/lib/clock.dart b/pkgs/clock/lib/clock.dart index 933dee8..e2b4ea6 100644 --- a/pkgs/clock/lib/clock.dart +++ b/pkgs/clock/lib/clock.dart
@@ -23,6 +23,18 @@ /// A predefined instance of [Clock] that's based on system clock. const SYSTEM_CLOCK = const Clock(); +/// Days in a month. This array uses 1-based month numbers, i.e. January is +/// the 1-st element in the array, not the 0-th. +const _DAYS_IN_MONTH = + const [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + +int _daysInMonth(int year, int month) => + (month == DateTime.FEBRUARY && _isLeapYear(year)) + ? 29 : _DAYS_IN_MONTH[month]; + +bool _isLeapYear(int year) => + (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)); + /// Provides points in time relative to the current point in time, for example: /// now, 2 days ago, 4 weeks from now, etc. /// @@ -133,10 +145,13 @@ /// Return the point in time [months] ago on the same date. DateTime monthsAgo(int months) { var time = now(); + var m = (time.month - months - 1) % 12 + 1; + var y = time.year - (months + 12 - time.month) ~/ 12; + var d = time.day.clamp(1, _daysInMonth(y, m)); return new DateTime( - time.year, - time.month - months, - time.day, + y, + m, + d, time.hour, time.minute, time.second, @@ -145,7 +160,21 @@ } /// Return the point in time [months] from now on the same date. - DateTime monthsFromNow(int months) => monthsAgo(-months); + DateTime monthsFromNow(int months) { + var time = now(); + var m = (time.month + months - 1) % 12 + 1; + var y = time.year + (months + time.month - 1) ~/ 12; + var d = time.day.clamp(1, _daysInMonth(y, m)); + return new DateTime( + y, + m, + d, + time.hour, + time.minute, + time.second, + time.millisecond + ); + } /// Return the point in time [years] ago on the same date. DateTime yearsAgo(int years) {
diff --git a/pkgs/clock/test/clock_test.dart b/pkgs/clock/test/clock_test.dart index a9e3bc6..bacb8e3 100644 --- a/pkgs/clock/test/clock_test.dart +++ b/pkgs/clock/test/clock_test.dart
@@ -17,9 +17,19 @@ import 'package:unittest/unittest.dart'; import 'package:quiver/time.dart'; +Clock from(int y, int m, int d) => new Clock.fixed(new DateTime(y, m, d)); + +expectDate(DateTime date, int y, int m, int d) { + expect(date, new DateTime(y, m, d)); +} + main() { group('clock', () { - var subject = new Clock.fixed(new DateTime(2013)); + Clock subject; + + setUp(() { + subject = new Clock.fixed(new DateTime(2013)); + }); test("should return a non-null value from system clock", () { expect(new Clock().now(), isNotNull); @@ -162,6 +172,30 @@ new DateTime(2013, 5, 1, 0, 0, 0, 0)); }); + test("should go from 2013-05-31 to 2012-11-30", () { + expectDate(from(2013, 5, 31).monthsAgo(6), 2012, 11, 30); + }); + + test("should go from 2013-03-31 to 2013-02-28 (common year)", () { + expectDate(from(2013, 3, 31).monthsAgo(1), 2013, 2, 28); + }); + + test("should go from 2013-05-31 to 2013-02-28 (common year)", () { + expectDate(from(2013, 5, 31).monthsAgo(3), 2013, 2, 28); + }); + + test("should go from 2004-03-31 to 2004-02-29 (leap year)", () { + expectDate(from(2004, 3, 31).monthsAgo(1), 2004, 2, 29); + }); + + test("should go from 2013-03-31 to 2013-06-30", () { + expectDate(from(2013, 3, 31).monthsFromNow(3), 2013, 6, 30); + }); + + test("should go from 2003-12-31 to 2004-02-29 (common to leap)", () { + expectDate(from(2003, 12, 31).monthsFromNow(2), 2004, 2, 29); + }); + test("should return time years ago on the same date", () { expect(subject.yearsAgo(1), new DateTime(2012, 1, 1, 0, 0, 0, 000)); // leap year