Clamp dates when going from long months to short months
diff --git a/lib/clock.dart b/lib/clock.dart
index 933dee8..e2b4ea6 100644
--- a/lib/clock.dart
+++ b/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/test/clock_test.dart b/test/clock_test.dart
index a9e3bc6..bacb8e3 100644
--- a/test/clock_test.dart
+++ b/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