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

Add option to return prior dates if ambiguous #35

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
26 changes: 26 additions & 0 deletions rules/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,32 @@ package common

import "github.com/olebedev/when/rules"

var MONTHS_DAYS = []int{
0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
}

func GetDays(year, month int) int {
if month > 12 || month < 0 {
return 0
}

if month != 2 {
return MONTHS_DAYS[month]
}

if year%4 == 0 {
if year%400 == 0 {
return 29
}
if year%100 == 0 {
return 28
}
return 29
}

return 28
}

var All = []rules.Rule{
SlashDMY(rules.Override),
}
11 changes: 9 additions & 2 deletions rules/common/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type Fixture struct {
Text string
Index int
Phrase string
Diff time.Duration
Want time.Time
}

func ApplyFixtures(t *testing.T, name string, w *when.Parser, fixt []Fixture) {
Expand All @@ -28,7 +28,7 @@ func ApplyFixtures(t *testing.T, name string, w *when.Parser, fixt []Fixture) {
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(null), "[%s] diff #%d", name, i)
require.Equal(t, f.Want, res.Time, "[%s] %s diff #%d", name, f.Phrase, i)
}
}

Expand Down Expand Up @@ -56,3 +56,10 @@ func TestAll(t *testing.T) {
fixt := []Fixture{}
ApplyFixtures(t, "common.All...", w, fixt)
}

func TestLeapYear(t *testing.T) {
require.Equal(t, common.GetDays(1999, 2), 28, "Normal year")
require.Equal(t, common.GetDays(2004, 2), 29, "Leap year")
require.Equal(t, common.GetDays(3000, 2), 28, "Century")
require.Equal(t, common.GetDays(2000, 2), 29, "Century divisible by 400")
}
64 changes: 32 additions & 32 deletions rules/common/slash_dmy.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,6 @@ also with "\", gift for windows' users
https://play.golang.org/p/29LkTfe1Xr
*/

var MONTHS_DAYS = []int{
0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
}

func getDays(year, month int) int {
// naive leap year check
if (year-2000)%4 == 0 && month == 2 {
return 29
}
return MONTHS_DAYS[month]
}

func SlashDMY(s rules.Strategy) rules.Rule {

return &rules.F{
Expand All @@ -58,39 +46,51 @@ func SlashDMY(s rules.Strategy) rules.Rule {
return false, nil
}

if month > 12 {
return false, nil
}

if day > GetDays(ref.Year(), month) {
// invalid date: day is after last day of the month
return false, nil
}

WithYear:
if year != -1 {
if getDays(year, month) >= day {
c.Year = &year
c.Month = &month
c.Day = &day
} else {
return false, nil
}
c.Year = &year
c.Month = &month
c.Day = &day
return true, nil
}

if int(ref.Month()) > month {
year = ref.Year() + 1
goto WithYear
}
if o.WantPast {
if month > int(ref.Month()) {
year = ref.Year() - 1
} else if month == int(ref.Month()) {
if day <= ref.Day() {
year = ref.Year()
} else {
year = ref.Year() - 1
}
} else {
year = ref.Year()
}

if int(ref.Month()) == month {
if getDays(ref.Year(), month) >= day {
if day > ref.Day() {
} else {
if month < int(ref.Month()) {
year = ref.Year() + 1
} else if month == int(ref.Month()) {
if day >= ref.Day() {
year = ref.Year()
} else if day < ref.Day() {
year = ref.Year() + 1
} else {
return false, nil
year = ref.Year() + 1
}
goto WithYear
} else {
return false, nil
year = ref.Year()
}
}

return true, nil
goto WithYear
},
}
}
48 changes: 40 additions & 8 deletions rules/common/slash_dmy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,26 @@ import (

func TestSlashDMY(t *testing.T) {
fixt := []Fixture{
{"The Deadline is 10/10/2016", 16, "10/10/2016", (284 - OFFSET) * 24 * time.Hour},
{"The Deadline is 1/2/2016", 16, "1/2/2016", (32 - OFFSET) * 24 * time.Hour},
{"The Deadline is 29/2/2016", 16, "29/2/2016", (60 - OFFSET) * 24 * time.Hour},
{"The Deadline is 10/10/2016", 16, "10/10/2016", time.Date(2016, 10, 10, 0, 0, 0, 0, time.UTC)},
{"The Deadline is 1/2/2016", 16, "1/2/2016", time.Date(2016, 2, 1, 0, 0, 0, 0, time.UTC)},
{"The Deadline is 29/2/2016", 16, "29/2/2016", time.Date(2016, 2, 29, 0, 0, 0, 0, time.UTC)},

// next year
{"The Deadline is 28/2", 16, "28/2", (59 + 366 - OFFSET) * 24 * time.Hour},
{"The Deadline is 28/02/2017", 16, "28/02/2017", (59 + 366 - OFFSET) * 24 * time.Hour},
{"The Deadline is 28/2", 16, "28/2", time.Date(2017, 2, 28, 0, 0, 0, 0, time.UTC)},
{"The Deadline is 28/02/2017", 16, "28/02/2017", time.Date(2017, 2, 28, 0, 0, 0, 0, time.UTC)},

// right after w/o a year
{"The Deadline is 28/07", 16, "28/07", (210 - OFFSET) * 24 * time.Hour},
{"The Deadline is 28/07", 16, "28/07", time.Date(2016, 7, 28, 0, 0, 0, 0, time.UTC)},

// before w/o a year
{"The Deadline is 30/06", 16, "30/06", (181 + 366 - OFFSET) * 24 * time.Hour},
{"The Deadline is 30/06", 16, "30/06", time.Date(2017, 6, 30, 0, 0, 0, 0, time.UTC)},

// prev day will be added to the future
{"The Deadline is 14/07", 16, "14/07", (195 + 366 - OFFSET) * 24 * time.Hour},
{"The Deadline is 14/07", 16, "14/07", time.Date(2017, 7, 14, 0, 0, 0, 0, time.UTC)},

// Existing doesn't work for a month in the future
{"The Deadline is 14/08", 16, "14/08", time.Date(2016, 8, 14, 0, 0, 0, 0, time.UTC)},
{"The Deadline is 15/07", 16, "15/07", time.Date(2016, 7, 15, 0, 0, 0, 0, time.UTC)},
}

w := when.New(nil)
Expand All @@ -35,3 +39,31 @@ func TestSlashDMY(t *testing.T) {
ApplyFixtures(t, "common.SlashDMY", w, fixt)

}

func TestSlashDMYPast(t *testing.T) {
fixt := []Fixture{
{"The Deadline is 10/10/2016", 16, "10/10/2016", time.Date(2016, 10, 10, 0, 0, 0, 0, time.UTC)},
{"The Deadline is 1/2/2016", 16, "1/2/2016", time.Date(2016, 2, 1, 0, 0, 0, 0, time.UTC)},
{"The Deadline is 29/2/2016", 16, "29/2/2016", time.Date(2016, 2, 29, 0, 0, 0, 0, time.UTC)},

// before w/o a year says same year
{"The Deadline is 30/06", 16, "30/06", time.Date(2016, 6, 30, 0, 0, 0, 0, time.UTC)},

// prev day will still be this year
{"The Deadline is 14/07", 16, "14/07", time.Date(2016, 7, 14, 0, 0, 0, 0, time.UTC)},

// after w/o a year is prior year
{"The Deadline is 28/07", 16, "28/07", time.Date(2015, 7, 28, 0, 0, 0, 0, time.UTC)},

// Regression tests: current date and furture month
{"The Deadline is 15/07", 16, "15/07", time.Date(2016, 7, 15, 0, 0, 0, 0, time.UTC)},
}

w := when.New(&rules.Options{
Distance: 5,
MatchByOrder: true,
WantPast: true})
w.Add(common.SlashDMY(rules.Skip))

ApplyFixtures(t, "common.SlashDMY", w, fixt)
}
94 changes: 94 additions & 0 deletions rules/common/slash_mdy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package common

import (
"regexp"
"strconv"
"time"

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

/*

- MM/DD/YYYY
- 3/14/2015
- 03/14/2015
- 3/14

also with "\", gift for windows' users
*/

func SlashMDY(s rules.Strategy) rules.Rule {

return &rules.F{
RegExp: regexp.MustCompile("(?i)(?:\\W|^)" +
"([0-3]{0,1}[0-9]{1})" +
"[\\/\\\\]" +
"([0-3]{0,1}[0-9]{1})" +
"(?:[\\/\\\\]" +
"((?:1|2)[0-9]{3})\\s*)?" +
"(?:\\W|$)"),
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
}

month, _ := strconv.Atoi(m.Captures[0])
day, _ := strconv.Atoi(m.Captures[1])
year := -1
if m.Captures[2] != "" {
year, _ = strconv.Atoi(m.Captures[2])
}

if day == 0 {
return false, nil
}

if month > 12 {
return false, nil
}

if day > GetDays(ref.Year(), month) {
// invalid date: day is after last day of the month
return false, nil
}

WithYear:
if year != -1 {
c.Year = &year
c.Month = &month
c.Day = &day
return true, nil
}

if o.WantPast {
if month > int(ref.Month()) {
year = ref.Year() - 1
} else if month == int(ref.Month()) {
if day <= ref.Day() {
year = ref.Year()
} else {
year = ref.Year() - 1
}
} else {
year = ref.Year()
}

} else {
if month < int(ref.Month()) {
year = ref.Year() + 1
} else if month == int(ref.Month()) {
if day >= ref.Day() {
year = ref.Year()
} else {
year = ref.Year() + 1
}
} else {
year = ref.Year()
}
}

goto WithYear
},
}
}
69 changes: 69 additions & 0 deletions rules/common/slash_mdy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package common_test

import (
"testing"
"time"

"github.com/olebedev/when"
"github.com/olebedev/when/rules"
"github.com/olebedev/when/rules/common"
)

func TestSlashMDY(t *testing.T) {
fixt := []Fixture{
{"The Deadline is 10/10/2016", 16, "10/10/2016", time.Date(2016, 10, 10, 0, 0, 0, 0, time.UTC)},
{"The Deadline is 2/1/2016", 16, "2/1/2016", time.Date(2016, 2, 1, 0, 0, 0, 0, time.UTC)},
{"The Deadline is 2/29/2016", 16, "2/29/2016", time.Date(2016, 2, 29, 0, 0, 0, 0, time.UTC)},

// next year
{"The Deadline is 2/28", 16, "2/28", time.Date(2017, 2, 28, 0, 0, 0, 0, time.UTC)},
{"The Deadline is 02/28/2017", 16, "02/28/2017", time.Date(2017, 2, 28, 0, 0, 0, 0, time.UTC)},

// right after w/o a year
{"The Deadline is 07/28", 16, "07/28", time.Date(2016, 7, 28, 0, 0, 0, 0, time.UTC)},

// before w/o a year
{"The Deadline is 06/30", 16, "06/30", time.Date(2017, 6, 30, 0, 0, 0, 0, time.UTC)},

// prev day will be added to the future
{"The Deadline is 07/14", 16, "07/14", time.Date(2017, time.July, 14, 0, 0, 0, 0, time.UTC)},

// Current day or future months
{"The Deadline is 8/14", 16, "8/14", time.Date(2016, 8, 14, 0, 0, 0, 0, time.UTC)},
{"The Deadline is 7/15", 16, "7/15", time.Date(2016, 7, 15, 0, 0, 0, 0, time.UTC)},
}

w := when.New(nil)
w.Add(common.SlashMDY(rules.Override))

ApplyFixtures(t, "common.SlashMDY", w, fixt)

}

func TestSlashMDYPast(t *testing.T) {
fixt := []Fixture{
{"The Deadline is 10/10/2016", 16, "10/10/2016", time.Date(2016, 10, 10, 0, 0, 0, 0, time.UTC)},
{"The Deadline is 2/1/2016", 16, "2/1/2016", time.Date(2016, 2, 1, 0, 0, 0, 0, time.UTC)},
{"The Deadline is 2/29/2016", 16, "2/29/2016", time.Date(2016, 2, 29, 0, 0, 0, 0, time.UTC)},

// before w/o a year says same year
{"The Deadline is 06/30", 16, "06/30", time.Date(2016, 6, 30, 0, 0, 0, 0, time.UTC)},

// prev day will still be this year
{"The Deadline is 07/14", 16, "07/14", time.Date(2016, 7, 14, 0, 0, 0, 0, time.UTC)},

// after w/o a year is prior year
{"The Deadline is 07/28", 16, "07/28", time.Date(2015, 7, 28, 0, 0, 0, 0, time.UTC)},

// Regression tests: current date and furture month
{"The Deadline is 07/15", 16, "07/15", time.Date(2016, 7, 15, 0, 0, 0, 0, time.UTC)},
}

w := when.New(&rules.Options{
Distance: 5,
MatchByOrder: true,
WantPast: true})
w.Add(common.SlashMDY(rules.Skip))

ApplyFixtures(t, "common.SlashMDY", w, fixt)
}
Loading