busybox/libbb/time.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * Utility routines.
   4 *
   5 * Copyright (C) 2007 Denys Vlasenko
   6 *
   7 * Licensed under GPLv2, see file LICENSE in this source tree.
   8 */
   9#include "libbb.h"
  10
  11/* Returns 0 if the time structure contains an absolute UTC time which
  12 * should not be subject to DST adjustment by the caller. */
  13int FAST_FUNC parse_datestr(const char *date_str, struct tm *ptm)
  14{
  15        char end = '\0';
  16        time_t t;
  17#if ENABLE_DESKTOP
  18/*
  19 * strptime is BIG: ~1k in uclibc, ~10k in glibc
  20 * We need it for 'month_name d HH:MM:SS YYYY', supported by GNU date,
  21 * but if we've linked it we might as well use it for everything.
  22 */
  23        static const char fmt_str[] ALIGN1 =
  24                "%R" "\0"               /* HH:MM */
  25                "%T" "\0"               /* HH:MM:SS */
  26                "%m.%d-%R" "\0"         /* mm.dd-HH:MM */
  27                "%m.%d-%T" "\0"         /* mm.dd-HH:MM:SS */
  28                "%Y.%m.%d-%R" "\0"      /* yyyy.mm.dd-HH:MM */
  29                "%Y.%m.%d-%T" "\0"      /* yyyy.mm.dd-HH:MM:SS */
  30                "%b %d %T %Y" "\0"      /* month_name d HH:MM:SS YYYY */
  31                "%Y-%m-%d %R" "\0"      /* yyyy-mm-dd HH:MM */
  32                "%Y-%m-%d %T" "\0"      /* yyyy-mm-dd HH:MM:SS */
  33# if ENABLE_FEATURE_TIMEZONE
  34                "%Y-%m-%d %R %z" "\0"   /* yyyy-mm-dd HH:MM TZ */
  35                "%Y-%m-%d %T %z" "\0"   /* yyyy-mm-dd HH:MM:SS TZ */
  36# endif
  37                "%Y-%m-%d %H" "\0"      /* yyyy-mm-dd HH */
  38                "%Y-%m-%d" "\0"         /* yyyy-mm-dd */
  39                /* extra NUL */;
  40        struct tm save;
  41        const char *fmt;
  42        char *endp;
  43
  44        save = *ptm;
  45        fmt = fmt_str;
  46        while (*fmt) {
  47                endp = strptime(date_str, fmt, ptm);
  48                if (endp && *endp == '\0') {
  49# if ENABLE_FEATURE_TIMEZONE
  50                        if (strchr(fmt, 'z')) {
  51                                /* we have timezone offset: obtain Unix time_t */
  52                                ptm->tm_sec -= ptm->tm_gmtoff;
  53                                ptm->tm_isdst = 0;
  54                                t = timegm(ptm);
  55                                if (t == (time_t)-1)
  56                                        break;
  57                                /* convert Unix time_t to struct tm in user's locale */
  58                                goto localise;
  59                        }
  60# endif
  61                        return 1;
  62                }
  63                *ptm = save;
  64                while (*++fmt)
  65                        continue;
  66                ++fmt;
  67        }
  68#else
  69        const char *last_colon = strrchr(date_str, ':');
  70
  71        if (last_colon != NULL) {
  72                /* Parse input and assign appropriately to ptm */
  73
  74                /* HH:MM */
  75                if (sscanf(date_str, "%u:%u%c",
  76                                        &ptm->tm_hour,
  77                                        &ptm->tm_min,
  78                                        &end) >= 2
  79                ) {
  80                        /* no adjustments needed */
  81                } else
  82                /* mm.dd-HH:MM */
  83                if (sscanf(date_str, "%u.%u-%u:%u%c",
  84                                        &ptm->tm_mon, &ptm->tm_mday,
  85                                        &ptm->tm_hour, &ptm->tm_min,
  86                                        &end) >= 4
  87                ) {
  88                        /* Adjust month from 1-12 to 0-11 */
  89                        ptm->tm_mon -= 1;
  90                } else
  91                /* yyyy.mm.dd-HH:MM */
  92                if (sscanf(date_str, "%u.%u.%u-%u:%u%c", &ptm->tm_year,
  93                                        &ptm->tm_mon, &ptm->tm_mday,
  94                                        &ptm->tm_hour, &ptm->tm_min,
  95                                        &end) >= 5
  96                /* yyyy-mm-dd HH:MM */
  97                 || sscanf(date_str, "%u-%u-%u %u:%u%c", &ptm->tm_year,
  98                                        &ptm->tm_mon, &ptm->tm_mday,
  99                                        &ptm->tm_hour, &ptm->tm_min,
 100                                        &end) >= 5
 101                ) {
 102                        ptm->tm_year -= 1900; /* Adjust years */
 103                        ptm->tm_mon -= 1; /* Adjust month from 1-12 to 0-11 */
 104                } else
 105                {
 106                        bb_error_msg_and_die(bb_msg_invalid_date, date_str);
 107                }
 108                if (end == ':') {
 109                        /* xxx:SS */
 110                        if (sscanf(last_colon + 1, "%u%c", &ptm->tm_sec, &end) == 1)
 111                                end = '\0';
 112                        /* else end != NUL and we error out */
 113                }
 114        } else
 115        if (strchr(date_str, '-')
 116            /* Why strchr('-') check?
 117             * sscanf below will trash ptm->tm_year, this breaks
 118             * if parse_str is "10101010" (iow, "MMddhhmm" form)
 119             * because we destroy year. Do these sscanf
 120             * only if we saw a dash in parse_str.
 121             */
 122                /* yyyy-mm-dd HH */
 123         && (sscanf(date_str, "%u-%u-%u %u%c", &ptm->tm_year,
 124                                &ptm->tm_mon, &ptm->tm_mday,
 125                                &ptm->tm_hour,
 126                                &end) >= 4
 127                /* yyyy-mm-dd */
 128             || sscanf(date_str, "%u-%u-%u%c", &ptm->tm_year,
 129                                &ptm->tm_mon, &ptm->tm_mday,
 130                                &end) >= 3
 131            )
 132        ) {
 133                ptm->tm_year -= 1900; /* Adjust years */
 134                ptm->tm_mon -= 1; /* Adjust month from 1-12 to 0-11 */
 135        } else
 136#endif /* ENABLE_DESKTOP */
 137        if (date_str[0] == '@') {
 138                if (sizeof(t) <= sizeof(long))
 139                        t = bb_strtol(date_str + 1, NULL, 10);
 140                else /* time_t is 64 bits but longs are smaller */
 141                        t = bb_strtoll(date_str + 1, NULL, 10);
 142                if (!errno) {
 143                        struct tm *lt;
 144 IF_FEATURE_TIMEZONE(localise:)
 145                        lt = localtime(&t);
 146                        if (lt) {
 147                                *ptm = *lt;
 148                                return 0;
 149                        }
 150                }
 151                end = '1';
 152        } else {
 153                /* Googled the following on an old date manpage:
 154                 *
 155                 * The canonical representation for setting the date/time is:
 156                 * cc   Century (either 19 or 20)
 157                 * yy   Year in abbreviated form (e.g. 89, 06)
 158                 * mm   Numeric month, a number from 1 to 12
 159                 * dd   Day, a number from 1 to 31
 160                 * HH   Hour, a number from 0 to 23
 161                 * MM   Minutes, a number from 0 to 59
 162                 * .SS  Seconds, a number from 0 to 61 (with leap seconds)
 163                 * Everything but the minutes is optional
 164                 *
 165                 * "touch -t DATETIME" format: [[[[[YY]YY]MM]DD]hh]mm[.ss]
 166                 * Some, but not all, Unix "date DATETIME" commands
 167                 * move [[YY]YY] past minutes mm field (!).
 168                 * Coreutils date does it, and SUS mandates it.
 169                 * (date -s DATETIME does not support this format. lovely!)
 170                 * In bbox, this format is special-cased in date applet
 171                 * (IOW: this function assumes "touch -t" format).
 172                 */
 173                unsigned cur_year = ptm->tm_year;
 174                int len = strchrnul(date_str, '.') - date_str;
 175
 176                /* MM[.SS] */
 177                if (len == 2 && sscanf(date_str, "%2u%2u%2u%2u""%2u%c" + 12,
 178                                        &ptm->tm_min,
 179                                        &end) >= 1) {
 180                } else
 181                /* HHMM[.SS] */
 182                if (len == 4 && sscanf(date_str, "%2u%2u%2u""%2u%2u%c" + 9,
 183                                        &ptm->tm_hour,
 184                                        &ptm->tm_min,
 185                                        &end) >= 2) {
 186                } else
 187                /* ddHHMM[.SS] */
 188                if (len == 6 && sscanf(date_str, "%2u%2u""%2u%2u%2u%c" + 6,
 189                                        &ptm->tm_mday,
 190                                        &ptm->tm_hour,
 191                                        &ptm->tm_min,
 192                                        &end) >= 3) {
 193                } else
 194                /* mmddHHMM[.SS] */
 195                if (len == 8 && sscanf(date_str, "%2u""%2u%2u%2u%2u%c" + 3,
 196                                        &ptm->tm_mon,
 197                                        &ptm->tm_mday,
 198                                        &ptm->tm_hour,
 199                                        &ptm->tm_min,
 200                                        &end) >= 4) {
 201                        /* Adjust month from 1-12 to 0-11 */
 202                        ptm->tm_mon -= 1;
 203                } else
 204                /* yymmddHHMM[.SS] */
 205                if (len == 10 && sscanf(date_str, "%2u%2u%2u%2u%2u%c",
 206                                        &ptm->tm_year,
 207                                        &ptm->tm_mon,
 208                                        &ptm->tm_mday,
 209                                        &ptm->tm_hour,
 210                                        &ptm->tm_min,
 211                                        &end) >= 5) {
 212                        /* Adjust month from 1-12 to 0-11 */
 213                        ptm->tm_mon -= 1;
 214                        if ((int)cur_year >= 50) { /* >= 1950 */
 215                                /* Adjust year: */
 216                                /* 1. Put it in the current century */
 217                                ptm->tm_year += (cur_year / 100) * 100;
 218                                /* 2. If too far in the past, +100 years */
 219                                if (ptm->tm_year < cur_year - 50)
 220                                        ptm->tm_year += 100;
 221                                /* 3. If too far in the future, -100 years */
 222                                if (ptm->tm_year > cur_year + 50)
 223                                        ptm->tm_year -= 100;
 224                        }
 225                } else
 226                /* ccyymmddHHMM[.SS] */
 227                if (len == 12 && sscanf(date_str, "%4u%2u%2u%2u%2u%c",
 228                                        &ptm->tm_year,
 229                                        &ptm->tm_mon,
 230                                        &ptm->tm_mday,
 231                                        &ptm->tm_hour,
 232                                        &ptm->tm_min,
 233                                        &end) >= 5) {
 234                        ptm->tm_year -= 1900; /* Adjust years */
 235                        ptm->tm_mon -= 1; /* Adjust month from 1-12 to 0-11 */
 236                } else {
 237 err:
 238                        bb_error_msg_and_die(bb_msg_invalid_date, date_str);
 239                }
 240                ptm->tm_sec = 0; /* assume zero if [.SS] is not given */
 241                if (end == '.') {
 242                        /* xxx.SS */
 243                        if (sscanf(strchr(date_str, '.') + 1, "%u%c",
 244                                        &ptm->tm_sec, &end) == 1)
 245                                end = '\0';
 246                        /* else end != NUL and we error out */
 247                }
 248                /* Users were confused by "date -s 20180923"
 249                 * working (not in the way they were expecting).
 250                 * It was interpreted as MMDDhhmm, and not bothered by
 251                 * "month #20" in the least. Prevent such cases:
 252                 */
 253                if (ptm->tm_sec > 60 /* allow "23:60" leap second */
 254                 || ptm->tm_min > 59
 255                 || ptm->tm_hour > 23
 256                 || ptm->tm_mday > 31
 257                 || ptm->tm_mon > 11 /* month# is 0..11, not 1..12 */
 258                ) {
 259                        goto err;
 260                }
 261        }
 262        if (end != '\0') {
 263                bb_error_msg_and_die(bb_msg_invalid_date, date_str);
 264        }
 265        return 1;
 266}
 267
 268time_t FAST_FUNC validate_tm_time(const char *date_str, struct tm *ptm)
 269{
 270        time_t t = mktime(ptm);
 271        if (t == (time_t) -1L) {
 272                bb_error_msg_and_die(bb_msg_invalid_date, date_str);
 273        }
 274        return t;
 275}
 276
 277static char* strftime_fmt(char *buf, unsigned len, time_t *tp, const char *fmt)
 278{
 279        time_t t;
 280        if (!tp) {
 281                tp = &t;
 282                time(tp);
 283        }
 284        /* Returns pointer to NUL */
 285        return buf + strftime(buf, len, fmt, localtime(tp));
 286}
 287
 288char* FAST_FUNC strftime_HHMMSS(char *buf, unsigned len, time_t *tp)
 289{
 290        return strftime_fmt(buf, len, tp, "%H:%M:%S");
 291}
 292
 293char* FAST_FUNC strftime_YYYYMMDDHHMMSS(char *buf, unsigned len, time_t *tp)
 294{
 295        return strftime_fmt(buf, len, tp, "%Y-%m-%d %H:%M:%S");
 296}
 297
 298#if ENABLE_MONOTONIC_SYSCALL
 299
 300/* Old glibc (< 2.3.4) does not provide this constant. We use syscall
 301 * directly so this definition is safe. */
 302#ifndef CLOCK_MONOTONIC
 303#define CLOCK_MONOTONIC 1
 304#endif
 305
 306static void get_mono(struct timespec *ts)
 307{
 308        if (clock_gettime(CLOCK_MONOTONIC, ts))
 309                bb_simple_error_msg_and_die("clock_gettime(MONOTONIC) failed");
 310}
 311unsigned long long FAST_FUNC monotonic_ns(void)
 312{
 313        struct timespec ts;
 314        get_mono(&ts);
 315        return ts.tv_sec * 1000000000ULL + ts.tv_nsec;
 316}
 317unsigned long long FAST_FUNC monotonic_us(void)
 318{
 319        struct timespec ts;
 320        get_mono(&ts);
 321        return ts.tv_sec * 1000000ULL + ts.tv_nsec/1000;
 322}
 323unsigned long long FAST_FUNC monotonic_ms(void)
 324{
 325        struct timespec ts;
 326        get_mono(&ts);
 327        return ts.tv_sec * 1000ULL + ts.tv_nsec/1000000;
 328}
 329unsigned FAST_FUNC monotonic_sec(void)
 330{
 331        struct timespec ts;
 332        get_mono(&ts);
 333        return ts.tv_sec;
 334}
 335
 336#else
 337
 338unsigned long long FAST_FUNC monotonic_ns(void)
 339{
 340        struct timeval tv;
 341        xgettimeofday(&tv);
 342        return tv.tv_sec * 1000000000ULL + tv.tv_usec * 1000;
 343}
 344unsigned long long FAST_FUNC monotonic_us(void)
 345{
 346        struct timeval tv;
 347        xgettimeofday(&tv);
 348        return tv.tv_sec * 1000000ULL + tv.tv_usec;
 349}
 350unsigned long long FAST_FUNC monotonic_ms(void)
 351{
 352        struct timeval tv;
 353        xgettimeofday(&tv);
 354        return tv.tv_sec * 1000ULL + tv.tv_usec / 1000;
 355}
 356unsigned FAST_FUNC monotonic_sec(void)
 357{
 358        return time(NULL);
 359}
 360
 361#endif
 362