kopia lustrzana https://github.com/espressif/esp-idf
576 wiersze
19 KiB
C
576 wiersze
19 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 1998-2001,2003-2011,2013 Stewart Heitmann
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
/*******************************************************************************
|
|
* arg_date: Implements the date command-line option
|
|
*
|
|
* This file is part of the argtable3 library.
|
|
*
|
|
* Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann
|
|
* <sheitmann@users.sourceforge.net>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of STEWART HEITMANN nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT,
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
******************************************************************************/
|
|
|
|
#include "argtable3.h"
|
|
|
|
#ifndef ARG_AMALGAMATION
|
|
#include "argtable3_private.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
char* arg_strptime(const char* buf, const char* fmt, struct tm* tm);
|
|
|
|
static void arg_date_resetfn(struct arg_date* parent) {
|
|
ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent));
|
|
parent->count = 0;
|
|
}
|
|
|
|
static int arg_date_scanfn(struct arg_date* parent, const char* argval) {
|
|
int errorcode = 0;
|
|
|
|
if (parent->count == parent->hdr.maxcount) {
|
|
errorcode = ARG_ERR_MAXCOUNT;
|
|
} else if (!argval) {
|
|
/* no argument value was given, leave parent->tmval[] unaltered but still count it */
|
|
parent->count++;
|
|
} else {
|
|
const char* pend;
|
|
struct tm tm = parent->tmval[parent->count];
|
|
|
|
/* parse the given argument value, store result in parent->tmval[] */
|
|
pend = arg_strptime(argval, parent->format, &tm);
|
|
if (pend && pend[0] == '\0')
|
|
parent->tmval[parent->count++] = tm;
|
|
else
|
|
errorcode = ARG_ERR_BADDATE;
|
|
}
|
|
|
|
ARG_TRACE(("%s:scanfn(%p) returns %d\n", __FILE__, parent, errorcode));
|
|
return errorcode;
|
|
}
|
|
|
|
static int arg_date_checkfn(struct arg_date* parent) {
|
|
int errorcode = (parent->count < parent->hdr.mincount) ? ARG_ERR_MINCOUNT : 0;
|
|
|
|
ARG_TRACE(("%s:checkfn(%p) returns %d\n", __FILE__, parent, errorcode));
|
|
return errorcode;
|
|
}
|
|
|
|
static void arg_date_errorfn(struct arg_date* parent, arg_dstr_t ds, int errorcode, const char* argval, const char* progname) {
|
|
const char* shortopts = parent->hdr.shortopts;
|
|
const char* longopts = parent->hdr.longopts;
|
|
const char* datatype = parent->hdr.datatype;
|
|
|
|
/* make argval NULL safe */
|
|
argval = argval ? argval : "";
|
|
|
|
arg_dstr_catf(ds, "%s: ", progname);
|
|
switch (errorcode) {
|
|
case ARG_ERR_MINCOUNT:
|
|
arg_dstr_cat(ds, "missing option ");
|
|
arg_print_option_ds(ds, shortopts, longopts, datatype, "\n");
|
|
break;
|
|
|
|
case ARG_ERR_MAXCOUNT:
|
|
arg_dstr_cat(ds, "excess option ");
|
|
arg_print_option_ds(ds, shortopts, longopts, argval, "\n");
|
|
break;
|
|
|
|
case ARG_ERR_BADDATE: {
|
|
struct tm tm;
|
|
char buff[200];
|
|
|
|
arg_dstr_catf(ds, "illegal timestamp format \"%s\"\n", argval);
|
|
memset(&tm, 0, sizeof(tm));
|
|
arg_strptime("1999-12-31 23:59:59", "%F %H:%M:%S", &tm);
|
|
strftime(buff, sizeof(buff), parent->format, &tm);
|
|
arg_dstr_catf(ds, "correct format is \"%s\"\n", buff);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct arg_date* arg_date0(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary) {
|
|
return arg_daten(shortopts, longopts, format, datatype, 0, 1, glossary);
|
|
}
|
|
|
|
struct arg_date* arg_date1(const char* shortopts, const char* longopts, const char* format, const char* datatype, const char* glossary) {
|
|
return arg_daten(shortopts, longopts, format, datatype, 1, 1, glossary);
|
|
}
|
|
|
|
struct arg_date*
|
|
arg_daten(const char* shortopts, const char* longopts, const char* format, const char* datatype, int mincount, int maxcount, const char* glossary) {
|
|
size_t nbytes;
|
|
struct arg_date* result;
|
|
|
|
/* foolproof things by ensuring maxcount is not less than mincount */
|
|
maxcount = (maxcount < mincount) ? mincount : maxcount;
|
|
|
|
/* default time format is the national date format for the locale */
|
|
if (!format)
|
|
format = "%x";
|
|
|
|
nbytes = sizeof(struct arg_date) /* storage for struct arg_date */
|
|
+ (size_t)maxcount * sizeof(struct tm); /* storage for tmval[maxcount] array */
|
|
|
|
/* allocate storage for the arg_date struct + tmval[] array. */
|
|
/* we use calloc because we want the tmval[] array zero filled. */
|
|
result = (struct arg_date*)xcalloc(1, nbytes);
|
|
|
|
/* init the arg_hdr struct */
|
|
result->hdr.flag = ARG_HASVALUE;
|
|
result->hdr.shortopts = shortopts;
|
|
result->hdr.longopts = longopts;
|
|
result->hdr.datatype = datatype ? datatype : format;
|
|
result->hdr.glossary = glossary;
|
|
result->hdr.mincount = mincount;
|
|
result->hdr.maxcount = maxcount;
|
|
result->hdr.parent = result;
|
|
result->hdr.resetfn = (arg_resetfn*)arg_date_resetfn;
|
|
result->hdr.scanfn = (arg_scanfn*)arg_date_scanfn;
|
|
result->hdr.checkfn = (arg_checkfn*)arg_date_checkfn;
|
|
result->hdr.errorfn = (arg_errorfn*)arg_date_errorfn;
|
|
|
|
/* store the tmval[maxcount] array immediately after the arg_date struct */
|
|
result->tmval = (struct tm*)(result + 1);
|
|
|
|
/* init the remaining arg_date member variables */
|
|
result->count = 0;
|
|
result->format = format;
|
|
|
|
ARG_TRACE(("arg_daten() returns %p\n", result));
|
|
return result;
|
|
}
|
|
|
|
/*-
|
|
* Copyright (c) 1997, 1998, 2005, 2008 The NetBSD Foundation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code was contributed to The NetBSD Foundation by Klaus Klein.
|
|
* Heavily optimised by David Laight
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
/*
|
|
* We do not implement alternate representations. However, we always
|
|
* check whether a given modifier is allowed for a certain conversion.
|
|
*/
|
|
#define ALT_E 0x01
|
|
#define ALT_O 0x02
|
|
#define LEGAL_ALT(x) \
|
|
{ \
|
|
if (alt_format & ~(x)) \
|
|
return (0); \
|
|
}
|
|
#define TM_YEAR_BASE (1900)
|
|
|
|
static int conv_num(const char**, int*, int, int);
|
|
|
|
static const char* day[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
|
|
|
|
static const char* abday[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
|
|
|
|
static const char* mon[12] = {"January", "February", "March", "April", "May", "June",
|
|
"July", "August", "September", "October", "November", "December"};
|
|
|
|
static const char* abmon[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
|
|
|
static const char* am_pm[2] = {"AM", "PM"};
|
|
|
|
static int arg_strcasecmp(const char* s1, const char* s2) {
|
|
const unsigned char* us1 = (const unsigned char*)s1;
|
|
const unsigned char* us2 = (const unsigned char*)s2;
|
|
while (tolower(*us1) == tolower(*us2++))
|
|
if (*us1++ == '\0')
|
|
return 0;
|
|
|
|
return tolower(*us1) - tolower(*--us2);
|
|
}
|
|
|
|
static int arg_strncasecmp(const char* s1, const char* s2, size_t n) {
|
|
if (n != 0) {
|
|
const unsigned char* us1 = (const unsigned char*)s1;
|
|
const unsigned char* us2 = (const unsigned char*)s2;
|
|
do {
|
|
if (tolower(*us1) != tolower(*us2++))
|
|
return tolower(*us1) - tolower(*--us2);
|
|
|
|
if (*us1++ == '\0')
|
|
break;
|
|
} while (--n != 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
char* arg_strptime(const char* buf, const char* fmt, struct tm* tm) {
|
|
char c;
|
|
const char* bp;
|
|
size_t len = 0;
|
|
int alt_format, i, split_year = 0;
|
|
|
|
bp = buf;
|
|
|
|
while ((c = *fmt) != '\0') {
|
|
/* Clear `alternate' modifier prior to new conversion. */
|
|
alt_format = 0;
|
|
|
|
/* Eat up white-space. */
|
|
if (isspace(c)) {
|
|
while (isspace((int)(*bp)))
|
|
bp++;
|
|
|
|
fmt++;
|
|
continue;
|
|
}
|
|
|
|
if ((c = *fmt++) != '%')
|
|
goto literal;
|
|
|
|
again:
|
|
switch (c = *fmt++) {
|
|
case '%': /* "%%" is converted to "%". */
|
|
literal:
|
|
if (c != *bp++)
|
|
return (0);
|
|
break;
|
|
|
|
/*
|
|
* "Alternative" modifiers. Just set the appropriate flag
|
|
* and start over again.
|
|
*/
|
|
case 'E': /* "%E?" alternative conversion modifier. */
|
|
LEGAL_ALT(0);
|
|
alt_format |= ALT_E;
|
|
goto again;
|
|
|
|
case 'O': /* "%O?" alternative conversion modifier. */
|
|
LEGAL_ALT(0);
|
|
alt_format |= ALT_O;
|
|
goto again;
|
|
|
|
/*
|
|
* "Complex" conversion rules, implemented through recursion.
|
|
*/
|
|
case 'c': /* Date and time, using the locale's format. */
|
|
LEGAL_ALT(ALT_E);
|
|
bp = arg_strptime(bp, "%x %X", tm);
|
|
if (!bp)
|
|
return (0);
|
|
break;
|
|
|
|
case 'D': /* The date as "%m/%d/%y". */
|
|
LEGAL_ALT(0);
|
|
bp = arg_strptime(bp, "%m/%d/%y", tm);
|
|
if (!bp)
|
|
return (0);
|
|
break;
|
|
|
|
case 'R': /* The time as "%H:%M". */
|
|
LEGAL_ALT(0);
|
|
bp = arg_strptime(bp, "%H:%M", tm);
|
|
if (!bp)
|
|
return (0);
|
|
break;
|
|
|
|
case 'r': /* The time in 12-hour clock representation. */
|
|
LEGAL_ALT(0);
|
|
bp = arg_strptime(bp, "%I:%M:%S %p", tm);
|
|
if (!bp)
|
|
return (0);
|
|
break;
|
|
|
|
case 'T': /* The time as "%H:%M:%S". */
|
|
LEGAL_ALT(0);
|
|
bp = arg_strptime(bp, "%H:%M:%S", tm);
|
|
if (!bp)
|
|
return (0);
|
|
break;
|
|
|
|
case 'X': /* The time, using the locale's format. */
|
|
LEGAL_ALT(ALT_E);
|
|
bp = arg_strptime(bp, "%H:%M:%S", tm);
|
|
if (!bp)
|
|
return (0);
|
|
break;
|
|
|
|
case 'x': /* The date, using the locale's format. */
|
|
LEGAL_ALT(ALT_E);
|
|
bp = arg_strptime(bp, "%m/%d/%y", tm);
|
|
if (!bp)
|
|
return (0);
|
|
break;
|
|
|
|
/*
|
|
* "Elementary" conversion rules.
|
|
*/
|
|
case 'A': /* The day of week, using the locale's form. */
|
|
case 'a':
|
|
LEGAL_ALT(0);
|
|
for (i = 0; i < 7; i++) {
|
|
/* Full name. */
|
|
len = strlen(day[i]);
|
|
if (arg_strncasecmp(day[i], bp, len) == 0)
|
|
break;
|
|
|
|
/* Abbreviated name. */
|
|
len = strlen(abday[i]);
|
|
if (arg_strncasecmp(abday[i], bp, len) == 0)
|
|
break;
|
|
}
|
|
|
|
/* Nothing matched. */
|
|
if (i == 7)
|
|
return (0);
|
|
|
|
tm->tm_wday = i;
|
|
bp += len;
|
|
break;
|
|
|
|
case 'B': /* The month, using the locale's form. */
|
|
case 'b':
|
|
case 'h':
|
|
LEGAL_ALT(0);
|
|
for (i = 0; i < 12; i++) {
|
|
/* Full name. */
|
|
len = strlen(mon[i]);
|
|
if (arg_strncasecmp(mon[i], bp, len) == 0)
|
|
break;
|
|
|
|
/* Abbreviated name. */
|
|
len = strlen(abmon[i]);
|
|
if (arg_strncasecmp(abmon[i], bp, len) == 0)
|
|
break;
|
|
}
|
|
|
|
/* Nothing matched. */
|
|
if (i == 12)
|
|
return (0);
|
|
|
|
tm->tm_mon = i;
|
|
bp += len;
|
|
break;
|
|
|
|
case 'C': /* The century number. */
|
|
LEGAL_ALT(ALT_E);
|
|
if (!(conv_num(&bp, &i, 0, 99)))
|
|
return (0);
|
|
|
|
if (split_year) {
|
|
tm->tm_year = (tm->tm_year % 100) + (i * 100);
|
|
} else {
|
|
tm->tm_year = i * 100;
|
|
split_year = 1;
|
|
}
|
|
break;
|
|
|
|
case 'd': /* The day of month. */
|
|
case 'e':
|
|
LEGAL_ALT(ALT_O);
|
|
if (!(conv_num(&bp, &tm->tm_mday, 1, 31)))
|
|
return (0);
|
|
break;
|
|
|
|
case 'k': /* The hour (24-hour clock representation). */
|
|
LEGAL_ALT(0);
|
|
/* FALLTHROUGH */
|
|
case 'H':
|
|
LEGAL_ALT(ALT_O);
|
|
if (!(conv_num(&bp, &tm->tm_hour, 0, 23)))
|
|
return (0);
|
|
break;
|
|
|
|
case 'l': /* The hour (12-hour clock representation). */
|
|
LEGAL_ALT(0);
|
|
/* FALLTHROUGH */
|
|
case 'I':
|
|
LEGAL_ALT(ALT_O);
|
|
if (!(conv_num(&bp, &tm->tm_hour, 1, 12)))
|
|
return (0);
|
|
if (tm->tm_hour == 12)
|
|
tm->tm_hour = 0;
|
|
break;
|
|
|
|
case 'j': /* The day of year. */
|
|
LEGAL_ALT(0);
|
|
if (!(conv_num(&bp, &i, 1, 366)))
|
|
return (0);
|
|
tm->tm_yday = i - 1;
|
|
break;
|
|
|
|
case 'M': /* The minute. */
|
|
LEGAL_ALT(ALT_O);
|
|
if (!(conv_num(&bp, &tm->tm_min, 0, 59)))
|
|
return (0);
|
|
break;
|
|
|
|
case 'm': /* The month. */
|
|
LEGAL_ALT(ALT_O);
|
|
if (!(conv_num(&bp, &i, 1, 12)))
|
|
return (0);
|
|
tm->tm_mon = i - 1;
|
|
break;
|
|
|
|
case 'p': /* The locale's equivalent of AM/PM. */
|
|
LEGAL_ALT(0);
|
|
/* AM? */
|
|
if (arg_strcasecmp(am_pm[0], bp) == 0) {
|
|
if (tm->tm_hour > 11)
|
|
return (0);
|
|
|
|
bp += strlen(am_pm[0]);
|
|
break;
|
|
}
|
|
/* PM? */
|
|
else if (arg_strcasecmp(am_pm[1], bp) == 0) {
|
|
if (tm->tm_hour > 11)
|
|
return (0);
|
|
|
|
tm->tm_hour += 12;
|
|
bp += strlen(am_pm[1]);
|
|
break;
|
|
}
|
|
|
|
/* Nothing matched. */
|
|
return (0);
|
|
|
|
case 'S': /* The seconds. */
|
|
LEGAL_ALT(ALT_O);
|
|
if (!(conv_num(&bp, &tm->tm_sec, 0, 61)))
|
|
return (0);
|
|
break;
|
|
|
|
case 'U': /* The week of year, beginning on sunday. */
|
|
case 'W': /* The week of year, beginning on monday. */
|
|
LEGAL_ALT(ALT_O);
|
|
/*
|
|
* XXX This is bogus, as we can not assume any valid
|
|
* information present in the tm structure at this
|
|
* point to calculate a real value, so just check the
|
|
* range for now.
|
|
*/
|
|
if (!(conv_num(&bp, &i, 0, 53)))
|
|
return (0);
|
|
break;
|
|
|
|
case 'w': /* The day of week, beginning on sunday. */
|
|
LEGAL_ALT(ALT_O);
|
|
if (!(conv_num(&bp, &tm->tm_wday, 0, 6)))
|
|
return (0);
|
|
break;
|
|
|
|
case 'Y': /* The year. */
|
|
LEGAL_ALT(ALT_E);
|
|
if (!(conv_num(&bp, &i, 0, 9999)))
|
|
return (0);
|
|
|
|
tm->tm_year = i - TM_YEAR_BASE;
|
|
break;
|
|
|
|
case 'y': /* The year within 100 years of the epoch. */
|
|
LEGAL_ALT(ALT_E | ALT_O);
|
|
if (!(conv_num(&bp, &i, 0, 99)))
|
|
return (0);
|
|
|
|
if (split_year) {
|
|
tm->tm_year = ((tm->tm_year / 100) * 100) + i;
|
|
break;
|
|
}
|
|
split_year = 1;
|
|
if (i <= 68)
|
|
tm->tm_year = i + 2000 - TM_YEAR_BASE;
|
|
else
|
|
tm->tm_year = i + 1900 - TM_YEAR_BASE;
|
|
break;
|
|
|
|
/*
|
|
* Miscellaneous conversions.
|
|
*/
|
|
case 'n': /* Any kind of white-space. */
|
|
case 't':
|
|
LEGAL_ALT(0);
|
|
while (isspace((int)(*bp)))
|
|
bp++;
|
|
break;
|
|
|
|
default: /* Unknown/unsupported conversion. */
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
/* LINTED functional specification */
|
|
return ((char*)bp);
|
|
}
|
|
|
|
static int conv_num(const char** buf, int* dest, int llim, int ulim) {
|
|
int result = 0;
|
|
|
|
/* The limit also determines the number of valid digits. */
|
|
int rulim = ulim;
|
|
|
|
if (**buf < '0' || **buf > '9')
|
|
return (0);
|
|
|
|
do {
|
|
result *= 10;
|
|
result += *(*buf)++ - '0';
|
|
rulim /= 10;
|
|
} while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9');
|
|
|
|
if (result < llim || result > ulim)
|
|
return (0);
|
|
|
|
*dest = result;
|
|
return (1);
|
|
}
|