mirror of
https://github.com/facebook/duckling.git
synced 2024-12-26 05:33:24 +03:00
cdd2f1c9cb
Summary: This is analogous to [Duckling] Don't produce trivially empty Tokens but that change did that for intersect, this one deals with interval. Reviewed By: patapizza Differential Revision: D5039215 fbshipit-source-id: 95bd821
1600 lines
42 KiB
Haskell
1600 lines
42 KiB
Haskell
-- Copyright (c) 2016-present, Facebook, Inc.
|
|
-- All rights reserved.
|
|
--
|
|
-- This source code is licensed under the BSD-style license found in the
|
|
-- LICENSE file in the root directory of this source tree. An additional grant
|
|
-- of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
|
|
{-# LANGUAGE GADTs #-}
|
|
{-# LANGUAGE NoRebindableSyntax #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
module Duckling.Time.EN.Rules
|
|
( rules ) where
|
|
|
|
import Control.Monad (liftM2)
|
|
import Data.Maybe
|
|
import Data.Text (Text)
|
|
import qualified Data.Text as Text
|
|
import Prelude
|
|
|
|
import Duckling.Dimensions.Types
|
|
import Duckling.Duration.Helpers (duration)
|
|
import Duckling.Numeral.Helpers (parseInt)
|
|
import Duckling.Numeral.Types (NumeralData (..))
|
|
import qualified Duckling.Numeral.Types as TNumeral
|
|
import Duckling.Ordinal.Types (OrdinalData (..))
|
|
import qualified Duckling.Ordinal.Types as TOrdinal
|
|
import Duckling.Regex.Types
|
|
import Duckling.Time.Helpers
|
|
import Duckling.Time.Types (TimeData (..))
|
|
import qualified Duckling.Time.Types as TTime
|
|
import qualified Duckling.TimeGrain.Types as TG
|
|
import Duckling.Types
|
|
|
|
ruleIntersect :: Rule
|
|
ruleIntersect = Rule
|
|
{ name = "intersect"
|
|
, pattern =
|
|
[ Predicate isNotLatent
|
|
, Predicate isNotLatent
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Time td1:Token Time td2:_) -> Token Time <$> intersect td1 td2
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleIntersectOf :: Rule
|
|
ruleIntersectOf = Rule
|
|
{ name = "intersect by \",\", \"of\", \"from\", \"'s\""
|
|
, pattern =
|
|
[ Predicate isNotLatent
|
|
, regex "of|from|for|'s|,"
|
|
, Predicate isNotLatent
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Time td1:_:Token Time td2:_) -> Token Time <$> intersect td1 td2
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleAbsorbOnTime :: Rule
|
|
ruleAbsorbOnTime = Rule
|
|
{ name = "on <date>"
|
|
, pattern =
|
|
[ regex "on"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:token:_) -> Just token
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleAbsorbOnADOW :: Rule
|
|
ruleAbsorbOnADOW = Rule
|
|
{ name = "on a <named-day>"
|
|
, pattern =
|
|
[ regex "on a"
|
|
, Predicate isADayOfWeek
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:token:_) -> Just token
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleAbsorbInMonth :: Rule
|
|
ruleAbsorbInMonth = Rule
|
|
{ name = "in <named-month>"
|
|
, pattern =
|
|
[ regex "in"
|
|
, Predicate isAMonth
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:token:_) -> Just token
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleAbsorbCommaTOD :: Rule
|
|
ruleAbsorbCommaTOD = Rule
|
|
{ name = "absorption of , after named day"
|
|
, pattern =
|
|
[ Predicate isADayOfWeek
|
|
, regex ","
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(token:_) -> Just token
|
|
_ -> Nothing
|
|
}
|
|
|
|
instants :: [(Text, String, TG.Grain, Int)]
|
|
instants =
|
|
[ ("now", "((just|right)\\s*)?now|immediately", TG.Second, 0)
|
|
, ("today", "todays?|(at this time)", TG.Day, 0)
|
|
, ("tomorrow", "(tmrw?|tomm?or?rows?)", TG.Day, 1)
|
|
, ("yesterday", "yesterdays?", TG.Day, - 1)
|
|
, ("end of month", "(the )?(EOM|end of (the )?month)", TG.Month, 1)
|
|
, ("end of year", "(the )?(EOY|end of (the )?year)", TG.Year, 1)
|
|
]
|
|
|
|
ruleInstants :: [Rule]
|
|
ruleInstants = map go instants
|
|
where
|
|
go (name, regexPattern, grain, n) = Rule
|
|
{ name = name
|
|
, pattern = [regex regexPattern]
|
|
, prod = \_ -> tt $ cycleNth grain n
|
|
}
|
|
|
|
ruleNextDOW :: Rule
|
|
ruleNextDOW = Rule
|
|
{ name = "this|next <day-of-week>"
|
|
, pattern =
|
|
[ regex "this|next"
|
|
, Predicate isADayOfWeek
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) -> tt $ predNth 0 True td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleThisTime :: Rule
|
|
ruleThisTime = Rule
|
|
{ name = "this <time>"
|
|
, pattern =
|
|
[ regex "this|current|coming"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) -> tt $ predNth 0 False td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleNextTime :: Rule
|
|
ruleNextTime = Rule
|
|
{ name = "next <time>"
|
|
, pattern =
|
|
[ regex "next"
|
|
, Predicate isNotLatent
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) -> tt $ predNth 0 True td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleLastTime :: Rule
|
|
ruleLastTime = Rule
|
|
{ name = "last <time>"
|
|
, pattern =
|
|
[ regex "(this past|last|previous)"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) -> tt $ predNth (- 1) False td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleTimeBeforeLastAfterNext :: Rule
|
|
ruleTimeBeforeLastAfterNext = Rule
|
|
{ name = "<time> before last|after next"
|
|
, pattern =
|
|
[ dimension Time
|
|
, regex "(before last|after next)"
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Time td:Token RegexMatch (GroupMatch (match:_)):_) ->
|
|
tt $ predNth 1 (match == "after next") td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleLastDOWOfTime :: Rule
|
|
ruleLastDOWOfTime = Rule
|
|
{ name = "last <day-of-week> of <time>"
|
|
, pattern =
|
|
[ regex "last"
|
|
, Predicate isADayOfWeek
|
|
, regex "of"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td1:_:Token Time td2:_) ->
|
|
tt $ predLastOf td1 td2
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleLastCycleOfTime :: Rule
|
|
ruleLastCycleOfTime = Rule
|
|
{ name = "last <cycle> of <time>"
|
|
, pattern =
|
|
[ regex "last"
|
|
, dimension TimeGrain
|
|
, regex "of|in"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token TimeGrain grain:_:Token Time td:_) ->
|
|
tt $ cycleLastOf grain td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleNthTimeOfTime :: Rule
|
|
ruleNthTimeOfTime = Rule
|
|
{ name = "nth <time> of <time>"
|
|
, pattern =
|
|
[ dimension Ordinal
|
|
, dimension Time
|
|
, regex "of|in"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Ordinal od:Token Time td1:_:Token Time td2:_) -> Token Time .
|
|
predNth (TOrdinal.value od - 1) False <$> intersect td2 td1
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleTheNthTimeOfTime :: Rule
|
|
ruleTheNthTimeOfTime = Rule
|
|
{ name = "the nth <time> of <time>"
|
|
, pattern =
|
|
[ regex "the"
|
|
, dimension Ordinal
|
|
, dimension Time
|
|
, regex "of|in"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Ordinal od:Token Time td1:_:Token Time td2:_) -> Token Time .
|
|
predNth (TOrdinal.value od - 1) False <$> intersect td2 td1
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleNthTimeAfterTime :: Rule
|
|
ruleNthTimeAfterTime = Rule
|
|
{ name = "nth <time> after <time>"
|
|
, pattern =
|
|
[ dimension Ordinal
|
|
, dimension Time
|
|
, regex "after"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Ordinal od:Token Time td1:_:Token Time td2:_) ->
|
|
tt $ predNthAfter (TOrdinal.value od - 1) td1 td2
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleTheNthTimeAfterTime :: Rule
|
|
ruleTheNthTimeAfterTime = Rule
|
|
{ name = "the nth <time> after <time>"
|
|
, pattern =
|
|
[ regex "the"
|
|
, dimension Ordinal
|
|
, dimension Time
|
|
, regex "after"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Ordinal od:Token Time td1:_:Token Time td2:_) ->
|
|
tt $ predNthAfter (TOrdinal.value od - 1) td1 td2
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleYear :: Rule
|
|
ruleYear = Rule
|
|
{ name = "year"
|
|
, pattern = [Predicate $ isIntegerBetween 1000 2100]
|
|
, prod = \tokens -> case tokens of
|
|
(token:_) -> do
|
|
n <- getIntValue token
|
|
tt $ year n
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleYearPastLatent :: Rule
|
|
ruleYearPastLatent = Rule
|
|
{ name = "past year (latent)"
|
|
, pattern =
|
|
[ Predicate $
|
|
liftM2 (||) (isIntegerBetween (- 10000) 0) (isIntegerBetween 25 999)
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(token:_) -> do
|
|
n <- getIntValue token
|
|
tt . mkLatent $ year n
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleYearFutureLatent :: Rule
|
|
ruleYearFutureLatent = Rule
|
|
{ name = "future year (latent)"
|
|
, pattern = [Predicate $ isIntegerBetween 2101 10000]
|
|
, prod = \tokens -> case tokens of
|
|
(token:_) -> do
|
|
n <- getIntValue token
|
|
tt . mkLatent $ year n
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleDOMLatent :: Rule
|
|
ruleDOMLatent = Rule
|
|
{ name = "<day-of-month> (ordinal)"
|
|
, pattern = [Predicate isDOMOrdinal]
|
|
, prod = \tokens -> case tokens of
|
|
(token:_) -> do
|
|
n <- getIntValue token
|
|
tt . mkLatent $ dayOfMonth n
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleTheDOMNumeral :: Rule
|
|
ruleTheDOMNumeral = Rule
|
|
{ name = "the <day-of-month> (number)"
|
|
, pattern =
|
|
[ regex "the"
|
|
, Predicate isDOMInteger
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:token:_) -> do
|
|
n <- getIntValue token
|
|
tt . mkLatent $ dayOfMonth n
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleTheDOMOrdinal :: Rule
|
|
ruleTheDOMOrdinal = Rule
|
|
{ name = "the <day-of-month> (ordinal)"
|
|
, pattern =
|
|
[ regex "the"
|
|
, Predicate isDOMOrdinal
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:
|
|
Token Ordinal (OrdinalData {TOrdinal.value = v}):
|
|
_) -> tt $ dayOfMonth v
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleNamedDOMOrdinal :: Rule
|
|
ruleNamedDOMOrdinal = Rule
|
|
{ name = "<named-month>|<named-day> <day-of-month> (ordinal)"
|
|
, pattern =
|
|
[ Predicate $ liftM2 (||) isAMonth isADayOfWeek
|
|
, Predicate isDOMOrdinal
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Time td:token:_) -> Token Time <$> intersectDOM td token
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleMonthDOMNumeral :: Rule
|
|
ruleMonthDOMNumeral = Rule
|
|
{ name = "<named-month> <day-of-month> (non ordinal)"
|
|
, pattern =
|
|
[ Predicate isAMonth
|
|
, Predicate isDOMInteger
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Time td:token:_) -> Token Time <$> intersectDOM td token
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleDOMOfMonth :: Rule
|
|
ruleDOMOfMonth = Rule
|
|
{ name = "<day-of-month> (ordinal or number) of <named-month>"
|
|
, pattern =
|
|
[ Predicate isDOMValue
|
|
, regex "of|in"
|
|
, Predicate isAMonth
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleDOMMonth :: Rule
|
|
ruleDOMMonth = Rule
|
|
{ name = "<day-of-month> (ordinal or number) <named-month>"
|
|
, pattern =
|
|
[ Predicate isDOMValue
|
|
, Predicate isAMonth
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(token:Token Time td:_) -> Token Time <$> intersectDOM td token
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleDOMOrdinalMonthYear :: Rule
|
|
ruleDOMOrdinalMonthYear = Rule
|
|
{ name = "<day-of-month>(ordinal) <named-month> year"
|
|
, pattern =
|
|
[ Predicate isDOMOrdinal
|
|
, Predicate isAMonth
|
|
, regex "(\\d{2,4})"
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(token:Token Time td:Token RegexMatch (GroupMatch (match:_)):_) -> do
|
|
intVal <- parseInt match
|
|
dom <- intersectDOM td token
|
|
Token Time <$> intersect dom (year intVal)
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleIdesOfMonth :: Rule
|
|
ruleIdesOfMonth = Rule
|
|
{ name = "the ides of <named-month>"
|
|
, pattern =
|
|
[ regex "the ides? of"
|
|
, Predicate isAMonth
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td@TimeData {TTime.form = Just (TTime.Month m)}:_) ->
|
|
Token Time <$>
|
|
intersect td (dayOfMonth $ if elem m [3, 5, 7, 10] then 15 else 13)
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleTODLatent :: Rule
|
|
ruleTODLatent = Rule
|
|
{ name = "time-of-day (latent)"
|
|
, pattern = [Predicate $ isIntegerBetween 0 23]
|
|
, prod = \tokens -> case tokens of
|
|
(token:_) -> do
|
|
n <- getIntValue token
|
|
tt . mkLatent $ hour True n
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleAtTOD :: Rule
|
|
ruleAtTOD = Rule
|
|
{ name = "at <time-of-day>"
|
|
, pattern =
|
|
[ regex "at|@"
|
|
, Predicate isATimeOfDay
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) -> tt $ notLatent td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleTODOClock :: Rule
|
|
ruleTODOClock = Rule
|
|
{ name = "<time-of-day> o'clock"
|
|
, pattern =
|
|
[ Predicate isATimeOfDay
|
|
, regex "o.?clock"
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Time td:_) -> tt $ notLatent td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleHHMM :: Rule
|
|
ruleHHMM = Rule
|
|
{ name = "hh:mm"
|
|
, pattern = [regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)"]
|
|
, prod = \tokens -> case tokens of
|
|
(Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do
|
|
h <- parseInt hh
|
|
m <- parseInt mm
|
|
tt $ hourMinute True h m
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleHHMMLatent :: Rule
|
|
ruleHHMMLatent = Rule
|
|
{ name = "hhmm (latent)"
|
|
, pattern =
|
|
[ regex "((?:[01]?\\d)|(?:2[0-3]))([0-5]\\d)"
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do
|
|
h <- parseInt hh
|
|
m <- parseInt mm
|
|
tt . mkLatent $ hourMinute True h m
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleHHMMSS :: Rule
|
|
ruleHHMMSS = Rule
|
|
{ name = "hh:mm:ss"
|
|
, pattern = [regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)[:.]([0-5]\\d)"]
|
|
, prod = \tokens -> case tokens of
|
|
(Token RegexMatch (GroupMatch (hh:mm:ss:_)):_) -> do
|
|
h <- parseInt hh
|
|
m <- parseInt mm
|
|
s <- parseInt ss
|
|
tt $ hourMinuteSecond True h m s
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleMilitaryAMPM :: Rule
|
|
ruleMilitaryAMPM = Rule
|
|
{ name = "hhmm (military) am|pm"
|
|
, pattern =
|
|
[ regex "((?:1[012]|0?\\d))([0-5]\\d)"
|
|
, regex "([ap])\\.?m?\\.?"
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token RegexMatch (GroupMatch (hh:mm:_)):Token RegexMatch (GroupMatch (ap:_)):_) -> do
|
|
h <- parseInt hh
|
|
m <- parseInt mm
|
|
tt . timeOfDayAMPM (hourMinute True h m) $
|
|
Text.toLower ap == "a"
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleTODAMPM :: Rule
|
|
ruleTODAMPM = Rule
|
|
{ name = "<time-of-day> am|pm"
|
|
, pattern =
|
|
[ Predicate isATimeOfDay
|
|
, regex "(in the )?([ap])(\\s|\\.)?m?\\.?"
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Time td:Token RegexMatch (GroupMatch (_:ap:_)):_) ->
|
|
tt . timeOfDayAMPM td $ Text.toLower ap == "a"
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleHONumeral :: Rule
|
|
ruleHONumeral = Rule
|
|
{ name = "<hour-of-day> <integer>"
|
|
, pattern =
|
|
[ Predicate $ liftM2 (&&) isNotLatent isAnHourOfDay
|
|
, Predicate $ isIntegerBetween 1 59
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
|
|
token:
|
|
_) -> do
|
|
n <- getIntValue token
|
|
tt $ hourMinute is12H hours n
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleHODHalf :: Rule
|
|
ruleHODHalf = Rule
|
|
{ name = "<hour-of-day> half"
|
|
, pattern =
|
|
[ Predicate isAnHourOfDay
|
|
, regex "half"
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
|
|
tt $ hourMinute is12H hours 30
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleHODQuarter :: Rule
|
|
ruleHODQuarter = Rule
|
|
{ name = "<hour-of-day> quarter"
|
|
, pattern =
|
|
[ Predicate isAnHourOfDay
|
|
, regex "(a|one)? ?quarter"
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
|
|
tt $ hourMinute is12H hours 15
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleNumeralToHOD :: Rule
|
|
ruleNumeralToHOD = Rule
|
|
{ name = "<integer> to|till|before <hour-of-day>"
|
|
, pattern =
|
|
[ Predicate $ isIntegerBetween 1 59
|
|
, regex "to|till|before|of"
|
|
, Predicate isAnHourOfDay
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(token:_:Token Time td:_) -> do
|
|
n <- getIntValue token
|
|
t <- minutesBefore n td
|
|
Just $ Token Time t
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleHalfToHOD :: Rule
|
|
ruleHalfToHOD = Rule
|
|
{ name = "half to|till|before <hour-of-day>"
|
|
, pattern =
|
|
[ regex "half (to|till|before|of)"
|
|
, Predicate isAnHourOfDay
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) -> Token Time <$> minutesBefore 30 td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleQuarterToHOD :: Rule
|
|
ruleQuarterToHOD = Rule
|
|
{ name = "quarter to|till|before <hour-of-day>"
|
|
, pattern =
|
|
[ regex "(a|one)? ?quarter (to|till|before|of)"
|
|
, Predicate isAnHourOfDay
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) -> Token Time <$> minutesBefore 15 td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleNumeralAfterHOD :: Rule
|
|
ruleNumeralAfterHOD = Rule
|
|
{ name = "integer after|past <hour-of-day>"
|
|
, pattern =
|
|
[ Predicate $ isIntegerBetween 1 59
|
|
, regex "after|past"
|
|
, Predicate isAnHourOfDay
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(token:_:Token Time td:_) -> do
|
|
n <- getIntValue token
|
|
t <- minutesAfter n td
|
|
Just $ Token Time t
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleHalfAfterHOD :: Rule
|
|
ruleHalfAfterHOD = Rule
|
|
{ name = "half after|past <hour-of-day>"
|
|
, pattern =
|
|
[ regex "half (after|past)"
|
|
, Predicate isAnHourOfDay
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) -> Token Time <$> minutesAfter 30 td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleQuarterAfterHOD :: Rule
|
|
ruleQuarterAfterHOD = Rule
|
|
{ name = "quarter after|past <hour-of-day>"
|
|
, pattern =
|
|
[ regex "(a|one)? ?quarter (after|past)"
|
|
, Predicate isAnHourOfDay
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) -> Token Time <$> minutesAfter 15 td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleHalfHOD :: Rule
|
|
ruleHalfHOD = Rule
|
|
{ name = "half <integer> (UK style hour-of-day)"
|
|
, pattern =
|
|
[ regex "half"
|
|
, Predicate isAnHourOfDay
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) -> Token Time <$> minutesAfter 30 td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleMMDDYYYY :: Rule
|
|
ruleMMDDYYYY = Rule
|
|
{ name = "mm/dd/yyyy"
|
|
, pattern =
|
|
[regex "(0?[1-9]|1[0-2])[/-](3[01]|[12]\\d|0?[1-9])[-/](\\d{2,4})"]
|
|
, prod = \tokens -> case tokens of
|
|
(Token RegexMatch (GroupMatch (mm:dd:yy:_)):_) -> do
|
|
y <- parseInt yy
|
|
m <- parseInt mm
|
|
d <- parseInt dd
|
|
tt $ yearMonthDay y m d
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleYYYYMMDD :: Rule
|
|
ruleYYYYMMDD = Rule
|
|
{ name = "yyyy-mm-dd"
|
|
, pattern = [regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"]
|
|
, prod = \tokens -> case tokens of
|
|
(Token RegexMatch (GroupMatch (yy:mm:dd:_)):_) -> do
|
|
y <- parseInt yy
|
|
m <- parseInt mm
|
|
d <- parseInt dd
|
|
tt $ yearMonthDay y m d
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleMMDD :: Rule
|
|
ruleMMDD = Rule
|
|
{ name = "mm/dd"
|
|
, pattern = [regex "(0?[1-9]|1[0-2])/(3[01]|[12]\\d|0?[1-9])"]
|
|
, prod = \tokens -> case tokens of
|
|
(Token RegexMatch (GroupMatch (mm:dd:_)):_) -> do
|
|
m <- parseInt mm
|
|
d <- parseInt dd
|
|
tt $ monthDay m d
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleNoonMidnightEOD :: Rule
|
|
ruleNoonMidnightEOD = Rule
|
|
{ name = "noon|midnight|EOD|end of day"
|
|
, pattern = [regex "(noon|midni(ght|te)|(the )?(EOD|end of (the )?day))"]
|
|
, prod = \tokens -> case tokens of
|
|
(Token RegexMatch (GroupMatch (match:_)):_) -> tt . hour False $
|
|
if match == "noon" then 12 else 0
|
|
_ -> Nothing
|
|
}
|
|
|
|
rulePartOfDays :: Rule
|
|
rulePartOfDays = Rule
|
|
{ name = "part of days"
|
|
, pattern = [regex "(morning|after ?noo?n|evening|night|(at )?lunch)"]
|
|
, prod = \tokens -> case tokens of
|
|
(Token RegexMatch (GroupMatch (match:_)):_) -> do
|
|
let (start, end) = case Text.toLower match of
|
|
"morning" -> (hour False 4, hour False 12)
|
|
"evening" -> (hour False 18, hour False 0)
|
|
"night" -> (hour False 18, hour False 0)
|
|
"lunch" -> (hour False 12, hour False 14)
|
|
"at lunch" -> (hour False 12, hour False 14)
|
|
_ -> (hour False 12, hour False 19)
|
|
td <- interval TTime.Open start end
|
|
tt . partOfDay $ mkLatent td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleEarlyMorning :: Rule
|
|
ruleEarlyMorning = Rule
|
|
{ name = "early morning"
|
|
, pattern =
|
|
[ regex "early ((in|hours of) the )?morning"
|
|
]
|
|
, prod = \_ -> Token Time . partOfDay . mkLatent <$>
|
|
interval TTime.Open (hour False 4) (hour False 9)
|
|
}
|
|
|
|
rulePODIn :: Rule
|
|
rulePODIn = Rule
|
|
{ name = "in|during the <part-of-day>"
|
|
, pattern =
|
|
[ regex "(in|during)( the)?"
|
|
, Predicate isAPartOfDay
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) -> tt $ notLatent td
|
|
_ -> Nothing
|
|
}
|
|
|
|
rulePODThis :: Rule
|
|
rulePODThis = Rule
|
|
{ name = "this <part-of-day>"
|
|
, pattern =
|
|
[ regex "this"
|
|
, Predicate isAPartOfDay
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) -> Token Time . partOfDay . notLatent <$>
|
|
intersect (cycleNth TG.Day 0) td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleTonight :: Rule
|
|
ruleTonight = Rule
|
|
{ name = "tonight"
|
|
, pattern = [regex "toni(ght|gth|te)"]
|
|
, prod = \_ -> do
|
|
let today = cycleNth TG.Day 0
|
|
evening <- interval TTime.Open (hour False 18) (hour False 0)
|
|
Token Time . partOfDay . notLatent <$> intersect today evening
|
|
}
|
|
|
|
ruleAfterPartofday :: Rule
|
|
ruleAfterPartofday = Rule
|
|
{ name = "after lunch/work/school"
|
|
, pattern =
|
|
[ regex "after[\\s-]?(lunch|work|school)"
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token RegexMatch (GroupMatch (match:_)):_) -> do
|
|
(start, end) <- case Text.toLower match of
|
|
"lunch" -> Just (hour False 13, hour False 17)
|
|
"work" -> Just (hour False 17, hour False 21)
|
|
"school" -> Just (hour False 15, hour False 21)
|
|
_ -> Nothing
|
|
td <- interval TTime.Open start end
|
|
Token Time . partOfDay . notLatent <$>
|
|
intersect (cycleNth TG.Day 0) td
|
|
_ -> Nothing
|
|
}
|
|
|
|
-- Since part of days are latent, general time intersection is blocked
|
|
ruleTimePOD :: Rule
|
|
ruleTimePOD = Rule
|
|
{ name = "<time> <part-of-day>"
|
|
, pattern =
|
|
[ dimension Time
|
|
, Predicate isAPartOfDay
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Time td:Token Time pod:_) -> Token Time <$> intersect pod td
|
|
_ -> Nothing
|
|
}
|
|
|
|
rulePODofTime :: Rule
|
|
rulePODofTime = Rule
|
|
{ name = "<part-of-day> of <time>"
|
|
, pattern =
|
|
[ Predicate isAPartOfDay
|
|
, regex "of"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Time pod:_:Token Time td:_) -> Token Time <$> intersect pod td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleWeekend :: Rule
|
|
ruleWeekend = Rule
|
|
{ name = "week-end"
|
|
, pattern = [regex "(week(\\s|-)?end|wkend)"]
|
|
, prod = \_ -> do
|
|
fri <- intersect (dayOfWeek 5) (hour False 18)
|
|
mon <- intersect (dayOfWeek 1) (hour False 0)
|
|
Token Time <$> interval TTime.Open fri mon
|
|
}
|
|
|
|
ruleSeasons :: Rule
|
|
ruleSeasons = Rule
|
|
{ name = "seasons"
|
|
, pattern = [regex "(summer|fall|autumn|winter|spring)"]
|
|
, prod = \tokens -> case tokens of
|
|
(Token RegexMatch (GroupMatch (match:_)):_) -> do
|
|
start <- case Text.toLower match of
|
|
"summer" -> Just $ monthDay 6 21
|
|
"fall" -> Just $ monthDay 9 23
|
|
"autumn" -> Just $ monthDay 9 23
|
|
"winter" -> Just $ monthDay 12 21
|
|
"spring" -> Just $ monthDay 3 20
|
|
_ -> Nothing
|
|
end <- case Text.toLower match of
|
|
"summer" -> Just $ monthDay 9 23
|
|
"fall" -> Just $ monthDay 12 21
|
|
"autumn" -> Just $ monthDay 12 21
|
|
"winter" -> Just $ monthDay 3 20
|
|
"spring" -> Just $ monthDay 6 21
|
|
_ -> Nothing
|
|
Token Time <$> interval TTime.Open start end
|
|
_ -> Nothing
|
|
|
|
}
|
|
|
|
ruleTODPrecision :: Rule
|
|
ruleTODPrecision = Rule
|
|
{ name = "<time-of-day> sharp|exactly"
|
|
, pattern =
|
|
[ Predicate isATimeOfDay
|
|
, regex "(sharp|exactly|-?ish|approximately)"
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Time td:_) -> tt $ notLatent td
|
|
_ -> Nothing
|
|
}
|
|
|
|
rulePrecisionTOD :: Rule
|
|
rulePrecisionTOD = Rule
|
|
{ name = "about|exactly <time-of-day>"
|
|
, pattern =
|
|
[ regex "(about|around|approximately|exactly)"
|
|
, Predicate $ isGrainFinerThan TG.Year
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) -> tt $ notLatent td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleIntervalMonthDDDD :: Rule
|
|
ruleIntervalMonthDDDD = Rule
|
|
{ name = "<month> dd-dd (interval)"
|
|
, pattern =
|
|
[ Predicate isAMonth
|
|
, regex "(3[01]|[12]\\d|0?[1-9])"
|
|
, regex "\\-|to|th?ru|through|(un)?til(l)?"
|
|
, regex "(3[01]|[12]\\d|0?[1-9])"
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
( Token Time td
|
|
:Token RegexMatch (GroupMatch (d1:_))
|
|
:_
|
|
:Token RegexMatch (GroupMatch (d2:_))
|
|
:_) -> do
|
|
dd1 <- parseInt d1
|
|
dd2 <- parseInt d2
|
|
dom1 <- intersect (dayOfMonth dd1) td
|
|
dom2 <- intersect (dayOfMonth dd2) td
|
|
Token Time <$> interval TTime.Closed dom1 dom2
|
|
_ -> Nothing
|
|
}
|
|
|
|
-- Blocked for :latent time. May need to accept certain latents only, like hours
|
|
ruleIntervalDash :: Rule
|
|
ruleIntervalDash = Rule
|
|
{ name = "<datetime> - <datetime> (interval)"
|
|
, pattern =
|
|
[ Predicate isNotLatent
|
|
, regex "\\-|to|th?ru|through|(un)?til(l)?"
|
|
, Predicate isNotLatent
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Time td1:_:Token Time td2:_) ->
|
|
Token Time <$> interval TTime.Closed td1 td2
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleIntervalFrom :: Rule
|
|
ruleIntervalFrom = Rule
|
|
{ name = "from <datetime> - <datetime> (interval)"
|
|
, pattern =
|
|
[ regex "from"
|
|
, dimension Time
|
|
, regex "\\-|to|th?ru|through|(un)?til(l)?"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td1:_:Token Time td2:_) ->
|
|
Token Time <$> interval TTime.Closed td1 td2
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleIntervalBetween :: Rule
|
|
ruleIntervalBetween = Rule
|
|
{ name = "between <time> and <time>"
|
|
, pattern =
|
|
[ regex "between"
|
|
, dimension Time
|
|
, regex "and"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td1:_:Token Time td2:_) ->
|
|
Token Time <$> interval TTime.Closed td1 td2
|
|
_ -> Nothing
|
|
}
|
|
|
|
-- Specific for time-of-day, to help resolve ambiguities
|
|
ruleIntervalTODDash :: Rule
|
|
ruleIntervalTODDash = Rule
|
|
{ name = "<time-of-day> - <time-of-day> (interval)"
|
|
, pattern =
|
|
[ Predicate $ liftM2 (&&) isNotLatent isATimeOfDay
|
|
, regex "\\-|:|to|th?ru|through|(un)?til(l)?"
|
|
, Predicate isATimeOfDay
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Time td1:_:Token Time td2:_) ->
|
|
Token Time <$> interval TTime.Closed td1 td2
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleIntervalTODFrom :: Rule
|
|
ruleIntervalTODFrom = Rule
|
|
{ name = "from <time-of-day> - <time-of-day> (interval)"
|
|
, pattern =
|
|
[ regex "(later than|from|(in[\\s-])?between)"
|
|
, Predicate isATimeOfDay
|
|
, regex "((but )?before)|\\-|to|th?ru|through|(un)?til(l)?"
|
|
, Predicate isATimeOfDay
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td1:_:Token Time td2:_) ->
|
|
Token Time <$> interval TTime.Closed td1 td2
|
|
_ -> Nothing
|
|
}
|
|
|
|
-- We can't take generic TOD (e.g. "6:30am - 9pm").
|
|
-- Those are handled by other rules.
|
|
ruleIntervalTODAMPM :: Rule
|
|
ruleIntervalTODAMPM = Rule
|
|
{ name = "hh(:mm) - <time-of-day> am|pm"
|
|
, pattern =
|
|
[ regex "(?:from )?((?:[01]?\\d)|(?:2[0-3]))([:.]([0-5]\\d))?"
|
|
, regex "\\-|:|to|th?ru|through|(un)?til(l)?"
|
|
, Predicate isATimeOfDay
|
|
, regex "(in the )?([ap])(\\s|\\.)?m?\\.?"
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token RegexMatch (GroupMatch (hh:_:mm:_)):
|
|
_:
|
|
Token Time td2:
|
|
Token RegexMatch (GroupMatch (_:ap:_)):
|
|
_) -> do
|
|
h <- parseInt hh
|
|
let ampm = Text.toLower ap == "a"
|
|
td1 = case parseInt mm of
|
|
Just m -> hourMinute True h m
|
|
Nothing -> hour True h
|
|
Token Time <$>
|
|
interval TTime.Closed (timeOfDayAMPM td1 ampm) (timeOfDayAMPM td2 ampm)
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleIntervalTODBetween :: Rule
|
|
ruleIntervalTODBetween = Rule
|
|
{ name = "between <time-of-day> and <time-of-day> (interval)"
|
|
, pattern =
|
|
[ regex "between"
|
|
, Predicate isATimeOfDay
|
|
, regex "and"
|
|
, Predicate isATimeOfDay
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td1:_:Token Time td2:_) ->
|
|
Token Time <$> interval TTime.Closed td1 td2
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleIntervalBy :: Rule
|
|
ruleIntervalBy = Rule
|
|
{ name = "by <time>"
|
|
, pattern =
|
|
[ regex "by"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) ->
|
|
Token Time <$> interval TTime.Open (cycleNth TG.Second 0) td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleIntervalByTheEndOf :: Rule
|
|
ruleIntervalByTheEndOf = Rule
|
|
{ name = "by the end of <time>"
|
|
, pattern =
|
|
[ regex "by (the )?end of"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) ->
|
|
Token Time <$> interval TTime.Closed (cycleNth TG.Second 0) td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleIntervalUntilTOD :: Rule
|
|
ruleIntervalUntilTOD = Rule
|
|
{ name = "until <time-of-day>"
|
|
, pattern =
|
|
[ regex "(anytime |sometimes? )?(before|(un)?til(l)?|through|up to)"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) -> tt $ withDirection TTime.Before td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleIntervalAfterTOD :: Rule
|
|
ruleIntervalAfterTOD = Rule
|
|
{ name = "after <time-of-day>"
|
|
, pattern =
|
|
[ regex "(anytime |sometimes? )?after"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) -> tt $ withDirection TTime.After td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleIntervalSinceTOD :: Rule
|
|
ruleIntervalSinceTOD = Rule
|
|
{ name = "since <time-of-day>"
|
|
, pattern =
|
|
[ regex "since"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Time td:_) -> tt $ withDirection TTime.After td
|
|
_ -> Nothing
|
|
}
|
|
|
|
daysOfWeek :: [(Text, String)]
|
|
daysOfWeek =
|
|
[ ( "Monday" , "monday|mon\\.?" )
|
|
, ( "Tuesday" , "tuesday|tues?\\.?" )
|
|
, ( "Wednesday", "wed?nesday|wed\\.?" )
|
|
, ( "Thursday" , "thursday|thu(rs?)?\\.?" )
|
|
, ( "Friday" , "friday|fri\\.?" )
|
|
, ( "Saturday" , "saturday|sat\\.?" )
|
|
, ( "Sunday" , "sunday|sun\\.?" )
|
|
]
|
|
|
|
ruleDaysOfWeek :: [Rule]
|
|
ruleDaysOfWeek = zipWith go daysOfWeek [1..7]
|
|
where
|
|
go (name, regexPattern) i = Rule
|
|
{ name = name
|
|
, pattern = [regex regexPattern]
|
|
, prod = \_ -> tt $ dayOfWeek i
|
|
}
|
|
|
|
months :: [(Text, String)]
|
|
months =
|
|
[ ( "January" , "january|jan\\.?" )
|
|
, ( "February" , "february|feb\\.?" )
|
|
, ( "March" , "march|mar\\.?" )
|
|
, ( "April" , "april|apr\\.?" )
|
|
, ( "May" , "may" )
|
|
, ( "June" , "june|jun\\.?" )
|
|
, ( "July" , "july|jul\\.?" )
|
|
, ( "August" , "august|aug\\.?" )
|
|
, ( "September", "september|sept?\\.?" )
|
|
, ( "October" , "october|oct\\.?" )
|
|
, ( "November" , "november|nov\\.?" )
|
|
, ( "December" , "december|dec\\.?" )
|
|
]
|
|
|
|
ruleMonths :: [Rule]
|
|
ruleMonths = zipWith go months [1..12]
|
|
where
|
|
go (name, regexPattern) i = Rule
|
|
{ name = name
|
|
, pattern = [regex regexPattern]
|
|
, prod = \_ -> tt $ month i
|
|
}
|
|
|
|
usHolidays :: [(Text, String, Int, Int)]
|
|
usHolidays =
|
|
[ ( "Christmas" , "(xmas|christmas)( day)?" , 12, 25 )
|
|
, ( "Christmas Eve" , "(xmas|christmas)( day)?('s)? eve", 12, 24 )
|
|
, ( "New Year's Eve" , "new year'?s? eve" , 12, 31 )
|
|
, ( "New Year's Day" , "new year'?s?( day)?" , 1 , 1 )
|
|
, ( "Valentine's Day" , "valentine'?s?( day)?" , 2 , 14 )
|
|
, ( "Independence Day", "independence day" , 7 , 4 )
|
|
, ( "Halloween" , "hall?owe?en( day)?" , 10, 31 )
|
|
]
|
|
|
|
ruleUSHolidays :: [Rule]
|
|
ruleUSHolidays = map go usHolidays
|
|
where
|
|
go (name, regexPattern, m, d) = Rule
|
|
{ name = name
|
|
, pattern = [regex regexPattern]
|
|
, prod = \_ -> tt $ monthDay m d
|
|
}
|
|
|
|
moreUSHolidays :: [(Text, String, Int, Int, Int)]
|
|
moreUSHolidays =
|
|
[ ( "Martin Luther King's Day" -- Third Monday of January
|
|
, "(MLK|Martin Luther King,?)( Jr.?| Junior)? day"
|
|
, 3, 1, 1
|
|
)
|
|
, ( "Father's Day" -- Third Sunday of June
|
|
, "father'?s?'? day"
|
|
, 2, 7, 6
|
|
)
|
|
, ( "Mother's Day" -- Second Sunday of May
|
|
, "mother'?s?'? day"
|
|
, 1, 7, 5
|
|
)
|
|
, ( "Thanksgiving Day" -- Fourth Thursday of November
|
|
, "thanks?giving( day)?"
|
|
, 4, 4, 11
|
|
)
|
|
, ( "Black Friday" -- Fourth Friday of November
|
|
, "black frid?day"
|
|
, 4, 5, 11
|
|
)
|
|
, ( "Labor Day" -- First Monday of September
|
|
, "labor day"
|
|
, 1, 1, 9
|
|
)
|
|
]
|
|
|
|
ruleMoreUSHolidays :: [Rule]
|
|
ruleMoreUSHolidays = map go moreUSHolidays
|
|
where
|
|
go (name, regexPattern, n, dow, m) = Rule
|
|
{ name = name
|
|
, pattern = [regex regexPattern]
|
|
, prod = \_ -> tt $ nthDOWOfMonth n dow m
|
|
}
|
|
|
|
-- Last Monday of May
|
|
ruleMemorialDay :: Rule
|
|
ruleMemorialDay = Rule
|
|
{ name = "Memorial Day"
|
|
, pattern = [regex "memorial day"]
|
|
, prod = \_ -> tt $ predLastOf (dayOfWeek 1) (month 5)
|
|
}
|
|
|
|
-- Long weekend before the last Monday of May
|
|
ruleMemorialDayWeekend :: Rule
|
|
ruleMemorialDayWeekend = Rule
|
|
{ name = "Memorial Day Weekend"
|
|
, pattern = [regex "memorial day week(\\s|-)?end"]
|
|
, prod = \_ ->
|
|
tt . longWEBefore $ predLastOf (dayOfWeek 1) (month 5)
|
|
}
|
|
|
|
-- Long weekend before the first Monday of September
|
|
ruleLaborDayWeekend :: Rule
|
|
ruleLaborDayWeekend = Rule
|
|
{ name = "Labor Day weekend"
|
|
, pattern = [regex "labor day week(\\s|-)?end"]
|
|
, prod = \_ -> tt . longWEBefore $ nthDOWOfMonth 1 1 9
|
|
}
|
|
|
|
ruleCycleThisLastNext :: Rule
|
|
ruleCycleThisLastNext = Rule
|
|
{ name = "this|last|next <cycle>"
|
|
, pattern =
|
|
[ regex "(this|current|coming|next|the following|last|past|previous)"
|
|
, dimension TimeGrain
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token RegexMatch (GroupMatch (match:_)):Token TimeGrain grain:_) ->
|
|
case Text.toLower match of
|
|
"this" -> tt $ cycleNth grain 0
|
|
"coming" -> tt $ cycleNth grain 0
|
|
"current" -> tt $ cycleNth grain 0
|
|
"last" -> tt . cycleNth grain $ - 1
|
|
"past" -> tt . cycleNth grain $ - 1
|
|
"previous" -> tt . cycleNth grain $ - 1
|
|
"next" -> tt $ cycleNth grain 1
|
|
"the following" -> tt $ cycleNth grain 1
|
|
_ -> Nothing
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleCycleTheAfterBeforeTime :: Rule
|
|
ruleCycleTheAfterBeforeTime = Rule
|
|
{ name = "the <cycle> after|before <time>"
|
|
, pattern =
|
|
[ regex "the"
|
|
, dimension TimeGrain
|
|
, regex "(after|before)"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
( _
|
|
: Token TimeGrain grain
|
|
: Token RegexMatch (GroupMatch (match:_))
|
|
: Token Time td
|
|
: _) ->
|
|
let n = if match == "after" then 1 else - 1 in
|
|
tt $ cycleNthAfter False grain n td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleCycleAfterBeforeTime :: Rule
|
|
ruleCycleAfterBeforeTime = Rule
|
|
{ name = "<cycle> after|before <time>"
|
|
, pattern =
|
|
[ dimension TimeGrain
|
|
, regex "(after|before)"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token TimeGrain grain:Token RegexMatch (GroupMatch (match:_)):Token Time td:_) ->
|
|
let n = if match == "after" then 1 else - 1 in
|
|
tt $ cycleNthAfter False grain n td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleCycleLastNextN :: Rule
|
|
ruleCycleLastNextN = Rule
|
|
{ name = "last|next n <cycle>"
|
|
, pattern =
|
|
[ regex "((last|past)|(next))"
|
|
, Predicate $ isIntegerBetween 1 9999
|
|
, dimension TimeGrain
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token RegexMatch (GroupMatch (match:_)):token:Token TimeGrain grain:_) -> do
|
|
n <- getIntValue token
|
|
tt . cycleN True grain $ if match == "next" then n else - n
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleCycleOrdinalOfTime :: Rule
|
|
ruleCycleOrdinalOfTime = Rule
|
|
{ name = "<ordinal> <cycle> of <time>"
|
|
, pattern =
|
|
[ dimension Ordinal
|
|
, dimension TimeGrain
|
|
, regex "of|in|from"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(token:Token TimeGrain grain:_:Token Time td:_) -> do
|
|
n <- getIntValue token
|
|
tt $ cycleNthAfter True grain (n - 1) td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleCycleTheOrdinalOfTime :: Rule
|
|
ruleCycleTheOrdinalOfTime = Rule
|
|
{ name = "the <ordinal> <cycle> of <time>"
|
|
, pattern =
|
|
[ regex "the"
|
|
, dimension Ordinal
|
|
, dimension TimeGrain
|
|
, regex "of|in|from"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:token:Token TimeGrain grain:_:Token Time td:_) -> do
|
|
n <- getIntValue token
|
|
tt $ cycleNthAfter True grain (n - 1) td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleCycleTheOfTime :: Rule
|
|
ruleCycleTheOfTime = Rule
|
|
{ name = "the <cycle> of <time>"
|
|
, pattern =
|
|
[ regex "the"
|
|
, dimension TimeGrain
|
|
, regex "of"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token TimeGrain grain:_:Token Time td:_) ->
|
|
tt $ cycleNthAfter True grain 0 td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleCycleOrdinalAfterTime :: Rule
|
|
ruleCycleOrdinalAfterTime = Rule
|
|
{ name = "<ordinal> <cycle> after <time>"
|
|
, pattern =
|
|
[ dimension Ordinal
|
|
, dimension TimeGrain
|
|
, regex "after"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(token:Token TimeGrain grain:_:Token Time td:_) -> do
|
|
n <- getIntValue token
|
|
tt $ cycleNthAfter True grain (n - 1) td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleCycleTheOrdinalAfterTime :: Rule
|
|
ruleCycleTheOrdinalAfterTime = Rule
|
|
{ name = "<ordinal> <cycle> after <time>"
|
|
, pattern =
|
|
[ regex "the"
|
|
, dimension Ordinal
|
|
, dimension TimeGrain
|
|
, regex "after"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:token:Token TimeGrain grain:_:Token Time td:_) -> do
|
|
n <- getIntValue token
|
|
tt $ cycleNthAfter True grain (n - 1) td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleCycleOrdinalQuarter :: Rule
|
|
ruleCycleOrdinalQuarter = Rule
|
|
{ name = "<ordinal> quarter"
|
|
, pattern =
|
|
[ dimension Ordinal
|
|
, Predicate $ isGrain TG.Quarter
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(token:_) -> do
|
|
n <- getIntValue token
|
|
tt . cycleNthAfter True TG.Quarter (n - 1) $
|
|
cycleNth TG.Year 0
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleCycleTheOrdinalQuarter :: Rule
|
|
ruleCycleTheOrdinalQuarter = Rule
|
|
{ name = "the <ordinal> quarter"
|
|
, pattern =
|
|
[ regex "the"
|
|
, dimension Ordinal
|
|
, Predicate $ isGrain TG.Quarter
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:token:_) -> do
|
|
n <- getIntValue token
|
|
tt . cycleNthAfter True TG.Quarter (n - 1) $
|
|
cycleNth TG.Year 0
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleCycleOrdinalQuarterYear :: Rule
|
|
ruleCycleOrdinalQuarterYear = Rule
|
|
{ name = "<ordinal> quarter <year>"
|
|
, pattern =
|
|
[ dimension Ordinal
|
|
, Predicate $ isGrain TG.Quarter
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(token:_:Token Time td:_) -> do
|
|
n <- getIntValue token
|
|
tt $ cycleNthAfter False TG.Quarter (n - 1) td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleDurationInWithinAfter :: Rule
|
|
ruleDurationInWithinAfter = Rule
|
|
{ name = "in|within|after <duration>"
|
|
, pattern =
|
|
[ regex "(in|within|after)"
|
|
, dimension Duration
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token RegexMatch (GroupMatch (match:_)):
|
|
Token Duration dd:
|
|
_) -> case Text.toLower match of
|
|
"within" -> Token Time <$>
|
|
interval TTime.Open (cycleNth TG.Second 0) (inDuration dd)
|
|
"after" -> tt . withDirection TTime.After $ inDuration dd
|
|
"in" -> tt $ inDuration dd
|
|
_ -> Nothing
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleDurationHenceAgo :: Rule
|
|
ruleDurationHenceAgo = Rule
|
|
{ name = "<duration> hence|ago"
|
|
, pattern =
|
|
[ dimension Duration
|
|
, regex "(hence|ago)"
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Duration dd:
|
|
Token RegexMatch (GroupMatch (match:_)):
|
|
_) -> case Text.toLower match of
|
|
"ago" -> tt $ durationAgo dd
|
|
_ -> tt $ inDuration dd
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleInNumeral :: Rule
|
|
ruleInNumeral = Rule
|
|
{ name = "in <number> (implicit minutes)"
|
|
, pattern =
|
|
[ regex "in"
|
|
, Predicate $ isIntegerBetween 0 60
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(_:Token Numeral (NumeralData {TNumeral.value = v}):_) ->
|
|
tt . inDuration . duration TG.Minute $ floor v
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleDurationAfterBeforeTime :: Rule
|
|
ruleDurationAfterBeforeTime = Rule
|
|
{ name = "<duration> after|before|from <time>"
|
|
, pattern =
|
|
[ dimension Duration
|
|
, regex "(after|before|from)"
|
|
, dimension Time
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Duration dd:
|
|
Token RegexMatch (GroupMatch (match:_)):
|
|
Token Time td:
|
|
_) -> case Text.toLower match of
|
|
"before" -> tt $ durationBefore dd td
|
|
_ -> tt $ durationAfter dd td
|
|
_ -> Nothing
|
|
}
|
|
|
|
ruleTimezone :: Rule
|
|
ruleTimezone = Rule
|
|
{ name = "<time> timezone"
|
|
, pattern =
|
|
[ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
|
|
, regex "\\b(YEKT|YEKST|YAKT|YAKST|WITA|WIT|WIB|WGT|WGST|WFT|WET|WEST|WAT|WAST|VUT|VLAT|VLAST|VET|UZT|UYT|UYST|UTC|ULAT|TVT|TMT|TLT|TKT|TJT|TFT|TAHT|SST|SRT|SGT|SCT|SBT|SAST|SAMT|RET|PYT|PYST|PWT|PST|PONT|PMST|PMDT|PKT|PHT|PHOT|PGT|PETT|PETST|PET|PDT|OMST|OMSST|NZST|NZDT|NUT|NST|NPT|NOVT|NOVST|NFT|NDT|NCT|MYT|MVT|MUT|MST|MSK|MSD|MMT|MHT|MDT|MAWT|MART|MAGT|MAGST|LINT|LHST|LHDT|KUYT|KST|KRAT|KRAST|KGT|JST|IST|IRST|IRKT|IRKST|IRDT|IOT|IDT|ICT|HOVT|HKT|GYT|GST|GMT|GILT|GFT|GET|GAMT|GALT|FNT|FKT|FKST|FJT|FJST|EST|EGT|EGST|EET|EEST|EDT|ECT|EAT|EAST|EASST|DAVT|ChST|CXT|CVT|CST|COT|CLT|CLST|CKT|CHAST|CHADT|CET|CEST|CDT|CCT|CAT|CAST|BTT|BST|BRT|BRST|BOT|BNT|AZT|AZST|AZOT|AZOST|AWST|AWDT|AST|ART|AQTT|ANAT|ANAST|AMT|AMST|ALMT|AKST|AKDT|AFT|AEST|AEDT|ADT|ACST|ACDT)\\b"
|
|
]
|
|
, prod = \tokens -> case tokens of
|
|
(Token Time td:
|
|
Token RegexMatch (GroupMatch (tz:_)):
|
|
_) -> Token Time <$> inTimezone tz td
|
|
_ -> Nothing
|
|
}
|
|
|
|
rules :: [Rule]
|
|
rules =
|
|
[ ruleIntersect
|
|
, ruleIntersectOf
|
|
, ruleAbsorbOnTime
|
|
, ruleAbsorbOnADOW
|
|
, ruleAbsorbInMonth
|
|
, ruleAbsorbCommaTOD
|
|
, ruleNextDOW
|
|
, ruleThisTime
|
|
, ruleNextTime
|
|
, ruleLastTime
|
|
, ruleTimeBeforeLastAfterNext
|
|
, ruleLastDOWOfTime
|
|
, ruleLastCycleOfTime
|
|
, ruleNthTimeOfTime
|
|
, ruleTheNthTimeOfTime
|
|
, ruleNthTimeAfterTime
|
|
, ruleTheNthTimeAfterTime
|
|
, ruleYear
|
|
, ruleYearPastLatent
|
|
, ruleYearFutureLatent
|
|
, ruleTheDOMNumeral
|
|
, ruleTheDOMOrdinal
|
|
, ruleDOMLatent
|
|
, ruleNamedDOMOrdinal
|
|
, ruleMonthDOMNumeral
|
|
, ruleDOMMonth
|
|
, ruleDOMOfMonth
|
|
, ruleDOMOrdinalMonthYear
|
|
, ruleIdesOfMonth
|
|
, ruleTODLatent
|
|
, ruleAtTOD
|
|
, ruleTODOClock
|
|
, ruleHHMM
|
|
, ruleHHMMLatent
|
|
, ruleHHMMSS
|
|
, ruleMilitaryAMPM
|
|
, ruleTODAMPM
|
|
, ruleHONumeral
|
|
, ruleHODHalf
|
|
, ruleHODQuarter
|
|
, ruleNumeralToHOD
|
|
, ruleHalfToHOD
|
|
, ruleQuarterToHOD
|
|
, ruleNumeralAfterHOD
|
|
, ruleHalfAfterHOD
|
|
, ruleQuarterAfterHOD
|
|
, ruleHalfHOD
|
|
, ruleMMDDYYYY
|
|
, ruleYYYYMMDD
|
|
, ruleMMDD
|
|
, ruleNoonMidnightEOD
|
|
, rulePartOfDays
|
|
, ruleEarlyMorning
|
|
, rulePODIn
|
|
, rulePODThis
|
|
, ruleTonight
|
|
, ruleAfterPartofday
|
|
, ruleTimePOD
|
|
, rulePODofTime
|
|
, ruleWeekend
|
|
, ruleSeasons
|
|
, ruleTODPrecision
|
|
, rulePrecisionTOD
|
|
, ruleIntervalMonthDDDD
|
|
, ruleIntervalDash
|
|
, ruleIntervalFrom
|
|
, ruleIntervalBetween
|
|
, ruleIntervalTODDash
|
|
, ruleIntervalTODFrom
|
|
, ruleIntervalTODAMPM
|
|
, ruleIntervalTODBetween
|
|
, ruleIntervalBy
|
|
, ruleIntervalByTheEndOf
|
|
, ruleIntervalUntilTOD
|
|
, ruleIntervalAfterTOD
|
|
, ruleIntervalSinceTOD
|
|
, ruleMemorialDay
|
|
, ruleMemorialDayWeekend
|
|
, ruleLaborDayWeekend
|
|
, ruleCycleThisLastNext
|
|
, ruleCycleTheAfterBeforeTime
|
|
, ruleCycleAfterBeforeTime
|
|
, ruleCycleLastNextN
|
|
, ruleCycleOrdinalOfTime
|
|
, ruleCycleTheOrdinalOfTime
|
|
, ruleCycleTheOfTime
|
|
, ruleCycleOrdinalAfterTime
|
|
, ruleCycleTheOrdinalAfterTime
|
|
, ruleCycleOrdinalQuarter
|
|
, ruleCycleTheOrdinalQuarter
|
|
, ruleCycleOrdinalQuarterYear
|
|
, ruleDurationInWithinAfter
|
|
, ruleDurationHenceAgo
|
|
, ruleDurationAfterBeforeTime
|
|
, ruleInNumeral
|
|
, ruleTimezone
|
|
]
|
|
++ ruleInstants
|
|
++ ruleDaysOfWeek
|
|
++ ruleMonths
|
|
++ ruleUSHolidays
|
|
++ ruleMoreUSHolidays
|