date.c (8402B)
- // utils-std: Collection of commonly available Unix tools
- // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
- // SPDX-License-Identifier: MPL-2.0
- #define _DEFAULT_SOURCE // tm_gmtoff/tm_zone
- #define _POSIX_C_SOURCE 200809L
- #define _XOPEN_SOURCE 700 // strptime is in XSI
- #include "../lib/iso_parse.h" /* iso_parse */
- #include <assert.h>
- #include <errno.h>
- #include <locale.h> /* setlocale() */
- #include <stdbool.h>
- #include <stdio.h> /* BUFSIZ, fprintf(), puts() */
- #include <stdlib.h> /* strtol() */
- #include <string.h> /* strerror */
- #include <time.h> /* time, localtime, tm, strftime, strptime, clock_settime */
- #include <unistd.h> /* getopt(), optarg, optind */
- const char *argv0 = "date";
- static size_t
- date_strftime(char *restrict buf,
- size_t buflen,
- const char *restrict fmt,
- const struct tm *restrict tm,
- long nsec)
- {
- size_t fmtlen = strlen(fmt);
- size_t printed = 0;
- if(fmtlen == 0)
- {
- buf[0] == '\0';
- return 0;
- }
- for(size_t i = 0; i < fmtlen;)
- {
- // size taken from musl strftime
- static char fmt_buf[100] = "";
- size_t fmt_bufi = 0;
- if(fmt[i] == '%')
- {
- fmt_buf[fmt_bufi++] = fmt[i++];
- if(fmt[i] == '%') // handle '%%'
- {
- *buf = '%';
- buf++;
- buflen--;
- printed++;
- i++;
- continue;
- }
- }
- for(; fmt[i] != '%' && i < fmtlen;)
- {
- fmt_buf[fmt_bufi++] = fmt[i++];
- assert(fmt_bufi < 100);
- }
- fmt_buf[fmt_bufi] = '\0';
- if(fmt_buf[0] == '%' && fmt_buf[1] == ':' && fmt_buf[2] == 'z')
- {
- size_t got =
- snprintf(buf, buflen, "%+.2ld:%.2ld", tm->tm_gmtoff / 3600, tm->tm_gmtoff % 3600 / 60);
- if(got == 0) return got;
- buf += got;
- buflen -= got;
- printed += got;
- }
- else if(fmt_buf[0] == '%' && fmt_buf[1] == 'N')
- {
- size_t got = snprintf(buf, buflen, "%09ld", nsec);
- if(got == 0) return got;
- buf += got;
- buflen -= got;
- printed += got;
- }
- else
- {
- size_t got = strftime(buf, buflen, fmt_buf, tm);
- if(got == 0) return got;
- buf += got;
- buflen -= got;
- printed += got;
- }
- }
- return printed;
- }
- static void
- usage(void)
- {
- fprintf(stderr, "\
- Usage:\n\
- date [-jRu] [-I iso_fmt] [-d datetime | -r epoch] [+format]\n\
- date [-jRu] [-I iso_fmt] mmddHHMM[[CC]yy] [+format]\n\
- date [-jRu] [-I iso_fmt] -f now_format now [+format]\n\
- ");
- }
- int
- main(int argc, char *argv[])
- {
- char outstr[BUFSIZ] = "";
- struct tm tm = {
- .tm_year = 0,
- .tm_mon = 0,
- .tm_mday = 0,
- .tm_hour = 0,
- .tm_min = 0,
- .tm_sec = 0,
- .tm_isdst = -1, // unknown if DST is in effect
- .tm_gmtoff = 0,
- .tm_zone = NULL,
- };
- struct timespec tp = {
- .tv_sec = 0,
- .tv_nsec = 0,
- };
- const char *format = "%c";
- const char *input_format = NULL;
- int uflag = 0;
- int dflag = 0, rflag = 0;
- bool jflag = false;
- bool settime = false;
- setlocale(LC_ALL, "");
- errno = 0;
- if(clock_gettime(CLOCK_REALTIME, &tp) != 0)
- {
- fprintf(stderr, "%s: error: Failed getting current time: %s\n", argv0, strerror(errno));
- return 1;
- }
- for(int c = -1; (c = getopt(argc, argv, ":d:f:I:jr:Ru")) != -1;)
- {
- const char *errstr = NULL;
- switch(c)
- {
- case 'd': /* Custom datetime */
- if(input_format != NULL)
- {
- fprintf(stderr, "%s: error: Cannot both use '-d' and '-f'\n", argv0);
- return 1;
- }
- if(rflag == 1)
- {
- fprintf(stderr, "%s: error: Cannot both use '-d' and '-r'\n", argv0);
- return 1;
- }
- iso_parse(optarg, &tm, &tp.tv_nsec, &errstr);
- dflag = 1;
- if(errstr != NULL)
- {
- fprintf(stderr, "%s: error: iso_parse(\"%s\", …): %s\n", argv0, optarg, errstr);
- return 1;
- }
- tp.tv_sec = mktime_tz(&tm);
- if(tp.tv_sec == (time_t)-1)
- {
- fprintf(stderr, "%s: error: mktime: %s\n", argv0, strerror(errno));
- return 1;
- }
- errno = 0;
- break;
- case 'f': /* input datetime format */
- if(dflag == 1)
- {
- fprintf(stderr, "%s: error: Cannot both use '-d' and '-f'\n", argv0);
- return 1;
- }
- if(rflag == 1)
- {
- fprintf(stderr, "%s: error: Cannot both use '-f' and '-r'\n", argv0);
- return 1;
- }
- input_format = optarg;
- settime = true;
- break;
- case 'r': /* seconds relative to epoch */
- {
- if(input_format != NULL)
- {
- fprintf(stderr, "%s: error: Cannot both use '-f' and '-r'\n", argv0);
- return 1;
- }
- if(dflag == 1)
- {
- fprintf(stderr, "%s: error: Cannot both use '-d' and '-r'\n", argv0);
- return 1;
- }
- char *endptr = NULL;
- errno = 0;
- tp.tv_sec = strtol(optarg, &endptr, 10);
- if(errno != 0)
- {
- fprintf(stderr, "%s: error: Failed parsing '-r %s'\n", argv0, optarg);
- return 1;
- }
- if(!(endptr == NULL || *endptr == '\0'))
- {
- fprintf(stderr, "%s: error: Invalid characters in '-r %s': %s\n", argv0, optarg, endptr);
- return 1;
- }
- break;
- }
- case 'R': /* Email (RFC 5322) format */
- format = "%a, %d %b %Y %H:%M:%S %z";
- break;
- case 'u': /* UTC timezone */
- uflag++;
- setenv("TZ", "UTC", 1);
- tzset();
- break;
- case 'I': /* ISO 8601 */
- /* note: %:z (±ZZ:ZZ) and %N (nanoseconds) are date(1) GNU-isms absent from C libraries including glibc */
- switch(optarg[0])
- {
- case 'h': // hours
- format = "%Y-%m-%dT%H%:z";
- break;
- case 'm': // minutes
- format = "%Y-%m-%dT%H:%M%:z";
- break;
- case 's': // seconds
- format = "%Y-%m-%dT%H:%M:%S%:z";
- break;
- case 'n': // ns, nanoseconds
- format = "%Y-%m-%dT%H:%M:%S,%N%:z";
- break;
- case 'd': // date
- default:
- format = "%Y-%m-%d";
- break;
- }
- break;
- case 'j':
- jflag = true;
- break;
- case ':':
- fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
- usage();
- return 1;
- case '?':
- fprintf(stderr, "%s: error: Unrecognised option: '-%c'\n", argv0, optopt);
- usage();
- return 1;
- }
- }
- argc -= optind;
- argv += optind;
- if(uflag)
- {
- if(gmtime_r(&tp.tv_sec, &tm) == NULL)
- {
- fprintf(stderr, "%s: error: gmtime_r: %s\n", argv0, strerror(errno));
- return 1;
- }
- }
- else
- {
- if(localtime_r(&tp.tv_sec, &tm) == NULL)
- {
- fprintf(stderr, "%s: error: localtime_r: %s\n", argv0, strerror(errno));
- return 1;
- }
- }
- if(argc > 0 && input_format != NULL)
- {
- char *res = strptime(argv[0], input_format, &tm);
- if(res == NULL)
- {
- fprintf(stderr,
- "%s: error: strptime(\"%s\", \"%s\", …) as passed by '-f' option failed\n",
- argv0,
- argv[0],
- input_format);
- return 1;
- }
- tp.tv_sec = mktime_tz(&tm);
- tp.tv_nsec = 0;
- if(tp.tv_sec == (time_t)-1)
- {
- fprintf(stderr, "%s: error: mktime: %s\n", argv0, strerror(errno));
- return 1;
- }
- errno = 0;
- argv++;
- argc--;
- }
- if(input_format == NULL && argc > 0 && *argv && **argv != '+')
- {
- const char *fmt = "%m%d%H%M";
- char *s = strptime(argv[0], fmt, &tm);
- if(s == NULL)
- {
- fprintf(stderr, "%s: error: strptime(\"%s\", \"%s\", …) returned NULL\n", argv0, *argv, fmt);
- return 1;
- }
- size_t rem = strlen(s);
- if(rem > 0)
- {
- switch(rem)
- {
- case 2:
- fmt = "%y";
- break;
- case 4:
- fmt = "%Y";
- break;
- default:
- fprintf(stderr,
- "%s: error: Got %zu trailing characters after \"%s\" for mmddHHMM\n",
- argv0,
- rem,
- fmt);
- return 1;
- }
- s = strptime(s, fmt, &tm);
- if(s == NULL)
- {
- fprintf(
- stderr, "%s: error: strptime(\"%s\", \"%s\", …) returned NULL\n", argv0, *argv, fmt);
- return 1;
- }
- }
- tp.tv_sec = mktime(&tm);
- tp.tv_nsec = 0;
- if(tp.tv_sec == (time_t)-1)
- {
- fprintf(stderr, "%s: error: mktime: %s\n", argv0, strerror(errno));
- return 1;
- }
- errno = 0;
- argv++;
- argc--;
- settime = true;
- }
- if(settime && !jflag)
- {
- if(clock_settime(CLOCK_REALTIME, &tp) != 0)
- {
- fprintf(stderr,
- "%s: error: clock_settime(CLOCK_REALTIME, {%ld, %ld}): %s\n",
- argv0,
- tp.tv_sec,
- tp.tv_nsec,
- strerror(errno));
- return 1;
- }
- }
- if(argc > 0 && *argv && **argv == '+')
- {
- format = *argv + 1;
- argv++;
- argc--;
- }
- errno = 0;
- if(date_strftime(outstr, sizeof(outstr), format, &tm, tp.tv_nsec) == 0 && errno != 0)
- {
- fprintf(stderr, "%s: error: Failed formatting time: %s\n", argv0, strerror(errno));
- return 1;
- }
- if(puts(outstr) < 0)
- {
- fprintf(stderr, "%s: error: Failed writing time: %s\n", argv0, strerror(errno));
- return 1;
- }
- return 0;
- }