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 && PLATFORM_LINUX # syscall(__NR_clock_gettime)
  46//config:       help
  47//config:         Support %[num]N format specifier. Adds ~250 bytes of code.
  48//config:
  49//config:config FEATURE_DATE_COMPAT
  50//config:       bool "Support weird 'date MMDDhhmm[[YY]YY][.ss]' format"
  51//config:       default y
  52//config:       depends on DATE
  53//config:       help
  54//config:         System time can be set by 'date -s DATE' and simply 'date DATE',
  55//config:         but formats of DATE string are different. 'date DATE' accepts
  56//config:         a rather weird MMDDhhmm[[YY]YY][.ss] format with completely
  57//config:         unnatural placement of year between minutes and seconds.
  58//config:         date -s (and other commands like touch -d) use more sensible
  59//config:         formats (for one, ISO format YYYY-MM-DD hh:mm:ss.ssssss).
  60//config:
  61//config:         With this option off, 'date DATE' is 'date -s DATE' support
  62//config:         the same format. With it on, 'date DATE' additionally supports
  63//config:         MMDDhhmm[[YY]YY][.ss] format.
  64
  65/* GNU coreutils 6.9 man page:
  66 * date [OPTION]... [+FORMAT]
  67 * date [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]
  68 * -d, --date=STRING
  69 *      display time described by STRING, not `now'
  70 * -f, --file=DATEFILE
  71 *      like --date once for each line of DATEFILE
  72 * -r, --reference=FILE
  73 *      display the last modification time of FILE
  74 * -R, --rfc-2822
  75 *      output date and time in RFC 2822 format.
  76 *      Example: Mon, 07 Aug 2006 12:34:56 -0600
  77 * --rfc-3339=TIMESPEC
  78 *      output date and time in RFC 3339 format.
  79 *      TIMESPEC='date', 'seconds', or 'ns'
  80 *      Date and time components are separated by a single space:
  81 *      2006-08-07 12:34:56-06:00
  82 * -s, --set=STRING
  83 *      set time described by STRING
  84 * -u, --utc, --universal
  85 *      print or set Coordinated Universal Time
  86 *
  87 * Busybox:
  88 * long options are not supported
  89 * -f is not supported
  90 * -I seems to roughly match --rfc-3339, but -I has _optional_ param
  91 *    (thus "-I seconds" doesn't work, only "-Iseconds"),
  92 *    and does not support -Ins
  93 * -D FMT is a bbox extension for _input_ conversion of -d DATE
  94 */
  95
  96//usage:#define date_trivial_usage
  97//usage:       "[OPTIONS] [+FMT] [TIME]"
  98//usage:#define date_full_usage "\n\n"
  99//usage:       "Display time (using +FMT), or set time\n"
 100//usage:     "\nOptions:"
 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#endif
 255        } else {
 256#if ENABLE_FEATURE_DATE_NANO
 257                /* libc has incredibly messy way of doing this,
 258                 * typically requiring -lrt. We just skip all this mess */
 259                syscall(__NR_clock_gettime, CLOCK_REALTIME, &ts);
 260#else
 261                time(&ts.tv_sec);
 262#endif
 263        }
 264        localtime_r(&ts.tv_sec, &tm_time);
 265
 266        /* If date string is given, update tm_time, and maybe set date */
 267        if (date_str != NULL) {
 268                /* Zero out fields - take her back to midnight! */
 269                tm_time.tm_sec = 0;
 270                tm_time.tm_min = 0;
 271                tm_time.tm_hour = 0;
 272
 273                /* Process any date input to UNIX time since 1 Jan 1970 */
 274                if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_HINT)) {
 275                        if (strptime(date_str, fmt_str2dt, &tm_time) == NULL)
 276                                bb_error_msg_and_die(bb_msg_invalid_date, date_str);
 277                } else {
 278                        parse_datestr(date_str, &tm_time);
 279                }
 280
 281                /* Correct any day of week and day of year etc. fields */
 282                tm_time.tm_isdst = -1;  /* Be sure to recheck dst */
 283                ts.tv_sec = validate_tm_time(date_str, &tm_time);
 284
 285                maybe_set_utc(opt);
 286
 287                /* if setting time, set it */
 288                if ((opt & OPT_SET) && stime(&ts.tv_sec) < 0) {
 289                        bb_perror_msg("can't set date");
 290                }
 291        }
 292
 293        /* Display output */
 294
 295        /* Deal with format string */
 296        if (fmt_dt2str == NULL) {
 297                int i;
 298                fmt_dt2str = buf_fmt_dt2str;
 299                if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) {
 300                        /* -I[SPEC]: 0:date 1:hours 2:minutes 3:seconds */
 301                        strcpy(fmt_dt2str, "%Y-%m-%dT%H:%M:%S");
 302                        i = 8 + 3 * ifmt;
 303                        if (ifmt != 0) {
 304                                /* TODO: if (ifmt==4) i += sprintf(&fmt_dt2str[i], ",%09u", nanoseconds); */
 305 format_utc:
 306                                fmt_dt2str[i++] = '%';
 307                                fmt_dt2str[i++] = (opt & OPT_UTC) ? 'Z' : 'z';
 308                        }
 309                        fmt_dt2str[i] = '\0';
 310                } else if (opt & OPT_RFC2822) {
 311                        /* -R. undo busybox.c setlocale */
 312                        if (ENABLE_LOCALE_SUPPORT)
 313                                setlocale(LC_TIME, "C");
 314                        strcpy(fmt_dt2str, "%a, %d %b %Y %H:%M:%S ");
 315                        i = sizeof("%a, %d %b %Y %H:%M:%S ")-1;
 316                        goto format_utc;
 317                } else { /* default case */
 318                        fmt_dt2str = (char*)"%a %b %e %H:%M:%S %Z %Y";
 319                }
 320        }
 321#if ENABLE_FEATURE_DATE_NANO
 322        else {
 323                /* User-specified fmt_dt2str */
 324                /* Search for and process "%N" */
 325                char *p = fmt_dt2str;
 326                while ((p = strchr(p, '%')) != NULL) {
 327                        int n, m;
 328                        unsigned pres, scale;
 329
 330                        p++;
 331                        if (*p == '%') {
 332                                p++;
 333                                continue;
 334                        }
 335                        n = strspn(p, "0123456789");
 336                        if (p[n] != 'N') {
 337                                p += n;
 338                                continue;
 339                        }
 340                        /* We have "%[nnn]N" */
 341                        p[-1] = '\0';
 342                        p[n] = '\0';
 343                        scale = 1;
 344                        pres = 9;
 345                        if (n) {
 346                                pres = xatoi_positive(p);
 347                                if (pres == 0)
 348                                        pres = 9;
 349                                m = 9 - pres;
 350                                while (--m >= 0)
 351                                        scale *= 10;
 352                        }
 353
 354                        m = p - fmt_dt2str;
 355                        p += n + 1;
 356                        fmt_dt2str = xasprintf("%s%0*u%s", fmt_dt2str, pres, (unsigned)ts.tv_nsec / scale, p);
 357                        p = fmt_dt2str + m;
 358                }
 359        }
 360#endif
 361
 362#define date_buf bb_common_bufsiz1
 363        if (*fmt_dt2str == '\0') {
 364                /* With no format string, just print a blank line */
 365                date_buf[0] = '\0';
 366        } else {
 367                /* Handle special conversions */
 368                if (strncmp(fmt_dt2str, "%f", 2) == 0) {
 369                        fmt_dt2str = (char*)"%Y.%m.%d-%H:%M:%S";
 370                }
 371                /* Generate output string */
 372                strftime(date_buf, sizeof(date_buf), fmt_dt2str, &tm_time);
 373        }
 374        puts(date_buf);
 375
 376        return EXIT_SUCCESS;
 377}
 378