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 d14bd69dbc4e2ec982bd4d976d0c6b5a9f53d56c..6e03203d0865636d48904b1bb26617f9d6a3ad5a 100644 (file)
@@ -190,3 +190,4 @@ doc/disorder_templates.5.in
 lib/t-arcfour
 lib/t-charset
 lib/t-event
 lib/t-arcfour
 lib/t-charset
 lib/t-event
+lib/t-dateparse
diff --git a/README b/README
index 5f09fc2ce1ef9170b476ed2b050dee834fc0af16..27657dbd0ffc64b05f7db80d589e2d33722fbb56 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 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)
 
 Binaries may derive extra copyright owners through linkage (binary distributors
 are expected to do their own legwork)
 
index f20dbbc663aab3fa67d5ba7cd5a96b2e60d3b528..0dd71c74eb3cfa700f7b420dfc1087175dd5a7e9 100644 (file)
@@ -54,6 +54,7 @@
 #include "authorize.h"
 #include "vector.h"
 #include "version.h"
 #include "authorize.h"
 #include "vector.h"
 #include "version.h"
+#include "dateparse.h"
 
 static disorder_client *client;
 
 
 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(),
 
 static void cf_schedule_play(char **argv) {
   if(disorder_schedule_add(getclient(),
-                          atoll(argv[0]),
+                          dateparse(argv[0]),
                           argv[1],
                           "play",
                           argv[2]))
                           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(),
 
 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],
                           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(),
 
 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],
                           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");
   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();
   while((n = getopt_long(argc, argv, "+hVc:dHlNu:p:", options, 0)) >= 0) {
     switch(n) {
     case 'h': help();
index 3156eb4aa9a1f9089d8dfae437c9a13058be2d08..9de40a96d4756128f6d51477c67c8819f7e16859 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
 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
 .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 6b7aec8fee9180a69462428b5a55661f7d33511c..16cdcea4f3a41e270ea8f82d12ab1e7bfc8adbb7 100644 (file)
 #
 
 TESTS=t-addr t-arcfour t-basen t-bits t-cache t-casefold t-charset     \
 #
 
 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
 
 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                             \
        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                                 \
        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_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
 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 ae6623ca85037a186bc0ae03b8774c24348f8750..a3feaa87683fa06d9562bc79290e7496faeedf92 100755 (executable)
@@ -82,7 +82,8 @@ def test():
                    "--config", disorder._configfile,
                    "--no-per-user-config",
                    "schedule-play",
                    "--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:"
                    "normal",
                    track])
     print " disorder schedule-list output:"
@@ -110,7 +111,8 @@ def test():
                    "--config", disorder._configfile,
                    "--no-per-user-config",
                    "schedule-set-global",
                    "--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"])
                    "normal",
                    "random-play",
                    "yes"])