Skip to content

Commit

Permalink
Added %N, %:z, --iso-8601 is fully implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
Eike-Flath committed Apr 28, 2024
1 parent 0ca95d7 commit bdaa0a3
Showing 1 changed file with 103 additions and 8 deletions.
111 changes: 103 additions & 8 deletions src/date/date.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@

static char *operand;

static struct tm *get_time(void) {
time_t t;
if (time(&t) == -1) {
static struct tm *get_time(long *nanos) {
struct timespec ts;
if (clock_gettime(CLOCK_REALTIME, &ts)) {
fprintf(stderr, "date: failed to retrieve time: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
return localtime(&t);
if (nanos)
*nanos = ts.tv_nsec;
return localtime(&ts.tv_sec);
}

static int set_time(char *str) {
Expand All @@ -38,7 +40,8 @@ static int set_time(char *str) {

#define s(i) (str[i] - '0')
struct tm tm;
memcpy(&tm, get_time(), sizeof tm);
long ns;
memcpy(&tm, get_time(&ns), sizeof tm);
bool century_specified = false;
switch (n) {
case 12:
Expand Down Expand Up @@ -72,7 +75,7 @@ static int set_time(char *str) {
fprintf(stderr, "date: '%s' does not specify a valid time\n", str);
return EXIT_FAILURE;
}
struct timespec ts = {.tv_sec = t};
struct timespec ts = {.tv_sec = t, .tv_nsec = ns};
if (clock_settime(CLOCK_REALTIME, &ts)) {
fprintf(stderr, "date: cannot set date: %s\n", strerror(errno));
return EXIT_FAILURE;
Expand Down Expand Up @@ -162,6 +165,97 @@ static void long_opt(char *opt) {
exit(EXIT_FAILURE);
}

/**
* Works just like strftime, except it recognizes formats like %N or %:z which
* are specified by the GNU date utility but not by the glibc strftime
* implementation.
*
* Currently missing: %::z, %:::z
*/
static size_t extended_strftime(char *restrict buf, size_t max,
const char *restrict fmt,
const struct tm *restrict tm, long nanos) {
size_t fmt_size = strlen(fmt) + 1;
char *f = malloc(fmt_size);
if (!f) {
fprintf(stderr, "date: malloc failed: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
// copy the fmt string into buffer so that we can mutate it
memcpy(f, fmt, fmt_size);
size_t n = 0;
char c;
int i, j = 0;
for (i = 0; (c = f[i]); i++) {
if (c != '%')
continue;
switch (f[i + 1]) {
case '%':
i++; // skip additional %
continue;
case ':':
if (f[i + 2] != 'z')
continue;
// fallthrough
case 'N':
if (i > j) {
// evaluate the "normal" format string until i using strftime
f[i] = '\0'; // tells strftime to stop at i
size_t m = strftime(buf, max - n, f + j, tm);
if (m == 0) {
free(f);
return 0;
}
n += m;
buf += m;
}
break;
default:
continue;
}
switch (f[i + 1]) {
case ':': // means %:z
if (max - n < 7) { // need at least 7 bytes: +hh:mm\0
free(f);
return 0;
}
strftime(buf, 6, "%z", tm); // write +hhmm\0
memmove(buf + 4, buf + 3, 2);
buf[3] = ':';
n += 6;
buf += 6;
j = i + 3;
break;
case 'N':
if (max - n < 10) {
free(f);
return 0;
}
snprintf(buf, 10, "%09ld", nanos);
n += 9;
buf += 9;
j = i + 2;
break;
default:
__builtin_unreachable();
}
i = j - 1;
}
if (i > j) {
// evaluate the "normal" format string until i using strftime
size_t m = strftime(buf, max - n, f + j, tm);
if (m == 0) {
free(f);
return 0;
}
n += m;
buf += m;
}
*buf = '\0';
free(f);
return n;
}

int main(int argc, char **argv) {
operand = NULL;
bool parse_options = true;
Expand Down Expand Up @@ -189,15 +283,16 @@ int main(int argc, char **argv) {
if (operand[0] != '+')
return set_time(operand);

struct tm *tm = get_time();
long ns;
struct tm *tm = get_time(&ns);

size_t cap = INITIAL_BUF_SIZE;
char *buf = malloc(cap);
if (!buf) {
fprintf(stderr, "date: malloc failed: %s\n", strerror(errno));
return EXIT_FAILURE;
}
while (!strftime(buf, cap, operand + 1, tm)) {
while (!extended_strftime(buf, cap, operand + 1, tm, ns)) {
cap *= 2;
char *new_buf = realloc(buf, cap);
if (!new_buf) {
Expand Down

0 comments on commit bdaa0a3

Please sign in to comment.