Skip to content

Commit

Permalink
Support Chinese time (follows up #30) (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
RexSkz committed Jun 13, 2023
1 parent 6afc86d commit eb47a0c
Show file tree
Hide file tree
Showing 16 changed files with 925 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
vendor
.idea
2 changes: 2 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* @olebedev
/rules/zh/ @RexSkz
55 changes: 55 additions & 0 deletions rules/zh/after_time.go
Original file line number Diff line number Diff line change
@@ -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
},
}
}
92 changes: 92 additions & 0 deletions rules/zh/casual_date.go
Original file line number Diff line number Diff line change
@@ -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
},
}
}
39 changes: 39 additions & 0 deletions rules/zh/casual_date_test.go
Original file line number Diff line number Diff line change
@@ -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|$)
*/
66 changes: 66 additions & 0 deletions rules/zh/casual_time.go
Original file line number Diff line number Diff line change
@@ -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
},
}
}
73 changes: 73 additions & 0 deletions rules/zh/exact_month_date.go
Original file line number Diff line number Diff line change
@@ -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
},
}
}
53 changes: 53 additions & 0 deletions rules/zh/exact_month_test.go
Original file line number Diff line number Diff line change
@@ -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|$)
*/

0 comments on commit eb47a0c

Please sign in to comment.