226 lines
5.4 KiB
C
226 lines
5.4 KiB
C
|
/* Parse dates for touch and date.
|
||
|
|
||
|
Copyright (C) 1989-1991, 1998, 2000-2020 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 <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#if USE_UNLOCKED_IO
|
||
|
# include "unlocked-io.h"
|
||
|
#endif
|
||
|
|
||
|
/* ISDIGIT differs from isdigit, as follows:
|
||
|
- Its arg may be any int or unsigned int; it need not be an unsigned char
|
||
|
or EOF.
|
||
|
- It's typically faster.
|
||
|
POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
|
||
|
isdigit unless it's important to use the locale's definition
|
||
|
of "digit" even when the host does not conform to POSIX. */
|
||
|
#define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
|
||
|
|
||
|
/*
|
||
|
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, size_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:
|
||
|
{
|
||
|
time_t now;
|
||
|
struct tm *tmp;
|
||
|
|
||
|
/* Use current year. */
|
||
|
time (&now);
|
||
|
tmp = localtime (&now);
|
||
|
if (! tmp)
|
||
|
return false;
|
||
|
tm->tm_year = tmp->tm_year;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
abort ();
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static bool
|
||
|
posix_time_parse (struct tm *tm, const char *s, unsigned int syntax_bits)
|
||
|
{
|
||
|
const char *dot = NULL;
|
||
|
int pair[6];
|
||
|
int *p;
|
||
|
size_t i;
|
||
|
|
||
|
size_t s_len = strlen (s);
|
||
|
size_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 (i = 0; i < len; i++)
|
||
|
if (!ISDIGIT (s[i]))
|
||
|
return false;
|
||
|
|
||
|
len /= 2;
|
||
|
for (i = 0; i < len; i++)
|
||
|
pair[i] = 10 * (s[2*i] - '0') + s[2*i + 1] - '0';
|
||
|
|
||
|
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 (ISDIGIT (dot[1]) && 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;
|
||
|
struct tm tm1;
|
||
|
time_t t;
|
||
|
|
||
|
if (! posix_time_parse (&tm0, s, syntax_bits))
|
||
|
return false;
|
||
|
|
||
|
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;
|
||
|
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))
|
||
|
{
|
||
|
/* 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. */
|
||
|
time_t dummy;
|
||
|
char buf[16];
|
||
|
char *b = stpcpy (buf, s);
|
||
|
strcpy (b - 2, "59");
|
||
|
if (!posixtime (&dummy, buf, syntax_bits))
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
*p = t;
|
||
|
return true;
|
||
|
}
|