Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F148387387
D53667.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
D53667.diff
View Options
diff --git a/bin/date/date.1 b/bin/date/date.1
--- a/bin/date/date.1
+++ b/bin/date/date.1
@@ -29,7 +29,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd November 5, 2025
+.Dd November 10, 2025
.Dt DATE 1
.Os
.Sh NAME
@@ -186,7 +186,7 @@
.Ar seconds ,
where
.Ar seconds
-is the number of seconds since the Epoch
+is the number of seconds since the Unix Epoch
(00:00:00 UTC, January 1, 1970;
see
.Xr time 3 ) ,
@@ -321,20 +321,43 @@
.Pp
An operand with a leading plus
.Pq Sq +
-sign signals a user-defined format string
+sign specifies a user-defined format string
which specifies the format in which to display the date and time.
The format string may contain any of the conversion specifications
described in the
.Xr strftime 3
-manual page and
-.Ql \&%N
-for nanoseconds, as well as any arbitrary text.
+manual page, as well as any arbitrary text.
+.Pp
+The following extensions to the regular
+.Xr strftime 3
+syntax are supported:
+.Bl -tag -width "xxxx"
+.It Cm \&% Ns Ar n Ns Cm N
+Replaced by the
+.Ar n Ns
+-digit fractional part of the number of seconds since the Unix Epoch.
+If
+.Ar n
+is omitted or zero, a default value of 9 is used, resulting in a
+number with nanosecond resolution (hence the choice of the letter
+.Sq N
+for this conversion).
+Note that the underlying clock may not necessarily support nanosecond
+resolution.
+.It Cm \&%-N
+As above, but automatically choose the precision based on the reported
+resolution of the underlying clock.
+If the
+.Fl r
+option was specified, the default precision of 9 digits is used.
+.El
+.Pp
A newline
.Pq Ql \en
character is always output after the characters specified by
the format string.
The format string for the default display is
-.Dq +%+ .
+.Dq %+ .
.Pp
If an operand does not have a leading plus sign, it is interpreted as
a value for setting the system's notion of the current date and time.
@@ -448,6 +471,13 @@
utility exits 0 on success, 1 if unable to set the date, and 2
if able to set the local date, but unable to set it globally.
.Sh EXAMPLES
+The command
+.Pp
+.Dl "date +%s.%3N"
+.Pp
+will print the time elapsed since the Unix Epoch with millisecond
+precision.
+.Pp
The command:
.Pp
.Dl "date ""+DATE: %Y-%m-%d%nTIME: %H:%M:%S"""
@@ -619,3 +649,9 @@
.Ql \&%N
conversion specification was added in
.Fx 14.1 .
+Support for the
+.Ql \&% Ns Ar n Ns Cm N
+and
+.Ql \&%-N
+variants was added in
+.Fx 15.1 .
diff --git a/bin/date/date.c b/bin/date/date.c
--- a/bin/date/date.c
+++ b/bin/date/date.c
@@ -36,6 +36,7 @@
#include <ctype.h>
#include <err.h>
#include <errno.h>
+#include <inttypes.h>
#include <locale.h>
#include <stdbool.h>
#include <stdio.h>
@@ -55,10 +56,10 @@
static void iso8601_usage(const char *) __dead2;
static void multipleformats(void);
static void printdate(const char *);
-static void printisodate(struct tm *, long);
+static void printisodate(struct tm *, long, long);
static void setthetime(const char *, const char *, int, struct timespec *);
static size_t strftime_ns(char * __restrict, size_t, const char * __restrict,
- const struct tm * __restrict, long);
+ const struct tm * __restrict, long, long);
static void usage(void) __dead2;
static const struct iso8601_fmt {
@@ -78,26 +79,24 @@
int
main(int argc, char *argv[])
{
- struct timespec ts;
+ struct timespec ts = { 0, 0 }, tres = { 0, 1 };
int ch, rflag;
bool Iflag, jflag, Rflag;
const char *format;
char buf[1024];
- char *fmt, *outzone = NULL;
- char *tmp;
+ char *end, *fmt, *outzone = NULL;
struct vary *v;
const struct vary *badv;
struct tm *lt;
struct stat sb;
size_t i;
+ intmax_t number;
v = NULL;
fmt = NULL;
(void) setlocale(LC_TIME, "");
rflag = 0;
Iflag = jflag = Rflag = 0;
- ts.tv_sec = 0;
- ts.tv_nsec = 0;
while ((ch = getopt(argc, argv, "f:I::jnRr:uv:z:")) != -1)
switch((char)ch) {
case 'f':
@@ -131,13 +130,15 @@
break;
case 'r': /* user specified seconds */
rflag = 1;
- ts.tv_sec = strtoq(optarg, &tmp, 0);
- if (*tmp != 0) {
- if (stat(optarg, &sb) == 0) {
- ts.tv_sec = sb.st_mtim.tv_sec;
- ts.tv_nsec = sb.st_mtim.tv_nsec;
- } else
- usage();
+ number = strtoimax(optarg, &end, 0);
+ if (end > optarg && *end == '\0') {
+ ts.tv_sec = number;
+ ts.tv_nsec = 0;
+ } else if (stat(optarg, &sb) == 0) {
+ ts.tv_sec = sb.st_mtim.tv_sec;
+ ts.tv_nsec = sb.st_mtim.tv_nsec;
+ } else {
+ usage();
}
break;
case 'u': /* do everything in UTC */
@@ -155,8 +156,12 @@
argc -= optind;
argv += optind;
- if (!rflag && clock_gettime(CLOCK_REALTIME, &ts) == -1)
- err(1, "clock_gettime");
+ if (!rflag) {
+ if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
+ err(1, "clock_gettime");
+ if (clock_getres(CLOCK_REALTIME, &tres) == -1)
+ err(1, "clock_getres");
+ }
format = "%+";
@@ -191,14 +196,14 @@
badv = vary_apply(v, lt);
if (badv) {
fprintf(stderr, "%s: Cannot apply date adjustment\n",
- badv->arg);
+ badv->arg);
vary_destroy(v);
usage();
}
vary_destroy(v);
if (Iflag)
- printisodate(lt, ts.tv_nsec);
+ printisodate(lt, ts.tv_nsec, tres.tv_nsec);
if (format == rfc2822_format)
/*
@@ -208,7 +213,8 @@
setlocale(LC_TIME, "C");
- (void)strftime_ns(buf, sizeof(buf), format, lt, ts.tv_nsec);
+ (void)strftime_ns(buf, sizeof(buf), format, lt,
+ ts.tv_nsec, tres.tv_nsec);
printdate(buf);
}
@@ -222,7 +228,7 @@
}
static void
-printisodate(struct tm *lt, long nsec)
+printisodate(struct tm *lt, long nsec, long res)
{
const struct iso8601_fmt *it;
char fmtbuf[64], buf[64], tzbuf[8];
@@ -231,10 +237,10 @@
for (it = iso8601_fmts; it <= iso8601_selected; it++)
strlcat(fmtbuf, it->format_string, sizeof(fmtbuf));
- (void)strftime_ns(buf, sizeof(buf), fmtbuf, lt, nsec);
+ (void)strftime_ns(buf, sizeof(buf), fmtbuf, lt, nsec, res);
if (iso8601_selected > iso8601_fmts) {
- (void)strftime_ns(tzbuf, sizeof(tzbuf), "%z", lt, nsec);
+ (void)strftime_ns(tzbuf, sizeof(tzbuf), "%z", lt, nsec, res);
memmove(&tzbuf[4], &tzbuf[3], 3);
tzbuf[3] = ':';
strlcat(buf, tzbuf, sizeof(buf));
@@ -370,16 +376,17 @@
*/
static size_t
strftime_ns(char * __restrict s, size_t maxsize, const char * __restrict format,
- const struct tm * __restrict t, long nsec)
+ const struct tm * __restrict t, long nsec, long res)
{
- size_t prefixlen;
size_t ret;
char *newformat;
char *oldformat;
const char *prefix;
const char *suffix;
const char *tok;
- bool seen_percent;
+ long number;
+ int i, len, prefixlen, width, zeroes;
+ bool seen_percent, seen_dash, seen_width;
seen_percent = false;
if ((newformat = strdup(format)) == NULL)
@@ -392,36 +399,85 @@
* If the previous token was a percent sign,
* then there are two percent tokens in a row.
*/
- if (seen_percent)
+ if (seen_percent) {
seen_percent = false;
- else
+ } else {
seen_percent = true;
+ seen_dash = seen_width = false;
+ prefixlen = tok - newformat;
+ width = 0;
+ }
break;
case 'N':
- if (seen_percent) {
- oldformat = newformat;
- prefix = oldformat;
- prefixlen = tok - oldformat - 1;
- suffix = tok + 1;
+ if (!seen_percent)
+ break;
+ oldformat = newformat;
+ prefix = oldformat;
+ suffix = tok + 1;
+ /*
+ * Prepare the number we are about to print. If
+ * the requested width is less than 9, we need to
+ * cut off the least significant digits. If it is
+ * more than 9, we will have to append zeroes.
+ */
+ if (seen_dash) {
/*
- * Construct a new format string from the
- * prefix (i.e., the part of the old format
- * from its beginning to the currently handled
- * "%N" conversion specification), the
- * nanoseconds, and the suffix (i.e., the part
- * of the old format from the next token to the
- * end).
+ * Calculate number of singificant digits
+ * based on res which is the clock's
+ * resolution in nanoseconds.
*/
- if (asprintf(&newformat, "%.*s%.9ld%s",
- (int)prefixlen, prefix, nsec,
- suffix) < 0) {
- err(1, "asprintf");
- }
- free(oldformat);
- tok = newformat + prefixlen + 9;
+ for (width = 9, number = res;
+ width > 0 && number > 0;
+ width--, number /= 10)
+ /* nothing */;
+ }
+ number = nsec;
+ zeroes = 0;
+ if (width == 0) {
+ width = 9;
+ } else if (width > 9) {
+ zeroes = width - 9;
+ width = 9;
+ } else {
+ for (i = 0; i < 9 - width; i++)
+ number /= 10;
}
+ /*
+ * Construct a new format string from the prefix
+ * (i.e., the part of the old format from its
+ * beginning to the currently handled "%N"
+ * conversion specification), the nanoseconds, and
+ * the suffix (i.e., the part of the old format
+ * from the next token to the end).
+ */
+ asprintf(&newformat, "%.*s%.*ld%.*d%n%s", prefixlen,
+ prefix, width, number, zeroes, 0, &len, suffix);
+ if (newformat == NULL)
+ err(1, "asprintf");
+ free(oldformat);
+ tok = newformat + len - 1;
seen_percent = false;
break;
+ case '-':
+ if (seen_percent) {
+ if (seen_dash || seen_width) {
+ seen_percent = false;
+ break;
+ }
+ seen_dash = true;
+ }
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if (seen_percent) {
+ if (seen_dash) {
+ seen_percent = false;
+ break;
+ }
+ width = width * 10 + *tok - '0';
+ seen_width = true;
+ }
+ break;
default:
seen_percent = false;
break;
diff --git a/bin/date/tests/format_string_test.sh b/bin/date/tests/format_string_test.sh
--- a/bin/date/tests/format_string_test.sh
+++ b/bin/date/tests/format_string_test.sh
@@ -132,6 +132,8 @@
format_string_test M M 04 20
format_string_test m m 02 11
format_string_test N N 000000000 000000000
+ format_string_test 3N 3N 000 000
+ format_string_test 12N 12N 000000000000 000000000000
format_string_test p p AM PM
format_string_test R R 07:04 21:20
format_string_test r r "07:04:03 AM" "09:20:00 PM"
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Mar 18, 1:04 PM (14 h, 54 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
29906794
Default Alt Text
D53667.diff (9 KB)
Attached To
Mode
D53667: date: Improve nanosecond support
Attached
Detach File
Event Timeline
Log In to Comment