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
  13/* This 'date' command supports only 2 time setting formats,
  14   all the GNU strftime stuff (its in libc, lets use it),
  15   setting time using UTC and displaying it, as well as
  16   an RFC 2822 compliant date output for shell scripting
  17   mail commands */
  18
  19/* Input parsing code is always bulky - used heavy duty libc stuff as
  20   much as possible, missed out a lot of bounds checking */
  21
  22//applet:IF_DATE(APPLET(date, BB_DIR_BIN, BB_SUID_DROP))
  23
  24//kbuild:lib-$(CONFIG_DATE) += date.o
  25
  26//config:config DATE
  27//config:       bool "date"
  28//config:       default y
  29//config:       help
  30//config:         date is used to set the system date or display the
  31//config:         current time in the given format.
  32//config:
  33//config:config FEATURE_DATE_ISOFMT
  34//config:       bool "Enable ISO date format output (-I)"
  35//config:       default y
  36//config:       depends on DATE
  37//config:       help
  38//config:         Enable option (-I) to output an ISO-8601 compliant
  39//config:         date/time string.
  40//config:
  41//config:# defaults to "no": stat's nanosecond field is a bit non-portable
  42//config:config FEATURE_DATE_NANO
  43//config:       bool "Support %[num]N nanosecond format specifier"
  44//config:       default n
  45//config:       depends on DATE  # syscall(__NR_clock_gettime)
  46//config:       select PLATFORM_LINUX
  47//config:       help
  48//config:         Support %[num]N format specifier. Adds ~250 bytes of code.
  49//config:
  50//config:config FEATURE_DATE_COMPAT
  51//config:       bool "Support weird 'date MMDDhhmm[[YY]YY][.ss]' format"
  52//config:       default y
  53//config:       depends on DATE
  54//config:       help
  55//config:         System time can be set by 'date -s DATE' and simply 'date DATE',
  56//config:         but formats of DATE string are different. 'date DATE' accepts
  57//config:         a rather weird MMDDhhmm[[YY]YY][.ss] format with completely
  58//config:         unnatural placement of year between minutes and seconds.
  59//config:         date -s (and other commands like touch -d) use more sensible
  60//config:         formats (for one, ISO format YYYY-MM-DD hh:mm:ss.ssssss).
  61//config:
  62//config:         With this option off, 'date DATE' is 'date -s DATE' support
  63//config:         the same format. With it on, 'date DATE' additionally supports
  64//config:         MMDDhhmm[[YY]YY][.ss] format.
  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:
 133//usage:#define date_example_usage
 134//usage:       "$ date\n"
 135//usage:       "Wed Apr 12 18:52:41 MDT 2000\n"
 136
 137#include "libbb.h"
 138#if ENABLE_FEATURE_DATE_NANO
 139# include <sys/syscall.h>
 140#endif
 141
 142enum {
 143        OPT_RFC2822   = (1 << 0), /* R */
 144        OPT_SET       = (1 << 1), /* s */
 145        OPT_UTC       = (1 << 2), /* u */
 146        OPT_DATE      = (1 << 3), /* d */
 147        OPT_REFERENCE = (1 << 4), /* r */
 148        OPT_TIMESPEC  = (1 << 5) * ENABLE_FEATURE_DATE_ISOFMT, /* I */
 149        OPT_HINT      = (1 << 6) * ENABLE_FEATURE_DATE_ISOFMT, /* D */
 150};
 151
 152static void maybe_set_utc(int opt)
 153{
 154        if (opt & OPT_UTC)
 155                putenv((char*)"TZ=UTC0");
 156}
 157
 158#if ENABLE_LONG_OPTS
 159static const char date_longopts[] ALIGN1 =
 160                "rfc-822\0"   No_argument       "R"
 161                "rfc-2822\0"  No_argument       "R"
 162                "set\0"       Required_argument "s"
 163                "utc\0"       No_argument       "u"
 164        /*      "universal\0" No_argument       "u" */
 165                "date\0"      Required_argument "d"
 166                "reference\0" Required_argument "r"
 167                ;
 168#endif
 169
 170int date_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 171int date_main(int argc UNUSED_PARAM, char **argv)
 172{
 173        struct timespec ts;
 174        struct tm tm_time;
 175        char buf_fmt_dt2str[64];
 176        unsigned opt;
 177        int ifmt = -1;
 178        char *date_str;
 179        char *fmt_dt2str;
 180        char *fmt_str2dt;
 181        char *filename;
 182        char *isofmt_arg = NULL;
 183
 184        opt_complementary = "d--s:s--d"
 185                IF_FEATURE_DATE_ISOFMT(":R--I:I--R");
 186        IF_LONG_OPTS(applet_long_options = date_longopts;)
 187        opt = getopt32(argv, "Rs:ud:r:"
 188                        IF_FEATURE_DATE_ISOFMT("I::D:"),
 189                        &date_str, &date_str, &filename
 190                        IF_FEATURE_DATE_ISOFMT(, &isofmt_arg, &fmt_str2dt));
 191        argv += optind;
 192        maybe_set_utc(opt);
 193
 194        if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_TIMESPEC)) {
 195                ifmt = 0; /* default is date */
 196                if (isofmt_arg) {
 197                        static const char isoformats[] ALIGN1 =
 198                                "date\0""hours\0""minutes\0""seconds\0"; /* ns? */
 199                        ifmt = index_in_substrings(isoformats, isofmt_arg);
 200                        if (ifmt < 0)
 201                                bb_show_usage();
 202                }
 203        }
 204
 205        fmt_dt2str = NULL;
 206        if (argv[0] && argv[0][0] == '+') {
 207                fmt_dt2str = &argv[0][1]; /* skip over the '+' */
 208                argv++;
 209        }
 210        if (!(opt & (OPT_SET | OPT_DATE))) {
 211                opt |= OPT_SET;
 212                date_str = argv[0]; /* can be NULL */
 213                if (date_str) {
 214#if ENABLE_FEATURE_DATE_COMPAT
 215                        int len = strspn(date_str, "0123456789");
 216                        if (date_str[len] == '\0'
 217                         || (date_str[len] == '.'
 218                            && isdigit(date_str[len+1])
 219                            && isdigit(date_str[len+2])
 220                            && date_str[len+3] == '\0'
 221                            )
 222                        ) {
 223                                /* Dreaded MMDDhhmm[[CC]YY][.ss] format!
 224                                 * It does not match -d or -s format.
 225                                 * Some users actually do use it.
 226                                 */
 227                                len -= 8;
 228                                if (len < 0 || len > 4 || (len & 1))
 229                                        bb_error_msg_and_die(bb_msg_invalid_date, date_str);
 230                                if (len != 0) { /* move YY or CCYY to front */
 231                                        char buf[4];
 232                                        memcpy(buf, date_str + 8, len);
 233                                        memmove(date_str + len, date_str, 8);
 234                                        memcpy(date_str, buf, len);
 235                                }
 236                        }
 237#endif
 238                        argv++;
 239                }
 240        }
 241        if (*argv)
 242                bb_show_usage();
 243
 244        /* Now we have parsed all the information except the date format
 245         * which depends on whether the clock is being set or read */
 246
 247        if (opt & OPT_REFERENCE) {
 248                struct stat statbuf;
 249                xstat(filename, &statbuf);
 250                ts.tv_sec = statbuf.st_mtime;
 251#if ENABLE_FEATURE_DATE_NANO
 252                ts.tv_nsec = statbuf.st_mtim.tv_nsec;
 253                /* Some toolchains use .st_mtimensec instead of st_mtim.tv_nsec.
 254                 * If you need #define _SVID_SOURCE 1 to enable st_mtim.tv_nsec,
 255                 * drop a mail to project mailing list please
 256                 */
 257#endif
 258        } else {
 259#if ENABLE_FEATURE_DATE_NANO
 260                /* libc has incredibly messy way of doing this,
 261                 * typically requiring -lrt. We just skip all this mess */
 262                syscall(__NR_clock_gettime, CLOCK_REALTIME, &ts);
 263#else
 264                time(&ts.tv_sec);
 265#endif
 266        }
 267        localtime_r(&ts.tv_sec, &tm_time);
 268
 269        /* If date string is given, update tm_time, and maybe set date */
 270        if (date_str != NULL) {
 271                /* Zero out fields - take her back to midnight! */
 272                tm_time.tm_sec = 0;
 273                tm_time.tm_min = 0;
 274                tm_time.tm_hour = 0;
 275
 276                /* Process any date input to UNIX time since 1 Jan 1970 */
 277                if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_HINT)) {
 278                        if (strptime(date_str, fmt_str2dt, &tm_time) == NULL)
 279                                bb_error_msg_and_die(bb_msg_invalid_date, date_str);
 280                } else {
 281                        parse_datestr(date_str, &tm_time);
 282                }
 283
 284                /* Correct any day of week and day of year etc. fields */
 285                /* Be sure to recheck dst (but not if date is time_t format) */
 286                if (date_str[0] != '@')
 287                        tm_time.tm_isdst = -1;
 288                ts.tv_sec = validate_tm_time(date_str, &tm_time);
 289
 290                maybe_set_utc(opt);
 291
 292                /* if setting time, set it */
 293                if ((opt & OPT_SET) && stime(&ts.tv_sec) < 0) {
 294                        bb_perror_msg("can't set date");
 295                }
 296        }
 297
 298        /* Display output */
 299
 300        /* Deal with format string */
 301        if (fmt_dt2str == NULL) {
 302                int i;
 303                fmt_dt2str = buf_fmt_dt2str;
 304                if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) {
 305                        /* -I[SPEC]: 0:date 1:hours 2:minutes 3:seconds */
 306                        strcpy(fmt_dt2str, "%Y-%m-%dT%H:%M:%S");
 307                        i = 8 + 3 * ifmt;
 308                        if (ifmt != 0) {
 309                                /* TODO: if (ifmt==4) i += sprintf(&fmt_dt2str[i], ",%09u", nanoseconds); */
 310 format_utc:
 311                                fmt_dt2str[i++] = '%';
 312                                fmt_dt2str[i++] = (opt & OPT_UTC) ? 'Z' : 'z';
 313                        }
 314                        fmt_dt2str[i] = '\0';
 315                } else if (opt & OPT_RFC2822) {
 316                        /* -R. undo busybox.c setlocale */
 317                        if (ENABLE_LOCALE_SUPPORT)
 318                                setlocale(LC_TIME, "C");
 319                        strcpy(fmt_dt2str, "%a, %d %b %Y %H:%M:%S ");
 320                        i = sizeof("%a, %d %b %Y %H:%M:%S ")-1;
 321                        goto format_utc;
 322                } else { /* default case */
 323                        fmt_dt2str = (char*)"%a %b %e %H:%M:%S %Z %Y";
 324                }
 325        }
 326#if ENABLE_FEATURE_DATE_NANO
 327        else {
 328                /* User-specified fmt_dt2str */
 329                /* Search for and process "%N" */
 330                char *p = fmt_dt2str;
 331                while ((p = strchr(p, '%')) != NULL) {
 332                        int n, m;
 333                        unsigned pres, scale;
 334
 335                        p++;
 336                        if (*p == '%') {
 337                                p++;
 338                                continue;
 339                        }
 340                        n = strspn(p, "0123456789");
 341                        if (p[n] != 'N') {
 342                                p += n;
 343                                continue;
 344                        }
 345                        /* We have "%[nnn]N" */
 346                        p[-1] = '\0';
 347                        p[n] = '\0';
 348                        scale = 1;
 349                        pres = 9;
 350                        if (n) {
 351                                pres = xatoi_positive(p);
 352                                if (pres == 0)
 353                                        pres = 9;
 354                                m = 9 - pres;
 355                                while (--m >= 0)
 356                                        scale *= 10;
 357                        }
 358
 359                        m = p - fmt_dt2str;
 360                        p += n + 1;
 361                        fmt_dt2str = xasprintf("%s%0*u%s", fmt_dt2str, pres, (unsigned)ts.tv_nsec / scale, p);
 362                        p = fmt_dt2str + m;
 363                }
 364        }
 365#endif
 366
 367#define date_buf bb_common_bufsiz1
 368        if (*fmt_dt2str == '\0') {
 369                /* With no format string, just print a blank line */
 370                date_buf[0] = '\0';
 371        } else {
 372                /* Handle special conversions */
 373                if (strncmp(fmt_dt2str, "%f", 2) == 0) {
 374                        fmt_dt2str = (char*)"%Y.%m.%d-%H:%M:%S";
 375                }
 376                /* Generate output string */
 377                strftime(date_buf, sizeof(date_buf), fmt_dt2str, &tm_time);
 378        }
 379        puts(date_buf);
 380
 381        return EXIT_SUCCESS;
 382}
 383