From d436bd52989e64f8c3cb8a543f446136b6ab1ac4 Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Sat, 24 May 2008 15:34:54 +0100 Subject: [PATCH] Command line interface now takes more human-friendly timestamps. This is done via a modified version of Glibc's getdate(3). Organization: Straylight/Edgeware From: Richard Kettlewell --- .bzrignore | 1 + README | 3 +- clients/disorder.c | 8 +- doc/disorder.1.in | 15 ++- lib/Makefile.am | 13 ++- lib/dateparse.c | 79 ++++++++++++++++ lib/dateparse.h | 39 ++++++++ lib/t-dateparse.c | 62 +++++++++++++ lib/xgetdate.c | 226 +++++++++++++++++++++++++++++++++++++++++++++ tests/schedule.py | 6 +- 10 files changed, 439 insertions(+), 13 deletions(-) create mode 100644 lib/dateparse.c create mode 100644 lib/dateparse.h create mode 100644 lib/t-dateparse.c create mode 100644 lib/xgetdate.c diff --git a/.bzrignore b/.bzrignore index d14bd69..6e03203 100644 --- a/.bzrignore +++ b/.bzrignore @@ -190,3 +190,4 @@ doc/disorder_templates.5.in lib/t-arcfour lib/t-charset lib/t-event +lib/t-dateparse diff --git a/README b/README index 5f09fc2..27657db 100644 --- a/README +++ b/README @@ -273,8 +273,7 @@ Portions copyright (C) 2007 Mark Wooding Portions extracted from MPG321, http://mpg321.sourceforge.net/ Copyright (C) 2001 Joe Drew Copyright (C) 2000-2001 Robert Leslie -Portions Copyright (C) 2000, 2001, 2002, 2003, 2005, 2006 Free Software -Foundation, Inc. +Portions Copyright (C) 1997-2006 Free Software Foundation, Inc. Binaries may derive extra copyright owners through linkage (binary distributors are expected to do their own legwork) diff --git a/clients/disorder.c b/clients/disorder.c index f20dbbc..0dd71c7 100644 --- a/clients/disorder.c +++ b/clients/disorder.c @@ -54,6 +54,7 @@ #include "authorize.h" #include "vector.h" #include "version.h" +#include "dateparse.h" static disorder_client *client; @@ -545,7 +546,7 @@ static void cf_schedule_del(char **argv) { static void cf_schedule_play(char **argv) { if(disorder_schedule_add(getclient(), - atoll(argv[0]), + dateparse(argv[0]), argv[1], "play", argv[2])) @@ -554,7 +555,7 @@ static void cf_schedule_play(char **argv) { static void cf_schedule_set_global(char **argv) { if(disorder_schedule_add(getclient(), - atoll(argv[0]), + dateparse(argv[0]), argv[1], "set-global", argv[2], @@ -564,7 +565,7 @@ static void cf_schedule_set_global(char **argv) { static void cf_schedule_unset_global(char **argv) { if(disorder_schedule_add(getclient(), - atoll(argv[0]), + dateparse(argv[0]), argv[1], "set-global", argv[2], @@ -726,6 +727,7 @@ int main(int argc, char **argv) { pcre_malloc = xmalloc; pcre_free = xfree; if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale"); + if(!setlocale(LC_TIME, "")) fatal(errno, "error calling setlocale"); while((n = getopt_long(argc, argv, "+hVc:dHlNu:p:", options, 0)) >= 0) { switch(n) { case 'h': help(); diff --git a/doc/disorder.1.in b/doc/disorder.1.in index 3156eb4..9de40a9 100644 --- a/doc/disorder.1.in +++ b/doc/disorder.1.in @@ -191,8 +191,19 @@ Each line contains the ID, a timestamp, 'N' or 'J' for normal or junk priority, the user, the action and action-specific data. .TP .B schedule-play \fIWHEN PRIORITY TRACK\fI -Play \fITRACK\fR at time \fIWHEN\fR (which must currently be a raw \fBtime_t\fR -value). +Play \fITRACK\fR at time \fIWHEN\fR. +Various time/date formats are supported depending on locale but the following +three will always work: +.RS +.RS +.TP +.B "YYYY-MM-DD HH:MM:SS" +.TP +.B "HH:MM:SS" +.TP +.B "HH:MM" +.RE +.RE .IP \fIPRIORITY\fR should be \fBjunk\fR or \fBnormal\fR. This determines how the event is handled if it becomes due when the server is diff --git a/lib/Makefile.am b/lib/Makefile.am index 6b7aec8..16cdcea 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -19,10 +19,10 @@ # TESTS=t-addr t-arcfour t-basen t-bits t-cache t-casefold t-charset \ - t-cookies t-event t-filepart t-hash t-heap t-hex t-kvp t-mime \ - t-printf t-regsub t-selection t-signame t-sink t-split \ - t-syscalls t-trackname t-unicode t-url t-utf8 t-vector t-words \ - t-wstat t-macros t-cgi + t-cookies t-dateparse t-event t-filepart t-hash t-heap t-hex \ + t-kvp t-mime t-printf t-regsub t-selection t-signame t-sink \ + t-split t-syscalls t-trackname t-unicode t-url t-utf8 t-vector \ + t-words t-wstat t-macros t-cgi noinst_LIBRARIES=libdisorder.a include_HEADERS=disorder.h @@ -48,6 +48,7 @@ libdisorder_a_SOURCES=charset.c charset.h \ client-common.c client-common.h \ configuration.c configuration.h \ cookies.c cookies.h \ + dateparse.c dateparse.h xgetdate.c \ defs.c defs.h \ eclient.c eclient.h \ event.c event.h \ @@ -161,6 +162,10 @@ t_cookies_SOURCES=t-cookies.c test.c test.h t_cookies_LDADD=libdisorder.a $(LIBPCRE) $(LIBICONV) $(LIBGC) t_cookies_DEPENDENCIES=libdisorder.a +t_dateparse_SOURCES=t-dateparse.c test.c test.h +t_dateparse_LDADD=libdisorder.a $(LIBPCRE) $(LIBICONV) $(LIBGC) +t_dateparse_DEPENDENCIES=libdisorder.a + t_event_SOURCES=t-event.c test.c test.h t_event_LDADD=libdisorder.a $(LIBPCRE) $(LIBICONV) $(LIBGC) t_event_DEPENDENCIES=libdisorder.a diff --git a/lib/dateparse.c b/lib/dateparse.c new file mode 100644 index 0000000..45de40c --- /dev/null +++ b/lib/dateparse.c @@ -0,0 +1,79 @@ +/* + * This file is part of DisOrder + * Copyright (C) 2008 Richard Kettlewell + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +/** @file lib/dateparse.c + * @brief Date parsing + */ + +#include +#include "types.h" + +#include + +#include "dateparse.h" +#include "log.h" + +/** @brief Date parsing patterns + * + * This set of patterns is designed to parse a specific time of a specific day, + * since that's what the scheduler needs. Other requirements might need other + * pattern lists. + */ +static const char *const datemsk[] = { + /* ISO format */ + "%Y-%m-%d %H:%M:%S", + /* "%Y-%m-%d %H:%M:%S %Z" - no, not sensibly supported anywhere */ + /* Locale-specific date + time */ + "%c", + "%Ec", + /* Locale-specific time, same day */ + "%X", + "%EX", + /* Generic time, same day */ + "%H:%M", + "%H:%M:%S", + NULL, +}; + +/** @brief Convert string to a @c time_t */ +time_t dateparse(const char *s) { + struct tm t; + int rc; + + switch(rc = xgetdate_r(s, &t, datemsk)) { + case 0: + return mktime(&t); + case 7: + fatal(0, "date string '%s' not in a recognized format", s); + case 8: + fatal(0, "date string '%s' not representable", s); + default: + fatal(0, "date string '%s' produced unexpected error %d", s, rc); + } +} + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/lib/dateparse.h b/lib/dateparse.h new file mode 100644 index 0000000..d86d14f --- /dev/null +++ b/lib/dateparse.h @@ -0,0 +1,39 @@ +/* + * This file is part of DisOrder + * Copyright (C) 2008 Richard Kettlewell + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +/** @file lib/dateparse.h + * @brief Date parsing + */ + +time_t dateparse(const char *s); +struct tm *xgetdate(const char *string, + const char *const *template); +int xgetdate_r(const char *string, + struct tm *tp, + const char *const *template); + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/lib/t-dateparse.c b/lib/t-dateparse.c new file mode 100644 index 0000000..cd599e8 --- /dev/null +++ b/lib/t-dateparse.c @@ -0,0 +1,62 @@ +/* + * This file is part of DisOrder. + * Copyright (C) 2008 Richard Kettlewell + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +#include "test.h" +#include +#include "dateparse.h" + +static void check_date(time_t when, + const char *format, + struct tm *(*convert)(const time_t *)) { + char buffer[128]; + time_t parsed; + + strftime(buffer, sizeof buffer, format, convert(&when)); + parsed = dateparse(buffer); + check_integer(parsed, when); + if(parsed != when) + fprintf(stderr, "format=%s formatted=%s\n", format, buffer); +} + +static void test_dateparse(void) { + time_t now = time(0); + check_date(now, "%Y-%m-%d %H:%M:%S", localtime); +#if 0 /* see dateparse.c */ + check_date(now, "%Y-%m-%d %H:%M:%S %Z", localtime); + check_date(now, "%Y-%m-%d %H:%M:%S %Z", gmtime); +#endif + check_date(now, "%c", localtime); + check_date(now, "%Ec", localtime); + check_date(now, "%X", localtime); + check_date(now, "%EX", localtime); + check_date(now, "%H:%M:%S", localtime); + /* This one needs a bodge: */ + check_date(now - now % 60, "%H:%M", localtime); +} + +TEST(dateparse); + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/lib/xgetdate.c b/lib/xgetdate.c new file mode 100644 index 0000000..de1c4d3 --- /dev/null +++ b/lib/xgetdate.c @@ -0,0 +1,226 @@ +/* Derived from getdate.c in glibc 2.3.6. This is pretty much + * standard getdate() except that you supply the template in an + * argument, rather than messing around with environment variables and + * files. */ + +/* Convert a string representation of time to a time value. + Copyright (C) 1997,1998,1999,2000,2001,2003 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Mark Kettenis , 1997. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#include +#include +#include +#include +#include + +#include "dateparse.h" + +#define TM_YEAR_BASE 1900 + + +/* Prototypes for local functions. */ +static int first_wday (int year, int mon, int wday); +static int check_mday (int year, int mon, int mday); + +# define isleap(year) \ + ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0)) + +/* Set to one of the following values to indicate an error. + 1 the DATEMSK environment variable is null or undefined, + 2 the template file cannot be opened for reading, + 3 failed to get file status information, + 4 the template file is not a regular file, + 5 an error is encountered while reading the template file, + 6 memory allication failed (not enough memory available), + 7 there is no line in the template that matches the input, + 8 invalid input specification Example: February 31 or a time is + specified that can not be represented in a time_t (representing + the time in seconds since 00:00:00 UTC, January 1, 1970) */ +int xgetdate_err; + + +/* Returns the first weekday WDAY of month MON in the year YEAR. */ +static int +first_wday (int year, int mon, int wday) +{ + struct tm tm; + + if (wday == INT_MIN) + return 1; + + memset (&tm, 0, sizeof (struct tm)); + tm.tm_year = year; + tm.tm_mon = mon; + tm.tm_mday = 1; + mktime (&tm); + + return (1 + (wday - tm.tm_wday + 7) % 7); +} + + +/* Returns 1 if MDAY is a valid day of the month in month MON of year + YEAR, and 0 if it is not. */ +static int +check_mday (int year, int mon, int mday) +{ + switch (mon) + { + case 0: + case 2: + case 4: + case 6: + case 7: + case 9: + case 11: + if (mday >= 1 && mday <= 31) + return 1; + break; + case 3: + case 5: + case 8: + case 10: + if (mday >= 1 && mday <= 30) + return 1; + break; + case 1: + if (mday >= 1 && mday <= (isleap (year) ? 29 : 28)) + return 1; + break; + } + + return 0; +} + + +int +xgetdate_r (const char *string, struct tm *tp, + const char *const *template) +{ + const char *line; + size_t len; + char *result = NULL; + time_t timer; + struct tm tm; + int mday_ok = 0; + + line = NULL; + len = 0; + while((line = *template++)) + { + /* Do the conversion. */ + tp->tm_year = tp->tm_mon = tp->tm_mday = tp->tm_wday = INT_MIN; + tp->tm_hour = tp->tm_sec = tp->tm_min = INT_MIN; + tp->tm_isdst = -1; + tp->tm_gmtoff = 0; + tp->tm_zone = NULL; + result = strptime (string, line, tp); + if (result && *result == '\0') + break; + } + + if (result == NULL || *result != '\0') + return 7; + + /* Get current time. */ + time (&timer); + localtime_r (&timer, &tm); + + /* If only the weekday is given, today is assumed if the given day + is equal to the current day and next week if it is less. */ + if (tp->tm_wday >= 0 && tp->tm_wday <= 6 && tp->tm_year == INT_MIN + && tp->tm_mon == INT_MIN && tp->tm_mday == INT_MIN) + { + tp->tm_year = tm.tm_year; + tp->tm_mon = tm.tm_mon; + tp->tm_mday = tm.tm_mday + (tp->tm_wday - tm.tm_wday + 7) % 7; + mday_ok = 1; + } + + /* If only the month is given, the current month is assumed if the + given month is equal to the current month and next year if it is + less and no year is given (the first day of month is assumed if + no day is given. */ + if (tp->tm_mon >= 0 && tp->tm_mon <= 11 && tp->tm_mday == INT_MIN) + { + if (tp->tm_year == INT_MIN) + tp->tm_year = tm.tm_year + (((tp->tm_mon - tm.tm_mon) < 0) ? 1 : 0); + tp->tm_mday = first_wday (tp->tm_year, tp->tm_mon, tp->tm_wday); + mday_ok = 1; + } + + /* If no hour, minute and second are given the current hour, minute + and second are assumed. */ + if (tp->tm_hour == INT_MIN && tp->tm_min == INT_MIN && tp->tm_sec == INT_MIN) + { + tp->tm_hour = tm.tm_hour; + tp->tm_min = tm.tm_min; + tp->tm_sec = tm.tm_sec; + } + + /* If no date is given, today is assumed if the given hour is + greater than the current hour and tomorrow is assumed if + it is less. */ + if (tp->tm_hour >= 0 && tp->tm_hour <= 23 + && tp->tm_year == INT_MIN && tp->tm_mon == INT_MIN + && tp->tm_mday == INT_MIN && tp->tm_wday == INT_MIN) + { + tp->tm_year = tm.tm_year; + tp->tm_mon = tm.tm_mon; + tp->tm_mday = tm.tm_mday + ((tp->tm_hour - tm.tm_hour) < 0 ? 1 : 0); + mday_ok = 1; + } + + /* Fill in the gaps. */ + if (tp->tm_year == INT_MIN) + tp->tm_year = tm.tm_year; + if (tp->tm_hour == INT_MIN) + tp->tm_hour = 0; + if (tp->tm_min == INT_MIN) + tp->tm_min = 0; + if (tp->tm_sec == INT_MIN) + tp->tm_sec = 0; + + /* Check if the day of month is within range, and if the time can be + represented in a time_t. We make use of the fact that the mktime + call normalizes the struct tm. */ + if ((!mday_ok && !check_mday (TM_YEAR_BASE + tp->tm_year, tp->tm_mon, + tp->tm_mday)) + || mktime (tp) == (time_t) -1) + return 8; + + return 0; +} + + + +struct tm * + xgetdate (const char *string, const char *const *template) +{ + /* Buffer returned by getdate. */ + static struct tm tmbuf; + int errval = xgetdate_r (string, &tmbuf, template); + + if (errval != 0) + { + getdate_err = errval; + return NULL; + } + + return &tmbuf; +} diff --git a/tests/schedule.py b/tests/schedule.py index ae6623c..a3feaa8 100755 --- a/tests/schedule.py +++ b/tests/schedule.py @@ -82,7 +82,8 @@ def test(): "--config", disorder._configfile, "--no-per-user-config", "schedule-play", - str(now + 4), + time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(now + 4)), "normal", track]) print " disorder schedule-list output:" @@ -110,7 +111,8 @@ def test(): "--config", disorder._configfile, "--no-per-user-config", "schedule-set-global", - str(now + 4), + time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(now + 4)), "normal", "random-play", "yes"]) -- [mdw]