Skip to content

Commit 987a8ee

Browse files
authored
Support for the RU date format (#46)
1 parent 3292cb6 commit 987a8ee

File tree

8 files changed

+266
-29
lines changed

8 files changed

+266
-29
lines changed

rules/ru/date.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package ru
2+
3+
import (
4+
"regexp"
5+
"strconv"
6+
"strings"
7+
"time"
8+
9+
"github.com/olebedev/when/rules"
10+
"github.com/pkg/errors"
11+
)
12+
13+
// https://go.dev/play/p/YsVdaraCwIP
14+
15+
func Date(s rules.Strategy) rules.Rule {
16+
return &rules.F{
17+
RegExp: regexp.MustCompile(`(?i)(?:\b|^)(\d{1,2})\s*(` + MONTHS_PATTERN + `)(?:\s*(\d{4}))?(?:\s*в\s*(\d{1,2}):(\d{2}))?(?:\b|$)`),
18+
Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) {
19+
if (c.Day != nil || c.Month != nil || c.Year != nil) || s != rules.Override {
20+
return false, nil
21+
}
22+
23+
day, err := strconv.Atoi(m.Captures[0])
24+
if err != nil {
25+
return false, errors.Wrap(err, "date rule: day")
26+
}
27+
28+
month, ok := MONTHS[strings.ToLower(m.Captures[1])]
29+
if !ok {
30+
return false, errors.New("date rule: invalid month")
31+
}
32+
33+
year := time.Now().Year()
34+
if m.Captures[2] != "" {
35+
year, err = strconv.Atoi(m.Captures[2])
36+
if err != nil {
37+
return false, errors.Wrap(err, "date rule: year")
38+
}
39+
}
40+
41+
hour, minute := 0, 0
42+
if m.Captures[3] != "" && m.Captures[4] != "" {
43+
hour, err = strconv.Atoi(m.Captures[3])
44+
if err != nil {
45+
return false, errors.Wrap(err, "date rule: hour")
46+
}
47+
minute, err = strconv.Atoi(m.Captures[4])
48+
if err != nil {
49+
return false, errors.Wrap(err, "date rule: minute")
50+
}
51+
}
52+
53+
c.Day = &day
54+
c.Month = pointerToInt(int(month))
55+
c.Year = &year
56+
c.Hour = &hour
57+
c.Minute = &minute
58+
59+
return true, nil
60+
},
61+
}
62+
}
63+
64+
func pointerToInt(v int) *int {
65+
return &v
66+
}

rules/ru/date_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package ru_test
2+
3+
import (
4+
"github.com/olebedev/when"
5+
"github.com/olebedev/when/rules"
6+
"github.com/olebedev/when/rules/ru"
7+
"testing"
8+
"time"
9+
)
10+
11+
func TestDate(t *testing.T) {
12+
w := when.New(nil)
13+
w.Add(ru.Date(rules.Override))
14+
15+
fixt := []Fixture{
16+
// Simple dates
17+
{"встреча 15 января 2024", 15, "15 января 2024", time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC).Sub(null)},
18+
{"5 марта 2025 запланирована встреча", 0, "5 марта 2025", time.Date(2025, 3, 5, 0, 0, 0, 0, time.UTC).Sub(null)},
19+
{"31 декабря 2023", 0, "31 декабря 2023", time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC).Sub(null)},
20+
21+
// Dates with time
22+
{"15 января 2024 в 9:30", 0, "15 января 2024 в 9:30", time.Date(2024, 1, 15, 9, 30, 0, 0, time.UTC).Sub(null)},
23+
{"5 марта 2025 в 15:00 запланирована встреча", 0, "5 марта 2025 в 15:00", time.Date(2025, 3, 5, 15, 0, 0, 0, time.UTC).Sub(null)},
24+
{"31 декабря 2023 в 23:59", 0, "31 декабря 2023 в 23:59", time.Date(2023, 12, 31, 23, 59, 0, 0, time.UTC).Sub(null)},
25+
}
26+
27+
ApplyFixtures(t, "ru.Date", w, fixt)
28+
}
29+
30+
func TestDateNil(t *testing.T) {
31+
w := when.New(nil)
32+
w.Add(ru.Date(rules.Override))
33+
34+
fixt := []Fixture{
35+
{"это текст без даты", 0, "", 0},
36+
{"15", 0, "", 0},
37+
{"15 чего-то", 0, "", 0},
38+
}
39+
40+
ApplyFixturesNil(t, "ru.Date nil", w, fixt)
41+
}

rules/ru/dot_date_time.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package ru
2+
3+
import (
4+
"regexp"
5+
"strconv"
6+
"time"
7+
8+
"github.com/olebedev/when/rules"
9+
"github.com/pkg/errors"
10+
)
11+
12+
// https://go.dev/play/p/vRzLhHHupUJ
13+
14+
func DotDateTime(s rules.Strategy) rules.Rule {
15+
return &rules.F{
16+
RegExp: regexp.MustCompile(`(?i)(?:^|\b)(\d{2})\.(\d{2})\.(\d{4})(?:\s+(\d{2}):(\d{2}))?(?:\b|$)`),
17+
Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) {
18+
if (c.Day != nil || c.Month != nil || c.Year != nil || c.Hour != nil || c.Minute != nil) && s != rules.Override {
19+
return false, nil
20+
}
21+
22+
day, err := strconv.Atoi(m.Captures[0])
23+
if err != nil {
24+
return false, errors.Wrap(err, "dot date time rule: day")
25+
}
26+
27+
month, err := strconv.Atoi(m.Captures[1])
28+
if err != nil {
29+
return false, errors.Wrap(err, "dot date time rule: month")
30+
}
31+
32+
year, err := strconv.Atoi(m.Captures[2])
33+
if err != nil {
34+
return false, errors.Wrap(err, "dot date time rule: year")
35+
}
36+
37+
hour, minute := 0, 0
38+
if m.Captures[3] != "" && m.Captures[4] != "" {
39+
hour, err = strconv.Atoi(m.Captures[3])
40+
if err != nil {
41+
return false, errors.Wrap(err, "dot date time rule: hour")
42+
}
43+
minute, err = strconv.Atoi(m.Captures[4])
44+
if err != nil {
45+
return false, errors.Wrap(err, "dot date time rule: minute")
46+
}
47+
}
48+
49+
if day > 0 && day <= 31 && month > 0 && month <= 12 {
50+
c.Day = &day
51+
c.Month = &month
52+
c.Year = &year
53+
c.Hour = &hour
54+
c.Minute = &minute
55+
return true, nil
56+
}
57+
58+
return false, nil
59+
},
60+
}
61+
}

rules/ru/dot_date_time_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package ru_test
2+
3+
import (
4+
"github.com/olebedev/when"
5+
"github.com/olebedev/when/rules"
6+
"github.com/olebedev/when/rules/ru"
7+
"testing"
8+
"time"
9+
)
10+
11+
func TestDotDateTime(t *testing.T) {
12+
w := when.New(nil)
13+
w.Add(ru.DotDateTime(rules.Override))
14+
15+
fixt := []Fixture{
16+
// Basic date/time formats
17+
{"встреча 15.01.2024 09:30", 15, "15.01.2024 09:30", time.Date(2024, 1, 15, 9, 30, 0, 0, time.UTC).Sub(null)},
18+
{"05.03.2025 15:00 запланирована встреча", 0, "05.03.2025 15:00", time.Date(2025, 3, 5, 15, 0, 0, 0, time.UTC).Sub(null)},
19+
{"31.12.2023 23:59", 0, "31.12.2023 23:59", time.Date(2023, 12, 31, 23, 59, 0, 0, time.UTC).Sub(null)},
20+
}
21+
22+
ApplyFixtures(t, "ru.DateTime", w, fixt)
23+
}
24+
25+
func TestDotDateTimeNil(t *testing.T) {
26+
w := when.New(nil)
27+
w.Add(ru.DotDateTime(rules.Override))
28+
29+
fixt := []Fixture{
30+
{"это текст без даты и времени", 0, "", 0},
31+
{"15.01", 0, "", 0},
32+
{"32.01.2024 15:00", 0, "", 0}, // некорректный день
33+
{"15.13.2024 15:00", 0, "", 0}, // некорректный месяц
34+
}
35+
36+
ApplyFixturesNil(t, "ru.DateTime nil", w, fixt)
37+
}

rules/ru/hour_minute.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
{"11.1pm", 0, "11.1pm", 0},
2323
{"11.10 pm", 0, "11.10 pm", 0},
2424
25-
https://play.golang.org/p/PmPBjHK4PA
25+
https://go.dev/play/p/QiSvUkrni6N
2626
*/
2727

2828
// 1. - int
@@ -31,12 +31,12 @@ import (
3131

3232
func HourMinute(s rules.Strategy) rules.Rule {
3333
return &rules.F{
34-
RegExp: regexp.MustCompile("(?i)(?:\\W|\\D|^)" +
34+
RegExp: regexp.MustCompile("(?i)(?:\\A|\\s|\\D)" +
3535
"((?:[0-1]{0,1}[0-9])|(?:2[0-3]))" +
3636
"(?:\\:|:|\\-|\\.)" +
3737
"((?:[0-5][0-9]))" +
3838
"(?:\\s*(утра|вечера|дня))?" +
39-
"(?:\\P{L}|$)"),
39+
"(?:\\s|\\D|\\z)"),
4040
Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) {
4141
if (c.Hour != nil || c.Minute != nil) && s != rules.Override {
4242
return false, nil

rules/ru/ru.go

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package ru
22

3-
import "github.com/olebedev/when/rules"
3+
import (
4+
"github.com/olebedev/when/rules"
5+
"time"
6+
)
47

58
var All = []rules.Rule{
69
Weekday(rules.Override),
@@ -9,6 +12,8 @@ var All = []rules.Rule{
912
Hour(rules.Override),
1013
HourMinute(rules.Override),
1114
Deadline(rules.Override),
15+
Date(rules.Override),
16+
DotDateTime(rules.Override),
1217
}
1318

1419
var WEEKDAY_OFFSET = map[string]int{
@@ -18,29 +23,29 @@ var WEEKDAY_OFFSET = map[string]int{
1823
"понедельник": 1,
1924
"понедельнику": 1,
2025
"понедельника": 1,
21-
"пн": 1,
22-
"вторник": 2,
23-
"вторника": 2,
24-
"вторнику": 2,
25-
"вт": 2,
26-
"среда": 3,
27-
"среду": 3,
28-
"среде": 3,
29-
"ср": 3,
30-
"четверг": 4,
31-
"четверга": 4,
32-
"четвергу": 4,
33-
"чт": 4,
34-
"пятница": 5,
35-
"пятнице": 5,
36-
"пятницы": 5,
37-
"пятницу": 5,
38-
"пт": 5,
39-
"суббота": 6,
40-
"субботы": 6,
41-
"субботе": 6,
42-
"субботу": 6,
43-
"сб": 6,
26+
"пн": 1,
27+
"вторник": 2,
28+
"вторника": 2,
29+
"вторнику": 2,
30+
"вт": 2,
31+
"среда": 3,
32+
"среду": 3,
33+
"среде": 3,
34+
"ср": 3,
35+
"четверг": 4,
36+
"четверга": 4,
37+
"четвергу": 4,
38+
"чт": 4,
39+
"пятница": 5,
40+
"пятнице": 5,
41+
"пятницы": 5,
42+
"пятницу": 5,
43+
"пт": 5,
44+
"суббота": 6,
45+
"субботы": 6,
46+
"субботе": 6,
47+
"субботу": 6,
48+
"сб": 6,
4449
}
4550

4651
var WEEKDAY_OFFSET_PATTERN = "(?:воскресенье|воскресенья|воск|понедельник|понедельнику|понедельника|пн|вторник|вторника|вторнику|вт|среда|среду|среде|ср|четверг|четверга|четвергу|чт|пятница|пятнице|пятницы|пятницу|пт|суббота|субботы|субботе|субботу|сб)"
@@ -65,3 +70,20 @@ var INTEGER_WORDS = map[string]int{
6570
}
6671

6772
var INTEGER_WORDS_PATTERN = `(?:час|один|одну|одного|два|две|три|четыре|пять|шесть|семь|восемь|девять|десять|одиннадцать|двенадцать)`
73+
74+
var MONTHS = map[string]time.Month{
75+
"января": time.January,
76+
"февраля": time.February,
77+
"марта": time.March,
78+
"апреля": time.April,
79+
"мая": time.May,
80+
"июня": time.June,
81+
"июля": time.July,
82+
"августа": time.August,
83+
"сентября": time.September,
84+
"октября": time.October,
85+
"ноября": time.November,
86+
"декабря": time.December,
87+
}
88+
89+
var MONTHS_PATTERN = `(?:января|февраля|марта|апреля|мая|июня|июля|августа|сентября|октября|ноября|декабря)`

rules/ru/ru_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ func TestAll(t *testing.T) {
6666
{"написать письмо до утра субботы ", 30, "до утра субботы", ((3 * 24) + 8) * time.Hour},
6767
{"написать письмо к субботе после обеда ", 30, "к субботе после обеда", ((3 * 24) + 15) * time.Hour},
6868
{"В субботу вечером", 0, "В субботу вечером", ((3 * 24) + 18) * time.Hour},
69+
70+
{"встреча 15 января 2024", 15, "15 января 2024", time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC).Sub(null)},
71+
{"5 марта 2025 запланирована встреча", 0, "5 марта 2025", time.Date(2025, 3, 5, 0, 0, 0, 0, time.UTC).Sub(null)},
72+
{"31 декабря 2023", 0, "31 декабря 2023", time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC).Sub(null)},
73+
{"15 января 2024 в 9:30", 0, "15 января 2024 в 9:30", time.Date(2024, 1, 15, 9, 30, 0, 0, time.UTC).Sub(null)},
74+
{"5 марта 2025 в 15:00 запланирована встреча", 0, "5 марта 2025 в 15:00", time.Date(2025, 3, 5, 15, 0, 0, 0, time.UTC).Sub(null)},
75+
{"31 декабря 2023 в 23:59", 0, "31 декабря 2023 в 23:59", time.Date(2023, 12, 31, 23, 59, 0, 0, time.UTC).Sub(null)},
76+
{"31 декабря", 0, "31 декабря", time.Date(time.Now().Year(), 12, 31, 0, 0, 0, 0, time.UTC).Sub(null)},
77+
{"встреча 15.01.2024 09:30", 15, "15.01.2024 09:30", time.Date(2024, 1, 15, 9, 30, 0, 0, time.UTC).Sub(null)},
78+
{"05.03.2025 15:00 запланирована встреча", 0, "05.03.2025 15:00", time.Date(2025, 3, 5, 15, 0, 0, 0, time.UTC).Sub(null)},
79+
{"31.12.2023 23:59", 0, "31.12.2023 23:59", time.Date(2023, 12, 31, 23, 59, 0, 0, time.UTC).Sub(null)},
80+
{"31.12.2023", 0, "31.12.2023", time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC).Sub(null)},
6981
}
7082

7183
ApplyFixtures(t, "ru.All...", w, fixt)

rules/zh/casual_date_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import (
1010
)
1111

1212
func TestCasualDate(t *testing.T) {
13-
// current is Monday
14-
now := time.Now()
1513
fixt := []Fixture{
1614
{"后天中午", 0, "后天", (2 * 24) * time.Hour},
1715
{"大后天中午", 0, "大后天", (3 * 24) * time.Hour},

0 commit comments

Comments
 (0)