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
3395e43a04
commit
2c77ec5419
@ -32,7 +32,7 @@
|
|||||||
.\" @(#)date.1 8.3 (Berkeley) 4/28/95
|
.\" @(#)date.1 8.3 (Berkeley) 4/28/95
|
||||||
.\" $FreeBSD$
|
.\" $FreeBSD$
|
||||||
.\"
|
.\"
|
||||||
.Dd June 1, 2018
|
.Dd August 4, 2018
|
||||||
.Dt DATE 1
|
.Dt DATE 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@ -64,6 +64,13 @@
|
|||||||
.Nm
|
.Nm
|
||||||
.Op Fl d Ar dst
|
.Op Fl d Ar dst
|
||||||
.Op Fl t Ar minutes_west
|
.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
|
.Sh DESCRIPTION
|
||||||
When invoked without arguments, the
|
When invoked without arguments, the
|
||||||
.Nm
|
.Nm
|
||||||
@ -113,6 +120,33 @@ provided rather than using the default
|
|||||||
format.
|
format.
|
||||||
Parsing is done using
|
Parsing is done using
|
||||||
.Xr strptime 3 .
|
.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
|
.It Fl j
|
||||||
Do not try to set the date.
|
Do not try to set the date.
|
||||||
This allows you to use the
|
This allows you to use the
|
||||||
@ -401,6 +435,14 @@ sets the time to
|
|||||||
.Li "2:32 PM" ,
|
.Li "2:32 PM" ,
|
||||||
without modifying the date.
|
without modifying the date.
|
||||||
.Pp
|
.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:
|
Finally the command:
|
||||||
.Pp
|
.Pp
|
||||||
.Dl "date -j -f ""%a %b %d %T %Z %Y"" ""`date`"" ""+%s"""
|
.Dl "date -j -f ""%a %b %d %T %Z %Y"" ""`date`"" ""+%s"""
|
||||||
@ -425,6 +467,19 @@ between
|
|||||||
and
|
and
|
||||||
.Xr timed 8
|
.Xr timed 8
|
||||||
fails.
|
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
|
.Sh SEE ALSO
|
||||||
.Xr locale 1 ,
|
.Xr locale 1 ,
|
||||||
.Xr gettimeofday 2 ,
|
.Xr gettimeofday 2 ,
|
||||||
@ -443,12 +498,22 @@ The
|
|||||||
utility is expected to be compatible with
|
utility is expected to be compatible with
|
||||||
.St -p1003.2 .
|
.St -p1003.2 .
|
||||||
The
|
The
|
||||||
.Fl d , f , j , n , r , t ,
|
.Fl d , f , I , j , n , r , t ,
|
||||||
and
|
and
|
||||||
.Fl v
|
.Fl v
|
||||||
options are all extensions to the standard.
|
options are all extensions to the standard.
|
||||||
|
.Pp
|
||||||
|
The format selected by the
|
||||||
|
.Fl I
|
||||||
|
flag is compatible with
|
||||||
|
.St -iso8601 .
|
||||||
.Sh HISTORY
|
.Sh HISTORY
|
||||||
A
|
A
|
||||||
.Nm
|
.Nm
|
||||||
command appeared in
|
command appeared in
|
||||||
.At v1 .
|
.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 <ctype.h>
|
||||||
#include <err.h>
|
#include <err.h>
|
||||||
#include <locale.h>
|
#include <locale.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@ -68,10 +69,25 @@ __FBSDID("$FreeBSD$");
|
|||||||
static time_t tval;
|
static time_t tval;
|
||||||
int retval;
|
int retval;
|
||||||
|
|
||||||
static void setthetime(const char *, const char *, int, int);
|
|
||||||
static void badformat(void);
|
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 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";
|
static const char *rfc2822_format = "%a, %d %b %Y %T %z";
|
||||||
|
|
||||||
int
|
int
|
||||||
@ -79,7 +95,7 @@ main(int argc, char *argv[])
|
|||||||
{
|
{
|
||||||
struct timezone tz;
|
struct timezone tz;
|
||||||
int ch, rflag;
|
int ch, rflag;
|
||||||
int jflag, nflag, Rflag;
|
bool Iflag, jflag, nflag, Rflag;
|
||||||
const char *format;
|
const char *format;
|
||||||
char buf[1024];
|
char buf[1024];
|
||||||
char *endptr, *fmt;
|
char *endptr, *fmt;
|
||||||
@ -89,15 +105,16 @@ main(int argc, char *argv[])
|
|||||||
const struct vary *badv;
|
const struct vary *badv;
|
||||||
struct tm *lt;
|
struct tm *lt;
|
||||||
struct stat sb;
|
struct stat sb;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
v = NULL;
|
v = NULL;
|
||||||
fmt = NULL;
|
fmt = NULL;
|
||||||
(void) setlocale(LC_TIME, "");
|
(void) setlocale(LC_TIME, "");
|
||||||
tz.tz_dsttime = tz.tz_minuteswest = 0;
|
tz.tz_dsttime = tz.tz_minuteswest = 0;
|
||||||
rflag = 0;
|
rflag = 0;
|
||||||
jflag = nflag = Rflag = 0;
|
Iflag = jflag = nflag = Rflag = 0;
|
||||||
set_timezone = 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) {
|
switch((char)ch) {
|
||||||
case 'd': /* daylight savings time */
|
case 'd': /* daylight savings time */
|
||||||
tz.tz_dsttime = strtol(optarg, &endptr, 10) ? 1 : 0;
|
tz.tz_dsttime = strtol(optarg, &endptr, 10) ? 1 : 0;
|
||||||
@ -108,6 +125,22 @@ main(int argc, char *argv[])
|
|||||||
case 'f':
|
case 'f':
|
||||||
fmt = optarg;
|
fmt = optarg;
|
||||||
break;
|
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':
|
case 'j':
|
||||||
jflag = 1; /* don't set time */
|
jflag = 1; /* don't set time */
|
||||||
break;
|
break;
|
||||||
@ -115,6 +148,8 @@ main(int argc, char *argv[])
|
|||||||
nflag = 1;
|
nflag = 1;
|
||||||
break;
|
break;
|
||||||
case 'R': /* RFC 2822 datetime format */
|
case 'R': /* RFC 2822 datetime format */
|
||||||
|
if (Iflag)
|
||||||
|
multipleformats();
|
||||||
Rflag = 1;
|
Rflag = 1;
|
||||||
break;
|
break;
|
||||||
case 'r': /* user specified seconds */
|
case 'r': /* user specified seconds */
|
||||||
@ -163,6 +198,8 @@ main(int argc, char *argv[])
|
|||||||
|
|
||||||
/* allow the operands in any order */
|
/* allow the operands in any order */
|
||||||
if (*argv && **argv == '+') {
|
if (*argv && **argv == '+') {
|
||||||
|
if (Iflag)
|
||||||
|
multipleformats();
|
||||||
format = *argv + 1;
|
format = *argv + 1;
|
||||||
++argv;
|
++argv;
|
||||||
}
|
}
|
||||||
@ -173,8 +210,11 @@ main(int argc, char *argv[])
|
|||||||
} else if (fmt != NULL)
|
} else if (fmt != NULL)
|
||||||
usage();
|
usage();
|
||||||
|
|
||||||
if (*argv && **argv == '+')
|
if (*argv && **argv == '+') {
|
||||||
|
if (Iflag)
|
||||||
|
multipleformats();
|
||||||
format = *argv + 1;
|
format = *argv + 1;
|
||||||
|
}
|
||||||
|
|
||||||
lt = localtime(&tval);
|
lt = localtime(&tval);
|
||||||
if (lt == NULL)
|
if (lt == NULL)
|
||||||
@ -188,6 +228,9 @@ main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
vary_destroy(v);
|
vary_destroy(v);
|
||||||
|
|
||||||
|
if (Iflag)
|
||||||
|
printisodate(lt);
|
||||||
|
|
||||||
if (format == rfc2822_format)
|
if (format == rfc2822_format)
|
||||||
/*
|
/*
|
||||||
* When using RFC 2822 datetime format, don't honor the
|
* When using RFC 2822 datetime format, don't honor the
|
||||||
@ -196,12 +239,40 @@ main(int argc, char *argv[])
|
|||||||
setlocale(LC_TIME, "C");
|
setlocale(LC_TIME, "C");
|
||||||
|
|
||||||
(void)strftime(buf, sizeof(buf), format, lt);
|
(void)strftime(buf, sizeof(buf), format, lt);
|
||||||
|
printdate(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
printdate(const char *buf)
|
||||||
|
{
|
||||||
(void)printf("%s\n", buf);
|
(void)printf("%s\n", buf);
|
||||||
if (fflush(stdout))
|
if (fflush(stdout))
|
||||||
err(1, "stdout");
|
err(1, "stdout");
|
||||||
exit(retval);
|
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'))
|
#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -326,13 +397,28 @@ badformat(void)
|
|||||||
usage();
|
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
|
static void
|
||||||
usage(void)
|
usage(void)
|
||||||
{
|
{
|
||||||
(void)fprintf(stderr, "%s\n%s\n",
|
(void)fprintf(stderr, "%s\n%s\n%s\n",
|
||||||
"usage: date [-jnRu] [-d dst] [-r seconds] [-t west] "
|
"usage: date [-jnRu] [-d dst] [-r seconds|file] [-t west] "
|
||||||
"[-v[+|-]val[ymwdHMS]] ... ",
|
"[-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);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,55 @@ ${desc}_test_body() {
|
|||||||
atf_add_test_case ${desc}_test
|
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()
|
atf_init_test_cases()
|
||||||
{
|
{
|
||||||
format_string_test A A Saturday Monday
|
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 z z +0000 +0000
|
||||||
format_string_test percent % % %
|
format_string_test percent % % %
|
||||||
format_string_test plus + "Sat Feb 7 07:04:03 UTC 1970" "Mon Nov 12 21:20:00 UTC 2001"
|
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…
x
Reference in New Issue
Block a user