/* Parse dates for touch and date. Copyright (C) 1989-1991, 1998, 2000-2023 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 . */ /* Yacc-based version written by Jim Kingdon and David MacKenzie. Rewritten by Jim Meyering. */ #include #include "posixtm.h" #include "c-ctype.h" #include "idx.h" #include "verify.h" #include #include /* 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 (ckd_add (&t, t, leapsec)) 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; } }