changeset 53:3cafb04ae4f7

Implement remaining date formats
author Lewin Bormann <lbo@spheniscida.de>
date Sat, 10 Dec 2016 20:32:16 +0100
parents 0a3d58b0cefa
children b56ef3fe4bff
files handler_remind.go
diffstat 1 files changed, 74 insertions(+), 45 deletions(-) [+]
line wrap: on
line diff
--- a/handler_remind.go	Sat Dec 10 18:12:04 2016 +0100
+++ b/handler_remind.go	Sat Dec 10 20:32:16 2016 +0100
@@ -15,59 +15,96 @@
 var (
 	hhmmRE       = regexp.MustCompile(`(\d{1,2}):(\d\d)`)
 	durRE        = regexp.MustCompile(`\+?(\d+)([smhd])`)
-	dateRE       = regexp.MustCompile(`(\d{4})-(\d\d)-(\d\d) (\d{1,2}):(\d\d)`)
-	dateOfWeekRE = regexp.MustCompile(`(Mo|Di|Mi|Do|Fr|Sa|So) (\d{1,2}):(\d\d)`)
+	dateRE       = regexp.MustCompile(`(\d{4})-(\d\d)-(\d\d) (\d{1,2}):(\d\d)(:..)?`)
+	dateOfWeekRE = regexp.MustCompile(`(Mo|Di|Mi|Do|Fr|Sa|So) (\d{1,2}):(\d\d)(:..)?`)
+
+	dayOfWeekOff = map[string]time.Weekday{
+		"So": time.Sunday,
+		"Mo": time.Monday,
+		"Di": time.Tuesday,
+		"Mi": time.Wednesday,
+		"Do": time.Thursday,
+		"Fr": time.Friday,
+		"Sa": time.Saturday,
+	}
 )
 
-func parseReminderString(s string) time.Time {
+// Parses a time, and returns a UTC time (respecting the value of flagLocalOffset)
+// Returns the parsed time (t.IsZero() if parsing was unsuccessful), and the index of the first non-time
+// character in the string
+func parseReminderString(s string) (time.Time, int) {
 	now := time.Now()
-	log.Println(s)
 
-	if m := hhmmRE.FindStringSubmatch(s); len(m) >= 3 {
-		log.Println(m)
-		h, err1 := strconv.ParseInt(m[1], 10, 32)
-		m, err2 := strconv.ParseInt(m[2], 10, 32)
+	if m := dateRE.FindStringSubmatch(s); len(m) >= 6 {
+		y, _ := strconv.ParseInt(m[1], 10, 32)
+		M, _ := strconv.ParseInt(m[2], 10, 32)
+		d, _ := strconv.ParseInt(m[3], 10, 32)
+		h, _ := strconv.ParseInt(m[4], 10, 32)
+		m, _ := strconv.ParseInt(m[5], 10, 32)
+
+		t := time.Date(int(y), time.Month(M), int(d), int(h), int(m), 0, 0, time.UTC)
+
+		return t.Add(-time.Duration(*flagLocalOffset) * time.Second), dateRE.FindStringIndex(s)[1]
+	} else if m := dateOfWeekRE.FindStringSubmatch(s); len(m) >= 4 {
+		h, _ := strconv.ParseInt(m[2], 10, 32)
+		min, _ := strconv.ParseInt(m[3], 10, 32)
+
+		if weekday, ok := dayOfWeekOff[m[1]]; ok {
+			today := now.Weekday()
+			// Any point in the day of the reminder. Useful for not having to do week/month/year switch calculations.
+			var pointInTargetDay time.Time
 
-		if err1 == nil && err2 == nil {
-			nowH, nowM, nowS := now.Clock()
-			timeOfDay := time.Duration(nowH)*time.Hour + time.Duration(nowM)*time.Minute + time.Duration(nowS)*time.Second
-			midnight := now.Add(-timeOfDay)
-			// duration since midnight of alert time
-			// timezone mucking-about because we're using UTC, but users are not
-			alert := time.Duration(h)*time.Hour + time.Duration(m)*time.Minute - (time.Duration(*flagLocalOffset) * time.Second)
+			// today's weekday means "in a week".
+			// If target is in same week, add difference
+			if today < weekday {
+				pointInTargetDay = now.Add(time.Duration(weekday-today) * 24 * time.Hour)
+			} else { // If target is next week, add time to end-of-week plus time to target plus 1 (Sat-to-Sun)
+				diff := int(weekday) + int(time.Saturday-today) + 1
+				pointInTargetDay = now.Add(time.Duration(diff) * 24 * time.Hour)
+			}
+			return time.Date(pointInTargetDay.Year(), pointInTargetDay.Month(), pointInTargetDay.Day(), int(h), int(min), 0, 0, time.UTC).Add(-time.Duration(*flagLocalOffset) * time.Second), dateOfWeekRE.FindStringIndex(s)[1]
+		} else {
+			return time.Unix(0, 0), 0
+		}
+	} else if m := hhmmRE.FindStringSubmatch(s); len(m) >= 3 { // this has to come after dateRE and dateOfWeekRE, because it's a sub match
+		// Not checking errors as the regex should not let through any non-digits
+		h, _ := strconv.ParseInt(m[1], 10, 32)
+		m, _ := strconv.ParseInt(m[2], 10, 32)
 
-			if alert > timeOfDay { // later today
-				return midnight.Add(alert)
-			} else { // that time tomorrow
-				return midnight.Add(24*time.Hour + alert)
-			}
-		} else {
-			return time.Unix(0, 0)
+		nowH, nowM, nowS := now.Clock()
+		timeOfDay := time.Duration(nowH)*time.Hour + time.Duration(nowM)*time.Minute + time.Duration(nowS)*time.Second
+		midnight := now.Add(-timeOfDay)
+		// duration since midnight of alert time
+		// timezone mucking-about because we're using UTC, but users are not
+		alert := time.Duration(h)*time.Hour + time.Duration(m)*time.Minute - (time.Duration(*flagLocalOffset) * time.Second)
+
+		if alert > timeOfDay { // later today
+			return midnight.Add(alert), hhmmRE.FindStringIndex(s)[1]
+		} else { // that time tomorrow
+			return midnight.Add(24*time.Hour + alert), hhmmRE.FindStringIndex(s)[1]
 		}
 	} else if m := durRE.FindStringSubmatch(s); len(m) >= 3 {
-		log.Println(m)
-		dur, err := strconv.ParseInt(m[1], 10, 32)
-
-		if err != nil {
-			return time.Unix(0, 0)
-		}
+		dur, _ := strconv.ParseInt(m[1], 10, 32)
+		var t time.Time
 
 		if m[2] == "s" {
-			return now.Add(time.Duration(dur) * time.Second)
+			t = now.Add(time.Duration(dur) * time.Second)
 		} else if m[2] == "m" {
-			return now.Add(time.Duration(dur) * time.Minute)
+			t = now.Add(time.Duration(dur) * time.Minute)
 		} else if m[2] == "h" {
-			return now.Add(time.Duration(dur) * time.Hour)
+			t = now.Add(time.Duration(dur) * time.Hour)
 		} else if m[2] == "d" {
-			return now.Add(time.Duration(dur) * 24 * time.Hour)
+			t = now.Add(time.Duration(dur) * 24 * time.Hour)
 		} else {
-			return time.Unix(0, 0)
+			t = time.Unix(0, 0)
 		}
+
+		return t, durRE.FindStringIndex(s)[1]
 	}
 
 	// rest is not implemented yet
 
-	return time.Unix(0, 0)
+	return time.Unix(0, 0), 0
 }
 
 // Handler for /remind messages
@@ -76,17 +113,9 @@
 func reminderHandler(ctx context.Context, msg message) (replyContent, error) {
 	// TODO: This doesn't work with multi-part formats like dateRE or dateOfWeekRE!
 	// first part is time
-	parts := strings.SplitN(msg.Text, " ", 2)
+	alertTime, restStart := parseReminderString(strings.Trim(msg.Text, " "))
 
-	if len(parts) < 2 {
-		log.Println("Not enough parts in message:", msg.Text)
-		return replyContent{text: "Tut mir leid, ich verstehe das Format nicht. Bitte benutze +XX{s,m,h,d} oder hh:mm. Zum Beispiel: +23m oder 14:45"},
-			nil
-	}
-
-	alertTime := parseReminderString(parts[0])
-
-	if alertTime.IsZero() {
+	if restStart == 0 || alertTime.IsZero() {
 		return replyContent{text: "Tut mir leid, ich verstehe das Format nicht. Bitte benutze +XX{s,m,h,d} oder hh:mm. Zum Beispiel: +23m oder 14:45"},
 			nil
 	}
@@ -99,7 +128,7 @@
 	}
 
 	r := reminder{
-		Text:    parts[1],
+		Text:    strings.Trim(msg.Text[restStart:], " "),
 		Owner:   msg.From.First_Name + " " + msg.From.Last_Name,
 		Due:     alertTime,
 		ChatID:  msg.Chat.ID,