Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ fmt.Printf(r.Time.String())
// 13:46:21 (correct)
```

#### Timezones

The timezone rule is not part of `common.All`, add it to have `10 pm EST` or `22:00 UTC+2` returned in that zone:

```go
w.Add(common.Timezone(rules.Override))
```

It knows `UTC` and `GMT` with an optional offset (`GMT-05:30`, `UTC+0530`) and a few upper case abbreviations (`EST`, `PDT`, `CET`, ...), each as a fixed offset.

### State of the project

The project is in a more-or-less complete state. It's used for one project already. Bugs will be fixed as soon as they will be found.
Expand Down
84 changes: 84 additions & 0 deletions rules/common/timezone.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package common

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

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

/*
- 10 pm EST
- 22:00 UTC
- 9am GMT+2
- 18:30 utc-05:00
- 18:30 GMT+0530

Sets the location of the time found by the other rules. A timezone on its
own is not a time, so the rule never yields a result by itself.

Abbreviations are matched in upper case only, so "est" in a sentence is
not taken for a timezone, and each one is a fixed offset from UTC.
*/

var timezoneOffsets = map[string]time.Duration{
"HST": -10 * time.Hour,
"AKST": -9 * time.Hour,
"AKDT": -8 * time.Hour,
"PST": -8 * time.Hour,
"PDT": -7 * time.Hour,
"MST": -7 * time.Hour,
"MDT": -6 * time.Hour,
"CST": -6 * time.Hour,
"CDT": -5 * time.Hour,
"EST": -5 * time.Hour,
"EDT": -4 * time.Hour,
"BST": 1 * time.Hour,
"CET": 1 * time.Hour,
"CEST": 2 * time.Hour,
"EET": 2 * time.Hour,
"EEST": 3 * time.Hour,
"JST": 9 * time.Hour,
"AEST": 10 * time.Hour,
"AEDT": 11 * time.Hour,
}

func Timezone(s rules.Strategy) rules.Rule {
abbreviations := make([]string, 0, len(timezoneOffsets))
for abbreviation := range timezoneOffsets {
abbreviations = append(abbreviations, abbreviation)
}
sort.Strings(abbreviations)

return &rules.F{
RegExp: regexp.MustCompile("(?:\\W|^)" +
"((?i:utc|gmt)(?:([+-])(\\d{1,2})(?::?(\\d{2}))?)?|" +
strings.Join(abbreviations, "|") + ")" +
"(?:\\W|$)"),
Applier: func(m *rules.Match, c *rules.Context, o *rules.Options, ref time.Time) (bool, error) {
if c.Location != nil && s != rules.Override {
return false, nil
}

name := strings.ToUpper(m.Captures[0])
offset, ok := timezoneOffsets[name]
if !ok {
hours, _ := strconv.Atoi(m.Captures[2])
minutes, _ := strconv.Atoi(m.Captures[3])
if hours > 14 || minutes > 59 {
return false, nil
}
offset = time.Duration(hours)*time.Hour + time.Duration(minutes)*time.Minute
if m.Captures[1] == "-" {
offset = -offset
}
}

c.Location = time.FixedZone(name, int(offset.Seconds()))
return false, nil
},
}
}
44 changes: 44 additions & 0 deletions rules/common/timezone_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package common_test

import (
"testing"
"time"

"github.com/olebedev/when"
"github.com/olebedev/when/rules"
"github.com/olebedev/when/rules/common"
"github.com/olebedev/when/rules/en"
"github.com/stretchr/testify/require"
)

func TestTimezone(t *testing.T) {
fixt := []Fixture{
{"call me at 10 pm EST", 11, "10 pm EST", 27 * time.Hour},
{"call me at 10 pm PDT", 11, "10 pm PDT", 29 * time.Hour},
{"call me at 10 pm CEST", 11, "10 pm CEST", 20 * time.Hour},
{"at 22:00 UTC", 3, "22:00 UTC", 22 * time.Hour},
{"at 22:00 utc+2", 3, "22:00 utc+2", 20 * time.Hour},
{"at 22:00 GMT-05:30", 3, "22:00 GMT-05:30", 27*time.Hour + 30*time.Minute},
{"at 22:00 GMT+0530", 3, "22:00 GMT+0530", 16*time.Hour + 30*time.Minute},

// abbreviations only in upper case, "est" is left alone
{"call me at 10 pm est", 11, "10 pm", 22 * time.Hour},
}

w := when.New(nil)
w.Add(en.All...)
w.Add(common.Timezone(rules.Override))

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

nilFixt := []Fixture{
{"the EST office", 0, "a timezone alone is not a time", 0},
}
ApplyFixturesNil(t, "common.Timezone nil", w, nilFixt)

res, err := w.Parse("call me at 10 pm EST", null)
require.Nil(t, err)
name, offset := res.Time.Zone()
require.Equal(t, "EST", name)
require.Equal(t, -5*60*60, offset)
}
Loading