From d3e240cb4503475c3a5ccbf957ed12a135a102ce Mon Sep 17 00:00:00 2001
From: Yaroslav Tykhiy <ytykhiy@gmail.com>
Date: Mon, 9 Aug 2004 13:43:39 +0000
Subject: [PATCH] Change the behaviour of `-v' so that, e.g., stepping a month
 back on March 31 won't take you to March 2 or 3 (now the result will be the
 last day of February.)

In general, now stepping by months from the last days of the current
month A will take you to the very last day of the target month B if
B is shorter than A.

The previous version would just step to March 31 and rely on mktime(3)
to correct the date.  Despite its simplicity, such way was counter-intuitive
to users and caused pain to shell script writers.

Noticed by:	Igor Timkin <ivt at gamma dot ru>
Approved by:	brian
MFC after:	2 weeks
---
 bin/date/date.1 | 24 +++++++++++++++++++++++-
 bin/date/vary.c |  7 +++++++
 2 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/bin/date/date.1 b/bin/date/date.1
index 917c5cd75fa4..c588489603de 100644
--- a/bin/date/date.1
+++ b/bin/date/date.1
@@ -31,7 +31,7 @@
 .\"     @(#)date.1	8.3 (Berkeley) 4/28/95
 .\" $FreeBSD$
 .\"
-.Dd November 17, 1993
+.Dd August 9, 2004
 .Dt DATE 1
 .Os
 .Sh NAME
@@ -218,6 +218,22 @@ When the date is adjusted to a specific value that occurs twice
 the resulting timezone will be set so that the date matches the earlier of
 the two times.
 .Pp
+Adjusting the date by months is inherently ambiguous because
+a month is a unit of variable length depending on the current date.
+This kind of date adjustment is applied in the most intuitive way.
+First of all,
+.Nm
+tries to preserve the day of the month.
+If it is impossible because the target month is shorter than the present one,
+the last day of the target month will be the result.
+For example, using
+.Fl v No +1m
+on May 31 will adjust the date to June 30, while using the same option
+on January 30 will result in the date adjusted to the last day of February.
+This approach is also believed to make the most sense for shell scripting.
+Nevertheless, be aware that going forth and back by the same number of
+months may take you to a different date.
+.Pp
 Refer to the examples below for further details.
 .El
 .Pp
@@ -295,6 +311,12 @@ will display the last day of February in the year 2000:
 .Pp
 .Dl "Tue Feb 29 03:18:00 GMT 2000"
 .Pp
+So will do the command:
+.Pp
+.Dl "date -v30d -v3m -v0y -v-1m"
+.Pp
+because there is no such date as the 30th of February.
+.Pp
 The command:
 .Pp
 .Dl "date -v1d -v+1m -v-1d -v-fri"
diff --git a/bin/date/vary.c b/bin/date/vary.c
index a314e4823ee1..5f0123110ee3 100644
--- a/bin/date/vary.c
+++ b/bin/date/vary.c
@@ -148,6 +148,8 @@ adjyear(struct tm *t, char type, int val, int mk)
 static int
 adjmon(struct tm *t, char type, int val, int istext, int mk)
 {
+  int lmdays;
+
   if (val < 0)
     return 0;
 
@@ -195,6 +197,11 @@ adjmon(struct tm *t, char type, int val, int istext, int mk)
       t->tm_mon = --val;
   }
 
+  /* e.g., -v-1m on March, 31 is the last day of February in common sense */
+  lmdays = daysinmonth(t);
+  if (t->tm_mday > lmdays)
+    t->tm_mday = lmdays;
+
   return !mk || domktime(t, type) != -1;
 }