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 tarball for details.
  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/* Default input handling to save surprising some people */
  23
  24/* GNU coreutils 6.9 man page:
  25 * date [OPTION]... [+FORMAT]
  26 * date [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]
  27 * -d, --date=STRING
  28 *      display time described by STRING, not `now'
  29 * -f, --file=DATEFILE
  30 *      like --date once for each line of DATEFILE
  31 * -r, --reference=FILE
  32 *      display the last modification time of FILE
  33 * -R, --rfc-2822
  34 *      output date and time in RFC 2822 format.
  35 *      Example: Mon, 07 Aug 2006 12:34:56 -0600
  36 * --rfc-3339=TIMESPEC
  37 *      output date and time in RFC 3339 format.
  38 *      TIMESPEC='date', 'seconds', or 'ns'
  39 *      Date and time components are separated by a single space:
  40 *      2006-08-07 12:34:56-06:00
  41 * -s, --set=STRING
  42 *      set time described by STRING
  43 * -u, --utc, --universal
  44 *      print or set Coordinated Universal Time
  45 *
  46 * Busybox:
  47 * long options are not supported
  48 * -f is not supported
  49 * -I seems to roughly match --rfc-3339, but -I has _optional_ param
  50 *    (thus "-I seconds" doesn't work, only "-Iseconds"),
  51 *    and does not support -Ins
  52 * -D FMT is a bbox extension for _input_ conversion of -d DATE
  53 */
  54#include "libbb.h"
  55
  56enum {
  57        OPT_RFC2822   = (1 << 0), /* R */
  58        OPT_SET       = (1 << 1), /* s */
  59        OPT_UTC       = (1 << 2), /* u */
  60        OPT_DATE      = (1 << 3), /* d */
  61        OPT_REFERENCE = (1 << 4), /* r */
  62        OPT_TIMESPEC  = (1 << 5) * ENABLE_FEATURE_DATE_ISOFMT, /* I */
  63        OPT_HINT      = (1 << 6) * ENABLE_FEATURE_DATE_ISOFMT, /* D */
  64};
  65
  66static void maybe_set_utc(int opt)
  67{
  68        if (opt & OPT_UTC)
  69                putenv((char*)"TZ=UTC0");
  70}
  71
  72#if ENABLE_LONG_OPTS
  73static const char date_longopts[] ALIGN1 =
  74                "rfc-822\0"   No_argument       "R"
  75                "rfc-2822\0"  No_argument       "R"
  76                "set\0"       Required_argument "s"
  77                "utc\0"       No_argument       "u"
  78        /*      "universal\0" No_argument       "u" */
  79                "date\0"      Required_argument "d"
  80                "reference\0" Required_argument "r"
  81                ;
  82#endif
  83
  84int date_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  85int date_main(int argc UNUSED_PARAM, char **argv)
  86{
  87        struct tm tm_time;
  88        char buf_fmt_dt2str[64];
  89        time_t tm;
  90        unsigned opt;
  91        int ifmt = -1;
  92        char *date_str;
  93        char *fmt_dt2str;
  94        char *fmt_str2dt;
  95        char *filename;
  96        char *isofmt_arg = NULL;
  97
  98        opt_complementary = "d--s:s--d"
  99                IF_FEATURE_DATE_ISOFMT(":R--I:I--R");
 100        IF_LONG_OPTS(applet_long_options = date_longopts;)
 101        opt = getopt32(argv, "Rs:ud:r:"
 102                        IF_FEATURE_DATE_ISOFMT("I::D:"),
 103                        &date_str, &date_str, &filename
 104                        IF_FEATURE_DATE_ISOFMT(, &isofmt_arg, &fmt_str2dt));
 105        argv += optind;
 106        maybe_set_utc(opt);
 107
 108        if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_TIMESPEC)) {
 109                ifmt = 0; /* default is date */
 110                if (isofmt_arg) {
 111                        static const char isoformats[] ALIGN1 =
 112                                "date\0""hours\0""minutes\0""seconds\0"; /* ns? */
 113                        ifmt = index_in_substrings(isoformats, isofmt_arg);
 114                        if (ifmt < 0)
 115                                bb_show_usage();
 116                }
 117        }
 118
 119        fmt_dt2str = NULL;
 120        if (argv[0] && argv[0][0] == '+') {
 121                fmt_dt2str = &argv[0][1]; /* skip over the '+' */
 122                argv++;
 123        }
 124        if (!(opt & (OPT_SET | OPT_DATE))) {
 125                opt |= OPT_SET;
 126                date_str = argv[0]; /* can be NULL */
 127                if (date_str) {
 128#if ENABLE_FEATURE_DATE_COMPAT
 129                        int len = strspn(date_str, "0123456789");
 130                        if (date_str[len] == '\0'
 131                         || (date_str[len] == '.'
 132                            && isdigit(date_str[len+1])
 133                            && isdigit(date_str[len+2])
 134                            && date_str[len+3] == '\0'
 135                            )
 136                        ) {
 137                                /* Dreaded MMDDhhmm[[CC]YY][.ss] format!
 138                                 * It does not match -d or -s format.
 139                                 * Some users actually do use it.
 140                                 */
 141                                len -= 8;
 142                                if (len < 0 || len > 4 || (len & 1))
 143                                        bb_error_msg_and_die(bb_msg_invalid_date, date_str);
 144                                if (len != 0) { /* move YY or CCYY to front */
 145                                        char buf[4];
 146                                        memcpy(buf, date_str + 8, len);
 147                                        memmove(date_str + len, date_str, 8);
 148                                        memcpy(date_str, buf, len);
 149                                }
 150                        }
 151#endif
 152                        argv++;
 153                }
 154        }
 155        if (*argv)
 156                bb_show_usage();
 157
 158        /* Now we have parsed all the information except the date format
 159         * which depends on whether the clock is being set or read */
 160
 161        if (opt & OPT_REFERENCE) {
 162                struct stat statbuf;
 163                xstat(filename, &statbuf);
 164                tm = statbuf.st_mtime;
 165        } else {
 166                time(&tm);
 167        }
 168        localtime_r(&tm, &tm_time);
 169
 170        /* If date string is given, update tm_time, and maybe set date */
 171        if (date_str != NULL) {
 172                /* Zero out fields - take her back to midnight! */
 173                tm_time.tm_sec = 0;
 174                tm_time.tm_min = 0;
 175                tm_time.tm_hour = 0;
 176
 177                /* Process any date input to UNIX time since 1 Jan 1970 */
 178                if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_HINT)) {
 179                        if (strptime(date_str, fmt_str2dt, &tm_time) == NULL)
 180                                bb_error_msg_and_die(bb_msg_invalid_date, date_str);
 181                } else {
 182                        parse_datestr(date_str, &tm_time);
 183                }
 184
 185                /* Correct any day of week and day of year etc. fields */
 186                tm_time.tm_isdst = -1;  /* Be sure to recheck dst */
 187                tm = validate_tm_time(date_str, &tm_time);
 188
 189                maybe_set_utc(opt);
 190
 191                /* if setting time, set it */
 192                if ((opt & OPT_SET) && stime(&tm) < 0) {
 193                        bb_perror_msg("can't set date");
 194                }
 195        }
 196
 197        /* Display output */
 198
 199        /* Deal with format string */
 200        if (fmt_dt2str == NULL) {
 201                int i;
 202                fmt_dt2str = buf_fmt_dt2str;
 203                if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) {
 204                        /* -I[SPEC]: 0:date 1:hours 2:minutes 3:seconds */
 205                        strcpy(fmt_dt2str, "%Y-%m-%dT%H:%M:%S");
 206                        i = 8 + 3 * ifmt;
 207                        if (ifmt != 0) {
 208                                /* TODO: if (ifmt==4) i += sprintf(&fmt_dt2str[i], ",%09u", nanoseconds); */
 209 format_utc:
 210                                fmt_dt2str[i++] = '%';
 211                                fmt_dt2str[i++] = (opt & OPT_UTC) ? 'Z' : 'z';
 212                        }
 213                        fmt_dt2str[i] = '\0';
 214                } else if (opt & OPT_RFC2822) {
 215                        /* -R. undo busybox.c setlocale */
 216                        if (ENABLE_LOCALE_SUPPORT)
 217                                setlocale(LC_TIME, "C");
 218                        strcpy(fmt_dt2str, "%a, %d %b %Y %H:%M:%S ");
 219                        i = sizeof("%a, %d %b %Y %H:%M:%S ")-1;
 220                        goto format_utc;
 221                } else { /* default case */
 222                        fmt_dt2str = (char*)"%a %b %e %H:%M:%S %Z %Y";
 223                }
 224        }
 225
 226#define date_buf bb_common_bufsiz1
 227        if (*fmt_dt2str == '\0') {
 228                /* With no format string, just print a blank line */
 229                date_buf[0] = '\0';
 230        } else {
 231                /* Handle special conversions */
 232                if (strncmp(fmt_dt2str, "%f", 2) == 0) {
 233                        fmt_dt2str = (char*)"%Y.%m.%d-%H:%M:%S";
 234                }
 235                /* Generate output string */
 236                strftime(date_buf, sizeof(date_buf), fmt_dt2str, &tm_time);
 237        }
 238        puts(date_buf);
 239
 240        return EXIT_SUCCESS;
 241}
 242