Module Name:    src
Committed By:   msaitoh
Date:           Fri Apr 17 10:24:09 UTC 2015

Modified Files:
        src/lib/libutil [netbsd-6]: parsedate.3 parsedate.y
        src/tests/lib/libutil [netbsd-6]: t_parsedate.c

Log Message:
Pull up following revision(s) (requested by nonaka in ticket #1291):
        tests/lib/libutil/t_parsedate.c: revision 1.4-1.13
        lib/libutil/parsedate.y: revision 1.14-1.20
        lib/libutil/parsedate.3: revision 1.11-1.14
Distinguish between a non-error result of (time_t)-1 and an error result.
Modernise the test code inside #ifdef TEST.
add rcsid
fix timezone bugs in rev.1.11 and rev.1.13.  PR/47916.
Some years don't need the "maybe add 1900 or 2000" adjustment
that was designed for handling two-digit abbreviated years.
For example, "1/2/70" still refers to the year 1970, as before,
but "70-01-02" now refers to the year 70.
* Add a new yyHaveFullYear member to struct dateinfo, to record whether
  or not the year needs to be adjusted.
* Code that parses years sets yyHaveFullYear=1 if they know that the
  year should not be adjusted (as is the case for ISO 8601 dates), or if
  they perform their own adjustment (as is the case for CVS timestamps).
* Move the year adjustment code into a new function, AdjustYear,
  instead of inline in Convert().
* Make Convert() assume the year doesn't need to be adjusted,
  and make Convert's callers first call AdjustYear() if appropriate.
A time like HH:MM:SS.sss says nothing about whether DST is on or off.
Add a new non-terminal "time_numericzone" for a time with
a numeric timezone.  Move some productions from "time" to "time_numericzone".
Increment yyHaveZone when encountering one of these.
Previously, input of the form "HH:MM:SS +ZZZZ" would not have set the
yyhaveZone flag.
Improved handling of local times.
* A magic value USE_LOCAL_TIME (defined as 99999) may be passed as the
  Timezone to Convert(), instructing it to use mktime() to work
  in the local time zone, instead of using mktime_z to work in UTC
  (and then adding the specified timezone offset).
* Some old code is removed now that there's no need to find the local
  timezone offset.
* Allow either one or both of the now and zone arguments to
  parsedate() to be NULL, treating them independently.  Previously,
  if either one was NULL, the other was ignored.
* If the zone argument is specified, then the current date is calculated
  in the specified zone, not in local time.
Also add some disabled debug code.
This should fix PR lib/47916.
Fix capitalization and typo, from Bug Hunting.
Document that errno may be used to distinguish between a
non-error result of -1 and an error.
Document that years in ISO 8601 dates are taken literally.
"69-09-10" is in the year 69, not 2069.
The tzoff argument is in minutes (behind/west of UTC), not seconds.
While here, also say that time = NULL and tzoff = NULL are independent.
The code doesn't yet implement that, but it will soon.
mention the PR# in the description like everyone else.
To be on the safe side, use the category/number notation when referring to
PRs (otherwise third-party sed-scripts might miss the references). Also
remove white-space.
* Test that parsedate("@0", ...) returns (time_t)0 regardless of timezone.
* Test that parsedate("@-1", NULL, NULL) returns (time_t)-1
  without setting errno.
Test parsedate("@-2",...) (should return -2 and not set errno);
and parsedate("@junk",...) (should return -1 and set errno).
We were already testing "@-1".
Add local parsecheck() function and use it for several tests.
Instead of just checking that parsedate(3) does not return an error,
also pass the result through localtime_r(3) or gmtime_r(3) and check the
year/month/day/hour/minute/second fields in the resulting struct tm.
Add comments for some non-obvious cases.
Also add a test with year=70, which is documented to be treated as 1970.
Adapt to a recent change in parsedate().
"9/10/69" still refers to 2069, and "9/10/70" still refers to 1970,
but "69-09-10" and "70-09-10" now refer to the years 69 and 70.
Add tests for PR lib/47916.  Some of these fail.
When tests fail, print all args, notjust the date string.
2013-07-09 was in British Summer TIme, so use tzoff=-60, not 0.


To generate a diff of this commit:
cvs rdiff -u -r1.10 -r1.10.8.1 src/lib/libutil/parsedate.3
cvs rdiff -u -r1.11.2.1 -r1.11.2.2 src/lib/libutil/parsedate.y
cvs rdiff -u -r1.3 -r1.3.2.1 src/tests/lib/libutil/t_parsedate.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/lib/libutil/parsedate.3
diff -u src/lib/libutil/parsedate.3:1.10 src/lib/libutil/parsedate.3:1.10.8.1
--- src/lib/libutil/parsedate.3:1.10	Wed Dec 22 09:12:28 2010
+++ src/lib/libutil/parsedate.3	Fri Apr 17 10:24:09 2015
@@ -1,4 +1,4 @@
-.\"     $NetBSD: parsedate.3,v 1.10 2010/12/22 09:12:28 wiz Exp $
+.\"     $NetBSD: parsedate.3,v 1.10.8.1 2015/04/17 10:24:09 msaitoh Exp $
 .\"
 .\" Copyright (c) 2006 The NetBSD Foundation, Inc.
 .\" All rights reserved.
@@ -27,7 +27,7 @@
 .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 .\" POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd December 20, 2010
+.Dd October 8, 2014
 .Dt PARSEDATE 3
 .Os
 .Sh NAME
@@ -44,17 +44,22 @@ The
 .Fn parsedate
 function parses a datetime from
 .Ar datestr
-described in english relative to an optional
+described in English relative to an optional
 .Ar time
-point and an optional timezone offset in seconds specified in
+point,
+and an optional timezone offset (in minutes behind/west of UTC)
+specified in
 .Ar tzoff .
-If either
+If
 .Ar time
-or
+is
+.Dv NULL 
+then the current time is used.
+If
 .Ar tzoff
-are
+is
 .Dv NULL ,
-then the current time and timezone offset are used.
+then the current time zone is used.
 .Pp
 The
 .Ar datestr
@@ -94,7 +99,7 @@ is unused so that it is not confused wit
 10,
 .Dv eleventh or eleven =
 11,
-.Dv twelfth or twoelve =
+.Dv twelfth or twelve =
 12.
 .Pp
 The following words are recognized in English only:
@@ -198,11 +203,14 @@ Timezone names:
 .Pp
 A variety of unambiguous dates are recognized:
 .Bl -tag -compact -width "20 Jun 1994"
-.It 69-09-10
+.It 9/10/69
 For years between 69-99 we assume 1900+ and for years between 0-68
 we assume 2000+.
 .It 2006-11-17
 An ISO-8601 date.
+.It 69-09-10
+The year in an ISO-8601 date is always taken literally,
+so this is the year 69, not 2069.
 .It 10/1/2000
 October 10, 2000; the common US format.
 .It 20 Jun 1994
@@ -210,7 +218,8 @@ October 10, 2000; the common US format.
 .It 1-sep-06
 Other common abbreviations.
 .It 1/11
-the year can be omitted
+The year can be omitted.
+This is the US month/day format.
 .El
 .Pp
 As well as times:
@@ -241,8 +250,20 @@ Tue Apr 20 03:06:49 UTC 1993
 returns the number of seconds passed since the Epoch, or
 .Dv \-1
 if the date could not be parsed properly.
+A non-error result of
+.Dv \-1
+can be distinguished from an error by setting
+.Va errno
+to
+.Dv 0
+before calling
+.Fn parsedate ,
+and checking the value of
+.Va errno
+afterwards.
 .Sh SEE ALSO
 .Xr date 1 ,
+.Xr errno 2 ,
 .Xr eeprom 8
 .Sh HISTORY
 The parser used in

Index: src/lib/libutil/parsedate.y
diff -u src/lib/libutil/parsedate.y:1.11.2.1 src/lib/libutil/parsedate.y:1.11.2.2
--- src/lib/libutil/parsedate.y:1.11.2.1	Fri Feb  8 20:30:34 2013
+++ src/lib/libutil/parsedate.y	Fri Apr 17 10:24:09 2015
@@ -12,8 +12,14 @@
 /* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */
 /* SUPPRESS 288 on yyerrlab *//* Label unused */
 
+#include <sys/cdefs.h>
+#ifdef __RCSID
+__RCSID("$NetBSD: parsedate.y,v 1.11.2.2 2015/04/17 10:24:09 msaitoh Exp $");
+#endif
+
 #include <stdio.h>
 #include <ctype.h>
+#include <errno.h>
 #include <string.h>
 #include <time.h>
 #include <util.h>
@@ -36,6 +42,7 @@
 #define HOUR(x)		((time_t)(x) * 60)
 #define SECSPERDAY	(24L * 60L * 60L)
 
+#define USE_LOCAL_TIME	99999 /* special case for Convert() and yyTimezone */
 
 /*
 **  An entry in the lexical lookup table.
@@ -63,22 +70,24 @@ typedef enum _MERIDIAN {
 
 
 struct dateinfo {
-	DSTMODE	yyDSTmode;
+	DSTMODE	yyDSTmode;	/* DST on/off/maybe */
 	time_t	yyDayOrdinal;
 	time_t	yyDayNumber;
 	int	yyHaveDate;
+	int	yyHaveFullYear;	/* if true, year is not abbreviated. */
+				/* if false, need to call AdjustYear(). */
 	int	yyHaveDay;
 	int	yyHaveRel;
 	int	yyHaveTime;
 	int	yyHaveZone;
-	time_t	yyTimezone;
-	time_t	yyDay;
-	time_t	yyHour;
-	time_t	yyMinutes;
-	time_t	yyMonth;
-	time_t	yySeconds;
-	time_t	yyYear;
-	MERIDIAN	yyMeridian;
+	time_t	yyTimezone;	/* Timezone as minutes ahead/east of UTC */
+	time_t	yyDay;		/* Day of month [1-31] */
+	time_t	yyHour;		/* Hour of day [0-24] or [1-12] */
+	time_t	yyMinutes;	/* Minute of hour [0-59] */
+	time_t	yyMonth;	/* Month of year [1-12] */
+	time_t	yySeconds;	/* Second of minute [0-60] */
+	time_t	yyYear;		/* Year, see also yyHaveFullYear */
+	MERIDIAN yyMeridian;	/* Interpret yyHour as AM/PM/24 hour clock */
 	time_t	yyRelMonth;
 	time_t	yyRelSeconds;
 };
@@ -110,6 +119,10 @@ spec	: /* NULL */
 item	: time {
 	    param->yyHaveTime++;
 	}
+	| time_numericzone {
+	    param->yyHaveTime++;
+	    param->yyHaveZone++;
+	}
 	| zone {
 	    param->yyHaveZone++;
 	}
@@ -138,6 +151,7 @@ item	: time {
 cvsstamp: tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER {
 	    param->yyYear = $1;
 	    if (param->yyYear < 100) param->yyYear += 1900;
+	    param->yyHaveFullYear = 1;
 	    param->yyMonth = $3;
 	    param->yyDay = $5;
 	    param->yyHour = $7;
@@ -148,8 +162,8 @@ cvsstamp: tUNUMBER '.' tUNUMBER '.' tUNU
 	}
 	;
 
-epochdate: AT_SIGN tUNUMBER {
-            time_t    when = $2;
+epochdate: AT_SIGN at_number {
+            time_t    when = $<Number>2;
             struct tm tmbuf;
             if (gmtime_r(&when, &tmbuf) != NULL) {
 		param->yyYear = tmbuf.tm_year + 1900;
@@ -168,11 +182,14 @@ epochdate: AT_SIGN tUNUMBER {
 		param->yyMinutes = 0;
 		param->yySeconds = 0;
 	    }
+	    param->yyHaveFullYear = 1;
 	    param->yyDSTmode = DSToff;
 	    param->yyTimezone = 0;
 	}
 	;
 
+at_number : tUNUMBER | tSNUMBER ;
+
 time	: tUNUMBER tMERIDIAN {
 	    param->yyHour = $1;
 	    param->yyMinutes = 0;
@@ -185,35 +202,35 @@ time	: tUNUMBER tMERIDIAN {
 	    param->yySeconds = 0;
 	    param->yyMeridian = $4;
 	}
-	| tUNUMBER ':' tUNUMBER tSNUMBER {
-	    param->yyHour = $1;
-	    param->yyMinutes = $3;
-	    param->yyMeridian = MER24;
-	    param->yyDSTmode = DSToff;
-	    param->yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
-	}
 	| tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
 	    param->yyHour = $1;
 	    param->yyMinutes = $3;
 	    param->yySeconds = $5;
 	    param->yyMeridian = $6;
 	}
-	| tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
+	| tUNUMBER ':' tUNUMBER ':' tUNUMBER '.' tUNUMBER {
 	    param->yyHour = $1;
 	    param->yyMinutes = $3;
 	    param->yySeconds = $5;
 	    param->yyMeridian = MER24;
+/* XXX: Do nothing with millis */
+	}
+	;
+
+time_numericzone : tUNUMBER ':' tUNUMBER tSNUMBER {
+	    param->yyHour = $1;
+	    param->yyMinutes = $3;
+	    param->yyMeridian = MER24;
 	    param->yyDSTmode = DSToff;
-	    param->yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
+	    param->yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
 	}
-	| tUNUMBER ':' tUNUMBER ':' tUNUMBER '.' tUNUMBER {
+	| tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
 	    param->yyHour = $1;
 	    param->yyMinutes = $3;
 	    param->yySeconds = $5;
 	    param->yyMeridian = MER24;
 	    param->yyDSTmode = DSToff;
-/* XXX: Do nothing with millis */
-/*	    param->yyTimezone = ($7 % 100 + ($7 / 100) * 60); */
+	    param->yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
 	}
 	;
 
@@ -264,6 +281,7 @@ date	: tUNUMBER '/' tUNUMBER {
 	| tUNUMBER tSNUMBER tSNUMBER {
 	    /* ISO 8601 format.  yyyy-mm-dd.  */
 	    param->yyYear = $1;
+	    param->yyHaveFullYear = 1;
 	    param->yyMonth = -$2;
 	    param->yyDay = -$3;
 	}
@@ -573,19 +591,36 @@ yyerror(struct dateinfo *param, const ch
 }
 
 
-/* Year is either
-   * A negative number, which means to use its absolute value (why?)
-   * A number from 0 to 99, which means a year from 1900 to 1999, or
-   * The actual year (>=100).  */
+/* Adjust year from a value that might be abbreviated, to a full value.
+ * e.g. convert 70 to 1970.
+ * Input Year is either:
+ *  - A negative number, which means to use its absolute value (why?)
+ *  - A number from 0 to 99, which means a year from 1900 to 1999, or
+ *  - The actual year (>=100).
+ * Returns the full year. */
+static time_t
+AdjustYear(time_t Year)
+{
+    /* XXX Y2K */
+    if (Year < 0)
+	Year = -Year;
+    if (Year < 70)
+	Year += 2000;
+    else if (Year < 100)
+	Year += 1900;
+    return Year;
+}
+
 static time_t
 Convert(
     time_t	Month,		/* month of year [1-12] */
     time_t	Day,		/* day of month [1-31] */
-    time_t	Year,		/* year; see above comment */
+    time_t	Year,		/* year, not abbreviated in any way */
     time_t	Hours,		/* Hour of day [0-24] */
     time_t	Minutes,	/* Minute of hour [0-59] */
     time_t	Seconds,	/* Second of minute [0-60] */
-    time_t	Timezone,	/* Timezone as seconds west of UTC */
+    time_t	Timezone,	/* Timezone as minutes east of UTC,
+				 * or USE_LOCAL_TIME special case */
     MERIDIAN	Meridian,	/* Hours are am/pm/24 hour clock */
     DSTMODE	DSTmode		/* DST on/off/maybe */
 )
@@ -593,14 +628,6 @@ Convert(
     struct tm tm = {.tm_sec = 0};
     time_t result;
 
-    /* XXX Y2K */
-    if (Year < 0)
-	Year = -Year;
-    if (Year < 70)
-	Year += 2000;
-    else if (Year < 100)
-	Year += 1900;
-
     tm.tm_sec = Seconds;
     tm.tm_min = Minutes;
     tm.tm_hour = Hours + (Meridian == MERpm ? 12 : 0);
@@ -613,9 +640,25 @@ Convert(
     default:     tm.tm_isdst = -1; break;
     }
 
-    /* We rely on mktime_z(NULL, ...) working in UTC, not in local time. */
-    result = mktime_z(NULL, &tm);
-    result -= Timezone;
+    if (Timezone == USE_LOCAL_TIME) {
+	    result = mktime(&tm);
+    } else {
+	    /* We rely on mktime_z(NULL, ...) working in UTC */
+	    result = mktime_z(NULL, &tm);
+	    result += Timezone * 60;
+    }
+
+#if PARSEDATE_DEBUG
+    fprintf(stderr, "%s(M=%jd D=%jd Y=%jd H=%jd M=%jd S=%jd Z=%jd"
+		    " mer=%d DST=%d)",
+	__func__,
+	(intmax_t)Month, (intmax_t)Day, (intmax_t)Year,
+	(intmax_t)Hours, (intmax_t)Minutes, (intmax_t)Seconds,
+	(intmax_t)Timezone, (int)Meridian, (int)DSTmode);
+    fprintf(stderr, " -> %jd", (intmax_t)result);
+    fprintf(stderr, " %s", ctime(&result));
+#endif
+
     return result;
 }
 
@@ -853,60 +896,38 @@ yylex(YYSTYPE *yylval, const char **yyIn
 
 #define TM_YEAR_ORIGIN 1900
 
-/* Yield A - B, measured in seconds.  */
-static time_t
-difftm (struct tm *a, struct tm *b)
-{
-  int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
-  int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
-  int days = (
-	      /* difference in day of year */
-	      a->tm_yday - b->tm_yday
-	      /* + intervening leap days */
-	      +  ((ay >> 2) - (by >> 2))
-	      -  (ay/100 - by/100)
-	      +  ((ay/100 >> 2) - (by/100 >> 2))
-	      /* + difference in years * 365 */
-	      +  (long)(ay-by) * 365
-	      );
-  return ((time_t)60*(60*(24*days + (a->tm_hour - b->tm_hour))
-	      + (a->tm_min - b->tm_min))
-	  + (a->tm_sec - b->tm_sec));
-}
-
 time_t
 parsedate(const char *p, const time_t *now, const int *zone)
 {
-    struct tm gmt, local, *gmt_ptr, *tm;
+    struct tm		local, *tm;
     time_t		nowt;
     int			zonet;
     time_t		Start;
     time_t		tod, rm;
     struct dateinfo	param;
+    int			saved_errno;
+    
+    saved_errno = errno;
+    errno = 0;
 
-    if (now == NULL || zone == NULL) {
+    if (now == NULL) {
         now = &nowt;
-	zone = &zonet;
 	(void)time(&nowt);
-
-	gmt_ptr = gmtime_r(now, &gmt);
+    }
+    if (zone == NULL) {
+	zone = &zonet;
+	zonet = USE_LOCAL_TIME;
 	if ((tm = localtime_r(now, &local)) == NULL)
 	    return -1;
-
-	if (gmt_ptr != NULL)
-	    zonet = difftm(&gmt, &local) / 60;
-	else
-	    /* We are on a system like VMS, where the system clock is
-	       in local time and the system has no concept of timezones.
-	       Hopefully we can fake this out (for the case in which the
-	       user specifies no timezone) by just saying the timezone
-	       is zero.  */
-	    zonet = 0;
-
-	if (local.tm_isdst)
-	    zonet += 60;
     } else {
-	if ((tm = localtime_r(now, &local)) == NULL)
+	/*
+	 * Should use the specified zone, not localtime.
+	 * Fake it using gmtime and arithmetic.
+	 * This is good enough because we use only the year/month/day,
+	 * not other fields of struct tm.
+	 */
+	time_t fake = *now + (*zone * 60);
+	if ((tm = gmtime_r(&fake, &local)) == NULL)
 	    return -1;
     }
     param.yyYear = tm->tm_year + 1900;
@@ -921,20 +942,27 @@ parsedate(const char *p, const time_t *n
     param.yyRelSeconds = 0;
     param.yyRelMonth = 0;
     param.yyHaveDate = 0;
+    param.yyHaveFullYear = 0;
     param.yyHaveDay = 0;
     param.yyHaveRel = 0;
     param.yyHaveTime = 0;
     param.yyHaveZone = 0;
 
     if (yyparse(&param, &p) || param.yyHaveTime > 1 || param.yyHaveZone > 1 ||
-	param.yyHaveDate > 1 || param.yyHaveDay > 1)
+	param.yyHaveDate > 1 || param.yyHaveDay > 1) {
+	errno = EINVAL;
 	return -1;
+    }
 
     if (param.yyHaveDate || param.yyHaveTime || param.yyHaveDay) {
+	if (! param.yyHaveFullYear) {
+		param.yyYear = AdjustYear(param.yyYear);
+		param.yyHaveFullYear = 1;
+	}
 	Start = Convert(param.yyMonth, param.yyDay, param.yyYear, param.yyHour,
 	    param.yyMinutes, param.yySeconds, param.yyTimezone,
 	    param.yyMeridian, param.yyDSTmode);
-	if (Start == -1)
+	if (Start == -1 && errno != 0)
 	    return -1;
     }
     else {
@@ -945,7 +973,7 @@ parsedate(const char *p, const time_t *n
 
     Start += param.yyRelSeconds;
     rm = RelativeMonth(Start, param.yyRelMonth, param.yyTimezone);
-    if (rm == -1)
+    if (rm == -1 && errno != 0)
 	return -1;
     Start += rm;
 
@@ -954,6 +982,8 @@ parsedate(const char *p, const time_t *n
 	Start += tod;
     }
 
+    if (errno == 0)
+	errno = saved_errno;
     return Start;
 }
 
@@ -962,21 +992,21 @@ parsedate(const char *p, const time_t *n
 
 /* ARGSUSED */
 int
-main(ac, av)
-    int		ac;
-    char	*av[];
+main(int ac, char *av[])
 {
     char	buff[128];
     time_t	d;
 
     (void)printf("Enter date, or blank line to exit.\n\t> ");
     (void)fflush(stdout);
-    while (gets(buff) && buff[0]) {
+    while (fgets(buff, sizeof(buff), stdin) && buff[0] != '\n') {
+	errno = 0;
 	d = parsedate(buff, NULL, NULL);
-	if (d == -1)
-	    (void)printf("Bad format - couldn't convert.\n");
+	if (d == -1 && errno != 0)
+	    (void)printf("Bad format - couldn't convert: %s\n",
+	        strerror(errno));
 	else
-	    (void)printf("%s", ctime(&d));
+	    (void)printf("%jd\t%s", (intmax_t)d, ctime(&d));
 	(void)printf("\t> ");
 	(void)fflush(stdout);
     }

Index: src/tests/lib/libutil/t_parsedate.c
diff -u src/tests/lib/libutil/t_parsedate.c:1.3 src/tests/lib/libutil/t_parsedate.c:1.3.2.1
--- src/tests/lib/libutil/t_parsedate.c:1.3	Sat Dec 17 19:07:34 2011
+++ src/tests/lib/libutil/t_parsedate.c	Fri Apr 17 10:24:09 2015
@@ -1,4 +1,4 @@
-/* $NetBSD: t_parsedate.c,v 1.3 2011/12/17 19:07:34 apb Exp $ */
+/* $NetBSD: t_parsedate.c,v 1.3.2.1 2015/04/17 10:24:09 msaitoh Exp $ */
 /*-
  * Copyright (c) 2010 The NetBSD Foundation, Inc.
  * All rights reserved.
@@ -29,61 +29,200 @@
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: t_parsedate.c,v 1.3 2011/12/17 19:07:34 apb Exp $");
+__RCSID("$NetBSD: t_parsedate.c,v 1.3.2.1 2015/04/17 10:24:09 msaitoh Exp $");
 
 #include <atf-c.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
 #include <util.h>
 
+/*
+ * ANY is used as a placeholder for values that do not need to be
+ * checked.  The actual value is arbitrary.  We don't use -1
+ * because some tests might want to use -1 as a literal value.
+ */
+#define ANY -30215
+
+/* parsecheck --
+ * call parsedate(), then call time_to_tm() on the result,
+ * and check that year/month/day/hour/minute/second are as expected.
+ *
+ * time_to_tm should usually be localtime_r or gmtime_r.
+ *
+ * Don't check values specified as ANY.
+ */
+static void
+parsecheck(const char *datestr, const time_t *reftime, const int *zoff,
+	struct tm * time_to_tm(const time_t *, struct tm *),
+	int year, int month, int day, int hour, int minute, int second)
+{
+	time_t t;
+	struct tm tm;
+	char argstr[128];
+
+	/*
+	 * printable version of the args.
+	 *
+	 * Note that printf("%.*d", 0, 0)) prints nothing at all,
+	 * while printf("%.*d", 1, val) prints the value as usual.
+	 */
+	snprintf(argstr, sizeof(argstr), "%s%s%s, %s%.*jd, %s%.*d",
+		/* NULL or \"<datestr>\" */
+		(datestr ? "\"" : ""),
+		(datestr ? datestr : "NULL"),
+		(datestr ? "\"" : ""),
+		/* NULL or *reftime */
+		(reftime ? "" : "NULL"),
+		(reftime ? 1 : 0), 
+		(reftime ? (intmax_t)*reftime : (intmax_t)0), 
+		/* NULL or *zoff */
+		(zoff ? "" : "NULL"),
+		(zoff ? 1 : 0), 
+		(zoff ? *zoff : 0));
+
+	ATF_CHECK_MSG((t = parsedate(datestr, reftime, zoff)) != -1,
+	    "parsedate(%s) returned -1\n", argstr);
+	ATF_CHECK(time_to_tm(&t, &tm) != NULL);
+	if (year != ANY)
+		ATF_CHECK_MSG(tm.tm_year + 1900 == year,
+		    "parsedate(%s) expected year %d got %d (+1900)\n",
+		    argstr, year, (int)tm.tm_year);
+	if (month != ANY)
+		ATF_CHECK_MSG(tm.tm_mon + 1 == month,
+		    "parsedate(%s) expected month %d got %d (+1)\n",
+		    argstr, month, (int)tm.tm_mon);
+	if (day != ANY)
+		ATF_CHECK_MSG(tm.tm_mday == day,
+		    "parsedate(%s) expected day %d got %d\n",
+		    argstr, day, (int)tm.tm_mday);
+	if (hour != ANY)
+		ATF_CHECK_MSG(tm.tm_hour == hour,
+		    "parsedate(%s) expected hour %d got %d\n",
+		    argstr, hour, (int)tm.tm_hour);
+	if (minute != ANY)
+		ATF_CHECK_MSG(tm.tm_min == minute,
+		    "parsedate(%s) expected minute %d got %d\n",
+		    argstr, minute, (int)tm.tm_min);
+	if (second != ANY)
+		ATF_CHECK_MSG(tm.tm_sec == second,
+		    "parsedate(%s) expected second %d got %d\n",
+		    argstr, second, (int)tm.tm_sec);
+}
+
 ATF_TC(dates);
 
 ATF_TC_HEAD(dates, tc)
 {
-	atf_tc_set_md_var(tc, "descr", "Test unambiguous dates");
+	atf_tc_set_md_var(tc, "descr", "Test unambiguous dates"
+	    " (PR lib/44255)");
 }
 
 ATF_TC_BODY(dates, tc)
 {
 
-//	atf_tc_expect_fail("PR lib/44255");
-	ATF_CHECK(parsedate("69-09-10", NULL, NULL) != -1);
-	ATF_CHECK(parsedate("2006-11-17", NULL, NULL) != -1);
-	ATF_CHECK(parsedate("10/1/2000", NULL, NULL) != -1);
-	ATF_CHECK(parsedate("20 Jun 1994", NULL, NULL) != -1);
-	ATF_CHECK(parsedate("23jun2001", NULL, NULL) != -1);
-	ATF_CHECK(parsedate("1-sep-06", NULL, NULL) != -1);
-	ATF_CHECK(parsedate("1/11", NULL, NULL) != -1);
-	ATF_CHECK(parsedate("1500-01-02", NULL, NULL) != -1);
-	ATF_CHECK(parsedate("9999-12-21", NULL, NULL) != -1);
+	parsecheck("9/10/69", NULL, NULL, localtime_r,
+		2069, 9, 10, 0, 0, 0); /* year < 70: add 2000 */
+	parsecheck("9/10/70", NULL, NULL, localtime_r,
+		1970, 9, 10, 0, 0, 0); /* 70 <= year < 100: add 1900 */
+	parsecheck("69-09-10", NULL, NULL, localtime_r,
+		69, 9, 10, 0, 0, 0); /* ISO8601 year remains unchanged */
+	parsecheck("70-09-10", NULL, NULL, localtime_r,
+		70, 9, 10, 0, 0, 0); /* ISO8601 year remains unchanged */
+	parsecheck("2006-11-17", NULL, NULL, localtime_r,
+		2006, 11, 17, 0, 0, 0);
+	parsecheck("10/1/2000", NULL, NULL, localtime_r,
+		2000, 10, 1, 0, 0, 0); /* month/day/year */
+	parsecheck("20 Jun 1994", NULL, NULL, localtime_r,
+		1994, 6, 20, 0, 0, 0);
+	parsecheck("23jun2001", NULL, NULL, localtime_r,
+		2001, 6, 23, 0, 0, 0);
+	parsecheck("1-sep-06", NULL, NULL, localtime_r,
+		2006, 9, 1, 0, 0, 0);
+	parsecheck("1/11", NULL, NULL, localtime_r,
+		ANY, 1, 11, 0, 0, 0); /* month/day */
+	parsecheck("1500-01-02", NULL, NULL, localtime_r,
+		1500, 1, 2, 0, 0, 0);
+	parsecheck("9999-12-21", NULL, NULL, localtime_r,
+		9999, 12, 21, 0, 0, 0);
 }
 
 ATF_TC(times);
 
 ATF_TC_HEAD(times, tc)
 {
-	atf_tc_set_md_var(tc, "descr", "Test times");
+	atf_tc_set_md_var(tc, "descr", "Test times"
+	    " (PR lib/44255)");
 }
 
 ATF_TC_BODY(times, tc)
 {
 
-//	atf_tc_expect_fail("PR lib/44255");
-	ATF_CHECK(parsedate("10:01", NULL, NULL) != -1);
-	ATF_CHECK(parsedate("10:12pm", NULL, NULL) != -1);
-	ATF_CHECK(parsedate("12:11:01.000012", NULL, NULL) != -1);
-	ATF_CHECK(parsedate("12:21-0500", NULL, NULL) != -1);
+	parsecheck("10:01", NULL, NULL, localtime_r,
+		ANY, ANY, ANY, 10, 1, 0);
+	parsecheck("10:12pm", NULL, NULL, localtime_r,
+		ANY, ANY, ANY, 22, 12, 0);
+	parsecheck("12:11:01.000012", NULL, NULL, localtime_r,
+		ANY, ANY, ANY, 12, 11, 1);
+	parsecheck("12:21-0500", NULL, NULL, gmtime_r,
+		ANY, ANY, ANY, 12+5, 21, 0);
+}
+
+ATF_TC(dsttimes);
+
+ATF_TC_HEAD(dsttimes, tc)
+{
+	atf_tc_set_md_var(tc, "descr", "Test DST transition times"
+	    " (PR lib/47916)");
+}
+
+ATF_TC_BODY(dsttimes, tc)
+{
+	struct tm tm;
+	time_t t;
+	int tzoff;
+
+	putenv(__UNCONST("TZ=EST"));
+	tzset();
+	parsecheck("12:0", NULL, NULL, localtime_r,
+		ANY, ANY, ANY, 12, 0, 0);
+
+	putenv(__UNCONST("TZ=Japan"));
+	tzset();
+	parsecheck("12:0", NULL, NULL, localtime_r,
+		ANY, ANY, ANY, 12, 0, 0);
+
+	/*
+	 * When the effective local time is Tue Jul  9 13:21:53 BST 2013,
+	 * check mktime("14:00")
+	 */
+	putenv(__UNCONST("TZ=Europe/London"));
+	tzset();
+	tm = (struct tm){
+		.tm_year = 2013-1900, .tm_mon = 7-1, .tm_mday = 9,
+		.tm_hour = 13, .tm_min = 21, .tm_sec = 53,
+		.tm_isdst = 0 };
+	t = mktime(&tm);
+	ATF_CHECK(t != (time_t)-1);
+	parsecheck("14:00", &t, NULL, localtime_r,
+		2013, 7, 9, 14, 0, 0);
+	tzoff = -60; /* British Summer Time */
+	parsecheck("14:00", &t, &tzoff, localtime_r,
+		2013, 7, 9, 14, 0, 0);
 }
 
 ATF_TC(relative);
 
 ATF_TC_HEAD(relative, tc)
 {
-	atf_tc_set_md_var(tc, "descr", "Test relative items");            
+	atf_tc_set_md_var(tc, "descr", "Test relative items"
+	    " (PR lib/44255)");
 }
 
 ATF_TC_BODY(relative, tc)
 {
 
-//	atf_tc_expect_fail("PR lib/44255");
 	ATF_CHECK(parsedate("-1 month", NULL, NULL) != -1);
 	ATF_CHECK(parsedate("last friday", NULL, NULL) != -1);
 	ATF_CHECK(parsedate("one week ago", NULL, NULL) != -1);
@@ -92,11 +231,49 @@ ATF_TC_BODY(relative, tc)
 	ATF_CHECK(parsedate("+2 years", NULL, NULL) != -1);
 }
 
+ATF_TC(atsecs);
+
+ATF_TC_HEAD(atsecs, tc)
+{
+	atf_tc_set_md_var(tc, "descr", "Test seconds past the epoch");
+}
+
+ATF_TC_BODY(atsecs, tc)
+{
+	int tzoff;
+
+	/* "@0" -> (time_t)0, regardless of timezone */
+	ATF_CHECK(parsedate("@0", NULL, NULL) == (time_t)0);
+	putenv(__UNCONST("TZ=Europe/Berlin"));
+	tzset();
+	ATF_CHECK(parsedate("@0", NULL, NULL) == (time_t)0);
+	putenv(__UNCONST("TZ=America/New_York"));
+	tzset();
+	ATF_CHECK(parsedate("@0", NULL, NULL) == (time_t)0);
+	tzoff = 0;
+	ATF_CHECK(parsedate("@0", NULL, &tzoff) == (time_t)0);
+	tzoff = 3600;
+	ATF_CHECK(parsedate("@0", NULL, &tzoff) == (time_t)0);
+	tzoff = -3600;
+	ATF_CHECK(parsedate("@0", NULL, &tzoff) == (time_t)0);
+
+	/* -1 or other negative numbers are not errors */
+	errno = 0;
+	ATF_CHECK(parsedate("@-1", NULL, &tzoff) == (time_t)-1 && errno == 0);
+	ATF_CHECK(parsedate("@-2", NULL, &tzoff) == (time_t)-2 && errno == 0);
+
+	/* junk is an error */
+	errno = 0;
+	ATF_CHECK(parsedate("@junk", NULL, NULL) == (time_t)-1 && errno != 0);
+}
+
 ATF_TP_ADD_TCS(tp)
 {
 	ATF_TP_ADD_TC(tp, dates);
 	ATF_TP_ADD_TC(tp, times);
+	ATF_TP_ADD_TC(tp, dsttimes);
 	ATF_TP_ADD_TC(tp, relative);
+	ATF_TP_ADD_TC(tp, atsecs);
 
 	return atf_no_error();
 }

Reply via email to