busybox/coreutils/date.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * Mini date implementation for busybox
   4 *
   5 * by Matthew Grant <grantma@anathoth.gen.nz>
   6 *
   7 * iso-format handling added by Robert Griebl <griebl@gmx.de>
   8 * bugfixes and cleanup by Bernhard Reutner-Fischer
   9 *
  10 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  11 */
  12/* This 'date' command supports only 2 time setting formats,
  13   all the GNU strftime stuff (its in libc, lets use it),
  14   setting time using UTC and displaying it, as well as
  15   an RFC 2822 compliant date output for shell scripting
  16   mail commands */
  17
  18/* Input parsing code is always bulky - used heavy duty libc stuff as
  19   much as possible, missed out a lot of bounds checking */
  20
  21//config:config DATE
  22//config:       bool "date (7 kb)"
  23//config:       default y
  24//config:       help
  25//config:       date is used to set the system date or display the
  26//config:       current time in the given format.
  27//config:
  28//config:config FEATURE_DATE_ISOFMT
  29//config:       bool "Enable ISO date format output (-I)"
  30//config:       default y
  31//config:       depends on DATE
  32//config:       help
  33//config:       Enable option (-I) to output an ISO-8601 compliant
  34//config:       date/time string.
  35//config:
  36//config:config FEATURE_DATE_NANO
  37//config:       bool "Support %[num]N nanosecond format specifier"
  38//config:       default n # stat's nanosecond field is a bit non-portable
  39//config:       depends on DATE
  40//config:       help
  41//config:       Support %[num]N format specifier. Adds ~250 bytes of code.
  42//config:
  43//config:config FEATURE_DATE_COMPAT
  44//config:       bool "Support weird 'date MMDDhhmm[[YY]YY][.ss]' format"
  45//config:       default y
  46//config:       depends on DATE
  47//config:       help
  48//config:       System time can be set by 'date -s DATE' and simply 'date DATE',
  49//config:       but formats of DATE string are different. 'date DATE' accepts
  50//config:       a rather weird MMDDhhmm[[YY]YY][.ss] format with completely
  51//config:       unnatural placement of year between minutes and seconds.
  52//config:       date -s (and other commands like touch -d) use more sensible
  53//config:       formats (for one, ISO format YYYY-MM-DD hh:mm:ss.ssssss).
  54//config:
  55//config:       With this option off, 'date DATE' and 'date -s DATE' support
  56//config:       the same format. With it on, 'date DATE' additionally supports
  57//config:       MMDDhhmm[[YY]YY][.ss] format.
  58
  59//applet:IF_DATE(APPLET_NOEXEC(date, date, BB_DIR_BIN, BB_SUID_DROP, date))
  60/* bb_common_bufsiz1 usage here is safe wrt NOEXEC: not expecting it to be zeroed. */
  61
  62//kbuild:lib-$(CONFIG_DATE) += date.o
  63
  64/* GNU coreutils 6.9 man page:
  65 * date [OPTION]... [+FORMAT]
  66 * date [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]
  67 * -d, --date=STRING
  68 *      display time described by STRING, not 'now'
  69 * -f, --file=DATEFILE
  70 *      like --date once for each line of DATEFILE
  71 * -r, --reference=FILE
  72 *      display the last modification time of FILE
  73 * -R, --rfc-2822
  74 *      output date and time in RFC 2822 format.
  75 *      Example: Mon, 07 Aug 2006 12:34:56 -0600
  76 * --rfc-3339=TIMESPEC
  77 *      output date and time in RFC 3339 format.
  78 *      TIMESPEC='date', 'seconds', or 'ns'
  79 *      Date and time components are separated by a single space:
  80 *      2006-08-07 12:34:56-06:00
  81 * -s, --set=STRING
  82 *      set time described by STRING
  83 * -u, --utc, --universal
  84 *      print or set Coordinated Universal Time
  85 *
  86 * Busybox:
  87 * long options are not supported
  88 * -f is not supported
  89 * -I seems to roughly match --rfc-3339, but -I has _optional_ param
  90 *    (thus "-I seconds" doesn't work, only "-Iseconds"),
  91 *    and does not support -Ins
  92 * -D FMT is a bbox extension for _input_ conversion of -d DATE
  93 */
  94
  95//usage:#define date_trivial_usage
  96//usage:       "[OPTIONS] [+FMT] [[-s] TIME]"
  97//usage:#define date_full_usage "\n\n"
  98//usage:       "Display time (using +FMT), or set time\n"
  99//usage:     "\n        -u              Work in UTC (don't convert to local time)"
 100//usage:     "\n        [-s] TIME       Set time to TIME"
 101//usage:     "\n        -d TIME         Display TIME, not 'now'"
 102//usage:        IF_FEATURE_DATE_ISOFMT(
 103//usage:     "\n        -D FMT          FMT (strptime format) for -s/-d TIME conversion"
 104////////^^^^^^^^^^^^^^^^^^^^^^ busybox invention, not compat
 105//usage:        )
 106//usage:     "\n        -r FILE         Display last modification time of FILE"
 107//usage:     "\n        -R              Output RFC-2822 date"
 108//usage:        IF_FEATURE_DATE_ISOFMT(
 109//usage:     "\n        -I[SPEC]        Output ISO-8601 date"
 110//usage:     "\n                        SPEC=date (default), hours, minutes, seconds or ns"
 111//usage:        )
 112//usage:     "\n"
 113//usage:     "\nRecognized TIME formats:"
 114//usage:     "\n        @seconds_since_1970"
 115//usage:     "\n        hh:mm[:ss]"
 116//usage:     "\n        [YYYY.]MM.DD-hh:mm[:ss]"
 117//usage:     "\n        YYYY-MM-DD hh:mm[:ss]"
 118//usage:     "\n        [[[[[YY]YY]MM]DD]hh]mm[.ss]"
 119//usage:        IF_FEATURE_DATE_COMPAT(
 120//usage:     "\n        'date TIME' form accepts MMDDhhmm[[YY]YY][.ss] instead"
 121//usage:        )
 122//usage:
 123//usage:#define date_example_usage
 124//usage:       "$ date\n"
 125//usage:       "Wed Apr 12 18:52:41 MDT 2000\n"
 126
 127#include "libbb.h"
 128#include "common_bufsiz.h"
 129#if ENABLE_FEATURE_DATE_NANO
 130# include <sys/syscall.h>
 131#endif
 132
 133enum {
 134        OPT_RFC2822   = (1 << 0), /* R */
 135        OPT_SET       = (1 << 1), /* s */
 136        OPT_UTC       = (1 << 2), /* u */
 137        OPT_DATE      = (1 << 3), /* d */
 138        OPT_REFERENCE = (1 << 4), /* r */
 139        OPT_ISO8601   = (1 << 5) * ENABLE_FEATURE_DATE_ISOFMT, /* I */
 140        OPT_STR2DT    = (1 << 6) * ENABLE_FEATURE_DATE_ISOFMT, /* D */
 141};
 142
 143#if ENABLE_LONG_OPTS
 144static const char date_longopts[] ALIGN1 =
 145                "rfc-822\0"   No_argument       "R"
 146                "rfc-2822\0"  No_argument       "R"
 147                "set\0"       Required_argument "s"
 148                "utc\0"       No_argument       "u"
 149        /*      "universal\0" No_argument       "u" */
 150                "date\0"      Required_argument "d"
 151                "reference\0" Required_argument "r"
 152                ;
 153#endif
 154
 155/* We are a NOEXEC applet.
 156 * Obstacles to NOFORK:
 157 * - we change env
 158 * - xasprintf result not freed
 159 * - after xasprintf we use other xfuncs
 160 */
 161
 162int date_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 163int date_main(int argc UNUSED_PARAM, char **argv)
 164{
 165        struct timespec ts;
 166        struct tm tm_time;
 167        char buf_fmt_dt2str[64];
 168        unsigned opt;
 169        int isofmt = -1;
 170        char *date_str;
 171        char *fmt_dt2str;
 172        char *fmt_str2dt;
 173        char *filename;
 174        char *isofmt_arg = NULL;
 175
 176        opt = getopt32long(argv, "^"
 177                        "Rs:ud:r:"
 178                        IF_FEATURE_DATE_ISOFMT("I::D:")
 179                        "\0"
 180                        "d--s:s--d"
 181                        IF_FEATURE_DATE_ISOFMT(":R--I:I--R"),
 182                        date_longopts,
 183                        &date_str, &date_str, &filename
 184                        IF_FEATURE_DATE_ISOFMT(, &isofmt_arg, &fmt_str2dt)
 185        );
 186        argv += optind;
 187
 188        if (opt & OPT_UTC)
 189                putenv((char*)"TZ=UTC0");
 190
 191        if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_ISO8601)) {
 192                isofmt = 0; /* default is date */
 193                if (isofmt_arg) {
 194                        static const char isoformats[] ALIGN1 =
 195                                "date\0""hours\0""minutes\0""seconds\0ns\0";
 196                        isofmt = index_in_substrings(isoformats, isofmt_arg);
 197                        if (isofmt < 0)
 198                                bb_show_usage();
 199                }
 200        }
 201
 202        fmt_dt2str = NULL;
 203        if (argv[0] && argv[0][0] == '+') {
 204                fmt_dt2str = &argv[0][1]; /* skip over the '+' */
 205                argv++;
 206        }
 207        if (!(opt & (OPT_SET | OPT_DATE))) { /* neither -s TIME nor -d TIME? */
 208                opt |= OPT_SET;
 209                date_str = argv[0]; /* can be NULL */
 210                if (date_str) {
 211#if ENABLE_FEATURE_DATE_COMPAT
 212                        int len = strspn(date_str, "0123456789");
 213                        if (date_str[len] == '\0'
 214                         || (date_str[len] == '.'
 215                            && isdigit(date_str[len+1])
 216                            && isdigit(date_str[len+2])
 217                            && date_str[len+3] == '\0'
 218                            )
 219                        ) {
 220                                /* Dreaded MMDDhhmm[[CC]YY][.ss] format!
 221                                 * It does not match -d or -s format.
 222                                 * Some users actually do use it.
 223                                 */
 224                                len -= 8;
 225                                if (len < 0 || len > 4 || (len & 1))
 226                                        bb_error_msg_and_die(bb_msg_invalid_date, date_str);
 227                                if (len != 0) { /* move YY or CCYY to front */
 228                                        char buf[4];
 229                                        memcpy(buf, date_str + 8, len);
 230                                        memmove(date_str + len, date_str, 8);
 231                                        memcpy(date_str, buf, len);
 232                                }
 233                        }
 234#endif
 235                        argv++;
 236                }
 237        }
 238        if (*argv)
 239                bb_show_usage();
 240
 241        /* Now we have parsed all the information except the date format
 242         * which depends on whether the clock is being set or read */
 243
 244        if (opt & OPT_REFERENCE) {
 245                struct stat statbuf;
 246                xstat(filename, &statbuf);
 247                ts.tv_sec = statbuf.st_mtime;
 248#if ENABLE_FEATURE_DATE_NANO
 249                ts.tv_nsec = statbuf.st_mtim.tv_nsec;
 250                /* Some toolchains use .st_mtimensec instead of st_mtim.tv_nsec.
 251                 * If you need #define _SVID_SOURCE 1 to enable st_mtim.tv_nsec,
 252                 * drop a mail to project mailing list please
 253                 */
 254#endif
 255        } else {
 256#if ENABLE_FEATURE_DATE_NANO
 257                clock_gettime(CLOCK_REALTIME, &ts);
 258#else
 259                time(&ts.tv_sec);
 260#endif
 261        }
 262#if !ENABLE_FEATURE_DATE_NANO
 263        ts.tv_nsec = 0;
 264#endif
 265        localtime_r(&ts.tv_sec, &tm_time);
 266
 267        /* If date string is given, update tm_time, and maybe set date */
 268        if (date_str != NULL) {
 269                /* Zero out fields - take her back to midnight! */
 270                tm_time.tm_sec = 0;
 271                tm_time.tm_min = 0;
 272                tm_time.tm_hour = 0;
 273
 274                /* Process any date input to UNIX time since 1 Jan 1970 */
 275                if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_STR2DT)) {
 276                        if (strptime(date_str, fmt_str2dt, &tm_time) == NULL)
 277                                bb_error_msg_and_die(bb_msg_invalid_date, date_str);
 278                } else {
 279                        parse_datestr(date_str, &tm_time);
 280                }
 281
 282                /* Correct any day of week and day of year etc. fields */
 283                /* Be sure to recheck dst (but not if date is time_t format) */
 284                if (date_str[0] != '@')
 285                        tm_time.tm_isdst = -1;
 286                ts.tv_sec = validate_tm_time(date_str, &tm_time);
 287                ts.tv_nsec = 0;
 288
 289                /* if setting time, set it */
 290                if ((opt & OPT_SET) && clock_settime(CLOCK_REALTIME, &ts) < 0) {
 291                        bb_simple_perror_msg("can't set date");
 292                }
 293        }
 294
 295        /* Display output */
 296
 297        /* Deal with format string */
 298        if (fmt_dt2str == NULL) {
 299                int i;
 300                fmt_dt2str = buf_fmt_dt2str;
 301                if (ENABLE_FEATURE_DATE_ISOFMT && isofmt >= 0) {
 302                        /* -I[SPEC]: 0:date 1:hours 2:minutes 3:seconds 4:ns*/
 303                        strcpy(fmt_dt2str, "%Y-%m-%dT%H:%M:%S");
 304                        i = 8 + 3 * isofmt;
 305                        if (isofmt != 0) {
 306                                int n;
 307                                if (isofmt == 4) {
 308                                        i -= 3;
 309                                        i += sprintf(&fmt_dt2str[i], ",%09u", (unsigned)ts.tv_nsec);
 310                                }
 311                                /* %z prints "+hhmm" timezone, but coreutils-8.30 prints "+hh:mm"! */
 312                                /* ...therefore this atrocity: */
 313                                n = strftime(&fmt_dt2str[i], 8, "%z", &tm_time);
 314                                i += n;
 315                                if (n == 5 && (fmt_dt2str[i-5] == '+' || fmt_dt2str[i-5] == '-')) {
 316                                        /* "mm" -> ":mm" */
 317                                        fmt_dt2str[i    ] = fmt_dt2str[i - 1];
 318                                        fmt_dt2str[i - 1] = fmt_dt2str[i - 2];
 319                                        fmt_dt2str[i - 2] = ':';
 320                                        i++;
 321                                }
 322                        }
 323                        fmt_dt2str[i] = '\0';
 324                } else if (opt & OPT_RFC2822) {
 325                        /* -R. undo busybox.c setlocale */
 326                        if (ENABLE_LOCALE_SUPPORT)
 327                                setlocale(LC_TIME, "C");
 328                        fmt_dt2str = (char*)"%a, %d %b %Y %H:%M:%S %z";
 329                } else { /* default case */
 330                        fmt_dt2str = (char*)"%a %b %e %H:%M:%S %Z %Y";
 331                }
 332        }
 333#if ENABLE_FEATURE_DATE_NANO
 334        else {
 335                /* User-specified fmt_dt2str */
 336                /* Search for and process "%N" */
 337                char *p = fmt_dt2str;
 338                while ((p = strchr(p, '%')) != NULL) {
 339                        int n, m;
 340                        unsigned pres, scale;
 341
 342                        p++;
 343                        if (*p == '%') {
 344                                p++;
 345                                continue;
 346                        }
 347                        n = strspn(p, "0123456789");
 348                        if (p[n] != 'N') {
 349                                p += n;
 350                                continue;
 351                        }
 352                        /* We have "%[nnn]N" */
 353                        p[-1] = '\0';
 354                        p[n] = '\0';
 355                        scale = 1;
 356                        pres = 9;
 357                        if (n) {
 358                                pres = xatoi_positive(p);
 359                                if (pres == 0)
 360                                        pres = 9;
 361                                m = 9 - pres;
 362                                while (--m >= 0)
 363                                        scale *= 10;
 364                        }
 365
 366                        m = p - fmt_dt2str;
 367                        p += n + 1;
 368                        fmt_dt2str = xasprintf("%s%0*u%s", fmt_dt2str, pres, (unsigned)ts.tv_nsec / scale, p);
 369                        p = fmt_dt2str + m;
 370                }
 371        }
 372#endif
 373
 374#define date_buf bb_common_bufsiz1
 375        setup_common_bufsiz();
 376        if (*fmt_dt2str == '\0') {
 377                /* With no format string, just print a blank line */
 378                date_buf[0] = '\0';
 379        } else {
 380                /* Generate output string */
 381                strftime(date_buf, COMMON_BUFSIZE, fmt_dt2str, &tm_time);
 382        }
 383        puts(date_buf);
 384
 385        return EXIT_SUCCESS;
 386}
 387