Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for the RU date format #46

Merged
merged 8 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions rules/ru/date.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package ru

import (
"regexp"
"strconv"
"strings"
"time"

"github.com/olebedev/when/rules"
"github.com/pkg/errors"
)

// https://go.dev/play/p/YsVdaraCwIP

func Date(s rules.Strategy) rules.Rule {
return &rules.F{
RegExp: regexp.MustCompile(`(?i)(?:\b|^)(\d{1,2})\s*(` + MONTHS_PATTERN + `)(?:\s*(\d{4}))?(?:\s*в\s*(\d{1,2}):(\d{2}))?(?:\b|$)`),
Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) {
if (c.Day != nil || c.Month != nil || c.Year != nil) || s != rules.Override {
return false, nil
}

day, err := strconv.Atoi(m.Captures[0])
if err != nil {
return false, errors.Wrap(err, "date rule: day")
}

month, ok := MONTHS[strings.ToLower(m.Captures[1])]
if !ok {
return false, errors.New("date rule: invalid month")
}

year := time.Now().Year()
if m.Captures[2] != "" {
year, err = strconv.Atoi(m.Captures[2])
if err != nil {
return false, errors.Wrap(err, "date rule: year")
}
}

hour, minute := 0, 0
if m.Captures[3] != "" && m.Captures[4] != "" {
hour, err = strconv.Atoi(m.Captures[3])
if err != nil {
return false, errors.Wrap(err, "date rule: hour")
}
minute, err = strconv.Atoi(m.Captures[4])
if err != nil {
return false, errors.Wrap(err, "date rule: minute")
}
}

c.Day = &day
c.Month = pointerToInt(int(month))
c.Year = &year
c.Hour = &hour
c.Minute = &minute

return true, nil
},
}
}

func pointerToInt(v int) *int {
return &v
}
41 changes: 41 additions & 0 deletions rules/ru/date_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package ru_test

import (
"github.com/olebedev/when"
"github.com/olebedev/when/rules"
"github.com/olebedev/when/rules/ru"
"testing"
"time"
)

func TestDate(t *testing.T) {
w := when.New(nil)
w.Add(ru.Date(rules.Override))

fixt := []Fixture{
// Simple dates
{"встреча 15 января 2024", 15, "15 января 2024", time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC).Sub(null)},
{"5 марта 2025 запланирована встреча", 0, "5 марта 2025", time.Date(2025, 3, 5, 0, 0, 0, 0, time.UTC).Sub(null)},
{"31 декабря 2023", 0, "31 декабря 2023", time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC).Sub(null)},

// Dates with time
{"15 января 2024 в 9:30", 0, "15 января 2024 в 9:30", time.Date(2024, 1, 15, 9, 30, 0, 0, time.UTC).Sub(null)},
{"5 марта 2025 в 15:00 запланирована встреча", 0, "5 марта 2025 в 15:00", time.Date(2025, 3, 5, 15, 0, 0, 0, time.UTC).Sub(null)},
{"31 декабря 2023 в 23:59", 0, "31 декабря 2023 в 23:59", time.Date(2023, 12, 31, 23, 59, 0, 0, time.UTC).Sub(null)},
}

ApplyFixtures(t, "ru.Date", w, fixt)
}

func TestDateNil(t *testing.T) {
w := when.New(nil)
w.Add(ru.Date(rules.Override))

fixt := []Fixture{
{"это текст без даты", 0, "", 0},
{"15", 0, "", 0},
{"15 чего-то", 0, "", 0},
}

ApplyFixturesNil(t, "ru.Date nil", w, fixt)
}
61 changes: 61 additions & 0 deletions rules/ru/dot_date_time.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package ru

import (
"regexp"
"strconv"
"time"

"github.com/olebedev/when/rules"
"github.com/pkg/errors"
)

// https://go.dev/play/p/vRzLhHHupUJ

func DotDateTime(s rules.Strategy) rules.Rule {
return &rules.F{
RegExp: regexp.MustCompile(`(?i)(?:^|\b)(\d{2})\.(\d{2})\.(\d{4})(?:\s+(\d{2}):(\d{2}))?(?:\b|$)`),
Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) {
if (c.Day != nil || c.Month != nil || c.Year != nil || c.Hour != nil || c.Minute != nil) && s != rules.Override {
return false, nil
}

day, err := strconv.Atoi(m.Captures[0])
if err != nil {
return false, errors.Wrap(err, "dot date time rule: day")
}

month, err := strconv.Atoi(m.Captures[1])
if err != nil {
return false, errors.Wrap(err, "dot date time rule: month")
}

year, err := strconv.Atoi(m.Captures[2])
if err != nil {
return false, errors.Wrap(err, "dot date time rule: year")
}

hour, minute := 0, 0
if m.Captures[3] != "" && m.Captures[4] != "" {
hour, err = strconv.Atoi(m.Captures[3])
if err != nil {
return false, errors.Wrap(err, "dot date time rule: hour")
}
minute, err = strconv.Atoi(m.Captures[4])
if err != nil {
return false, errors.Wrap(err, "dot date time rule: minute")
}
}

if day > 0 && day <= 31 && month > 0 && month <= 12 {
c.Day = &day
c.Month = &month
c.Year = &year
c.Hour = &hour
c.Minute = &minute
return true, nil
}

return false, nil
},
}
}
37 changes: 37 additions & 0 deletions rules/ru/dot_date_time_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package ru_test

import (
"github.com/olebedev/when"
"github.com/olebedev/when/rules"
"github.com/olebedev/when/rules/ru"
"testing"
"time"
)

func TestDotDateTime(t *testing.T) {
w := when.New(nil)
w.Add(ru.DotDateTime(rules.Override))

fixt := []Fixture{
// Basic date/time formats
{"встреча 15.01.2024 09:30", 15, "15.01.2024 09:30", time.Date(2024, 1, 15, 9, 30, 0, 0, time.UTC).Sub(null)},
{"05.03.2025 15:00 запланирована встреча", 0, "05.03.2025 15:00", time.Date(2025, 3, 5, 15, 0, 0, 0, time.UTC).Sub(null)},
{"31.12.2023 23:59", 0, "31.12.2023 23:59", time.Date(2023, 12, 31, 23, 59, 0, 0, time.UTC).Sub(null)},
}

ApplyFixtures(t, "ru.DateTime", w, fixt)
}

func TestDotDateTimeNil(t *testing.T) {
w := when.New(nil)
w.Add(ru.DotDateTime(rules.Override))

fixt := []Fixture{
{"это текст без даты и времени", 0, "", 0},
{"15.01", 0, "", 0},
{"32.01.2024 15:00", 0, "", 0}, // некорректный день
{"15.13.2024 15:00", 0, "", 0}, // некорректный месяц
}

ApplyFixturesNil(t, "ru.DateTime nil", w, fixt)
}
6 changes: 3 additions & 3 deletions rules/ru/hour_minute.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
{"11.1pm", 0, "11.1pm", 0},
{"11.10 pm", 0, "11.10 pm", 0},

https://play.golang.org/p/PmPBjHK4PA
https://go.dev/play/p/QiSvUkrni6N
*/

// 1. - int
Expand All @@ -31,12 +31,12 @@ import (

func HourMinute(s rules.Strategy) rules.Rule {
return &rules.F{
RegExp: regexp.MustCompile("(?i)(?:\\W|\\D|^)" +
RegExp: regexp.MustCompile("(?i)(?:\\A|\\s|\\D)" +
"((?:[0-1]{0,1}[0-9])|(?:2[0-3]))" +
"(?:\\:|:|\\-|\\.)" +
"((?:[0-5][0-9]))" +
"(?:\\s*(утра|вечера|дня))?" +
"(?:\\P{L}|$)"),
"(?:\\s|\\D|\\z)"),
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
Expand Down
70 changes: 46 additions & 24 deletions rules/ru/ru.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package ru

import "github.com/olebedev/when/rules"
import (
"github.com/olebedev/when/rules"
"time"
)

var All = []rules.Rule{
Weekday(rules.Override),
Expand All @@ -9,6 +12,8 @@ var All = []rules.Rule{
Hour(rules.Override),
HourMinute(rules.Override),
Deadline(rules.Override),
Date(rules.Override),
DotDateTime(rules.Override),
}

var WEEKDAY_OFFSET = map[string]int{
Expand All @@ -18,29 +23,29 @@ var WEEKDAY_OFFSET = map[string]int{
"понедельник": 1,
"понедельнику": 1,
"понедельника": 1,
"пн": 1,
"вторник": 2,
"вторника": 2,
"вторнику": 2,
"вт": 2,
"среда": 3,
"среду": 3,
"среде": 3,
"ср": 3,
"четверг": 4,
"четверга": 4,
"четвергу": 4,
"чт": 4,
"пятница": 5,
"пятнице": 5,
"пятницы": 5,
"пятницу": 5,
"пт": 5,
"суббота": 6,
"субботы": 6,
"субботе": 6,
"субботу": 6,
"сб": 6,
"пн": 1,
"вторник": 2,
"вторника": 2,
"вторнику": 2,
"вт": 2,
"среда": 3,
"среду": 3,
"среде": 3,
"ср": 3,
"четверг": 4,
"четверга": 4,
"четвергу": 4,
"чт": 4,
"пятница": 5,
"пятнице": 5,
"пятницы": 5,
"пятницу": 5,
"пт": 5,
"суббота": 6,
"субботы": 6,
"субботе": 6,
"субботу": 6,
"сб": 6,
}

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

var INTEGER_WORDS_PATTERN = `(?:час|один|одну|одного|два|две|три|четыре|пять|шесть|семь|восемь|девять|десять|одиннадцать|двенадцать)`

var MONTHS = map[string]time.Month{
"января": time.January,
"февраля": time.February,
"марта": time.March,
"апреля": time.April,
"мая": time.May,
"июня": time.June,
"июля": time.July,
"августа": time.August,
"сентября": time.September,
"октября": time.October,
"ноября": time.November,
"декабря": time.December,
}

var MONTHS_PATTERN = `(?:января|февраля|марта|апреля|мая|июня|июля|августа|сентября|октября|ноября|декабря)`
12 changes: 12 additions & 0 deletions rules/ru/ru_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ func TestAll(t *testing.T) {
{"написать письмо до утра субботы ", 30, "до утра субботы", ((3 * 24) + 8) * time.Hour},
{"написать письмо к субботе после обеда ", 30, "к субботе после обеда", ((3 * 24) + 15) * time.Hour},
{"В субботу вечером", 0, "В субботу вечером", ((3 * 24) + 18) * time.Hour},

{"встреча 15 января 2024", 15, "15 января 2024", time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC).Sub(null)},
{"5 марта 2025 запланирована встреча", 0, "5 марта 2025", time.Date(2025, 3, 5, 0, 0, 0, 0, time.UTC).Sub(null)},
{"31 декабря 2023", 0, "31 декабря 2023", time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC).Sub(null)},
{"15 января 2024 в 9:30", 0, "15 января 2024 в 9:30", time.Date(2024, 1, 15, 9, 30, 0, 0, time.UTC).Sub(null)},
{"5 марта 2025 в 15:00 запланирована встреча", 0, "5 марта 2025 в 15:00", time.Date(2025, 3, 5, 15, 0, 0, 0, time.UTC).Sub(null)},
{"31 декабря 2023 в 23:59", 0, "31 декабря 2023 в 23:59", time.Date(2023, 12, 31, 23, 59, 0, 0, time.UTC).Sub(null)},
{"31 декабря", 0, "31 декабря", time.Date(time.Now().Year(), 12, 31, 0, 0, 0, 0, time.UTC).Sub(null)},
{"встреча 15.01.2024 09:30", 15, "15.01.2024 09:30", time.Date(2024, 1, 15, 9, 30, 0, 0, time.UTC).Sub(null)},
{"05.03.2025 15:00 запланирована встреча", 0, "05.03.2025 15:00", time.Date(2025, 3, 5, 15, 0, 0, 0, time.UTC).Sub(null)},
{"31.12.2023 23:59", 0, "31.12.2023 23:59", time.Date(2023, 12, 31, 23, 59, 0, 0, time.UTC).Sub(null)},
{"31.12.2023", 0, "31.12.2023", time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC).Sub(null)},
}

ApplyFixtures(t, "ru.All...", w, fixt)
Expand Down
2 changes: 0 additions & 2 deletions rules/zh/casual_date_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import (
)

func TestCasualDate(t *testing.T) {
// current is Monday
now := time.Now()
Comment on lines -13 to -14
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is occasional, right? If so, can you please move it back

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have restored the changes, although with this version, my tests in zh/casual_date_test are currently not

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the reversion may have been a mistake, as my tests are not passing in zh/casual_date_test. Could we consider removing the reversion to ensure the tests function

Copy link
Collaborator

@RexSkz RexSkz Oct 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rturovtsev I looked into it and found that it is a bug. You can remove this line safely :)

I'm not sure why the original author in PR #30 added this line, but it is this line that fails the test.

The function zh_test.ApplyFixtures uses the time 2022/03/14 00:00:00 UTC as now, but this line uses a new variable that shadows it. We are now in 2024, a leap year, so the result is 86,400 seconds more than "expected" (in the year 2022). 😂

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rturovtsev, then ca you please remove this line then so all the test are passing.

@RexSkz, thanks for clarification, I was not too sure what is going on on that side of the project. I should have noticed this earlier, my bad.

Copy link
Collaborator

@RexSkz RexSkz Nov 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@olebedev Hi! Can you tell me if there are plans to create a new release?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi there, no plans whatsoever, but I will roll this change out as a new tag soon-ish, in a matter of one or two days, bear with me.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you =)

fixt := []Fixture{
{"后天中午", 0, "后天", (2 * 24) * time.Hour},
{"大后天中午", 0, "大后天", (3 * 24) * time.Hour},
Expand Down
Loading