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