170 lines
4.1 KiB
Go
170 lines
4.1 KiB
Go
package service
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
reTimeHHMM = regexp.MustCompile(`^(\d{1,2}):(\d{2})$`)
|
|
reTimeHHJMM = regexp.MustCompile(`^(\d{1,2})時(\d{1,2})分$`)
|
|
reTimeHHJ = regexp.MustCompile(`^(\d{1,2})時$`)
|
|
reDateMD = regexp.MustCompile(`^(\d{1,2})/(\d{1,2})$`)
|
|
reDateMJDJ = regexp.MustCompile(`^(\d{1,2})月(\d{1,2})日$`)
|
|
)
|
|
|
|
var weekdayNames = map[string]time.Weekday{
|
|
"月": time.Monday,
|
|
"火": time.Tuesday,
|
|
"水": time.Wednesday,
|
|
"木": time.Thursday,
|
|
"金": time.Friday,
|
|
"土": time.Saturday,
|
|
"日": time.Sunday,
|
|
}
|
|
|
|
// ParseAddCommand parses the arguments after /add.
|
|
// Format: <title> <date> [time] [#subject] [!priority]
|
|
// Returns title, dueDate, subject, priority (Go string: "high"/"medium"/"low"), error.
|
|
// On format error, err.Error() == "usage" means show usage hint.
|
|
func ParseAddCommand(text string) (title string, dueDate time.Time, subject string, priority string, err error) {
|
|
priority = "medium"
|
|
tokens := strings.Fields(text)
|
|
if len(tokens) == 0 {
|
|
err = fmt.Errorf("usage")
|
|
return
|
|
}
|
|
|
|
// Extract #subject and !priority modifiers
|
|
var core []string
|
|
for _, tok := range tokens {
|
|
switch {
|
|
case strings.HasPrefix(tok, "#") && len(tok) > 1:
|
|
subject = tok[1:]
|
|
case strings.HasPrefix(tok, "!") && len(tok) > 1:
|
|
priority = parsePriorityJP(tok[1:])
|
|
default:
|
|
core = append(core, tok)
|
|
}
|
|
}
|
|
|
|
if len(core) < 2 {
|
|
err = fmt.Errorf("usage")
|
|
return
|
|
}
|
|
|
|
// Extract optional time token from end
|
|
hour, minute := 23, 59
|
|
if h, m, ok := parseTimeToken(core[len(core)-1]); ok {
|
|
hour, minute = h, m
|
|
core = core[:len(core)-1]
|
|
}
|
|
|
|
if len(core) < 2 {
|
|
err = fmt.Errorf("usage")
|
|
return
|
|
}
|
|
|
|
// Extract date token from end
|
|
dateTok := core[len(core)-1]
|
|
parsedDate, ok := parseDateToken(dateTok, hour, minute)
|
|
if !ok {
|
|
err = fmt.Errorf("日付が読み取れませんでした: %q\n使える形式: 今日/明日/明後日/月〜日/MM/DD/M月D日", dateTok)
|
|
return
|
|
}
|
|
dueDate = parsedDate
|
|
core = core[:len(core)-1]
|
|
|
|
if len(core) == 0 {
|
|
err = fmt.Errorf("usage")
|
|
return
|
|
}
|
|
title = strings.Join(core, " ")
|
|
return
|
|
}
|
|
|
|
func parsePriorityJP(s string) string {
|
|
switch s {
|
|
case "高":
|
|
return "high"
|
|
case "低":
|
|
return "low"
|
|
default:
|
|
return "medium"
|
|
}
|
|
}
|
|
|
|
func parseTimeToken(s string) (hour, minute int, ok bool) {
|
|
if m := reTimeHHMM.FindStringSubmatch(s); m != nil {
|
|
h, _ := strconv.Atoi(m[1])
|
|
min, _ := strconv.Atoi(m[2])
|
|
if h <= 23 && min <= 59 {
|
|
return h, min, true
|
|
}
|
|
}
|
|
if m := reTimeHHJMM.FindStringSubmatch(s); m != nil {
|
|
h, _ := strconv.Atoi(m[1])
|
|
min, _ := strconv.Atoi(m[2])
|
|
if h <= 23 && min <= 59 {
|
|
return h, min, true
|
|
}
|
|
}
|
|
if m := reTimeHHJ.FindStringSubmatch(s); m != nil {
|
|
h, _ := strconv.Atoi(m[1])
|
|
if h <= 23 {
|
|
return h, 0, true
|
|
}
|
|
}
|
|
return 0, 0, false
|
|
}
|
|
|
|
func parseDateToken(s string, hour, minute int) (time.Time, bool) {
|
|
now := time.Now()
|
|
loc := now.Location()
|
|
|
|
switch s {
|
|
case "今日":
|
|
return time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, loc), true
|
|
case "明日":
|
|
d := now.AddDate(0, 0, 1)
|
|
return time.Date(d.Year(), d.Month(), d.Day(), hour, minute, 0, 0, loc), true
|
|
case "明後日":
|
|
d := now.AddDate(0, 0, 2)
|
|
return time.Date(d.Year(), d.Month(), d.Day(), hour, minute, 0, 0, loc), true
|
|
}
|
|
|
|
if wd, ok := weekdayNames[s]; ok {
|
|
d := nextWeekdayFrom(now, wd)
|
|
return time.Date(d.Year(), d.Month(), d.Day(), hour, minute, 0, 0, loc), true
|
|
}
|
|
|
|
var month, day int
|
|
if m := reDateMD.FindStringSubmatch(s); m != nil {
|
|
month, _ = strconv.Atoi(m[1])
|
|
day, _ = strconv.Atoi(m[2])
|
|
} else if m := reDateMJDJ.FindStringSubmatch(s); m != nil {
|
|
month, _ = strconv.Atoi(m[1])
|
|
day, _ = strconv.Atoi(m[2])
|
|
} else {
|
|
return time.Time{}, false
|
|
}
|
|
|
|
if month < 1 || month > 12 || day < 1 || day > 31 {
|
|
return time.Time{}, false
|
|
}
|
|
t := time.Date(now.Year(), time.Month(month), day, hour, minute, 0, 0, loc)
|
|
if t.Before(now) {
|
|
t = t.AddDate(1, 0, 0)
|
|
}
|
|
return t, true
|
|
}
|
|
|
|
// nextWeekdayFrom returns the date of the next occurrence of wd from 'from'.
|
|
func nextWeekdayFrom(from time.Time, wd time.Weekday) time.Time {
|
|
days := (int(wd) - int(from.Weekday()) + 7) % 7
|
|
return from.AddDate(0, 0, days)
|
|
}
|