From 620c4eeca5cd1dda90a26527aa6d782edb1b5271 Mon Sep 17 00:00:00 2001
From: Lars Henriksen <LarsHenriksen@get2net.dk>
Date: Sat, 1 Dec 2018 19:54:14 +0100
Subject: CLI: Revert to and update of parse_datetimearg()

An earlier commit ("CLI: take input date format from configuration file,
do not accept time") replaced parse_datetimearg() with parse_datearg()
and eliminated time-of-day from command line date arguments. This made
the full use of filter options impossible.

That earlier commit is reverted and updated. The parse_datearg()
function is replaced by an updated parse_datetimearg() function that

- takes the date format from the configuration file
- accepts date, date-time or time

The updated parse_datetimearg() function has been extended to report
back the type of the date string received in order to set (filter)
options correctly.  Input dates for query ranges (--from, --to, --days)
are still limited to dates only.

Signed-off-by: Lars Henriksen <LarsHenriksen@get2net.dk>
Signed-off-by: Lukas Fleischer <lfleischer@calcurse.org>
---
 src/args.c        | 147 +++++++++++++++++++++++++++++++++++-------------------
 src/calcurse.h    |   7 ++-
 src/ui-calendar.c |   6 +++
 3 files changed, 106 insertions(+), 54 deletions(-)

(limited to 'src')

diff --git a/src/args.c b/src/args.c
index b1bf5ec..d85e099 100644
--- a/src/args.c
+++ b/src/args.c
@@ -44,6 +44,14 @@
 
 #include "calcurse.h"
 
+/* Input types for parse_datetimearg() */
+enum {
+	ARG_DATE,
+	ARG_DATE_TIME,
+	ARG_TIME,
+	ARG_ERR
+};
+
 /* Long options */
 enum {
 	OPT_FILTER_TYPE = 1000,
@@ -277,23 +285,53 @@ date_arg_from_to(long from, long to, int add_line, const char *fmt_apt,
 }
 
 /*
- * Convert a string with a date into the Unix time for midnight of that day.
+ * Convert a string with a (local time) date, date-time or time into
+ * the Unix time for that point in time as follows:
+ * - a date only is converted to midnight (beginning) of that day
+ * - a date-time is converted to that day and time
+ * - a time only is converted to that time of the current day
  * The date format is taken from the user configuration.
+ * The type of the input string is returned in the type argument.
  */
-static time_t parse_datearg(const char *str)
+
+static time_t parse_datetimearg(const char *str, int *type)
 {
+	char *date = mem_strdup(str);
+	*type = ARG_ERR;
+	char *time;
 	struct date day;
-
-	if (parse_date(str, conf.input_datefmt,
-		       (int *)&day.yyyy, (int *)&day.mm, (int *)&day.dd, NULL))
-		return date2sec(day, 0, 0);
-	else
+	unsigned hour, min;
+	time_t ret;
+
+	time = strchr(date, ' ');
+	if (time) { /* Date and time? */
+		*time++ = '\0';
+		if (!parse_time(time, &hour, &min))
+			return -1;
+		if (!parse_date(date, conf.input_datefmt,
+		    (int *)&day.yyyy, (int *)&day.mm, (int *)&day.dd, NULL))
+			return -1;
+		ret = date2sec(day, hour, min);
+		*type = ARG_DATE_TIME;
+	} else /* Date?*/ if (parse_date(date, conf.input_datefmt,
+		    (int *)&day.yyyy, (int *)&day.mm, (int *)&day.dd, NULL)) {
+		ret = date2sec(day, 0, 0);
+		*type = ARG_DATE;
+	} else /* Time? */ if (parse_time(date, &hour, &min)) {
+		ret = date2sec(*ui_calendar_get_today(), hour, min);
+		*type = ARG_TIME;
+	} else
 		return -1;
+	return ret;
 }
 
+/*
+ * Parse a "from,to" date range.
+ * For an open-end range no change occurs at the open end.
+ */
 static int parse_daterange(const char *str, time_t *date_from, time_t *date_to)
 {
-	int ret = 0;
+	int type, ret = 0;
 	char *s = mem_strdup(str);
 	char *p = strchr(s, ',');
 
@@ -304,21 +342,17 @@ static int parse_daterange(const char *str, time_t *date_from, time_t *date_to)
 	p++;
 
 	if (*s != '\0') {
-		*date_from = parse_datearg(s);
+		*date_from = parse_datetimearg(s, &type);
 		if (*date_from == -1)
 			goto cleanup;
-	} else {
-		*date_from = -1;
 	}
 
 	if (*p != '\0') {
-		*date_to = parse_datearg(p);
+		*date_to = parse_datetimearg(p, &type);
 		if (*date_to == -1)
 			goto cleanup;
-		/* One second before next midnight. */
-		*date_to = date_sec_change(*date_to, 0, 1) - 1;
-	} else {
-		*date_to = -1;
+		if (type == ARG_DATE)
+			*date_to = ENDOFDAY(*date_to);
 	}
 
 	ret = 1;
@@ -390,7 +424,7 @@ int parse_args(int argc, char **argv)
 	const char *cfile = NULL, *ifile = NULL, *confdir = NULL;
 
 	int non_interactive = 1;
-	int ch, cpid;
+	int ch, cpid, type;
 	regex_t reg;
 	char buf[BUFSIZ];
 	struct tm tm;
@@ -497,9 +531,9 @@ int parse_args(int argc, char **argv)
 				EXIT_IF(range == 0, _("invalid range: %s"),
 					optarg);
 			} else {
-				from = parse_datearg(optarg);
-				EXIT_IF(from == -1, _("invalid date: %s"),
-					optarg);
+				from = parse_datetimearg(optarg, &type);
+				EXIT_IF(from == -1 || type != ARG_DATE,
+					_("invalid date: %s"), optarg);
 			}
 
 			filter.type_mask |= TYPE_MASK_CAL;
@@ -544,8 +578,9 @@ int parse_args(int argc, char **argv)
 		case 's':
 			if (!optarg)
 				optarg = "today";
-			from = parse_datearg(optarg);
-			EXIT_IF(from == -1, _("invalid date: %s"), optarg);
+			from = parse_datetimearg(optarg, &type);
+			EXIT_IF(from == -1 || type != ARG_DATE,
+				_("invalid date: %s"), optarg);
 			filter.type_mask |= TYPE_MASK_CAL;
 			query = 1;
 			break;
@@ -606,78 +641,84 @@ int parse_args(int argc, char **argv)
 			filter.regex = &reg;
 			filter_opt = 1;
 			break;
-		/* Assume that the date argument is midnight of the given day. */
+		/*
+		 * A date only is a time span (a day) and interpreted thus:
+		 * "to" means "to end of day"
+		 * "after" means "from start of next day"
+		 */
 		case OPT_FILTER_START_FROM:
-			/* Midnight. */
-			filter.start_from = parse_datearg(optarg);
+			filter.start_from = parse_datetimearg(optarg, &type);
 			EXIT_IF(filter.start_from == -1,
 				_("invalid date: %s"), optarg);
 			filter_opt = 1;
 			break;
 		case OPT_FILTER_START_TO:
-			filter.start_to = parse_datearg(optarg);
+			filter.start_to = parse_datetimearg(optarg, &type);
 			EXIT_IF(filter.start_to == -1,
 				_("invalid date: %s"), optarg);
-			/* Next midnight less one second. */
-			filter.start_to = date_sec_change(filter.start_to, 0, 1) - 1;
+			if (type == ARG_DATE)
+				filter.start_to = ENDOFDAY(filter.start_to);
 			filter_opt = 1;
 			break;
 		case OPT_FILTER_START_AFTER:
-			filter.start_from = parse_datearg(optarg);
+			filter.start_from = parse_datetimearg(optarg, &type);
 			EXIT_IF(filter.start_from == -1,
 				_("invalid date: %s"), optarg);
-			/* Next midnight (belongs to the next day). */
-			filter.start_from = date_sec_change(filter.start_from, 0, 1);
+			if (type == ARG_DATE)
+				filter.start_from = NEXTDAY(filter.start_from);
+			else
+				filter.start_from++;
 			filter_opt = 1;
 			break;
 		case OPT_FILTER_START_BEFORE:
-			filter.start_to = parse_datearg(optarg);
+			filter.start_to = parse_datetimearg(optarg, &type);
 			EXIT_IF(filter.start_to == -1,
 				_("invalid date: %s"), optarg);
-			/* One second before midnight. */
 			filter.start_to--;
 			filter_opt = 1;
 			break;
 		case OPT_FILTER_START_RANGE:
-			EXIT_IF(!parse_daterange(optarg, &filter.start_from,
-						 &filter.start_to),
+			/* Set initialization values in case of open-end range. */
+			filter.start_from = filter.start_to = -1;
+			EXIT_IF(!parse_daterange(optarg, &filter.start_from, &filter.start_to),
 				_("invalid date range: %s"), optarg);
 			filter_opt = 1;
 			break;
 		case OPT_FILTER_END_FROM:
-			/* Midnight. */
-			filter.end_from = parse_datearg(optarg);
+			filter.end_from = parse_datetimearg(optarg, &type);
 			EXIT_IF(filter.end_from == -1,
 				_("invalid date: %s"), optarg);
 			filter_opt = 1;
 			break;
 		case OPT_FILTER_END_TO:
-			filter.end_to = parse_datearg(optarg);
+			filter.end_to = parse_datetimearg(optarg, &type);
 			EXIT_IF(filter.end_to == -1,
 				_("invalid date: %s"), optarg);
-			/* Next midnight less one second. */
-			filter.end_to = date_sec_change(filter.end_to, 0, 1) - 1;
+			if (type == ARG_DATE)
+				filter.end_to = ENDOFDAY(filter.end_to);
 			filter_opt = 1;
 			break;
 		case OPT_FILTER_END_AFTER:
-			filter.end_from = parse_datearg(optarg);
+			filter.end_from = parse_datetimearg(optarg, &type);
 			EXIT_IF(filter.end_from == -1,
 				_("invalid date: %s"), optarg);
-			/* Next midnight (belongs to the next day). */
-			filter.end_from = date_sec_change(filter.end_from, 0, 1);
+			if (type == ARG_DATE)
+				filter.end_from = NEXTDAY(filter.end_from);
+			else
+				filter.end_from++;
 			filter_opt = 1;
 			break;
 		case OPT_FILTER_END_BEFORE:
-			filter.end_to = parse_datearg(optarg);
+			filter.end_to = parse_datetimearg(optarg, &type);
 			EXIT_IF(filter.end_to == -1,
 				_("invalid date: %s"), optarg);
-			/* One second before midnight. */
 			filter.end_to--;
 			filter_opt = 1;
 			break;
 		case OPT_FILTER_END_RANGE:
-			EXIT_IF(!parse_daterange(optarg, &filter.end_from,
-						 &filter.end_to),
+			/* Set default values in case of open-ended range. */
+			filter.start_from = filter.start_to = -1;
+			EXIT_IF(!parse_daterange(optarg, &filter.end_from, &filter.end_to),
 				_("invalid date range: %s"), optarg);
 			filter_opt = 1;
 			break;
@@ -696,13 +737,15 @@ int parse_args(int argc, char **argv)
 			filter_opt = 1;
 			break;
 		case OPT_FROM:
-			from = parse_datearg(optarg);
-			EXIT_IF(from == -1, _("invalid date: %s"), optarg);
+			from = parse_datetimearg(optarg, &type);
+			EXIT_IF(from == -1 || type != ARG_DATE,
+				_("invalid date: %s"), optarg);
 			query_range = 1;
 			break;
 		case OPT_TO:
-			to = parse_datearg(optarg);
-			EXIT_IF(to == -1, _("invalid date: %s"), optarg);
+			to = parse_datetimearg(optarg, &type);
+			EXIT_IF(to == -1 || type != ARG_DATE,
+				_("invalid date: %s"), optarg);
 			query_range = 1;
 			break;
 		case OPT_DAYS:
@@ -790,7 +833,7 @@ int parse_args(int argc, char **argv)
 	if (from == -1)
 		from = get_today();
 	if (to == -1)
-		to = from;
+		to = ENDOFDAY(from);
 	EXIT_IF(to < from, _("end date cannot come before start date"));
 	if (range > 0)
 		to = date_sec_change(from, 0, range - 1);
diff --git a/src/calcurse.h b/src/calcurse.h
index 8626393..c6ddc8d 100644
--- a/src/calcurse.h
+++ b/src/calcurse.h
@@ -137,10 +137,12 @@
 /*
  * Note the difference between the number of seconds in a day and daylength
  * in seconds. The two may differ when DST is in effect (daylength is either
- * 23, 24 or 25 hours. The argument to DAYLEN is of type time_t.
+ * 23, 24 or 25 hours. The argument "date" is assumed to be of type time_t.
  */
 #define DAYINSEC        (DAYINMIN * MININSEC)
-#define DAYLEN(date)	(date_sec_change((date), 0, 1) - (date))
+#define NEXTDAY(date)	date_sec_change((date), 0, 1)
+#define DAYLEN(date)	(NEXTDAY(date) - (date))
+#define ENDOFDAY(date)	(NEXTDAY(date) - 1)
 #define HOURINSEC       (HOURINMIN * MININSEC)
 
 #define MAXDAYSPERMONTH 31
@@ -760,6 +762,7 @@ int ui_calendar_get_view(void);
 void ui_calendar_start_date_thread(void);
 void ui_calendar_stop_date_thread(void);
 void ui_calendar_set_current_date(void);
+struct date *ui_calendar_get_today(void);
 void ui_calendar_set_first_day_of_week(enum wday);
 void ui_calendar_change_first_day_of_week(void);
 unsigned ui_calendar_week_begins_on_monday(void);
diff --git a/src/ui-calendar.c b/src/ui-calendar.c
index a668efb..407f294 100644
--- a/src/ui-calendar.c
+++ b/src/ui-calendar.c
@@ -144,6 +144,12 @@ void ui_calendar_set_current_date(void)
 	pthread_mutex_unlock(&date_thread_mutex);
 }
 
+/* Return the current date. */
+struct date *ui_calendar_get_today(void)
+{
+	return &today;
+}
+
 /* Needed to display sunday or monday as the first day of week in calendar. */
 void ui_calendar_set_first_day_of_week(enum wday first_day)
 {
-- 
cgit v1.2.3-70-g09d2