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"
 }
_______________________________________________
svn-src-head@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to