118 lines
2.5 KiB
Go
118 lines
2.5 KiB
Go
//go:generate peg -inline -switch grammar.peg
|
|
|
|
// Package naturaldate provides natural date time parsing.
|
|
package naturaldate
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// day duration.
|
|
var day = time.Hour * 24
|
|
|
|
// week duration.
|
|
var week = time.Hour * 24 * 7
|
|
|
|
// Direction is the direction used for ambiguous expressions.
|
|
type Direction int
|
|
|
|
// Directions available.
|
|
const (
|
|
Past Direction = iota
|
|
Future
|
|
)
|
|
|
|
// Option function.
|
|
type Option func(*parser)
|
|
|
|
// WithDirection sets the direction used for ambiguous expressions. By default
|
|
// the Past direction is used, so "sunday" will be the previous Sunday, rather
|
|
// than the next Sunday.
|
|
func WithDirection(d Direction) Option {
|
|
return func(p *parser) {
|
|
switch d {
|
|
case Past:
|
|
p.direction = -1
|
|
case Future:
|
|
p.direction = 1
|
|
default:
|
|
panic("unhandled direction")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse query string.
|
|
func Parse(s string, ref time.Time, options ...Option) (time.Time, error) {
|
|
p := &parser{
|
|
Buffer: strings.ToLower(s),
|
|
direction: -1,
|
|
t: ref,
|
|
}
|
|
|
|
for _, o := range options {
|
|
o(p)
|
|
}
|
|
|
|
p.Init()
|
|
|
|
if err := p.Parse(); err != nil {
|
|
return time.Time{}, err
|
|
}
|
|
|
|
p.Execute()
|
|
// p.PrintSyntaxTree()
|
|
return p.t, nil
|
|
}
|
|
|
|
// withDirection returns duration with direction.
|
|
func (p *parser) withDirection(d time.Duration) time.Duration {
|
|
return d * time.Duration(p.direction)
|
|
}
|
|
|
|
// prevWeekday returns the previous week day relative to time t.
|
|
func prevWeekday(t time.Time, day time.Weekday) time.Time {
|
|
d := t.Weekday() - day
|
|
if d <= 0 {
|
|
d += 7
|
|
}
|
|
return t.Add(-time.Hour * 24 * time.Duration(d))
|
|
}
|
|
|
|
// nextWeekday returns the next week day relative to time t.
|
|
func nextWeekday(t time.Time, day time.Weekday) time.Time {
|
|
d := day - t.Weekday()
|
|
if d <= 0 {
|
|
d += 7
|
|
}
|
|
return t.Add(time.Hour * 24 * time.Duration(d))
|
|
}
|
|
|
|
// nextMonth returns the next month relative to time t.
|
|
func nextMonth(t time.Time, month time.Month) time.Time {
|
|
y := t.Year()
|
|
if month-t.Month() <= 0 {
|
|
y++
|
|
}
|
|
_, _, day := t.Date()
|
|
hour, min, sec := t.Clock()
|
|
return time.Date(y, month, day, hour, min, sec, 0, t.Location())
|
|
}
|
|
|
|
// prevMonth returns the next month relative to time t.
|
|
func prevMonth(t time.Time, month time.Month) time.Time {
|
|
y := t.Year()
|
|
if t.Month()-month <= 0 {
|
|
y--
|
|
}
|
|
_, _, day := t.Date()
|
|
hour, min, sec := t.Clock()
|
|
return time.Date(y, month, day, hour, min, sec, 0, t.Location())
|
|
}
|
|
|
|
// truncateDay returns a date truncated to the day.
|
|
func truncateDay(t time.Time) time.Time {
|
|
y, m, d := t.Date()
|
|
return time.Date(y, m, d, 0, 0, 0, 0, t.Location())
|
|
}
|