Bug 111077 - date(1): /bin/date -j -f "%b %Y" "Feb 2007" +%m returns 03 for Feb!!
Summary: date(1): /bin/date -j -f "%b %Y" "Feb 2007" +%m returns 03 for Feb!!
Status: Closed FIXED
Alias: None
Product: Base System
Classification: Unclassified
Component: bin (show other bugs)
Version: Unspecified
Hardware: Any Any
: Normal Affects Only Me
Assignee: freebsd-bugs (Nobody)
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2007-03-31 23:10 UTC by Ryan Pavely
Modified: 2014-10-02 15:37 UTC (History)
1 user (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Ryan Pavely 2007-03-31 23:10:05 UTC
Date input of "mmm yyyy" for Feb always returns 03.

Flaw exists across all know bsd versions, intel, amd, 64bit, not, etc.

Fix: 

Got me :>
How-To-Repeat: > # /bin/date -j -f "%b %Y" "Jan 2007" +%m
> 01
> # /bin/date -j -f "%b %Y" "Feb 2007" +%m
> 03
> # /bin/date -j -f "%m %Y" "02 2007" +%m
> 03
> # /bin/date -j -f "%m %Y" "02 2007" +%m-%b
> 03-Mar
Comment 1 Giorgos Keramidas freebsd_committer freebsd_triage 2007-04-02 03:07:05 UTC
Hmmm, not that we are in the 2nd of April, I cannot seem to reproduce
this by running /bin/date on the command line, on a 6.2-RELEASE or a
7.0-CURRENT system:

  $ date -j -f '%b %Y' 'Feb 2007' '+%m'
  02
  $

As I found out, this happens when the current value of %d when /bin/date
runs is larger than the number of days February has.  Then date(1) gets
the value of %d from the current time, and this overflows from February
into March (i.e. if you ran the tests on the same day that you submitted
this bug report, the value of %d was 31, which is clearly not a valid %d
value for any February).

The date utility initializes a `struct tm' structure in setthetime()
with the current value of date/time using localtime().  Then strptime()
is called with the format specified and it parses *only* the parts which
are explicitly mentioned in the format string "%b %Y".  The value of the
current day-of-the-month should be left untouched by strptime(), and it
is.  But I think that strptime() tries to rationalize an invalid value,
when it finds one.  It's easy to reproduce this by setting a breakpoint
in setthetime() while /bin/date runs, and tweaking the value of "day of
the month" which is returned by localtime() in place:

% keramida@kobe:/home/keramida/tmp/date$ gdb date
% GNU gdb 6.1.1 [FreeBSD]
% Copyright 2004 Free Software Foundation, Inc.
% GDB is free software, covered by the GNU General Public License, and you are
% welcome to change it and/or distribute copies of it under certain conditions.
% Type "show copying" to see the conditions.
% There is absolutely no warranty for GDB.  Type "show warranty" for details.
% This GDB was configured as "i386-marcel-freebsd"...No symbol table is loaded.  Use the "file" command.
%
% (gdb) b setthetime
% Breakpoint 1 at 0x8049213: file date.c, line 189.
% (gdb) run -j -f "%b %Y" "Feb 2007" +%m
% Starting program: /home/keramida/tmp/date/date -j -f "%b %Y" "Feb 2007" +%m
%
% Breakpoint 1, setthetime (fmt=0xbfbfe9a7 "%b %Y", \
%     p=0xbfbfe9ad "Feb 2007", jflag=1, nflag=0) at date.c:189
% 189             if (fmt != NULL) {
% (gdb) n
% 190                     lt = localtime(&tval);
% (gdb)
% 191                     t = strptime(p, fmt, lt);
% (gdb) print *lt
% $1 = {tm_sec = 12, tm_min = 54, tm_hour = 4, tm_mday = 2, tm_mon = 3,
%       tm_year = 107, tm_wday = 1, tm_yday = 91, tm_isdst = 1,
%       tm_gmtoff = 10800, tm_zone = 0x28184270 "EEST"}
% (gdb) print lt->tm_mday =31
% $2 = 31
% (gdb) print *lt
% $3 = {tm_sec = 12, tm_min = 54, tm_hour = 4, tm_mday = 31, tm_mon = 3,
%       tm_year = 107, tm_wday = 1, tm_yday = 91, tm_isdst = 1,
%       tm_gmtoff = 10800, tm_zone = 0x28184270 "EEST"}
% (gdb) n
% 192                     if (t == NULL) {
% (gdb) c
% Continuing.
% 03
%
% Program exited normally.
% (gdb)

By asking strptime() to parse a struct tm which contains tm_mday set to
31 with a format specifier of "%b" and an input string which says "Feb"
we get "03" in the output (i.e. "March").

I don't know if strptime() should return an error in this case, instead
of trying to "overflow" into the next calendar month.  Both cases have,
arguably, a logical explanation, but the overflow case is surprising.

My own personal preference would be that strptime() returns an error in
this case.  This will certainly cause a mild disturbance when users run
/bin/date in the case you tried and get an error if the current day of
the month is more than 28 (or 29 on leap years), but when /bin/date is
used without an explicit %d value in both the format string of -j and
the input data, it runs with an underspecified input value.  Giving
input which is underspecified and getting an error is, IMHO, slightly
better than getting surprising results.

- Giorgos
Comment 2 Pedro F. Giffuni freebsd_committer freebsd_triage 2014-10-02 15:37:14 UTC
(In reply to paradox from comment #0)
> Date input of "mmm yyyy" for Feb always returns 03.
> 
> Flaw exists across all know bsd versions, intel, amd, 64bit, not, etc.
> 
> Fix: 
> 
> Got me :>
> How-To-Repeat: > # /bin/date -j -f "%b %Y" "Jan 2007" +%m
> > 01
> > # /bin/date -j -f "%b %Y" "Feb 2007" +%m
> > 03
> > # /bin/date -j -f "%m %Y" "02 2007" +%m
> > 03
> > # /bin/date -j -f "%m %Y" "02 2007" +%m-%b
> > 03-Mar

I cannot reproduce this on 11-current:

$ /bin/date -j -f "%b %Y" "Jan 2007" +%m
01
$ /bin/date -j -f "%b %Y" "Feb 2007" +%m
02
$ /bin/date -j -f "%m %Y" "02 2007" +%m
02
$ /bin/date -j -f "%m %Y" "02 2007" +%m-%b
02-Feb