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