svn commit: r337332 - in head/bin/date: . tests

Conrad Meyer cem at FreeBSD.org
Sat Aug 4 21:54:32 UTC 2018


Author: cem
Date: Sat Aug  4 21:54:30 2018
New Revision: 337332
URL: https://svnweb.freebsd.org/changeset/base/337332

Log:
  date(1): Add ISO 8601 formatting option
  
  The new flag is named '-I'.  It is documented in the manual page and covered
  by basic unit tests.

Modified:
  head/bin/date/date.1
  head/bin/date/date.c
  head/bin/date/tests/format_string_test.sh

Modified: head/bin/date/date.1
==============================================================================
--- head/bin/date/date.1	Sat Aug  4 21:41:10 2018	(r337331)
+++ head/bin/date/date.1	Sat Aug  4 21:54:30 2018	(r337332)
@@ -32,7 +32,7 @@
 .\"     @(#)date.1	8.3 (Berkeley) 4/28/95
 .\" $FreeBSD$
 .\"
-.Dd June 1, 2018
+.Dd August 4, 2018
 .Dt DATE 1
 .Os
 .Sh NAME
@@ -64,6 +64,13 @@
 .Nm
 .Op Fl d Ar dst
 .Op Fl t Ar minutes_west
+.Nm
+.Op Fl jnu
+.Op Fl I Ns Op Ar FMT
+.Op Fl f Ar input_fmt
+.Op Fl r Ar ...
+.Op Fl v Ar ...
+.Op Ar new_date
 .Sh DESCRIPTION
 When invoked without arguments, the
 .Nm
@@ -113,6 +120,33 @@ provided rather than using the default
 format.
 Parsing is done using
 .Xr strptime 3 .
+.It Fl I Ns Op Ar FMT
+Use
+.St -iso8601
+output format.
+.Ar FMT
+may be omitted, in which case the default is
+.Sq date .
+Valid
+.Ar FMT
+values are
+.Sq date ,
+.Sq hours ,
+.Sq minutes ,
+and
+.Sq seconds .
+The date and time is formatted to the specified precision.
+When
+.Ar FMT
+is
+.Sq hours
+(or the more precise
+.Sq minutes
+or
+.Sq seconds ) ,
+the
+.St -iso8601
+format includes the timezone.
 .It Fl j
 Do not try to set the date.
 This allows you to use the
@@ -401,6 +435,14 @@ sets the time to
 .Li "2:32 PM" ,
 without modifying the date.
 .Pp
+The command
+.Pp
+.Dl "TZ=America/Los_Angeles date -Iseconds -r 1533415339"
+.Pp
+will display
+.Pp
+.Dl "2018-08-04T13:42:19-07:00"
+.Pp
 Finally the command:
 .Pp
 .Dl "date -j -f ""%a %b %d %T %Z %Y"" ""`date`"" ""+%s"""
@@ -425,6 +467,19 @@ between
 and
 .Xr timed 8
 fails.
+.Pp
+It is invalid to combine the
+.Fl I
+flag with either
+.Fl R
+or an output format
+.Dq ( + Ns ... )
+operand.
+If this occurs,
+.Nm
+prints:
+.Ql multiple output formats specified
+and exits with an error status.
 .Sh SEE ALSO
 .Xr locale 1 ,
 .Xr gettimeofday 2 ,
@@ -443,12 +498,22 @@ The
 utility is expected to be compatible with
 .St -p1003.2 .
 The
-.Fl d , f , j , n , r , t ,
+.Fl d , f , I , j , n , r , t ,
 and
 .Fl v
 options are all extensions to the standard.
+.Pp
+The format selected by the
+.Fl I
+flag is compatible with
+.St -iso8601 .
 .Sh HISTORY
 A
 .Nm
 command appeared in
 .At v1 .
+.Pp
+The
+.Fl I
+flag was added in
+.Fx 12.0 .

Modified: head/bin/date/date.c
==============================================================================
--- head/bin/date/date.c	Sat Aug  4 21:41:10 2018	(r337331)
+++ head/bin/date/date.c	Sat Aug  4 21:54:30 2018	(r337332)
@@ -51,6 +51,7 @@ __FBSDID("$FreeBSD$");
 #include <ctype.h>
 #include <err.h>
 #include <locale.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -68,10 +69,25 @@ __FBSDID("$FreeBSD$");
 static time_t tval;
 int retval;
 
-static void setthetime(const char *, const char *, int, int);
 static void badformat(void);
+static void iso8601_usage(const char *);
+static void multipleformats(void);
+static void printdate(const char *);
+static void printisodate(struct tm *);
+static void setthetime(const char *, const char *, int, int);
 static void usage(void);
 
+static const struct iso8601_fmt {
+	const char *refname;
+	const char *format_string;
+} iso8601_fmts[] = {
+	{ "date", "%Y-%m-%d" },
+	{ "hours", "T%H" },
+	{ "minutes", ":%M" },
+	{ "seconds", ":%S" },
+};
+static const struct iso8601_fmt *iso8601_selected;
+
 static const char *rfc2822_format = "%a, %d %b %Y %T %z";
 
 int
@@ -79,7 +95,7 @@ main(int argc, char *argv[])
 {
 	struct timezone tz;
 	int ch, rflag;
-	int jflag, nflag, Rflag;
+	bool Iflag, jflag, nflag, Rflag;
 	const char *format;
 	char buf[1024];
 	char *endptr, *fmt;
@@ -89,15 +105,16 @@ main(int argc, char *argv[])
 	const struct vary *badv;
 	struct tm *lt;
 	struct stat sb;
+	size_t i;
 
 	v = NULL;
 	fmt = NULL;
 	(void) setlocale(LC_TIME, "");
 	tz.tz_dsttime = tz.tz_minuteswest = 0;
 	rflag = 0;
-	jflag = nflag = Rflag = 0;
+	Iflag = jflag = nflag = Rflag = 0;
 	set_timezone = 0;
-	while ((ch = getopt(argc, argv, "d:f:jnRr:t:uv:")) != -1)
+	while ((ch = getopt(argc, argv, "d:f:I::jnRr:t:uv:")) != -1)
 		switch((char)ch) {
 		case 'd':		/* daylight savings time */
 			tz.tz_dsttime = strtol(optarg, &endptr, 10) ? 1 : 0;
@@ -108,6 +125,22 @@ main(int argc, char *argv[])
 		case 'f':
 			fmt = optarg;
 			break;
+		case 'I':
+			if (Rflag)
+				multipleformats();
+			Iflag = 1;
+			if (optarg == NULL) {
+				iso8601_selected = iso8601_fmts;
+				break;
+			}
+			for (i = 0; i < nitems(iso8601_fmts); i++)
+				if (strcmp(optarg, iso8601_fmts[i].refname) == 0)
+					break;
+			if (i == nitems(iso8601_fmts))
+				iso8601_usage(optarg);
+
+			iso8601_selected = &iso8601_fmts[i];
+			break;
 		case 'j':
 			jflag = 1;	/* don't set time */
 			break;
@@ -115,6 +148,8 @@ main(int argc, char *argv[])
 			nflag = 1;
 			break;
 		case 'R':		/* RFC 2822 datetime format */
+			if (Iflag)
+				multipleformats();
 			Rflag = 1;
 			break;
 		case 'r':		/* user specified seconds */
@@ -163,6 +198,8 @@ main(int argc, char *argv[])
 
 	/* allow the operands in any order */
 	if (*argv && **argv == '+') {
+		if (Iflag)
+			multipleformats();
 		format = *argv + 1;
 		++argv;
 	}
@@ -173,8 +210,11 @@ main(int argc, char *argv[])
 	} else if (fmt != NULL)
 		usage();
 
-	if (*argv && **argv == '+')
+	if (*argv && **argv == '+') {
+		if (Iflag)
+			multipleformats();
 		format = *argv + 1;
+	}
 
 	lt = localtime(&tval);
 	if (lt == NULL)
@@ -188,6 +228,9 @@ main(int argc, char *argv[])
 	}
 	vary_destroy(v);
 
+	if (Iflag)
+		printisodate(lt);
+
 	if (format == rfc2822_format)
 		/*
 		 * When using RFC 2822 datetime format, don't honor the
@@ -196,12 +239,40 @@ main(int argc, char *argv[])
 		setlocale(LC_TIME, "C");
 
 	(void)strftime(buf, sizeof(buf), format, lt);
+	printdate(buf);
+}
+
+static void
+printdate(const char *buf)
+{
 	(void)printf("%s\n", buf);
 	if (fflush(stdout))
 		err(1, "stdout");
 	exit(retval);
 }
 
+static void
+printisodate(struct tm *lt)
+{
+	const struct iso8601_fmt *it;
+	char fmtbuf[32], buf[32], tzbuf[8];
+
+	fmtbuf[0] = 0;
+	for (it = iso8601_fmts; it <= iso8601_selected; it++)
+		strlcat(fmtbuf, it->format_string, sizeof(fmtbuf));
+
+	(void)strftime(buf, sizeof(buf), fmtbuf, lt);
+
+	if (iso8601_selected > iso8601_fmts) {
+		(void)strftime(tzbuf, sizeof(tzbuf), "%z", lt);
+		memmove(&tzbuf[4], &tzbuf[3], 3);
+		tzbuf[3] = ':';
+		strlcat(buf, tzbuf, sizeof(buf));
+	}
+
+	printdate(buf);
+}
+
 #define	ATOI2(s)	((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
 
 static void
@@ -327,12 +398,27 @@ badformat(void)
 }
 
 static void
+iso8601_usage(const char *badarg)
+{
+	errx(1, "invalid argument '%s' for -I", badarg);
+}
+
+static void
+multipleformats(void)
+{
+	errx(1, "multiple output formats specified");
+}
+
+static void
 usage(void)
 {
-	(void)fprintf(stderr, "%s\n%s\n",
-	    "usage: date [-jnRu] [-d dst] [-r seconds] [-t west] "
-	    "[-v[+|-]val[ymwdHMS]] ... ",
+	(void)fprintf(stderr, "%s\n%s\n%s\n",
+	    "usage: date [-jnRu] [-d dst] [-r seconds|file] [-t west] "
+	    "[-v[+|-]val[ymwdHMS]]",
 	    "            "
-	    "[-f fmt date | [[[[[cc]yy]mm]dd]HH]MM[.ss]] [+format]");
+	    "[-I[date | hours | minutes | seconds]]",
+	    "            "
+	    "[-f fmt date | [[[[[cc]yy]mm]dd]HH]MM[.ss]] [+format]"
+	    );
 	exit(1);
 }

Modified: head/bin/date/tests/format_string_test.sh
==============================================================================
--- head/bin/date/tests/format_string_test.sh	Sat Aug  4 21:41:10 2018	(r337331)
+++ head/bin/date/tests/format_string_test.sh	Sat Aug  4 21:54:30 2018	(r337332)
@@ -48,6 +48,55 @@ ${desc}_test_body() {
 	atf_add_test_case ${desc}_test
 }
 
+iso8601_check()
+{
+	local arg flags exp_output_1 exp_output_2
+
+	arg="${1}"
+	flags="${2}"
+	exp_output_1="${3}"
+	exp_output_2="${4}"
+
+	atf_check -o "inline:${exp_output_1}\n" \
+	    date $flags -r ${TEST1} "-I${arg}"
+	atf_check -o "inline:${exp_output_2}\n" \
+	    date $flags -r ${TEST2} "-I${arg}"
+}
+
+iso8601_string_test()
+{
+	local desc arg exp_output_1 exp_output_2 flags
+
+	desc="${1}"
+	arg="${2}"
+	flags="${3}"
+	exp_output_1="${4}"
+	exp_output_2="${5}"
+
+	atf_test_case iso8601_${desc}_test
+	eval "
+iso8601_${desc}_test_body() {
+	iso8601_check '${arg}' '${flags}' '${exp_output_1}' '${exp_output_2}'
+}"
+	atf_add_test_case iso8601_${desc}_test
+
+	if [ -z "$flags" ]; then
+	    atf_test_case iso8601_${desc}_parity
+	    eval "
+iso8601_${desc}_parity_body() {
+	local exp1 exp2
+
+	atf_require_prog gdate
+
+	exp1=\"\$(gdate --date '@${TEST1}' '-I${arg}')\"
+	exp2=\"\$(gdate --date '@${TEST2}' '-I${arg}')\"
+
+	iso8601_check '${arg}' '' \"\${exp1}\" \"\${exp2}\"
+}"
+	    atf_add_test_case iso8601_${desc}_parity
+	fi
+}
+
 atf_init_test_cases()
 {
 	format_string_test A A Saturday Monday
@@ -89,4 +138,12 @@ atf_init_test_cases()
 	format_string_test z z +0000 +0000
 	format_string_test percent % % %
 	format_string_test plus + "Sat Feb  7 07:04:03 UTC 1970" "Mon Nov 12 21:20:00 UTC 2001"
+
+	iso8601_string_test default "" "" "1970-02-07" "2001-11-12"
+	iso8601_string_test date date "" "1970-02-07" "2001-11-12"
+	iso8601_string_test hours hours "" "1970-02-07T07+00:00" "2001-11-12T21+00:00"
+	iso8601_string_test minutes minutes "" "1970-02-07T07:04+00:00" "2001-11-12T21:20+00:00"
+	iso8601_string_test seconds seconds "" "1970-02-07T07:04:03+00:00" "2001-11-12T21:20:00+00:00"
+	# BSD date(1) does not support fractional seconds at this time.
+	#iso8601_string_test ns ns "" "1970-02-07T07:04:03,000000000+00:00" "2001-11-12T21:20:00,000000000+00:00"
 }


More information about the svn-src-head mailing list