diff --git a/bin/date/date.c b/bin/date/date.c --- a/bin/date/date.c +++ b/bin/date/date.c @@ -372,13 +372,14 @@ strftime_ns(char * __restrict s, size_t maxsize, const char * __restrict format, const struct tm * __restrict t, long nsec) { - size_t prefixlen; size_t ret; char *newformat; char *oldformat; const char *prefix; const char *suffix; const char *tok; + long number; + int i, len, prefixlen, width, zeroes; bool seen_percent; seen_percent = false; @@ -392,36 +393,53 @@ * If the previous token was a percent sign, * then there are two percent tokens in a row. */ - if (seen_percent) - seen_percent = false; - else - seen_percent = true; + seen_percent = !seen_percent; + prefixlen = tok - newformat; + width = 0; break; case 'N': - if (seen_percent) { - oldformat = newformat; - prefix = oldformat; - prefixlen = tok - oldformat - 1; - suffix = tok + 1; - /* - * 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). - */ - if (asprintf(&newformat, "%.*s%.9ld%s", - (int)prefixlen, prefix, nsec, - suffix) < 0) { - err(1, "asprintf"); - } - free(oldformat); - tok = newformat + prefixlen + 9; + 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. + */ + 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 '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + width = width * 10 + *tok - '0'; + 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"