From eb47a0c50d6bddae6b6e9359517aee9bcf341798 Mon Sep 17 00:00:00 2001 From: Rex Zeng Date: Tue, 13 Jun 2023 13:50:02 +0800 Subject: [PATCH] Support Chinese time (follows up #30) (#39) --- .gitignore | 1 + CODEOWNERS | 2 + rules/zh/after_time.go | 55 +++++++++ rules/zh/casual_date.go | 92 ++++++++++++++++ rules/zh/casual_date_test.go | 39 +++++++ rules/zh/casual_time.go | 66 +++++++++++ rules/zh/exact_month_date.go | 73 ++++++++++++ rules/zh/exact_month_test.go | 53 +++++++++ rules/zh/hour_minute.go | 76 +++++++++++++ rules/zh/hour_minute_test.go | 35 ++++++ rules/zh/tradition_hour.go | 63 +++++++++++ rules/zh/tradition_hour_test.go | 29 +++++ rules/zh/weedkay.go | 79 +++++++++++++ rules/zh/weekday_test.go | 36 ++++++ rules/zh/zh.go | 190 ++++++++++++++++++++++++++++++++ rules/zh/zh_test.go | 36 ++++++ 16 files changed, 925 insertions(+) create mode 100644 CODEOWNERS create mode 100644 rules/zh/after_time.go create mode 100644 rules/zh/casual_date.go create mode 100644 rules/zh/casual_date_test.go create mode 100644 rules/zh/casual_time.go create mode 100644 rules/zh/exact_month_date.go create mode 100644 rules/zh/exact_month_test.go create mode 100644 rules/zh/hour_minute.go create mode 100644 rules/zh/hour_minute_test.go create mode 100644 rules/zh/tradition_hour.go create mode 100644 rules/zh/tradition_hour_test.go create mode 100644 rules/zh/weedkay.go create mode 100644 rules/zh/weekday_test.go create mode 100644 rules/zh/zh.go create mode 100644 rules/zh/zh_test.go diff --git a/.gitignore b/.gitignore index 22d0d82..ecdf2d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ vendor +.idea diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..0bcd11d --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +* @olebedev +/rules/zh/ @RexSkz diff --git a/rules/zh/after_time.go b/rules/zh/after_time.go new file mode 100644 index 0000000..2be39fe --- /dev/null +++ b/rules/zh/after_time.go @@ -0,0 +1,55 @@ +package zh + +import ( + "regexp" + "strconv" + "time" + + "github.com/olebedev/when/rules" +) + +/* +5/五 分钟后 +5 小时后 +*/ + +func AfterTime(s rules.Strategy) rules.Rule { + return &rules.F{ + RegExp: regexp.MustCompile("(?i)" + + "((?:[0-9]{0,3}))?" + + "(" + INTEGER_WORDS_PATTERN[3:] + "?" + "\\s*" + + "(?:(分|分钟|小时|天|周|月)\\s*)" + + "(后)" + + "(?:\\W|$)", + ), + Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { + if c.Hour != nil && s != rules.Override { + return false, nil + } + duration, _ := strconv.Atoi(m.Captures[0]) + + if d, exist := INTEGER_WORDS[compressStr(m.Captures[1])]; exist { + duration = d + } + if m.Captures[1] == "半" && m.Captures[2] == "小时" { + c.Duration = time.Minute * time.Duration(30) + return true, nil + } + + switch m.Captures[2] { + case "分钟", "分": + c.Duration = time.Minute * time.Duration(duration) + case "小时": + c.Duration = time.Hour * time.Duration(duration) + case "天": + c.Duration = time.Hour * 24 * time.Duration(duration) + case "周": + c.Duration = time.Hour * 24 * 7 * time.Duration(duration) + case "月": + _, _ = c.Time(time.Now().AddDate(0, duration, 0)) + } + + return true, nil + }, + } +} diff --git a/rules/zh/casual_date.go b/rules/zh/casual_date.go new file mode 100644 index 0000000..ff6a083 --- /dev/null +++ b/rules/zh/casual_date.go @@ -0,0 +1,92 @@ +package zh + +import ( + "regexp" + "strconv" + "strings" + "time" + + "github.com/AlekSi/pointer" + "github.com/olebedev/when/rules" +) + +func CasualDate(s rules.Strategy) rules.Rule { + overwrite := s == rules.Override + + return &rules.F{ + RegExp: regexp.MustCompile("(?i)" + + "(大前|前|昨|今天|今|明|大后|后|下下|下|上|上上)" + "(天|月|个月|年|儿)" + + "(1[0-9]|2[0-9]|3[0-1]|[1-9]|" + DAY_WORDS_PATTERN + ")?" + "(?:\\s*)?" + + "(日|号)?" + + "", + // "(?:\\W|$)", + ), + Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { + lower := compressStr(strings.TrimSpace(m.String())) + + switch { + case strings.Contains(lower, "号"), strings.Contains(lower, "日"): + day, _ := strconv.Atoi(m.Captures[2]) + c.Day = pointer.ToInt(day) + } + + switch { + + case strings.Contains(lower, "后年"): + c.Year = pointer.ToInt(ref.Year() + 2) + case strings.Contains(lower, "明年"): + c.Year = pointer.ToInt(ref.Year() + 1) + case strings.Contains(lower, "下下"): + monthInt := int(ref.Month()) + 2 + c.Month = pointer.ToInt(monthInt) + case strings.Contains(lower, "下月"), strings.Contains(lower, "下个月"): + monthInt := int(ref.Month()) + 1 + c.Month = pointer.ToInt(monthInt) + case strings.Contains(lower, "上上"): + monthInt := int(ref.Month()) - 2 + c.Month = pointer.ToInt(monthInt) + case strings.Contains(lower, "上月"), strings.Contains(lower, "上个月"): + monthInt := int(ref.Month()) - 1 + c.Month = pointer.ToInt(monthInt) + case strings.Contains(lower, "今晚"), strings.Contains(lower, "晚上"): + if c.Hour == nil && c.Minute == nil || overwrite { + c.Hour = pointer.ToInt(22) + c.Minute = pointer.ToInt(0) + } + case strings.Contains(lower, "今天"), strings.Contains(lower, "今儿"): + // c.Hour = pointer.ToInt(18) + case strings.Contains(lower, "明天"), strings.Contains(lower, "明儿"): + if c.Duration == 0 || overwrite { + c.Duration += time.Hour * 24 + } + case strings.Contains(lower, "昨天"): + if c.Duration == 0 || overwrite { + c.Duration -= time.Hour * 24 + } + case strings.Contains(lower, "大前天"): + if c.Duration == 0 || overwrite { + c.Duration -= time.Hour * 24 * 3 + } + case strings.Contains(lower, "前天"): + if c.Duration == 0 || overwrite { + c.Duration -= time.Hour * 24 * 2 + } + case strings.Contains(lower, "昨晚"): + if (c.Hour == nil && c.Duration == 0) || overwrite { + c.Hour = pointer.ToInt(23) + c.Duration -= time.Hour * 24 + } + case strings.Contains(lower, "大后天"): + if c.Duration == 0 || overwrite { + c.Duration += time.Hour * 24 * 3 + } + case strings.Contains(lower, "后天"): + if c.Duration == 0 || overwrite { + c.Duration += time.Hour * 24 * 2 + } + } + + return true, nil + }, + } +} diff --git a/rules/zh/casual_date_test.go b/rules/zh/casual_date_test.go new file mode 100644 index 0000000..09bd0fc --- /dev/null +++ b/rules/zh/casual_date_test.go @@ -0,0 +1,39 @@ +package zh_test + +import ( + "testing" + "time" + + "github.com/olebedev/when" + "github.com/olebedev/when/rules" + "github.com/olebedev/when/rules/zh" +) + +func TestCasualDate(t *testing.T) { + // current is Monday + now := time.Now() + fixt := []Fixture{ + {"后天中午", 0, "后天", (2 * 24) * time.Hour}, + {"大后天中午", 0, "大后天", (3 * 24) * time.Hour}, + {"昨天", 0, "昨天", (-1 * 24) * time.Hour}, + {"前天", 0, "前天", (-2 * 24) * time.Hour}, + {"大前天", 0, "大前天", (-3 * 24) * time.Hour}, + {"下月", 0, "下月", (31 * 24) * time.Hour}, + {"下个月", 0, "下个月", (31 * 24) * time.Hour}, + {"下下月", 0, "下下月", (31*24 + 30*24) * time.Hour}, + {"下下个月", 0, "下下个月", (31*24 + 30*24) * time.Hour}, + {"明年", 0, "明年", (365 * 24) * time.Hour}, + {"后年", 0, "后年", now.AddDate(2, 0, 0).Sub(now)}, + {"下月6号", 0, "下月6号", 552 * time.Hour}, + } + + w := when.New(nil) + + w.Add(zh.CasualDate(rules.Override)) + + ApplyFixtures(t, "zh.TestCasualDate", w, fixt) +} + +/* + (([1-9](?:月|-|/|\.|))|1[0-2])\s*(月|-|/|\.|)\s*([1-9]|1[0-9]|2[0-9]|3[0-1])\s*(日|号)?(?:\W|$) +*/ diff --git a/rules/zh/casual_time.go b/rules/zh/casual_time.go new file mode 100644 index 0000000..45394a6 --- /dev/null +++ b/rules/zh/casual_time.go @@ -0,0 +1,66 @@ +package zh + +import ( + "regexp" + "strings" + "time" + + "github.com/AlekSi/pointer" + "github.com/olebedev/when/rules" +) + +func CasualTime(s rules.Strategy) rules.Rule { + overwrite := s == rules.Override + + return &rules.F{ + RegExp: regexp.MustCompile(`(?i)(?:\W|^)((今天)?\s*(早晨|下午|傍晚|中午|晚上))`), + Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { + + lower := strings.ToLower(strings.TrimSpace(m.String())) + + if (c.Hour != nil || c.Minute != nil) && !overwrite { + return false, nil + } + + switch { + case strings.Contains(lower, "晚上"): + if o.Evening != 0 { + c.Hour = &o.Evening + } else { + c.Hour = pointer.ToInt(20) + } + c.Minute = pointer.ToInt(0) + case strings.Contains(lower, "下午"): + if o.Afternoon != 0 { + c.Hour = &o.Afternoon + } else { + c.Hour = pointer.ToInt(15) + } + c.Minute = pointer.ToInt(0) + case strings.Contains(lower, "傍晚"): + if o.Evening != 0 { + c.Hour = &o.Evening + } else { + c.Hour = pointer.ToInt(18) + } + c.Minute = pointer.ToInt(0) + case strings.Contains(lower, "早晨"): + if o.Morning != 0 { + c.Hour = &o.Morning + } else { + c.Hour = pointer.ToInt(8) + } + c.Minute = pointer.ToInt(0) + case strings.Contains(lower, "中午"): + if o.Noon != 0 { + c.Hour = &o.Noon + } else { + c.Hour = pointer.ToInt(12) + } + c.Minute = pointer.ToInt(0) + } + + return true, nil + }, + } +} diff --git a/rules/zh/exact_month_date.go b/rules/zh/exact_month_date.go new file mode 100644 index 0000000..3bfccb5 --- /dev/null +++ b/rules/zh/exact_month_date.go @@ -0,0 +1,73 @@ +package zh + +import ( + "regexp" + "strconv" + "time" + + "github.com/olebedev/when/rules" +) + +/* + 规则名称:精确到月份的日期 +*/ + +func ExactMonthDate(s rules.Strategy) rules.Rule { + overwrite := s == rules.Override + + return &rules.F{ + RegExp: regexp.MustCompile("" + + "(?:\\b|^)" + // can't use \W here due to Chinese characters + "(?:" + + "(1[0-2]|[1-9]|" + MON_WORDS_PATTERN + ")" + "(?:\\s*)" + + "(月|-|/|\\.)" + "(?:\\s*)" + + ")?" + + "(?:" + + "(1[0-9]|2[0-9]|3[0-1]|[1-9]|" + DAY_WORDS_PATTERN + ")" + "(?:\\s*)" + + "(日|号)?" + + ")?", + ), + + Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { + _ = overwrite + + // the default value of month is the current month, and the default + // value of day is the first day of the month, so that we can handle + // cases like "4月" (Apr 1st) and "12号" (12th this month) + var monInt = int(ref.Month()) + var dayInt = 1 + var exist bool + + if m.Captures[1] == "" && m.Captures[3] == "" { + return false, nil + } + + if m.Captures[0] != "" { + monInt, exist = MON_WORDS[compressStr(m.Captures[0])] + if !exist { + mon, err := strconv.Atoi(m.Captures[0]) + if err != nil { + return false, nil + } + monInt = mon + } + } + + if m.Captures[2] != "" { + dayInt, exist = DAY_WORDS[compressStr(m.Captures[2])] + if !exist { + day, err := strconv.Atoi(m.Captures[2]) + if err != nil { + return false, nil + } + dayInt = day + } + } + + c.Month = &monInt + c.Day = &dayInt + + return true, nil + }, + } +} diff --git a/rules/zh/exact_month_test.go b/rules/zh/exact_month_test.go new file mode 100644 index 0000000..316540a --- /dev/null +++ b/rules/zh/exact_month_test.go @@ -0,0 +1,53 @@ +package zh_test + +import ( + "testing" + "time" + + "github.com/olebedev/when" + "github.com/olebedev/when/rules" + "github.com/olebedev/when/rules/zh" +) + +func TestExactMonthDate(t *testing.T) { + // current is Monday + fixt := []Fixture{ + {"4月1日", 0, "4月1日", (18 * 24) * time.Hour}, + {"4月2日", 0, "4月2日", (19 * 24) * time.Hour}, + {"4月 2日", 0, "4月 2日", (19 * 24) * time.Hour}, + {"4 月 2 日", 0, "4 月 2 日", (19 * 24) * time.Hour}, + {"四月一日", 0, "四月一日", (18 * 24) * time.Hour}, + {"四月1日", 0, "四月1日", (18 * 24) * time.Hour}, + {"四月", 0, "四月", (18 * 24) * time.Hour}, + {"十一月一日", 0, "十一月一日", 5568 * time.Hour}, + {"四月三十日", 0, "四月三十日", 1128 * time.Hour}, + {"4月30日", 0, "4月30日", 1128 * time.Hour}, + {"5月1号", 0, "5月1号", 1152 * time.Hour}, + {"5/1", 0, "5/1", 1152 * time.Hour}, + {"5月1日", 0, "5月1日", 1152 * time.Hour}, + {"五月", 0, "五月", 1152 * time.Hour}, + {"12号", 0, "12号", (-2 * 24) * time.Hour}, + } + + w := when.New(nil) + + w.Add(zh.ExactMonthDate(rules.Override)) + + ApplyFixtures(t, "zh.ExactMonthDate", w, fixt) +} + +func TestExactMonthDateNil(t *testing.T) { + fixt := []Fixture{ + {"41", 0, "", (18 * 24) * time.Hour}, + } + + w := when.New(nil) + + w.Add(zh.ExactMonthDate(rules.Override)) + ApplyFixturesNil(t, "zh.ExactMonthDate", w, fixt) + +} + +/* + (([1-9](?:月|-|/|\.|))|1[0-2])\s*(月|-|/|\.|)\s*([1-9]|1[0-9]|2[0-9]|3[0-1])\s*(日|号)?(?:\W|$) +*/ diff --git a/rules/zh/hour_minute.go b/rules/zh/hour_minute.go new file mode 100644 index 0000000..39fe0d2 --- /dev/null +++ b/rules/zh/hour_minute.go @@ -0,0 +1,76 @@ +package zh + +import ( + "regexp" + "strconv" + "time" + + "github.com/olebedev/when/rules" +) + +/* + "上午 5点" + "上午 5 点" + "下午 3点" + "下午 3 点" + "下午 3点半" + "下午 3点30" + "下午 3:30" + "下午 3:30" + "下午 三点半" +*/ + +func HourMinute(s rules.Strategy) rules.Rule { + return &rules.F{ + RegExp: regexp.MustCompile("(?i)" + + "(?:(凌\\s*晨|早\\s*晨|早\\s*上|上\\s*午|下\\s*午|晚\\s*上|今晚)?\\s*)" + + "((?:[0-1]{0,1}[0-9])|(?:2[0-3]))?" + "(?:\\s*)" + + "(" + INTEGER_WORDS_PATTERN[3:] + "?" + + "(\\:|:|\\-|点)" + + "((?:[0-5][0-9]))?" + + "(" + INTEGER_WORDS_PATTERN + "+)?" + + "(?:\\W|$)"), + Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { + if (c.Hour != nil || c.Minute != nil) && s != rules.Override { + return false, nil + } + + hour, exist := INTEGER_WORDS[m.Captures[2]] // 中文 + if !exist { + hour, _ = strconv.Atoi(m.Captures[1]) + } + + if hour > 24 { + return false, nil + } + + minutes, exist := INTEGER_WORDS[m.Captures[5]] + if !exist { + minutes, _ = strconv.Atoi(m.Captures[4]) + } + + if minutes > 59 { + return false, nil + } + c.Minute = &minutes + + lower := compressStr(m.Captures[0]) + switch lower { + case "上午", "凌晨", "早晨", "早上": + c.Hour = &hour + case "下午", "晚上", "今晚": + if hour < 12 { + hour += 12 + } + c.Hour = &hour + case "": + if hour > 23 { + return false, nil + } + c.Hour = &hour + + } + return true, nil + }, + } +} diff --git a/rules/zh/hour_minute_test.go b/rules/zh/hour_minute_test.go new file mode 100644 index 0000000..c7985a2 --- /dev/null +++ b/rules/zh/hour_minute_test.go @@ -0,0 +1,35 @@ +package zh_test + +import ( + "github.com/olebedev/when/rules/zh" + "testing" + "time" + + "github.com/olebedev/when" + "github.com/olebedev/when/rules" +) + +func TestHourMinute(t *testing.T) { + // current is Monday + fixt := []Fixture{ + {"上午 11:30", 0, "上午 11:30", 11*time.Hour + 30*time.Minute}, + {"下午 3:30", 0, "下午 3:30", 15*time.Hour + 30*time.Minute}, + {"下午 3点半", 0, "下午 3点半", 15*time.Hour + 30*time.Minute}, + {"凌晨 3点半", 0, "凌晨 3点半", 3*time.Hour + 30*time.Minute}, + {"晚上8:00", 0, "晚上8:00", 20*time.Hour + 0*time.Minute}, + {"晚上9:32", 0, "晚上9:32", 21*time.Hour + 32*time.Minute}, + {"晚 上 8:00", 0, "晚 上 8:00", 20*time.Hour + 0*time.Minute}, + {"晚上 8 点干啥去", 0, "晚上 8 点", 20*time.Hour + 0*time.Minute}, + {"他俩凌晨 3点去散步太可怕了", 6, "凌晨 3点", 3*time.Hour + 0*time.Minute}, + {"早晨八点一刻", 0, "早晨八点一刻", 8*time.Hour + 15*time.Minute}, + {"早上八点半", 0, "早上八点半", 8*time.Hour + 30*time.Minute}, + {"今晚八点", 0, "今晚八点", 20 * time.Hour}, + {"今晚八点半", 0, "今晚八点半", 20*time.Hour + 30*time.Minute}, + } + + w := when.New(nil) + + w.Add(zh.HourMinute(rules.Override)) + + ApplyFixtures(t, "zh.HourMinute", w, fixt) +} diff --git a/rules/zh/tradition_hour.go b/rules/zh/tradition_hour.go new file mode 100644 index 0000000..cadb5bf --- /dev/null +++ b/rules/zh/tradition_hour.go @@ -0,0 +1,63 @@ +package zh + +import ( + "regexp" + "time" + + "github.com/olebedev/when/rules" +) + +/* + 子时 23:00 - 01:00 + 丑时 01:00 - 03:00 + 寅时 03:00 - 05:00 + 卯时 05:00 - 07:00 + 辰时 07:00 - 09:00 + 巳时 09:00 - 11:00 + 午时 11:00 - 13:00 + 未时 13:00 - 15:00 + 申时 15:00 - 17:00 + 酉时 17:00 - 19:00 + 戌时 19:00 - 21:00 + 亥时 21:00 - 23:00 +*/ + +func TraditionHour(s rules.Strategy) rules.Rule { + return &rules.F{ + RegExp: regexp.MustCompile("" + + "(?:(子\\s?时|丑\\s?时|寅\\s?时|卯\\s?时|辰\\s?时|巳\\s?时|午\\s?时|未\\s?时|申\\s?时|酉\\s?时|戌\\s?时|亥\\s?时))\\s?" + + "(?:(一\\s?刻|二\\s?刻|两\\s?刻|三\\s?刻|四\\s?刻|五\\s?刻|六\\s?刻|七\\s?刻|1\\s?刻|2\\s?刻|3\\s?刻|4\\s?刻|5\\s?刻|6\\s?刻|7\\s?刻))?", + ), + Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { + if c.Hour != nil && s != rules.Override { + return false, nil + } + hour, exist := TRADITION_HOUR_WORDS[compressStr(m.Captures[0])] + if !exist { + return false, nil + } + c.Hour = &hour + zero := 0 + c.Minute = &zero + if minute, exist := TRADITION_MINUTE_WORDS[compressStr(m.Captures[1])]; exist { + if minute > 60 { + hour := *c.Hour + 1 + c.Hour = &hour + minute = minute - 60 + c.Minute = &minute + } else { + c.Minute = &minute + } + } + return true, nil + }, + } +} + +func compressStr(str string) string { + if str == "" { + return "" + } + reg := regexp.MustCompile(`\s+`) + return reg.ReplaceAllString(str, "") +} diff --git a/rules/zh/tradition_hour_test.go b/rules/zh/tradition_hour_test.go new file mode 100644 index 0000000..33fa4aa --- /dev/null +++ b/rules/zh/tradition_hour_test.go @@ -0,0 +1,29 @@ +package zh_test + +import ( + "github.com/olebedev/when/rules/zh" + "testing" + "time" + + "github.com/olebedev/when" + "github.com/olebedev/when/rules" +) + +func TestTraditionHour(t *testing.T) { + // current is Monday + fixt := []Fixture{ + {"午 时123", 0, "午 时", 11 * time.Hour}, + {"子时", 0, "子时", 23 * time.Hour}, + {"午时太阳正好", 0, "午时", 11 * time.Hour}, + {"我们在酉时喝一杯吧", 9, "酉时", 17 * time.Hour}, + {"午时三刻问斩", 0, "午时三刻", 11*time.Hour + 45*time.Minute}, + {"午时四刻吃饭", 0, "午时四刻", 12 * time.Hour}, + {"戌时1刻", 0, "戌时1刻", 19*time.Hour + 15*time.Minute}, + } + + w := when.New(nil) + + w.Add(zh.TraditionHour(rules.Override)) + + ApplyFixtures(t, "zh.TraditionHour", w, fixt) +} diff --git a/rules/zh/weedkay.go b/rules/zh/weedkay.go new file mode 100644 index 0000000..07ba74f --- /dev/null +++ b/rules/zh/weedkay.go @@ -0,0 +1,79 @@ +package zh + +import ( + "regexp" + "strings" + "time" + + "github.com/olebedev/when/rules" +) + +func Weekday(s rules.Strategy) rules.Rule { + overwrite := s == rules.Override + + return &rules.F{ + RegExp: regexp.MustCompile("(?i)" + + "(?:(本|这|下|上|这个|下个|上个|下下)\\s*)?" + + "(?:(周|礼拜|星期)\\s*)" + + "(1|2|3|4|5|6|天|一|二|三|四|五|六|日)" + + "(?:\\W|$)", + ), + + Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) { + _ = overwrite + + if strings.TrimSpace(m.Captures[1]) == "" { + return false, nil + } + + day := strings.ToLower(strings.TrimSpace(m.Captures[2])) + norm := strings.ToLower(strings.TrimSpace(m.Captures[0])) + if norm == "" { + norm = "本" + } + dayInt, ok := WEEKDAY_OFFSET[day] + if !ok { + return false, nil + } + + if c.Duration != 0 && !overwrite { + return false, nil + } + + // Switch: + switch { + case strings.Contains(norm, "上"): + diff := int(ref.Weekday()) - dayInt + c.Duration = -time.Duration(7+diff) * 24 * time.Hour + case strings.Contains(norm, "下下"): + diff := dayInt - int(ref.Weekday()) + c.Duration = time.Duration(7+7+diff) * 24 * time.Hour + case strings.Contains(norm, "下"): + diff := dayInt - int(ref.Weekday()) + c.Duration = time.Duration(7+diff) * 24 * time.Hour + case strings.Contains(norm, "本") || strings.Contains(norm, "这"): + if int(ref.Weekday()) < dayInt { + diff := dayInt - int(ref.Weekday()) + if diff > 0 { + c.Duration = time.Duration(diff*24) * time.Hour + } else if diff < 0 { + c.Duration = time.Duration(7+diff) * 24 * time.Hour + } else { + c.Duration = 7 * 24 * time.Hour + } + } else if int(ref.Weekday()) > dayInt { + diff := int(ref.Weekday()) - dayInt + if diff > 0 { + c.Duration = -time.Duration(diff*24) * time.Hour + } else if diff < 0 { + c.Duration = -time.Duration(7+diff) * 24 * time.Hour + } else { + c.Duration = -(7 * 24 * time.Hour) + } + } + } + + return true, nil + }, + } +} diff --git a/rules/zh/weekday_test.go b/rules/zh/weekday_test.go new file mode 100644 index 0000000..e8718a2 --- /dev/null +++ b/rules/zh/weekday_test.go @@ -0,0 +1,36 @@ +package zh_test + +import ( + "github.com/olebedev/when/rules/zh" + "testing" + "time" + + "github.com/olebedev/when" + "github.com/olebedev/when/rules" +) + +func TestWeekday(t *testing.T) { + // current is Monday + fixt := []Fixture{ + {"和你下周一吃饭", 6, "下周一", 7 * 24 * time.Hour}, + {"下星期三", 0, "下星期三", 9 * 24 * time.Hour}, + {"和小西本周三一起打羽毛球", 9, "本周三", 2 * 24 * time.Hour}, + {"这周三", 0, "这周三", 2 * 24 * time.Hour}, + {"这礼拜四浇花", 0, "这礼拜四", 3 * 24 * time.Hour}, + {"这星期 4", 0, "这星期 4", 3 * 24 * time.Hour}, + {"和李星期这星期 4喝茶", 12, "这星期 4", 3 * 24 * time.Hour}, + {"周日", 0, "周日", 6 * 24 * time.Hour}, + {"下周日", 0, "下周日", (6 + 7) * 24 * time.Hour}, + {"2下周天", 1, "下周天", (6 + 7) * 24 * time.Hour}, + {"上周三", 0, "上周三", -5 * 24 * time.Hour}, + {"下个周三", 0, "下个周三", (7 + 2) * 24 * time.Hour}, + {"1下个礼拜 3", 1, "下个礼拜 3", (7 + 2) * 24 * time.Hour}, + {"下下礼拜 3", 0, "下下礼拜 3", (7 + 7 + 2) * 24 * time.Hour}, + } + + w := when.New(nil) + + w.Add(zh.Weekday(rules.Override)) + + ApplyFixtures(t, "zh.Weekday", w, fixt) +} diff --git a/rules/zh/zh.go b/rules/zh/zh.go new file mode 100644 index 0000000..8ceeba5 --- /dev/null +++ b/rules/zh/zh.go @@ -0,0 +1,190 @@ +package zh + +import "github.com/olebedev/when/rules" + +var All = []rules.Rule{ + Weekday(rules.Override), + CasualDate(rules.Override), + CasualTime(rules.Override), + HourMinute(rules.Override), + ExactMonthDate(rules.Override), + TraditionHour(rules.Override), + AfterTime(rules.Override), +} + +var WEEKDAY_OFFSET = map[string]int{ + "天": 7, + "一": 1, + "二": 2, + "三": 3, + "四": 4, + "五": 5, + "六": 6, + "1": 1, + "2": 2, + "3": 3, + "4": 4, + "5": 5, + "6": 6, + "日": 7, +} + +var INTEGER_WORDS = map[string]int{ + "零一": 1, + "零二": 2, + "零三": 3, + "零四": 4, + "零五": 5, + "零六": 6, + "零七": 7, + "零八": 8, + "零九": 9, + "一": 1, + "二": 2, + "两": 2, + "三": 3, + "四": 4, + "五": 5, + "六": 6, + "七": 7, + "八": 8, + "九": 9, + "十": 10, + "十一": 11, + "十二": 12, + "十三": 13, + "十四": 14, + "十五": 15, + "十六": 16, + "十七": 17, + "十八": 18, + "十九": 19, + "二十": 20, + "二十一": 21, + "二十二": 22, + "二十三": 23, + "二十五": 25, + "二十六": 26, + "二十七": 27, + "二十八": 28, + "二十九": 29, + "三十": 30, + "三十一": 31, + "三十二": 32, + "三十三": 33, + "三十四": 34, + "三十五": 35, + "三十六": 36, + "三十七": 37, + "三十八": 38, + "三十九": 39, + "四十": 40, + "四十一": 41, + "四十二": 42, + "四十三": 43, + "四十四": 44, + "四十五": 45, + "四十六": 46, + "四十七": 47, + "四十八": 48, + "四十九": 49, + "五十": 50, + "五十一": 51, + "五十二": 52, + "五十三": 53, + "五十四": 54, + "五十五": 55, + "五十六": 56, + "五十七": 57, + "五十八": 58, + "五十九": 59, + "零": 0, + "半": 30, + "一刻": 15, +} +var INTEGER_WORDS_PATTERN = `(?:一刻|半|零一|零二|零三|零四|零五|零六|零七|零八|零九|一|两|二|三|四|五|六|七|八|九|十|十一|十二|十三|十四|十五|十六|十七|十八|十九|二十|二十一|二十二|二十三|二十五|二十六|二十七|二十八|二十九|三十|三十一|三十二|三十三|三十四|三十五|三十六|三十七|三十八|三十九|四十|四十一|四十二|四十三|四十四|四十五|四十六|四十七|四十八|四十九|五十|五十一|五十二|五十三|五十四|五十五|五十六|五十七|五十八|五十九|零)` + +var TRADITION_HOUR_WORDS = map[string]int{ + "子时": 23, + "丑时": 1, + "寅时": 3, + "卯时": 5, + "辰时": 7, + "巳时": 9, + "午时": 11, + "未时": 13, + "申时": 15, + "酉时": 17, + "戌时": 19, + "亥时": 21, +} +var TRADITION_MINUTE_WORDS = map[string]int{ + "一刻": 15, + "两刻": 30, + "二刻": 30, + "三刻": 45, + "四刻": 60, + "五刻": 75, + "六刻": 90, + "七刻": 105, + "八刻": 120, + "1刻": 15, + "2刻": 30, + "3刻": 45, + "4刻": 60, + "5刻": 75, + "6刻": 90, + "7刻": 105, + "8刻": 120, +} + +var MON_WORDS = map[string]int{ + "一": 1, + "二": 2, + "三": 3, + "四": 4, + "五": 5, + "六": 6, + "七": 7, + "八": 8, + "九": 9, + "十": 10, + "十一": 11, + "十二": 12, +} + +var MON_WORDS_PATTERN = `十一|十二|一|二|三|四|五|六|七|八|九|十` + +var DAY_WORDS = map[string]int{ + "一": 1, + "二": 2, + "四": 4, + "五": 5, + "六": 6, + "七": 7, + "八": 8, + "九": 9, + "十一": 11, + "十二": 12, + "十三": 13, + "十四": 14, + "十五": 15, + "十六": 16, + "十七": 17, + "十八": 18, + "十九": 19, + "十": 10, + "二十一": 21, + "二十二": 22, + "二十三": 23, + "二十五": 25, + "二十六": 26, + "二十七": 27, + "二十八": 28, + "二十九": 29, + "二十": 20, + "三十一": 31, + "三十": 30, + "三": 3, +} +var DAY_WORDS_PATTERN = `一|二|四|五|六|七|八|九|十一|十二|十三|十四|十五|十六|十七|十八|十九|十|二十一|二十二|二十三|二十五|二十六|二十七|二十八|二十九|二十|三十一|三十|三` diff --git a/rules/zh/zh_test.go b/rules/zh/zh_test.go new file mode 100644 index 0000000..9496836 --- /dev/null +++ b/rules/zh/zh_test.go @@ -0,0 +1,36 @@ +package zh_test + +import ( + "github.com/olebedev/when" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +var now = time.Date(2022, time.March, 14, 0, 0, 0, 0, time.UTC) + +type Fixture struct { + Text string + Index int + Phrase string + Diff time.Duration +} + +func ApplyFixtures(t *testing.T, name string, w *when.Parser, fixt []Fixture) { + for i, f := range fixt { + res, err := w.Parse(f.Text, now) + require.Nil(t, err, "[%s] err #%d", name, i) + require.NotNil(t, res, "[%s] res #%d", name, i) + require.Equal(t, f.Index, res.Index, "[%s] index #%d", name, i) + require.Equal(t, f.Phrase, res.Text, "[%s] text #%d", name, i) + require.Equal(t, f.Diff, res.Time.Sub(now), "[%s] diff #%d", name, i) + } +} + +func ApplyFixturesNil(t *testing.T, name string, w *when.Parser, fixt []Fixture) { + for i, f := range fixt { + res, err := w.Parse(f.Text, now) + require.Nil(t, err, "[%s] err #%d", name, i) + require.Nil(t, res, "[%s] res #%d", name, i) + } +}