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.
This commit is contained in:
parent
7997bd2bb8
commit
627fbf85f7
@ -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 .
|
||||
|
104
bin/date/date.c
104
bin/date/date.c
@ -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
|
||||
@ -326,13 +397,28 @@ badformat(void)
|
||||
usage();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user