diff options
Diffstat (limited to 'lib/posixtm.c')
-rw-r--r-- | lib/posixtm.c | 208 |
1 files changed, 208 insertions, 0 deletions
diff --git a/lib/posixtm.c b/lib/posixtm.c new file mode 100644 index 0000000..b00cef4 --- /dev/null +++ b/lib/posixtm.c @@ -0,0 +1,208 @@ +/* Parse dates for touch and date. + + Copyright (C) 1989-1991, 1998, 2000-2022 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Yacc-based version written by Jim Kingdon and David MacKenzie. + Rewritten by Jim Meyering. */ + +#include <config.h> + +#include "posixtm.h" + +#include "c-ctype.h" +#include "idx.h" +#include "intprops.h" +#include "verify.h" + +#include <string.h> + +/* + POSIX requires: + + touch -t [[CC]YY]mmddhhmm[.ss] FILE... + 8, 10, or 12 digits, followed by optional .ss + (PDS_CENTURY | PDS_SECONDS) + + touch mmddhhmm[YY] FILE... (obsoleted by POSIX 1003.1-2001) + 8 or 10 digits, YY (if present) must be in the range 69-99 + (PDS_TRAILING_YEAR | PDS_PRE_2000) + + date mmddhhmm[[CC]YY] + 8, 10, or 12 digits + (PDS_TRAILING_YEAR | PDS_CENTURY) + +*/ + +static bool +year (struct tm *tm, const int *digit_pair, idx_t n, unsigned int syntax_bits) +{ + switch (n) + { + case 1: + tm->tm_year = *digit_pair; + /* Deduce the century based on the year. + POSIX requires that 00-68 be interpreted as 2000-2068, + and that 69-99 be interpreted as 1969-1999. */ + if (digit_pair[0] <= 68) + { + if (syntax_bits & PDS_PRE_2000) + return false; + tm->tm_year += 100; + } + break; + + case 2: + if (! (syntax_bits & PDS_CENTURY)) + return false; + tm->tm_year = digit_pair[0] * 100 + digit_pair[1] - 1900; + break; + + case 0: + { + /* Use current year. */ + time_t now = time (NULL); + struct tm *tmp = localtime (&now); + if (! tmp) + return false; + tm->tm_year = tmp->tm_year; + } + break; + + default: + assume (false); + } + + return true; +} + +static bool +posix_time_parse (struct tm *tm, const char *s, unsigned int syntax_bits) +{ + const char *dot = NULL; + int pair[6]; + + idx_t s_len = strlen (s); + idx_t len = s_len; + + if (syntax_bits & PDS_SECONDS) + { + dot = strchr (s, '.'); + if (dot) + { + len = dot - s; + if (s_len - len != 3) + return false; + } + } + + if (! (8 <= len && len <= 12 && len % 2 == 0)) + return false; + + for (idx_t i = 0; i < len; i++) + if (!c_isdigit (s[i])) + return false; + + len /= 2; + for (idx_t i = 0; i < len; i++) + pair[i] = 10 * (s[2*i] - '0') + s[2*i + 1] - '0'; + + int *p = pair; + if (! (syntax_bits & PDS_TRAILING_YEAR)) + { + if (! year (tm, p, len - 4, syntax_bits)) + return false; + p += len - 4; + len = 4; + } + + /* Handle 8 digits worth of 'MMDDhhmm'. */ + tm->tm_mon = *p++ - 1; + tm->tm_mday = *p++; + tm->tm_hour = *p++; + tm->tm_min = *p++; + len -= 4; + + /* Handle any trailing year. */ + if (syntax_bits & PDS_TRAILING_YEAR) + { + if (! year (tm, p, len, syntax_bits)) + return false; + } + + /* Handle seconds. */ + if (!dot) + tm->tm_sec = 0; + else if (c_isdigit (dot[1]) && c_isdigit (dot[2])) + tm->tm_sec = 10 * (dot[1] - '0') + dot[2] - '0'; + else + return false; + + return true; +} + +/* Parse a POSIX-style date, returning true if successful. */ + +bool +posixtime (time_t *p, const char *s, unsigned int syntax_bits) +{ + struct tm tm0; + bool leapsec = false; + + if (! posix_time_parse (&tm0, s, syntax_bits)) + return false; + + while (true) + { + struct tm tm1; + tm1.tm_sec = tm0.tm_sec; + tm1.tm_min = tm0.tm_min; + tm1.tm_hour = tm0.tm_hour; + tm1.tm_mday = tm0.tm_mday; + tm1.tm_mon = tm0.tm_mon; + tm1.tm_year = tm0.tm_year; + tm1.tm_wday = -1; + tm1.tm_isdst = -1; + time_t t = mktime (&tm1); + + if (tm1.tm_wday < 0) + return false; + + /* Reject dates like "September 31" and times like "25:61". + However, allow a seconds count of 60 even in time zones that do + not support leap seconds, treating it as the following second; + POSIX requires this. */ + if (! ((tm0.tm_year ^ tm1.tm_year) + | (tm0.tm_mon ^ tm1.tm_mon) + | (tm0.tm_mday ^ tm1.tm_mday) + | (tm0.tm_hour ^ tm1.tm_hour) + | (tm0.tm_min ^ tm1.tm_min) + | (tm0.tm_sec ^ tm1.tm_sec))) + { + if (INT_ADD_WRAPV (t, leapsec, &t)) + return false; + *p = t; + return true; + } + + /* Any mismatch without 60 in the tm_sec field is invalid. */ + if (tm0.tm_sec != 60) + return false; + + /* Allow times like 01:35:60 or 23:59:60. */ + tm0.tm_sec = 59; + leapsec = true; + } +} |