aboutsummaryrefslogtreecommitdiffstats
path: root/worker/lib/daterange.go
diff options
context:
space:
mode:
authorRobin Jarry <robin@jarry.cc>2023-10-17 14:40:08 +0200
committerRobin Jarry <robin@jarry.cc>2023-10-28 19:24:55 +0200
commit57088312fdd8e602a084bd5736a0e22a34be9ec0 (patch)
tree8c5544262cf8c1772ec661748cfa4d5491ff4c77 /worker/lib/daterange.go
parent591659b52867cb118d1f82d41693a02123935e0c (diff)
downloadaerc-57088312fdd8e602a084bd5736a0e22a34be9ec0.tar.gz
worker: move shared code to lib
Avoid importing code from worker/lib into lib. It should only be the other way around. Move the message parsing code used by maildir, notmuch, mbox and the eml viewer into a lib/rfc822 package. Adapt imports accordingly. Signed-off-by: Robin Jarry <robin@jarry.cc> Reviewed-by: Koni Marti <koni.marti@gmail.com> Tested-by: Moritz Poldrack <moritz@poldrack.dev> Tested-by: Inwit <inwit@sindominio.net>
Diffstat (limited to 'worker/lib/daterange.go')
-rw-r--r--worker/lib/daterange.go471
1 files changed, 0 insertions, 471 deletions
diff --git a/worker/lib/daterange.go b/worker/lib/daterange.go
deleted file mode 100644
index b08bf177..00000000
--- a/worker/lib/daterange.go
+++ /dev/null
@@ -1,471 +0,0 @@
-package lib
-
-import (
- "fmt"
- "strings"
- "time"
-
- "git.sr.ht/~rjarry/aerc/log"
-)
-
-const dateFmt = "2006-01-02"
-
-// ParseDateRange parses a date range into a start and end date. Dates are
-// expected to be in the YYYY-MM-DD format.
-//
-// Start and end dates are connected by the range operator ".." where end date
-// is not included in the date range.
-//
-// ParseDateRange can also parse open-ended ranges, i.e. start.. or ..end are
-// allowed.
-//
-// Relative date terms (such as "1 week 1 day" or "1w 1d") can be used, too.
-func ParseDateRange(s string) (start, end time.Time, err error) {
- s = cleanInput(s)
- s = ensureRangeOp(s)
- i := strings.Index(s, "..")
- switch {
- case i < 0:
- // single date
- start, err = translate(s)
- if err != nil {
- err = fmt.Errorf("failed to parse date: %w", err)
- return
- }
- end = start.AddDate(0, 0, 1)
-
- case i == 0:
- // end date only
- if len(s) < 2 {
- err = fmt.Errorf("no date found")
- return
- }
- end, err = translate(s[2:])
- if err != nil {
- err = fmt.Errorf("failed to parse date: %w", err)
- return
- }
-
- case i > 0:
- // start date first
- start, err = translate(s[:i])
- if err != nil {
- err = fmt.Errorf("failed to parse date: %w", err)
- return
- }
- if len(s[i:]) <= 2 {
- return
- }
- // and end dates if available
- end, err = translate(s[(i + 2):])
- if err != nil {
- err = fmt.Errorf("failed to parse date: %w", err)
- return
- }
- }
-
- return
-}
-
-type dictFunc = func(bool) time.Time
-
-// dict is a dictionary to translate words to dates. Map key must be at least 3
-// characters for matching purposes.
-var dict map[string]dictFunc = map[string]dictFunc{
- "today": func(_ bool) time.Time {
- return time.Now()
- },
- "yesterday": func(_ bool) time.Time {
- return time.Now().AddDate(0, 0, -1)
- },
- "week": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -7
- }
- return time.Now().AddDate(0, 0,
- daydiff(time.Monday)+diff)
- },
- "month": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -1
- }
- t := time.Now()
- return t.AddDate(0, diff, -t.Day()+1)
- },
- "year": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -1
- }
- t := time.Now()
- return t.AddDate(diff, 0, -t.YearDay()+1)
- },
- "monday": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -7
- }
- return time.Now().AddDate(0, 0,
- daydiff(time.Monday)+diff)
- },
- "tuesday": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -7
- }
- return time.Now().AddDate(0, 0,
- daydiff(time.Tuesday)+diff)
- },
- "wednesday": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -7
- }
- return time.Now().AddDate(0, 0,
- daydiff(time.Wednesday)+diff)
- },
- "thursday": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -7
- }
- return time.Now().AddDate(0, 0,
- daydiff(time.Thursday)+diff)
- },
- "friday": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -7
- }
- return time.Now().AddDate(0, 0,
- daydiff(time.Friday)+diff)
- },
- "saturday": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -7
- }
- return time.Now().AddDate(0, 0,
- daydiff(time.Saturday)+diff)
- },
- "sunday": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -7
- }
- return time.Now().AddDate(0, 0,
- daydiff(time.Sunday)+diff)
- },
- "january": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -1
- }
- t := time.Now()
- return t.AddDate(diff,
- monthdiff(time.January), -t.Day()+1)
- },
- "february": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -1
- }
- t := time.Now()
- return t.AddDate(diff,
- monthdiff(time.February), -t.Day()+1)
- },
- "march": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -1
- }
- t := time.Now()
- return t.AddDate(diff,
- monthdiff(time.March), -t.Day()+1)
- },
- "april": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -1
- }
- t := time.Now()
- return t.AddDate(diff,
- monthdiff(time.April), -t.Day()+1)
- },
- "may": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -1
- }
- t := time.Now()
- return t.AddDate(diff,
- monthdiff(time.May), -t.Day()+1)
- },
- "june": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -1
- }
- t := time.Now()
- return t.AddDate(diff,
- monthdiff(time.June), -t.Day()+1)
- },
- "july": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -1
- }
- t := time.Now()
- return t.AddDate(diff,
- monthdiff(time.July), -t.Day()+1)
- },
- "august": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -1
- }
- t := time.Now()
- return t.AddDate(diff,
- monthdiff(time.August), -t.Day()+1)
- },
- "september": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -1
- }
- t := time.Now()
- return t.AddDate(diff,
- monthdiff(time.September), -t.Day()+1)
- },
- "october": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -1
- }
- t := time.Now()
- return t.AddDate(diff,
- monthdiff(time.October), -t.Day()+1)
- },
- "november": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -1
- }
- t := time.Now()
- return t.AddDate(diff,
- monthdiff(time.November), -t.Day()+1)
- },
- "december": func(this bool) time.Time {
- diff := 0
- if !this {
- diff = -1
- }
- t := time.Now()
- return t.AddDate(diff,
- monthdiff(time.December), -t.Day()+1)
- },
-}
-
-func daydiff(d time.Weekday) int {
- daydiff := d - time.Now().Weekday()
- if daydiff > 0 {
- return int(daydiff) - 7
- }
- return int(daydiff)
-}
-
-func monthdiff(d time.Month) int {
- monthdiff := d - time.Now().Month()
- if monthdiff > 0 {
- return int(monthdiff) - 12
- }
- return int(monthdiff)
-}
-
-// translate translates regular time words into date strings
-func translate(s string) (time.Time, error) {
- if s == "" {
- return time.Now(), fmt.Errorf("empty string")
- }
- log.Tracef("input: %s", s)
- s0 := s
-
- // if next characters is integer, then parse a relative date
- if '0' <= s[0] && s[0] <= '9' && hasUnit(s) {
- relDate, err := ParseRelativeDate(s)
- if err != nil {
- log.Errorf("could not parse relative date from '%s': %v",
- s0, err)
- } else {
- log.Tracef("relative date: translated to %v from %s",
- relDate, s0)
- return bod(relDate.Apply(time.Now())), nil
- }
- }
-
- // consult dictionary for terms translation
- s, this, hasPrefix := handlePrefix(s)
- for term, dateFn := range dict {
- if term == "month" && !hasPrefix {
- continue
- }
- if strings.Contains(term, s) {
- log.Tracef("dictionary: translated to %s from %s",
- term, s0)
- return bod(dateFn(this)), nil
- }
- }
-
- // this is a regular date, parse it in the normal format
- log.Infof("parse: translates %s to regular format", s0)
- return time.Parse(dateFmt, s)
-}
-
-// bod returns the begin of the day
-func bod(t time.Time) time.Time {
- y, m, d := t.Date()
- return time.Date(y, m, d, 0, 0, 0, 0, t.Location())
-}
-
-func handlePrefix(s string) (string, bool, bool) {
- var hasPrefix bool
- this := true
- if strings.HasPrefix(s, "this") {
- hasPrefix = true
- s = strings.TrimPrefix(s, "this")
- }
- if strings.HasPrefix(s, "last") {
- hasPrefix = true
- this = false
- s = strings.TrimPrefix(s, "last")
- }
- return s, this, hasPrefix
-}
-
-func cleanInput(s string) string {
- s = strings.ToLower(s)
- s = strings.ReplaceAll(s, " ", "")
- s = strings.ReplaceAll(s, "_", "")
- return s
-}
-
-// RelDate is the relative date in the past, e.g. yesterday would be
-// represented as RelDate{0,0,1}.
-type RelDate struct {
- Year uint
- Month uint
- Day uint
-}
-
-func (d RelDate) Apply(t time.Time) time.Time {
- return t.AddDate(-int(d.Year), -int(d.Month), -int(d.Day))
-}
-
-// ParseRelativeDate parses a string of relative terms into a DateAdd.
-//
-// Syntax: N (year|month|week|day) ..
-//
-// The following are valid inputs:
-// 5weeks1day
-// 5w1d
-//
-// Adapted from the Go stdlib in src/time/format.go
-func ParseRelativeDate(s string) (RelDate, error) {
- s0 := s
- s = cleanInput(s)
- var da RelDate
- for s != "" {
- var n uint
-
- var err error
-
- // expect an integer
- if !('0' <= s[0] && s[0] <= '9') {
- return da, fmt.Errorf("not a valid relative term: %s",
- s0)
- }
-
- // consume integer
- n, s, err = leadingInt(s)
- if err != nil {
- return da, fmt.Errorf("cannot read integer in %s",
- s0)
- }
-
- // consume the units
- i := 0
- for ; i < len(s); i++ {
- c := s[i]
- if '0' <= c && c <= '9' {
- break
- }
- }
- if i == 0 {
- return da, fmt.Errorf("missing unit in %s", s0)
- }
-
- u := s[:i]
- s = s[i:]
- switch u[0] {
- case 'y':
- da.Year += n
- case 'm':
- da.Month += n
- case 'w':
- da.Day += 7 * n
- case 'd':
- da.Day += n
- default:
- return da, fmt.Errorf("unknown unit %s in %s", u, s0)
- }
-
- }
-
- return da, nil
-}
-
-func hasUnit(s string) (has bool) {
- for _, u := range "ymwd" {
- if strings.Contains(s, string(u)) {
- return true
- }
- }
- return false
-}
-
-// leadingInt parses and returns the leading integer in s.
-//
-// Adapted from the Go stdlib in src/time/format.go
-func leadingInt(s string) (x uint, rem string, err error) {
- i := 0
- for ; i < len(s); i++ {
- c := s[i]
- if c < '0' || c > '9' {
- break
- }
- x = x*10 + uint(c) - '0'
- }
- return x, s[i:], nil
-}
-
-func ensureRangeOp(s string) string {
- if strings.Contains(s, "..") {
- return s
- }
- s0 := s
- for _, m := range []string{"this", "last"} {
- for _, u := range []string{"year", "month", "week"} {
- term := m + u
- if strings.Contains(s, term) {
- if m == "last" {
- return s0 + "..this" + u
- } else {
- return s0 + ".."
- }
- }
- }
- }
- return s0
-}