chiark / gitweb /
Command line interface now takes more human-friendly timestamps. This
authorRichard Kettlewell <rjk@greenend.org.uk>
Sat, 24 May 2008 14:34:54 +0000 (15:34 +0100)
committerRichard Kettlewell <rjk@greenend.org.uk>
Sat, 24 May 2008 14:34:54 +0000 (15:34 +0100)
is done via a modified version of Glibc's getdate(3).

.bzrignore
README
clients/disorder.c
doc/disorder.1.in
lib/Makefile.am
lib/dateparse.c [new file with mode: 0644]
lib/dateparse.h [new file with mode: 0644]
lib/t-dateparse.c [new file with mode: 0644]
lib/xgetdate.c [new file with mode: 0644]
tests/schedule.py

index d14bd69..6e03203 100644 (file)
@@ -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 (file)
--- 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)
 
index f20dbbc..0dd71c7 100644 (file)
@@ -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();
index 3156eb4..9de40a9 100644 (file)
@@ -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
index 6b7aec8..16cdcea 100644 (file)
 #
 
 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 (file)
index 0000000..45de40c
--- /dev/null
@@ -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 <config.h>
+#include "types.h"
+
+#include <time.h>
+
+#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 (file)
index 0000000..d86d14f
--- /dev/null
@@ -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 (file)
index 0000000..cd599e8
--- /dev/null
@@ -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 <time.h>
+#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 (file)
index 0000000..de1c4d3
--- /dev/null
@@ -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 <kettenis@phys.uva.nl>, 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 <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#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;
+}
index ae6623c..a3feaa8 100755 (executable)
@@ -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"])