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 = bb_strtol(date_str + 1, NULL, 10);
  94                if (!errno) {
  95                        struct tm *lt = localtime(&t);
  96                        if (lt) {
  97                                *ptm = *lt;
  98                                return;
  99                        }
 100                }
 101                end = '1';
 102        } else {
 103                /* Googled the following on an old date manpage:
 104                 *
 105                 * The canonical representation for setting the date/time is:
 106                 * cc   Century (either 19 or 20)
 107                 * yy   Year in abbreviated form (e.g. 89, 06)
 108                 * mm   Numeric month, a number from 1 to 12
 109                 * dd   Day, a number from 1 to 31
 110                 * HH   Hour, a number from 0 to 23
 111                 * MM   Minutes, a number from 0 to 59
 112                 * .SS  Seconds, a number from 0 to 61 (with leap seconds)
 113                 * Everything but the minutes is optional
 114                 *
 115                 * "touch -t DATETIME" format: [[[[[YY]YY]MM]DD]hh]mm[.ss]
 116                 * Some, but not all, Unix "date DATETIME" commands
 117                 * move [[YY]YY] past minutes mm field (!).
 118                 * Coreutils date does it, and SUS mandates it.
 119                 * (date -s DATETIME does not support this format. lovely!)
 120                 * In bbox, this format is special-cased in date applet
 121                 * (IOW: this function assumes "touch -t" format).
 122                 */
 123                unsigned cur_year = ptm->tm_year;
 124                int len = strchrnul(date_str, '.') - date_str;
 125
 126                /* MM[.SS] */
 127                if (len == 2 && sscanf(date_str, "%2u%2u%2u%2u""%2u%c" + 12,
 128                                        &ptm->tm_min,
 129                                        &end) >= 1) {
 130                } else
 131                /* HHMM[.SS] */
 132                if (len == 4 && sscanf(date_str, "%2u%2u%2u""%2u%2u%c" + 9,
 133                                        &ptm->tm_hour,
 134                                        &ptm->tm_min,
 135                                        &end) >= 2) {
 136                } else
 137                /* ddHHMM[.SS] */
 138                if (len == 6 && sscanf(date_str, "%2u%2u""%2u%2u%2u%c" + 6,
 139                                        &ptm->tm_mday,
 140                                        &ptm->tm_hour,
 141                                        &ptm->tm_min,
 142                                        &end) >= 3) {
 143                } else
 144                /* mmddHHMM[.SS] */
 145                if (len == 8 && sscanf(date_str, "%2u""%2u%2u%2u%2u%c" + 3,
 146                                        &ptm->tm_mon,
 147                                        &ptm->tm_mday,
 148                                        &ptm->tm_hour,
 149                                        &ptm->tm_min,
 150                                        &end) >= 4) {
 151                        /* Adjust month from 1-12 to 0-11 */
 152                        ptm->tm_mon -= 1;
 153                } else
 154                /* yymmddHHMM[.SS] */
 155                if (len == 10 && sscanf(date_str, "%2u%2u%2u%2u%2u%c",
 156                                        &ptm->tm_year,
 157                                        &ptm->tm_mon,
 158                                        &ptm->tm_mday,
 159                                        &ptm->tm_hour,
 160                                        &ptm->tm_min,
 161                                        &end) >= 5) {
 162                        /* Adjust month from 1-12 to 0-11 */
 163                        ptm->tm_mon -= 1;
 164                        if ((int)cur_year >= 50) { /* >= 1950 */
 165                                /* Adjust year: */
 166                                /* 1. Put it in the current century */
 167                                ptm->tm_year += (cur_year / 100) * 100;
 168                                /* 2. If too far in the past, +100 years */
 169                                if (ptm->tm_year < cur_year - 50)
 170                                        ptm->tm_year += 100;
 171                                /* 3. If too far in the future, -100 years */
 172                                if (ptm->tm_year > cur_year + 50)
 173                                        ptm->tm_year -= 100;
 174                        }
 175                } else
 176                /* ccyymmddHHMM[.SS] */
 177                if (len == 12 && sscanf(date_str, "%4u%2u%2u%2u%2u%c",
 178                                        &ptm->tm_year,
 179                                        &ptm->tm_mon,
 180                                        &ptm->tm_mday,
 181                                        &ptm->tm_hour,
 182                                        &ptm->tm_min,
 183                                        &end) >= 5) {
 184                        ptm->tm_year -= 1900; /* Adjust years */
 185                        ptm->tm_mon -= 1; /* Adjust month from 1-12 to 0-11 */
 186                } else {
 187 err:
 188                        bb_error_msg_and_die(bb_msg_invalid_date, date_str);
 189                }
 190                ptm->tm_sec = 0; /* assume zero if [.SS] is not given */
 191                if (end == '.') {
 192                        /* xxx.SS */
 193                        if (sscanf(strchr(date_str, '.') + 1, "%u%c",
 194                                        &ptm->tm_sec, &end) == 1)
 195                                end = '\0';
 196                        /* else end != NUL and we error out */
 197                }
 198                /* Users were confused by "date -s 20180923"
 199                 * working (not in the way they were expecting).
 200                 * It was interpreted as MMDDhhmm, and not bothered by
 201                 * "month #20" in the least. Prevent such cases:
 202                 */
 203                if (ptm->tm_sec > 60 /* allow "23:60" leap second */
 204                 || ptm->tm_min > 59
 205                 || ptm->tm_hour > 23
 206                 || ptm->tm_mday > 31
 207                 || ptm->tm_mon > 11 /* month# is 0..11, not 1..12 */
 208                ) {
 209                        goto err;
 210                }
 211        }
 212        if (end != '\0') {
 213                bb_error_msg_and_die(bb_msg_invalid_date, date_str);
 214        }
 215}
 216
 217time_t FAST_FUNC validate_tm_time(const char *date_str, struct tm *ptm)
 218{
 219        time_t t = mktime(ptm);
 220        if (t == (time_t) -1L) {
 221                bb_error_msg_and_die(bb_msg_invalid_date, date_str);
 222        }
 223        return t;
 224}
 225
 226static char* strftime_fmt(char *buf, unsigned len, time_t *tp, const char *fmt)
 227{
 228        time_t t;
 229        if (!tp) {
 230                tp = &t;
 231                time(tp);
 232        }
 233        /* Returns pointer to NUL */
 234        return buf + strftime(buf, len, fmt, localtime(tp));
 235}
 236
 237char* FAST_FUNC strftime_HHMMSS(char *buf, unsigned len, time_t *tp)
 238{
 239        return strftime_fmt(buf, len, tp, "%H:%M:%S");
 240}
 241
 242char* FAST_FUNC strftime_YYYYMMDDHHMMSS(char *buf, unsigned len, time_t *tp)
 243{
 244        return strftime_fmt(buf, len, tp, "%Y-%m-%d %H:%M:%S");
 245}
 246
 247#if ENABLE_MONOTONIC_SYSCALL
 248
 249#include <sys/syscall.h>
 250/* Old glibc (< 2.3.4) does not provide this constant. We use syscall
 251 * directly so this definition is safe. */
 252#ifndef CLOCK_MONOTONIC
 253#define CLOCK_MONOTONIC 1
 254#endif
 255
 256/* libc has incredibly messy way of doing this,
 257 * typically requiring -lrt. We just skip all this mess */
 258static void get_mono(struct timespec *ts)
 259{
 260        if (syscall(__NR_clock_gettime, CLOCK_MONOTONIC, ts))
 261                bb_error_msg_and_die("clock_gettime(MONOTONIC) failed");
 262}
 263unsigned long long FAST_FUNC monotonic_ns(void)
 264{
 265        struct timespec ts;
 266        get_mono(&ts);
 267        return ts.tv_sec * 1000000000ULL + ts.tv_nsec;
 268}
 269unsigned long long FAST_FUNC monotonic_us(void)
 270{
 271        struct timespec ts;
 272        get_mono(&ts);
 273        return ts.tv_sec * 1000000ULL + ts.tv_nsec/1000;
 274}
 275unsigned long long FAST_FUNC monotonic_ms(void)
 276{
 277        struct timespec ts;
 278        get_mono(&ts);
 279        return ts.tv_sec * 1000ULL + ts.tv_nsec/1000000;
 280}
 281unsigned FAST_FUNC monotonic_sec(void)
 282{
 283        struct timespec ts;
 284        get_mono(&ts);
 285        return ts.tv_sec;
 286}
 287
 288#else
 289
 290unsigned long long FAST_FUNC monotonic_ns(void)
 291{
 292        struct timeval tv;
 293        gettimeofday(&tv, NULL);
 294        return tv.tv_sec * 1000000000ULL + tv.tv_usec * 1000;
 295}
 296unsigned long long FAST_FUNC monotonic_us(void)
 297{
 298        struct timeval tv;
 299        gettimeofday(&tv, NULL);
 300        return tv.tv_sec * 1000000ULL + tv.tv_usec;
 301}
 302unsigned long long FAST_FUNC monotonic_ms(void)
 303{
 304        struct timeval tv;
 305        gettimeofday(&tv, NULL);
 306        return tv.tv_sec * 1000ULL + tv.tv_usec / 1000;
 307}
 308unsigned FAST_FUNC monotonic_sec(void)
 309{
 310        return time(NULL);
 311}
 312
 313#endif
 314