Duckling/Time/EN/Rules.hs (2,681 lines of code) (raw):

-- 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. {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NoRebindableSyntax #-} {-# LANGUAGE OverloadedStrings #-} module Duckling.Time.EN.Rules where import Control.Applicative ((<|>)) import Control.Monad (guard) import Data.Maybe import Prelude import qualified Data.Text as Text import Duckling.Dimensions.Types import Duckling.Duration.Helpers (duration, isGrain) import Duckling.Duration.Types (DurationData (..)) import Duckling.Numeral.Helpers (isNatural, parseInt) import Duckling.Numeral.Types (NumeralData (..)) import Duckling.Ordinal.Types (OrdinalData (..)) import Duckling.Regex.Types import Duckling.Time.Computed import Duckling.Time.Helpers import Duckling.Time.HolidayHelpers import Duckling.Time.Types (TimeData (..), TimeIntervalType (..)) import Duckling.Types import qualified Duckling.Duration.Types as TDuration import qualified Duckling.Numeral.Types as TNumeral import qualified Duckling.Ordinal.Types as TOrdinal import qualified Duckling.Time.Types as TTime import qualified Duckling.TimeGrain.Types as TG ruleIntersect :: Rule ruleIntersect = Rule { name = "intersect" , pattern = [ Predicate $ isGrainFinerThan TG.Year , Predicate $ or . sequence [isNotLatent, isGrainOfTime TG.Year] ] , prod = \tokens -> case tokens of (Token Time td1:Token Time td2:_) | not (TTime.latent td1) || not (TTime.latent td2) -> Token Time . notLatent <$> 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 . notLatent <$> intersect td1 td2 _ -> Nothing } ruleIntersectYear :: Rule ruleIntersectYear = Rule { name = "intersect by \",\", \"of\", \"from\" for year" , pattern = [ Predicate isNotLatent , regex "of|from|," , Predicate $ isGrainOfTime TG.Year ] , prod = \tokens -> case tokens of (Token Time td1:_:Token Time td2:_) -> Token Time . notLatent <$> intersect td1 td2 _ -> Nothing } ruleAbsorbOnDay :: Rule ruleAbsorbOnDay = Rule { name = "on <day>" , pattern = [ regex "on" , Predicate $ isGrainOfTime TG.Day ] , 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 } ruleAbsorbInMonthYear :: Rule ruleAbsorbInMonthYear = Rule { name = "in|during <named-month>|year" , pattern = [ regex "in|during" , Predicate $ or . sequence [isAMonth, isGrainOfTime TG.Year] ] , prod = \tokens -> case tokens of (_:Token Time td:_) -> tt $ notLatent td _ -> Nothing } ruleAbsorbCommaTOD :: Rule ruleAbsorbCommaTOD = Rule { name = "absorption of , after named day" , pattern = [ Predicate isADayOfWeek , regex "," ] , prod = \tokens -> case tokens of (token:_) -> Just token _ -> Nothing } ruleInstants :: [Rule] ruleInstants = mkRuleInstants [ ("right now" , TG.Second, 0 , "((just|right)\\s*)now|immediately") , ("today" , TG.Day , 0 , "todays?|(at this time)" ) , ("tomorrow" , TG.Day , 1 , "(tmrw?|tomm?or?rows?)" ) , ("yesterday" , TG.Day , - 1, "yesterdays?" ) ] ruleNow :: Rule ruleNow = Rule { name = "now" , pattern = [ regex "now|at\\sthe\\smoment|atm" ] , prod = \_ -> tt now } ruleASAP :: Rule ruleASAP = Rule { name = "as soon as possible" , pattern = [ regex "asap|as\\ssoon\\sas\\spossible" ] , prod = \_ -> tt $ withDirection TTime.After now } ruleNextDOW :: Rule ruleNextDOW = Rule { name = "this|next <day-of-week>" , pattern = [ regex "(this|next)" , Predicate isADayOfWeek ] , prod = \case ( Token RegexMatch (GroupMatch (match:_)): Token Time dow: _) -> do td <- case Text.toLower match of "this" -> Just $ predNth 0 True dow "next" -> intersect dow $ cycleNth TG.Week 1 _ -> Nothing tt td _ -> Nothing } ruleThisTime :: Rule ruleThisTime = Rule { name = "this <time>" , pattern = [ regex "this|current|coming" , Predicate isOkWithThisNext ] , 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 isOkWithThisNext ] , 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)" , Predicate isOkWithThisNext ] , prod = \tokens -> case tokens of (_:Token Time td:_) -> tt $ predNth (- 1) False td _ -> Nothing } ruleLastWeekendOfMonth :: Rule ruleLastWeekendOfMonth = Rule { name = "last weekend of <named-month>" , pattern = [ regex "last\\s(week(\\s|-)?end|wkend)\\s(of|in)" , Predicate isAMonth ] , prod = \tokens -> case tokens of (_:Token Time td2:_) -> tt $ predLastOf weekend td2 _ -> 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 (Text.toLower match == "after next") td _ -> Nothing } ruleOrdinalDOWOfTime :: Rule ruleOrdinalDOWOfTime = Rule { name = "first|second|third|fourth|fifth <day-of-week> of <time>" , pattern = [ Predicate $ isOrdinalBetween 1 5 , Predicate isADayOfWeek , regex "(of|in)" , dimension Time ] , prod = \case ( token: Token Time td1: _: Token Time td2: _) -> do ord <- getIntValue token td <- Just $ predNthAfter (ord-1) td1 td2 tt td _ -> Nothing } ruleLastDOWOfTime :: Rule ruleLastDOWOfTime = Rule { name = "last <day-of-week> of <time>" , pattern = [ regex "last" , Predicate isADayOfWeek , regex "(of|in)" , dimension Time ] , prod = \case ( _: 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 } ruleLastNight :: Rule ruleLastNight = Rule { name = "last night" , pattern = [ regex "(late )?last night" ] , prod = \tokens -> case tokens of (Token RegexMatch (GroupMatch (match:_)):_) -> let hours = if Text.toLower match == "late " then 3 else 6 start = durationBefore (DurationData hours TG.Hour) today in Token Time . partOfDay . notLatent <$> interval TTime.Open start today _ -> Nothing } ruleNthTimeOfTime :: Rule ruleNthTimeOfTime = Rule { name = "nth <day-of-week> of <month-or-greater>" , pattern = [ dimension Ordinal , Predicate isADayOfWeek , regex "of|in" , Predicate $ not . isGrainFinerThan TG.Month ] , 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 <day-of-week> of <month-or-greater>" , pattern = [ regex "the" , dimension Ordinal , Predicate isADayOfWeek , regex "of|in" , Predicate $ not . isGrainFinerThan TG.Month ] , 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 } ruleNDOWFromTime :: Rule ruleNDOWFromTime = Rule { name = "<integer> <day-of-week> from <time>" , pattern = [ dimension Numeral , Predicate isADayOfWeek , regex "from" , dimension Time ] , prod = \tokens -> case tokens of (token:Token Time td1:_:Token Time td2:_) -> do n <- getIntValue token tt $ predNthAfter (n - 1) td1 td2 _ -> Nothing } ruleYearLatent :: Rule ruleYearLatent = Rule { name = "year (latent)" , pattern = [ Predicate $ isIntegerBetween 25 10000 ] , prod = \case (token:_) -> do n <- getIntValue token tt $ mkLatent $ year n _ -> Nothing } ruleYearADBC :: Rule ruleYearADBC = Rule { name = "<year> (bc|ad)" , pattern = [ Predicate $ isIntegerBetween 1 10000 , regex "(a\\.?d\\.?|b\\.?c\\.?)" ] , prod = \case (token:Token RegexMatch (GroupMatch (ab:_)):_) -> do y <- getIntValue token tt $ yearADBC $ if Text.head (Text.toLower ab) == 'b' then -y else y _ -> 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 $ or . sequence [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 } ruleTheDOMOfMonth :: Rule ruleTheDOMOfMonth = Rule { name = "the <day-of-month> (ordinal or number) of <named-month>" , pattern = [ regex "the" , Predicate isDOMValue , regex "of|in" , Predicate isAMonth ] , prod = \tokens -> case tokens of (_:token:_:Token Time td:_) -> 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 } ruleDOMMonthYear :: Rule ruleDOMMonthYear = Rule { name = "<day-of-month>(ordinal or number)/<named-month>/year" , pattern = [ Predicate isDOMValue , regex "[-/\\s]" , Predicate isAMonth , regex "[-/\\s]" , 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 } 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 (n < 13) 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 } rulePODatTOD :: Rule rulePODatTOD = Rule { name = "<part-of-day> at <time-of-day>" , pattern = [ Predicate isAPartOfDay , regex "at|@" , Predicate isATimeOfDay ] , prod = \tokens -> case tokens of (Token Time TimeData{TTime.timePred = TTime.IntersectPredicate (TTime.TimeIntervalsPredicate _ TTime.TimeDatePredicate{TTime.tdHour = Just (False, start)} _) _}:_: Token Time tod@TimeData{TTime.form = Just (TTime.TimeOfDay (Just hours) True)}:_) -> tt $ timeOfDayAMPM (start < 12 || hours == 12) tod (Token Time TimeData{TTime.timePred = TTime.TimeIntervalsPredicate _ TTime.TimeDatePredicate{TTime.tdHour = Just (False, start)} _}:_: Token Time tod@TimeData{TTime.form = Just (TTime.TimeOfDay (Just hours) True)}:_) -> tt $ timeOfDayAMPM (start < 12 || hours == 12) tod _ -> Nothing } rulePODintersectTODlatent :: Rule rulePODintersectTODlatent = Rule { name = "<part-of-day> <latent-time-of-day> (latent)" , pattern = [ Predicate isAPartOfDay , Predicate isATimeOfDay ] , prod = \tokens -> case tokens of (Token Time td1:Token Time td2:_) | not (TTime.latent td1) || TTime.latent td2 -> Token Time . mkLatent <$> intersect td1 td2 _ -> 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 (h /= 0 && h < 12) h m _ -> Nothing } ruleHHhMM :: Rule ruleHHhMM = Rule { name = "hhhmm" , pattern = [ regex "(?<!/)((?:[01]?\\d)|(?:2[0-3]))h(([0-5]\\d)|(?!\\d))" ] , prod = \case (Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do h <- parseInt hh m <- parseInt mm <|> Just 0 tt $ hourMinute False h m _ -> Nothing } ruleHHMMLatent :: Rule ruleHHMMLatent = Rule { name = "hhmm (latent)" , pattern = [ regex "((?:[01]?\\d)|(?:2[0-3]))([0-5]\\d)(?!.\\d)" ] , prod = \tokens -> case tokens of (Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do h <- parseInt hh m <- parseInt mm tt $ mkLatent $ hourMinute (h < 12) 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 (h < 12) 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 (Text.toLower ap == "a") $ hourMinute True h m _ -> 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@TimeData{TTime.latent = True}: Token RegexMatch (GroupMatch (_:ap:_:"":_)): _) -> tt $ mkLatent $ timeOfDayAMPM (Text.toLower ap == "a") td (Token Time td@TimeData{TTime.form = Just (TTime.TimeOfDay (Just hours) _)}: Token RegexMatch (GroupMatch (_:ap:_)): _) | hours < 13 -> tt $ timeOfDayAMPM (Text.toLower ap == "a") td _ -> Nothing } ruleHONumeral :: Rule ruleHONumeral = Rule { name = "<hour-of-day> <integer>" , pattern = [ Predicate isAnHourOfDay , Predicate $ isIntegerBetween 10 59 ] , prod = \tokens -> case tokens of (Token Time TimeData{TTime.form = Just (TTime.TimeOfDay (Just hours) is12H) ,TTime.latent = isLatent}: token: _) -> do n <- getIntValue token if isLatent then tt $ mkLatent $ hourMinute is12H hours n else tt $ hourMinute is12H hours n _ -> Nothing } ruleHONumeralDash :: Rule ruleHONumeralDash = Rule { name = "<hour-of-day> - <integer-as-word>" , pattern = [ Predicate isAnHourOfDay , regex "-(?!\\d)" , Predicate $ isIntegerBetween 10 59 ] , prod = \tokens -> case tokens of (Token Time TimeData{TTime.form = Just (TTime.TimeOfDay (Just hours) is12H) ,TTime.latent = isLatent}: _: token: _) -> do n <- getIntValue token let lt = if isLatent then mkLatent else id tt $ lt $ hourMinute is12H hours n _ -> Nothing } ruleHONumeralAlt :: Rule ruleHONumeralAlt = Rule { name = "<hour-of-day> zero <integer>" , pattern = [ Predicate isAnHourOfDay , regex "(zero|o(h|u)?)" , Predicate $ isIntegerBetween 1 9 ] , prod = \case ( Token Time TimeData{TTime.form = Just (TTime.TimeOfDay (Just hours) is12H) , TTime.latent = isLatent}: _: token: _) -> do n <- getIntValue token if isLatent then tt $ mkLatent $ hourMinute is12H hours n else tt $ hourMinute is12H hours n _ -> Nothing } ruleHONumeralAltDash :: Rule ruleHONumeralAltDash = Rule { name = "<hour-of-day> - zero - <integer>" , pattern = [ Predicate isAnHourOfDay , regex "-" , regex "(zero|o(h|u)?)" , regex "-(?!\\d)" , Predicate $ isIntegerBetween 1 9 ] , prod = \case ( Token Time TimeData{TTime.form = Just (TTime.TimeOfDay (Just hours) is12H) , TTime.latent = isLatent}: _:_:_: token: _) -> do n <- getIntValue token let lt = if isLatent then mkLatent else id tt $ lt $ 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 Token Time <$> minutesBefore n td _ -> 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 Token Time <$> minutesAfter n td _ -> 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 "(for )?((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 } ruleMMYYYY :: Rule ruleMMYYYY = Rule { name = "mm/yyyy" , pattern = [ regex "(0?[1-9]|1[0-2])[/-](\\d{4})" ] , prod = \case (Token RegexMatch (GroupMatch (mm:yy:_)):_) -> do y <- parseInt yy m <- parseInt mm tt $ yearMonth y m _ -> Nothing } ruleYYYYMM :: Rule ruleYYYYMM = Rule { name = "yyyy-mm" , pattern = [ regex "(\\d{4})\\s*[/-]\\s*(1[0-2]|0?[1-9])" ] , prod = \case (Token RegexMatch (GroupMatch (yy:mm:_)):_) -> do y <- parseInt yy m <- parseInt mm tt $ yearMonth y m _ -> 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 = \case (Token RegexMatch (GroupMatch (yy:mm:dd:_)):_) -> do y <- parseInt yy m <- parseInt mm d <- parseInt dd tt $ yearMonthDay y m d _ -> Nothing } ruleYYYYQQ :: Rule ruleYYYYQQ = Rule { name = "yyyyqq" , pattern = [ regex "(\\d{2,4})q([1-4])" ] , prod = \case (Token RegexMatch (GroupMatch (yy:qq:_)):_) -> do y <- parseInt yy q <- parseInt qq tt $ cycleNthAfter True TG.Quarter (q - 1) $ year y _ -> 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 = \case (Token RegexMatch (GroupMatch (match:_)):_) -> tt $ hour False $ if Text.toLower match == "noon" then 12 else 0 _ -> Nothing } rulePartOfDays :: Rule rulePartOfDays = Rule { name = "part of days" , pattern = [ regex "(morning|after ?noo?n(ish)?|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 0, 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 0) (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 today td _ -> Nothing } ruleTonight :: Rule ruleTonight = Rule { name = "tonight" , pattern = [regex "(late )?toni(ght|gth|te)s?"] , prod = \tokens -> case tokens of (Token RegexMatch (GroupMatch (match:_)):_) -> do let h = if Text.toLower match == "late " then 21 else 18 evening <- interval TTime.Open (hour False h) (hour False 0) Token Time . partOfDay . notLatent <$> intersect today evening _ -> Nothing } 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 today 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)s?" ] , prod = \_ -> tt $ mkOkForThisNext weekend } ruleWeek :: Rule ruleWeek = Rule { name = "week" , pattern = [ regex "(all|rest of the|the) week" ] , prod = \case (Token RegexMatch (GroupMatch (match:_)):_) -> let end = cycleNthAfter True TG.Day (-2) $ cycleNth TG.Week 1 period = case Text.toLower match of "all" -> interval Closed (cycleNth TG.Week 0) end "rest of the" -> interval Open today end "the" -> interval Open today end _ -> Nothing in case Text.toLower match of "the" -> Token Time . mkLatent <$> period _ -> Token Time <$> period _ -> Nothing } ruleSeason :: Rule ruleSeason = Rule { name = "last|this|next <season>" , pattern = [ regex "(this|current|next|last|past|previous) seasons?" ] , prod = \case (Token RegexMatch (GroupMatch (match:_)):_) -> do n <- case Text.toLower match of "this" -> Just 0 "current" -> Just 0 "last" -> Just (-1) "past" -> Just (-1) "previous" -> Just (-1) "next" -> Just 1 _ -> Nothing tt $ predNth n False season _ -> Nothing } ruleSeasons :: [Rule] ruleSeasons = mkRuleSeasons [ ( "summer", "summer" , monthDay 6 21, monthDay 9 23 ) , ( "fall" , "fall|autumn", monthDay 9 23, monthDay 12 21 ) , ( "winter", "winter" , monthDay 12 21, monthDay 3 20 ) , ( "spring", "spring" , monthDay 3 20, monthDay 6 21 ) ] 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 } ruleTODPOD :: Rule ruleTODPOD = Rule { name = "<time-of-day> <part-of-day>" , pattern = [ Predicate isATimeOfDay , Predicate isAPartOfDay ] , prod = \tokens -> case tokens of (Token Time td:Token Time pod:_) -> Token Time . notLatent <$> intersect td pod _ -> 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 , Predicate isDOMValue , regex "\\-|to|th?ru|through|(un)?til(l)?" , Predicate isDOMValue ] , prod = \tokens -> case tokens of (Token Time td: token1: _: token2: _) -> do dom1 <- intersectDOM td token1 dom2 <- intersectDOM td token2 Token Time <$> interval TTime.Closed dom1 dom2 _ -> Nothing } ruleIntervalDDDDMonth :: Rule ruleIntervalDDDDMonth = Rule { name = "dd-dd <month> (interval)" , pattern = [ Predicate isDOMValue , regex "\\-|to|th?ru|through|(un)?til(l)?" , Predicate isDOMValue , Predicate isAMonth ] , prod = \tokens -> case tokens of (token1: _: token2: Token Time td: _) -> do dom1 <- intersectDOM td token1 dom2 <- intersectDOM td token2 Token Time <$> interval TTime.Closed dom1 dom2 _ -> Nothing } ruleIntervalFromMonthDDDD :: Rule ruleIntervalFromMonthDDDD = Rule { name = "from <month> dd-dd (interval)" , pattern = [ regex "from" , Predicate isAMonth , Predicate isDOMValue , regex "\\-|to|th?ru|through|(un)?til(l)?" , Predicate isDOMValue ] , prod = \tokens -> case tokens of (_: Token Time td: token1: _: token2: _) -> do dom1 <- intersectDOM td token1 dom2 <- intersectDOM td token2 Token Time <$> interval TTime.Closed dom1 dom2 _ -> Nothing } ruleIntervalFromDDDDMonth :: Rule ruleIntervalFromDDDDMonth = Rule { name = "from the <day-of-month> (ordinal or number) to the <day-of-month> (ordinal or number) <named-month> (interval)" , pattern = [ regex "from( the)?" , Predicate isDOMValue , regex "\\-|to( the)?|th?ru|through|(un)?til(l)?" , Predicate isDOMValue , Predicate isAMonth ] , prod = \tokens -> case tokens of (_: token1: _: token2: Token Time td: _) -> do dom1 <- intersectDOM td token1 dom2 <- intersectDOM td token2 Token Time <$> interval TTime.Closed dom1 dom2 _ -> Nothing } ruleIntervalFromDDDDOfMonth :: Rule ruleIntervalFromDDDDOfMonth = Rule { name = "from the <day-of-month> (ordinal or number) to the <day-of-month> (ordinal or number) of <named-month> (interval)" , pattern = [ regex "from( the)?" , Predicate isDOMValue , regex "\\-|to( the)?|th?ru|through|(un)?til(l)?" , Predicate isDOMValue , regex "of" , Predicate isAMonth ] , prod = \tokens -> case tokens of (_: token1: _: token2: _: Token Time td: _) -> do dom1 <- intersectDOM td token1 dom2 <- intersectDOM td token2 Token Time <$> interval TTime.Closed dom1 dom2 _ -> Nothing } -- In order to support latent year ranges, e.g. "1960 - 1961", we impose -- the following constraints: -- 1. Neither year can be negative. -- 2. The first year must be less than the second year. -- 3. The years must be within the interval [1000,10000]. -- (1): We could try to allow negative years, but years in natural language are -- almost never written as negative, so it's unnecessary complication. -- (2): Year ranges in natural language are written -- <earlier year> - <later year>. No need to derive the ordering from -- something which is not likely a year range. -- (3): Four+ digits prevents phone numbers (e.g. "333-444-5555") from -- registering false positives. -- In everyday language, people are more likely to mention years closer -- to the present than very far in the past or future (closer times are -- more relevant). -- Of course, this means we do not have the ability to parse something like -- "300 - 600" as a year interval. But, prior to implementing this -- rule, we already did not have that ability. -- -- These guidelines are not perfect, but they work. They can be iterated on and -- improved going forward. ruleIntervalYearLatent :: Rule ruleIntervalYearLatent = Rule { name = "<year> (latent) - <year> (latent) (interval)" , pattern = [ Predicate $ isIntegerBetween 1000 10000 , regex "\\-|to|th?ru|through|(un)?til(l)?" , Predicate $ isIntegerBetween 1000 10000 ] , prod = \case (t1:_:t2:_) -> do y1 <- getIntValue t1 y2 <- getIntValue t2 guard (y1 < y2) Token Time <$> interval TTime.Closed (year y1) (year y2) _ -> Nothing } -- Blocked for :latent time. ruleIntervalDash :: Rule ruleIntervalDash = Rule { name = "<datetime> - <datetime> (interval)" , pattern = [ Predicate isNotLatent , regex "\\-|to|th?ru|through|(un)?til(l)?" , Predicate isNotLatent ] , prod = \case (Token Time td1:_:Token Time td2:_) -> Token Time <$> interval TTime.Closed td1 td2 _ -> Nothing } ruleIntervalSlash :: Rule ruleIntervalSlash = Rule { name = "<datetime>/<datetime> (interval)" , pattern = [ Predicate isNotLatent , regex "/" , Predicate isNotLatent ] , prod = \tokens -> case tokens of (Token Time td1:_:Token Time td2:_) | sameGrain td1 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 $ and . sequence [isNotLatent, isATimeOfDay] , regex "\\-|:|to|th?ru|through|(un)?til(l)?" , Predicate isATimeOfDay ] , prod = \case (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 ampm td1) (timeOfDayAMPM ampm td2) _ -> 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 = \case (_: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 = \case (_:Token Time td:_) -> Token Time <$> interval TTime.Open now 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 now td _ -> Nothing } ruleIntervalUntilTime :: Rule ruleIntervalUntilTime = Rule { name = "until <time>" , 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 $ notLatent td _ -> Nothing } ruleIntervalAfterFromSinceTime :: Rule ruleIntervalAfterFromSinceTime = Rule { name = "from|since|after <time>" , pattern = [ regex "from|since|(anytime |sometimes? )?after" , dimension Time ] , prod = \tokens -> case tokens of (_:Token Time td:_) -> tt $ withDirection TTime.After $ notLatent td _ -> Nothing } ruleDaysOfWeek :: [Rule] ruleDaysOfWeek = mkRuleDaysOfWeek [ ( "Monday" , "mondays?|mon\\.?" ) , ( "Tuesday" , "tuesdays?|tues?\\.?" ) , ( "Wednesday", "wed?nesdays?|wed\\.?" ) , ( "Thursday" , "thursdays?|thu(rs?)?\\.?" ) , ( "Friday" , "fridays?|fri\\.?" ) , ( "Saturday" , "saturdays?|sat\\.?" ) , ( "Sunday" , "sundays?|sun\\.?" ) ] ruleMonths :: [Rule] ruleMonths = mkRuleMonthsWithLatent [ ( "January" , "january|jan\\.?" , False ) , ( "February" , "february|feb\\.?" , False ) , ( "March" , "march|mar\\.?" , False ) , ( "April" , "april|apr\\.?" , False ) , ( "May" , "may" , True ) , ( "June" , "june|jun\\.?" , False ) , ( "July" , "july|jul\\.?" , False ) , ( "August" , "august|aug\\.?" , False ) , ( "September", "september|sept?\\.?", False ) , ( "October" , "october|oct\\.?" , False ) , ( "November" , "november|nov\\.?" , False ) , ( "December" , "december|dec\\.?" , False ) ] rulePartOfMonth :: Rule rulePartOfMonth = Rule { name = "part of <named-month>" , pattern = [ regex "(early|mid|late)-?( of)?" , Predicate isAMonth ] , prod = \tokens -> case tokens of (Token RegexMatch (GroupMatch (match:_)):Token Time td:_) -> do (sd, ed) <- case Text.toLower match of "early" -> Just (1, 10) "mid" -> Just (11, 20) "late" -> Just (21, -1) _ -> Nothing start <- intersect td $ dayOfMonth sd end <- if ed /= -1 then intersect td $ dayOfMonth ed else Just $ cycleLastOf TG.Day td Token Time <$> interval TTime.Open start end _ -> Nothing } ruleEndOrBeginningOfMonth :: Rule ruleEndOrBeginningOfMonth = Rule { name = "at the beginning|end of <named-month>" , pattern = [ regex "(at the )?(beginning|end) of" , Predicate isAMonth ] , prod = \tokens -> case tokens of (Token RegexMatch (GroupMatch (_:match:_)):Token Time td:_) -> do (sd, ed) <- case Text.toLower match of "beginning" -> Just (1, 10) "end" -> Just (21, -1) _ -> Nothing start <- intersect td $ dayOfMonth sd end <- if ed /= -1 then intersect td $ dayOfMonth ed else Just $ cycleLastOf TG.Day td Token Time <$> interval TTime.Open start end _ -> Nothing } ruleEndOfMonth :: Rule ruleEndOfMonth = Rule { name = "end of month" , pattern = [ regex "(by (the )?|(at )?the )?(EOM|end of (the )?month)" ] , prod = \tokens -> case tokens of (Token RegexMatch (GroupMatch (match:_)):_) | (Just start, Just end) <- parsed -> Token Time <$> interval TTime.Open start end where cycleMonth = cycleNth TG.Month parsed = if "by" `Text.isPrefixOf` Text.toLower match then ( Just now , intersect (dayOfMonth 1) $ cycleMonth 1) else ( intersect (dayOfMonth 21) $ cycleMonth 0 , Just $ cycleLastOf TG.Day $ cycleMonth 0) _ -> Nothing } ruleBeginningOfMonth :: Rule ruleBeginningOfMonth = Rule { name = "beginning of month" , pattern = [ regex "((at )?the )?(BOM|beginning of (the )?month)" ] , prod = \_ -> do start <- intersect (dayOfMonth 1) $ cycleNth TG.Month 0 end <- intersect (dayOfMonth 10) $ cycleNth TG.Month 0 Token Time <$> interval TTime.Open start end } ruleEndOrBeginningOfYear :: Rule ruleEndOrBeginningOfYear = Rule { name = "at the beginning|end of <year>" , pattern = [ regex "(at the )?(beginning|end) of" , Predicate $ isGrainOfTime TG.Year ] , prod = \tokens -> case tokens of (Token RegexMatch (GroupMatch (_:match:_)):Token Time td:_) -> do (sd, ed) <- case Text.toLower match of "beginning" -> Just (1, 4) "end" -> Just (9, -1) _ -> Nothing start <- intersect td $ month sd end <- if ed /= -1 then intersect td $ cycleLastOf TG.Month $ month ed else cycleNthAfter False TG.Year 1 <$> intersect td (month 1) Token Time <$> interval TTime.Open start end _ -> Nothing } ruleEndOfYear :: Rule ruleEndOfYear = Rule { name = "end of year" , pattern = [ regex "(by (the )?|(at )?the )?(EOY|end of (the )?year)" ] , prod = \tokens -> case tokens of (Token RegexMatch (GroupMatch (match:_)):_) -> do start <- std end <- intersect (month 1) $ cycleYear 1 Token Time <$> interval TTime.Open start end where std = if "by" `Text.isPrefixOf` Text.toLower match then Just now else intersect (month 9) $ cycleYear 0 cycleYear = cycleNth TG.Year _ -> Nothing } ruleBeginningOfYear :: Rule ruleBeginningOfYear = Rule { name = "beginning of year" , pattern = [ regex "((at )?the )?(BOY|beginning of (the )?year)" ] , prod = \_ -> do start <- intersect (month 1) $ cycleNth TG.Year 0 end <- intersect (month 4) $ cycleNth TG.Year 0 Token Time <$> interval TTime.Open start end } ruleEndOrBeginningOfWeek :: Rule ruleEndOrBeginningOfWeek = Rule { name = "at the beginning|end of <week>" , pattern = [ regex "(at the )?(beginning|end) of" , Predicate $ isGrainOfTime TG.Week ] , prod = \tokens -> case tokens of (Token RegexMatch (GroupMatch (_:match1:_)):Token Time td:_) -> do (sd, ed) <- case Text.toLower match1 of "beginning" -> Just (1, 3) "end" -> Just (5, 7) _ -> Nothing start <- intersect td $ dayOfWeek sd end <- intersect td $ dayOfWeek ed Token Time <$> interval TTime.Open start end _ -> Nothing } ruleClosest :: Rule ruleClosest = Rule { name = "the closest <day> to <time>" , pattern = [ regex "the\\s+closest" , Predicate $ isGrainOfTime TG.Day , regex "to" , dimension Time ] , prod = \case (_:Token Time td1:_:Token Time td2:_) -> tt $ predNthClosest 0 td1 td2 _ -> Nothing } ruleNthClosest :: Rule ruleNthClosest = Rule { name = "the <ordinal> closest <day> to <time>" , pattern = [ regex "the" , dimension Ordinal , regex "closest" , Predicate $ isGrainOfTime TG.Day , regex "to" , dimension Time ] , prod = \case (_:token:_:Token Time td1:_:Token Time td2:_) -> do n <- getIntValue token tt $ predNthClosest (n - 1) td1 td2 _ -> Nothing } rulePeriodicHolidays :: [Rule] rulePeriodicHolidays = mkRuleHolidays -- Fixed dates, year over year [ ( "Africa Day", "africa(n (freedom|liberation))? day", monthDay 5 25 ) , ( "Africa Industrialization Day", "africa industrialization day", monthDay 11 20 ) , ( "All Saints' Day", "all saints' day", monthDay 11 1 ) , ( "All Souls' Day", "all souls' day", monthDay 11 2 ) , ( "April Fools", "(april|all) fool'?s('? day)?", monthDay 4 1 ) , ( "Arabic Language Day", "arabic language day", monthDay 12 18 ) , ( "Assumption of Mary", "assumption of mary", monthDay 8 15 ) , ( "Boxing Day", "boxing day", monthDay 12 26 ) , ( "Chinese Language Day", "chinese language day", monthDay 4 20 ) , ( "Christmas", "(xmas|christmas)( day)?", monthDay 12 25 ) , ( "Christmas Eve", "(xmas|christmas)( day)?('s)? eve", monthDay 12 24 ) , ( "Day of Remembrance for all Victims of Chemical Warfare", "day of remembrance for all victims of chemical warfare", monthDay 4 29 ) , ( "Day of Remembrance of the Victims of the Rwanda Genocide", "day of remembrance of the victims of the rwanda genocide", monthDay 4 7 ) , ( "Day of the Seafarer", "day of the seafarer", monthDay 6 25 ) , ( "Earth Day", "earth day", monthDay 4 22 ) , ( "English Language Day", "english language day", monthDay 4 23 ) , ( "Epiphany", "Epiphany", monthDay 1 6 ) , ( "Feast of St Francis of Assisi", "feast of st\\.? francis of assisi", monthDay 10 4 ) , ( "Feast of the Immaculate Conception", "feast of the immaculate conception", monthDay 12 8 ) , ( "Global Day of Parents", "global day of parents", monthDay 6 1 ) , ( "Halloween", "hall?owe?en( day)?", monthDay 10 31 ) , ( "Human Rights Day", "human rights? day", monthDay 12 10 ) , ( "International Albinism Awareness Day", "international albinism awareness day", monthDay 6 13 ) , ( "International Anti-Corruption Day", "international anti(\\-|\\s)corruption day", monthDay 12 9 ) , ( "International Asteroid Day", "international asteroid day", monthDay 6 30 ) , ( "International Celebrate Bisexuality Day", "international celebrate bisexuality day", monthDay 9 23 ) , ( "International Chernobyl Disaster Remembrance Day", "international chernobyl disaster remembrance day", monthDay 4 26 ) , ( "International Civil Aviation Day", "international civil aviation day", monthDay 12 7 ) , ( "International Customs Day", "international customs day", monthDay 1 26 ) , ( "International Day Against Drug Abuse and Illicit Trafficking", "international day against drug abuse and illicit trafficking", monthDay 6 26 ) , ( "International Day against Nuclear Tests", "international day against nuclear tests", monthDay 8 29 ) , ( "International Day for Biological Diversity", "international day for biological diversity|world biodiversity day", monthDay 5 22 ) , ( "International Day for Monuments and Sites", "international day for monuments and sites", monthDay 4 18 ) , ( "International Day for Preventing the Exploitation of the Environment in War and Armed Conflict", "international day for preventing the exploitation of the environment in war and armed conflict", monthDay 11 6 ) , ( "International Day for South-South Cooperation", "international day for south(\\-|\\s)south cooperation", monthDay 9 12 ) , ( "International Day for Tolerance", "international day for tolerance", monthDay 11 16 ) , ( "International Day for the Abolition of Slavery", "international day for the abolition of slavery", monthDay 12 2 ) , ( "International Day for the Elimination of Racial Discrimination", "international day for the elimination of racial discrimination", monthDay 3 21 ) , ( "International Day for the Elimination of Sexual Violence in Conflict", "international day for the elimination of sexual violence in conflict", monthDay 6 19 ) , ( "International Day for the Elimination of Violence against Women", "international day for the elimination of violence against women", monthDay 11 25 ) , ( "International Day for the Eradication of Poverty", "international day for the eradication of poverty", monthDay 10 17 ) , ( "International Day for the Preservation of the Ozone Layer", "international day for the preservation of the ozone Layer", monthDay 9 16 ) , ( "International Day for the Remembrance of the Slave Trade and its Abolition", "international day for the remembrance of the slave trade and its abolition", monthDay 8 23 ) , ( "International Day for the Right to the Truth concerning Gross Human Rights Violations and for the Dignity of Victims", "international day for the right to the truth concerning gross human rights violations and for the dignity of victims", monthDay 3 24 ) , ( "International Day for the Total Elimination of Nuclear Weapons", "international day for the total elimination of nuclear weapons", monthDay 9 26 ) , ( "International Day in Support of Victims of Torture", "international day in support of victims of torture", monthDay 6 26 ) , ( "International Day of Charity", "international day of charity", monthDay 9 5 ) , ( "International Day of Commemoration in Memory of the Victims of the Holocaust", "international day of commemoration in memory of the victims of the holocaust", monthDay 1 27 ) , ( "International Day of Democracy", "international day of democracy", monthDay 9 15 ) , ( "International Day of Disabled Persons", "international day of disabled persons", monthDay 12 3 ) , ( "International Day of Families", "international day of families", monthDay 5 15 ) , ( "International Day of Family Remittances", "international day of family remittances", monthDay 6 16 ) , ( "International Day of Forests", "international day of forests", monthDay 3 21 ) , ( "International Day of Friendship", "international day of friendship", monthDay 7 30 ) , ( "International Day of Happiness", "international day of happiness", monthDay 3 20 ) , ( "International Day of Human Space Flight", "international day of human space flight", monthDay 4 12 ) , ( "International Day of Innocent Children Victims of Aggression", "international day of innocent children victims of aggression", monthDay 6 4 ) , ( "International Day of Non-Violence", "international day of non(\\-|\\s)violence", monthDay 10 2 ) , ( "International Day of Nowruz", "international day of nowruz", monthDay 3 21 ) , ( "International Day of Older Persons", "international day of older persons", monthDay 10 1 ) , ( "International Day of Peace", "international day of peace", monthDay 9 21 ) , ( "International Day of Persons with Disabilities", "international day of persons with disabilities", monthDay 12 3 ) , ( "International Day of Remembrance of Slavery Victims and the Transatlantic Slave Trade", "international day of remembrance of slavery victims and the transatlantic slave trade", monthDay 3 25 ) , ( "International Day of Rural Women", "international day of rural women", monthDay 10 15 ) , ( "International Day of Solidarity with Detained and Missing Staff Members", "international day of solidarity with detained and missing staff members", monthDay 3 25 ) , ( "International Day of Solidarity with the Palestinian People", "international day of solidarity with the palestinian people", monthDay 11 29 ) , ( "International Day of Sport for Development and Peace", "international day of sport for development and peace", monthDay 4 6 ) , ( "International Day of United Nations Peacekeepers", "international day of united nations peacekeepers", monthDay 5 29 ) , ( "International Day of Women and Girls in Science", "international day of women and girls in science", monthDay 2 11 ) , ( "International Day of Yoga", "international day of yoga", monthDay 6 21 ) , ( "International Day of Zero Tolerance for Female Genital Mutilation", "international day of zero tolerance for female genital mutilation", monthDay 2 6 ) , ( "International Day of the Girl Child", "international day of the girl child", monthDay 10 11 ) , ( "International Day of the Victims of Enforced Disappearances", "international day of the victims of enforced disappearances", monthDay 8 30 ) , ( "International Day of the World's Indigenous People", "international day of the world'?s indigenous people", monthDay 8 9 ) , ( "International Day to End Impunity for Crimes against Journalists", "international day to end impunity for crimes against journalists", monthDay 11 2 ) , ( "International Day to End Obstetric Fistula", "international day to end obstetric fistula", monthDay 5 23 ) , ( "International Day for Disaster Reduction", "iddr|international day for (natural )?disaster reduction", monthDay 10 13 ) , ( "International Human Solidarity Day", "international human solidarity day", monthDay 12 20 ) , ( "International Jazz Day", "international jazz day", monthDay 4 30 ) , ( "International Literacy Day", "international literacy day", monthDay 9 8 ) , ( "International Men's Day", "international men'?s day", monthDay 11 19 ) , ( "International Migrants Day", "international migrants day", monthDay 12 18 ) , ( "International Mother Language Day", "international mother language day", monthDay 2 21 ) , ( "International Mountain Day", "international mountain day", monthDay 12 11 ) , ( "International Nurses Day", "international nurses day", monthDay 5 12 ) , ( "International Overdose Awareness Day", "international overdose awareness day", monthDay 8 31 ) , ( "International Volunteer Day for Economic and Social Development", "international volunteer day for economic and social development", monthDay 12 5 ) , ( "International Widows' Day", "international widows'? day", monthDay 6 23 ) , ( "International Women's Day", "international women'?s day", monthDay 3 8 ) , ( "International Youth Day", "international youth day", monthDay 8 12 ) , ( "May Day", "may day", monthDay 5 1 ) , ( "Nelson Mandela Day", "nelson mandela day", monthDay 7 18 ) , ( "New Year's Day", "new year'?s?( day)?", monthDay 1 1 ) , ( "New Year's Eve", "new year'?s? eve", monthDay 12 31 ) , ( "Orthodox Christmas Day", "orthodox christmas day", monthDay 1 7 ) , ( "Orthodox New Year", "orthodox new year", monthDay 1 14 ) , ( "Public Service Day", "public service day", monthDay 6 23 ) , ( "St. George's Day", "(saint|st\\.?) george'?s day|feast of saint george", monthDay 4 23 ) , ( "St Patrick's Day", "(saint|st\\.?) (patrick|paddy)'?s day", monthDay 3 17 ) , ( "St. Stephen's Day", "(saint|st\\.?) stephen'?s day", monthDay 12 26 ) , ( "Time of Remembrance and Reconciliation for Those Who Lost Their Lives during the Second World War", "time of remembrance and reconciliation for those who lost their lives during the second world war", monthDay 5 8 ) , ( "Ugadi", "y?ugadi|samvatsaradi|chaitra sukh?ladi", ugadi) , ( "United Nations Day", "united nations day", monthDay 10 24 ) , ( "United Nations' Mine Awareness Day", "united nations'? mine awareness day", monthDay 4 4 ) , ( "United Nations' World Health Day", "united nations'? world health day", monthDay 4 7 ) , ( "Universal Children's Day", "universal children'?s day", monthDay 11 20 ) , ( "Valentine's Day", "valentine'?s?( day)?", monthDay 2 14 ) , ( "World AIDS Day", "world aids day", monthDay 12 1 ) , ( "World Autism Awareness Day", "world autism awareness day", monthDay 4 2 ) , ( "World Autoimmune Arthritis Day", "world autoimmune arthritis day", monthDay 5 20 ) , ( "World Blood Donor Day", "world blood donor day", monthDay 6 14 ) , ( "World Book and Copyright Day", "world book and copyright day", monthDay 4 23 ) , ( "World Braille Day", "world braille day", monthDay 1 4 ) , ( "World Cancer Day", "world cancer day", monthDay 2 4 ) , ( "World Cities Day", "world cities day", monthDay 10 31 ) , ( "World CP Day", "world (cerebral palsy| cp) day", monthDay 10 6 ) , ( "World Day Against Child Labour", "world day against child labour", monthDay 6 12 ) , ( "World Day against Trafficking in Persons", "world day against trafficking in persons", monthDay 7 30 ) , ( "World Day for Audiovisual Heritage", "world day for audiovisual heritage", monthDay 10 27 ) , ( "World Day for Cultural Diversity for Dialogue and Development", "world day for cultural diversity for dialogue and development", monthDay 5 21 ) , ( "World Day for Safety and Health at Work", "world day for safety and health at work", monthDay 4 28 ) , ( "World Day for the Abolition of Slavery", "world day for the abolition of slavery", monthDay 12 2 ) , ( "World Day of Social Justice", "world day of social justice", monthDay 2 20 ) , ( "World Day of the Sick", "world day of the sick", monthDay 2 11 ) , ( "World Day to Combat Desertification and Drought", "world day to combat desertification and drought", monthDay 6 17 ) , ( "World Development Information Day", "world development information day", monthDay 10 24 ) , ( "World Diabetes Day", "world diabetes day", monthDay 11 14 ) , ( "World Down Syndrome Day", "world down syndrome day", monthDay 3 21 ) , ( "World Elder Abuse Awareness Day", "world elder abuse awareness day", monthDay 6 15 ) , ( "World Environment Day", "world environment day", monthDay 6 5 ) , ( "World Food Day", "world food day", monthDay 10 16 ) , ( "World Genocide Commemoration Day", "world genocide commemoration day", monthDay 12 9 ) , ( "World Heart Day", "world heart day", monthDay 9 29 ) , ( "World Hepatitis Day", "world hepatitis day", monthDay 7 28 ) , ( "World Humanitarian Day", "world humanitarian day", monthDay 8 19 ) , ( "World Information Society Day", "world information society day", monthDay 5 17 ) , ( "World Intellectual Property Day", "world intellectual property day", monthDay 4 26 ) , ( "World Malaria Day", "world malaria day", monthDay 4 25 ) , ( "World Mental Health Day", "world mental health day", monthDay 10 10 ) , ( "World Meteorological Day", "world meteorological day", monthDay 3 23 ) , ( "World No Tobacco Day", "world no tobacco day", monthDay 5 31 ) , ( "World Oceans Day", "world oceans day", monthDay 6 8 ) , ( "World Ovarian Cancer Day", "world ovarian cancer day", monthDay 5 8 ) , ( "World Pneumonia Day", "world pneumonia day", monthDay 11 12 ) , ( "World Poetry Day", "world poetry day", monthDay 3 21 ) , ( "World Population Day", "world population day", monthDay 7 11 ) , ( "World Post Day", "world post day", monthDay 10 9 ) , ( "World Prematurity Day", "world prematurity day", monthDay 11 17 ) , ( "World Press Freedom Day", "world press freedom day", monthDay 5 3 ) , ( "World Rabies Day", "world rabies day", monthDay 9 28 ) , ( "World Radio Day", "world radio day", monthDay 2 13 ) , ( "World Refugee Day", "world refugee day", monthDay 6 20 ) , ( "World Science Day for Peace and Development", "world science day for peace and development", monthDay 11 10 ) , ( "World Sexual Health Day", "world sexual health day", monthDay 9 4 ) , ( "World Soil Day", "world soil day", monthDay 12 5 ) , ( "World Stroke Day", "world stroke day", monthDay 10 29 ) , ( "World Suicide Prevention Day", "world suicide prevention day", monthDay 9 10 ) , ( "World Teachers' Day", "world teachers'? day", monthDay 10 5 ) , ( "World Television Day", "world television day", monthDay 11 21 ) , ( "World Toilet Day", "world toilet day", monthDay 11 19 ) , ( "World Tourism Day", "world tourism day", monthDay 9 27 ) , ( "World Tuberculosis Day", "world tuberculosis day", monthDay 3 24 ) , ( "World Tuna Day", "world tuna day", monthDay 5 2 ) , ( "World Vegan Day", "world vegan day", monthDay 11 1 ) , ( "World Vegetarian Day", "world vegetarian day", monthDay 10 1 ) , ( "World Water Day", "world water day", monthDay 3 22 ) , ( "World Wetlands Day", "world wetlands day", monthDay 2 2 ) , ( "World Wildlife Day", "world wildlife day", monthDay 3 3 ) , ( "World Youth Skills Day", "world youth skills day", monthDay 7 15 ) , ( "Zero Discrimination Day", "zero discrimination day", monthDay 3 1 ) -- Fixed day/week/month, year over year , ( "Commonwealth Day", "commonwealth day", nthDOWOfMonth 2 1 3 ) , ( "Day of Remembrance for Road Traffic Victims" , "(world )?day of remembrance for road traffic victims" , nthDOWOfMonth 3 7 11 ) , ( "International Day of Cooperatives" , "international day of co\\-?operatives", nthDOWOfMonth 1 6 7 ) , ( "Martin Luther King's Day" , "(MLK|Martin Luther King('?s)?,?)( Jr\\.?| Junior)? day|(civil|idaho human) rights day" , nthDOWOfMonth 3 1 1 ) -- The day after Thanksgiving (not always the fourth Friday of November) , ( "Black Friday", "black frid?day" , cycleNthAfter False TG.Day 1 $ nthDOWOfMonth 4 4 11 ) , ( "World Habitat Day", "world habitat day", nthDOWOfMonth 1 1 10 ) , ( "World Kidney Day", "world kidney day", nthDOWOfMonth 2 4 3 ) , ( "World Leprosy Day", "world leprosy day" , predLastOf (dayOfWeek 7) (month 1) ) , ( "World Maritime Day", "world maritime day" , predLastOf (dayOfWeek 4) (month 9) ) , ( "World Migratory Bird Day", "world migratory bird day" , nthDOWOfMonth 2 6 5 ) , ( "World Philosophy Day", "world philosophy day", nthDOWOfMonth 3 4 11 ) , ( "World Religion Day", "world religion day", nthDOWOfMonth 3 7 1 ) , ( "World Sight Day", "world sight day", nthDOWOfMonth 2 4 10 ) -- Other , ( "Boss's Day", "boss'?s?( day)?" , predNthClosest 0 weekday (monthDay 10 16) ) ] ruleComputedHolidays :: [Rule] ruleComputedHolidays = mkRuleHolidays [ ( "Ascension Day", "ascension\\s+(thurs)?day" , cycleNthAfter False TG.Day 39 easterSunday ) , ( "Ash Wednesday", "ash\\s+wednesday|carnival" , cycleNthAfter False TG.Day (-46) easterSunday ) , ( "Ashura", "(day of )?ashura" , cycleNthAfter False TG.Day 9 muharram ) , ( "Bhai Dooj", "bhai(ya)?\\s+d(u|oo)j|bhau\\-beej|bhai\\s+(tika|phonta)" , cycleNthAfter False TG.Day 4 dhanteras ) -- 6th day after Diwali , ( "Chhath", "chhathi?|chhath (parv|puja)|dala (chhath|puja)|surya shashthi" , cycleNthAfter False TG.Day 8 dhanteras ) , ( "Boghi", "boghi|bogi\\s+pandigai" , cycleNthAfter False TG.Day (-1) thaiPongal ) , ( "Chinese New Year", "chinese\\s+(lunar\\s+)?new\\s+year('?s(\\s+day)?)?" , chineseNewYear ) , ( "Clean Monday" , "(orthodox\\s+)?(ash|clean|green|pure|shrove)\\s+monday|monday of lent" , cycleNthAfter False TG.Day (-48) orthodoxEaster ) , ( "Corpus Christi", "(the feast of )?corpus\\s+christi" , cycleNthAfter False TG.Day 60 easterSunday ) , ( "Dhanteras", "dhanatrayodashi|dhanteras|dhanvantari\\s+trayodashi" , dhanteras ) , ( "Diwali", "deepavali|diwali|lakshmi\\s+puja" , cycleNthAfter False TG.Day 2 dhanteras ) , ( "Durga Ashtami", "(durga|maha)(\\s+a)?shtami" , cycleNthAfter False TG.Day 7 navaratri ) , ( "Easter Monday", "easter\\s+mon(day)?" , cycleNthAfter False TG.Day 1 easterSunday ) , ( "Easter Sunday", "easter(\\s+sun(day)?)?", easterSunday ) , ( "Eid al-Adha", "bakr[\\-\\s]e?id|e?id [au]l\\-adha|sacrifice feast" , eidalAdha ) , ( "Eid al-Fitr", "eid al\\-fitr", eidalFitr ) , ( "Govardhan Puja", "govardhan\\s+puja|annak(u|oo)t" , cycleNthAfter False TG.Day 3 dhanteras ) , ( "Good Friday", "(good|great|holy)\\s+fri(day)?" , cycleNthAfter False TG.Day (-2) easterSunday ) , ( "Guru Gobind Singh Jayanti" , "guru\\s+(gobind|govind)\\s+singh\\s+(birthday|jayanti)" , guruGobindSinghJayanti ) , ( "Holi", "(rangwali )?holi|dhuleti|dhulandi|phagwah" , cycleNthAfter False TG.Day 39 vasantPanchami ) , ( "Holika Dahan", "holika dahan|kamudu pyre|chhoti holi" , cycleNthAfter False TG.Day 38 vasantPanchami ) , ( "Holy Saturday" , "(black|holy (and great )?|joyous)sat(urday)?|the great sabbath|easter eve" , cycleNthAfter False TG.Day (-1) easterSunday ) , ( "Islamic New Year", "(arabic|hijri|islamic) new year|amun jadid|muharram" , muharram ) , ( "Isra and Mi'raj" , "isra and mi'raj|(the )?prophet'?s'? ascension|(the )?ascension to heaven|the night journey" , cycleNthAfter False TG.Day 26 rajab ) , ( "Jumu'atul-Wida", "jumu'atul\\-widaa?'?|jamat[\\-\\s]ul[\\-\\s]vida" , predNthAfter (-1) (dayOfWeek 5) eidalFitr ) , ( "Kaanum Pongal", "(kaanum|kanni)\\s+pongal" , cycleNthAfter False TG.Day 2 thaiPongal ) , ( "Lag BaOmer", "lag b[a']omer", lagBaOmer ) , ( "Vaisakhi", "mesadi|[bv]aisakhi|vaisakhadi|vasakhi|vaishakhi", vaisakhi) , ( "Laylat al-Qadr" , "laylat al[\\-\\s][qk]adr|night of (destiny|measures|power|value)" , cycleNthAfter False TG.Day 26 ramadan ) , ( "Lazarus Saturday", "lazarus\\s+saturday" , cycleNthAfter False TG.Day (-8) orthodoxEaster ) , ( "Maha Navami", "maha\\s+navami", cycleNthAfter False TG.Day 8 navaratri ) , ( "Maha Saptami", "maha\\s+saptami", cycleNthAfter False TG.Day 6 navaratri ) , ( "Mattu Pongal", "maa?ttu\\s+pongal" , cycleNthAfter False TG.Day 1 thaiPongal ) , ( "Maundy Thursday" , "(covenant|(great and )?holy|maundy|sheer)\\s+thu(rsday)?|thu(rsday)? of mysteries" , cycleNthAfter False TG.Day (-3) easterSunday ) , ( "Mawlid" , "mawlid(\\s+al\\-nab(awi|i\\s+al\\-sharif))?|mevli[dt]|mulud|birth(day)? of (the )?prophet( muhammad)?|(the )?prophet's birthday" , mawlid ) , ( "Naraka Chaturdashi" , "naraka? (nivaran )?chaturdashi|(kali|roop) chaudas|choti diwali" , cycleNthAfter False TG.Day 1 dhanteras ) , ( "Orthodox Easter Monday", "orthodox\\s+easter\\s+mon(day)?" , cycleNthAfter False TG.Day 1 orthodoxEaster ) , ( "Orthodox Easter Sunday", "orthodox\\s+easter(\\s+sun(day)?)?|pascha?" , orthodoxEaster ) , ( "Orthodox Holy Saturday", "orthodox\\s+holy\\s+sat(urday)?|the great sabbath" , cycleNthAfter False TG.Day (-1) orthodoxEaster ) , ( "Orthodox Good Friday", "orthodox\\s+(great|good)(\\s+and\\s+holy)?\\s+friday" , cycleNthAfter False TG.Day (-2) orthodoxEaster ) , ( "Orthodox Palm Sunday", "orthodox\\s+(branch|palm|yew)\\s+sunday" , cycleNthAfter False TG.Day (-7) orthodoxEaster ) , ( "Palm Sunday", "(branch|palm|yew)\\s+sunday" , cycleNthAfter False TG.Day (-7) easterSunday ) , ( "Pentecost", "pentecost|white sunday|whitsunday" , cycleNthAfter False TG.Day 49 easterSunday ) , ( "Purim", "purim", purim ) , ( "Raksha Bandhan", "raksha(\\s+)?bandhan|rakhi", rakshaBandhan ) , ( "Rama Navami", "rama\\s+navami", ramaNavami) , ( "Ganesh Chaturthi", "(ganesh|vinayaka)\\s+chaturthi", ganeshChaturthi ) , ( "Ratha-Yatra", "ratha(\\-|\\s+)?yatra|rathjatra|chariot\\s+festival" , rathaYatra ) , ( "Pargat Diwas", "pargat diwas|(maharishi )?valmiki jayanti", pargatDiwas ) , ( "Mahavir Jayanti", "(mahavir|mahaveer) (jayanti|janma kalyanak)" , mahavirJayanti ) , ( "Maha Shivaratri", "maha(\\s+)?shivaratri", mahaShivaRatri) , ( "Dayananda Saraswati Jayanti","((maharishi|swami) )?(dayananda )?saraswati jayanti" , saraswatiJayanti ) , ( "Karva Chauth", "karva\\s+chauth|karaka\\s+chaturthi" , karvaChauth) , ( "Krishna Janmashtami", "(krishna )?janmashtami|gokulashtami", krishnaJanmashtami ) , ( "Shemini Atzeret", "shemini\\s+atzeret" , cycleNthAfter False TG.Day 21 roshHashana ) , ( "Shrove Tuesday", "pancake (tues)?day|shrove tuesday|mardi gras" , cycleNthAfter False TG.Day (-47) easterSunday ) , ( "Shushan Purim", "shushan\\s+purim", cycleNthAfter False TG.Day 1 purim ) , ( "Simchat Torah", "simc?hat\\s+torah" , cycleNthAfter False TG.Day 22 roshHashana ) , ( "Thai Pongal" , "(thai )?pongal|pongal pandigai|(makara? |magha )?sankranth?i|maghi" , thaiPongal ) , ( "Thiru Onam", "(thiru(v|\\s+))?onam", thiruOnam ) , ( "Tisha B'Av", "tisha b'av", tishaBAv ) , ( "Trinity Sunday", "trinity\\s+sunday" , cycleNthAfter False TG.Day 56 easterSunday ) , ( "Vasant Panchami", "[bv]asant\\s+panchami", vasantPanchami ) , ( "Vijayadashami", "dasara|duss(eh|he)ra|vijayadashami" , cycleNthAfter False TG.Day 9 navaratri ) -- 15th day of Shevat , ( "Tu BiShvat", "tu b[i']shvat", tuBishvat ) -- day of the full moon in May in the Gregorian calendar , ( "Vesak", "v(e|ai)sak(ha)?|buddha (day|purnima)", vesak ) , ( "Yom Ha'atzmaut", "yom ha'?atzmaut", yomHaatzmaut ) , ( "Yom HaShoah" , "yom hashoah|yom hazikaron lashoah ve-lag'vurah|holocaust (remembrance )?day" , cycleNthAfter False TG.Day 12 passover ) , ( "Yom Kippur", "yom\\s+kippur", cycleNthAfter False TG.Day 9 roshHashana ) , ( "Whit Monday", "(pentecost|whit)\\s+monday|monday of the holy spirit" , cycleNthAfter False TG.Day 50 easterSunday ) -- Rabindra Jayanti 25th day of the Bengali month of Boishakh , ( "Rabindra Jayanti", "rabindra(nath)?\\s+jayanti", rabindraJayanti ) , ("Guru Ravidass Jayanti", "guru\\s+ravidass?\\s+(birthday|jayanti)" , ravidassJayanti ) ] ruleComputedHolidays' :: [Rule] ruleComputedHolidays' = mkRuleHolidays' [ ( "Global Youth Service Day", "global youth service day|gysd" , let start = globalYouthServiceDay end = cycleNthAfter False TG.Day 2 globalYouthServiceDay in interval TTime.Open start end ) , ( "Great Lent", "great\\s+(fast|lent)" , let start = cycleNthAfter False TG.Day (-48) orthodoxEaster end = cycleNthAfter False TG.Day (-9) orthodoxEaster in interval TTime.Open start end ) , ( "Hanukkah", "c?hann?ukk?ah" , let start = chanukah end = cycleNthAfter False TG.Day 7 chanukah in interval TTime.Open start end ) , ( "Lent", "lent" , let start = cycleNthAfter False TG.Day (-46) easterSunday end = cycleNthAfter False TG.Day (-1) easterSunday in interval TTime.Open start end ) , ( "Navaratri", "durga\\s+puja|durgotsava|nava?rath?ri" , let start = navaratri end = cycleNthAfter False TG.Day 9 navaratri in interval TTime.Open start end ) , ( "Passover", "passover|pesa[ck]?h" , let start = passover end = cycleNthAfter False TG.Day 8 passover in interval TTime.Open start end ) , ( "Ramadan", "rama[dt]h?an|ramzaa?n" , let start = ramadan end = cycleNthAfter False TG.Day (-1) eidalFitr in interval TTime.Open start end ) , ( "Rosh Hashanah", "rosh hashann?ah?|yom teruah" , let start = roshHashana end = cycleNthAfter False TG.Day 2 roshHashana in interval TTime.Open start end ) , ( "Shavuot", "feast of weeks|shavu'?oth?|shovuos" , let start = cycleNthAfter False TG.Day 50 passover end = cycleNthAfter False TG.Day 52 passover in interval TTime.Open start end ) , ( "Sukkot", "feast of (booths|tabernacles|the ingathering)|su[ck]{2}o[st]" , let start = cycleNthAfter False TG.Day 14 roshHashana end = cycleNthAfter False TG.Day 22 roshHashana in interval TTime.Open start end ) -- Does not account for leap years, so every 365 days. , ( "Parsi New Year", "parsi new year|jamshedi navroz" , predEveryNDaysFrom 365 (2020, 8, 16) ) , ( "Earth Hour", "earth hour" , computeEarthHour ) , ( "King's Day", "king's day|koningsdag" , computeKingsDay ) ] ruleCycleThisLastNext :: Rule ruleCycleThisLastNext = Rule { name = "this|last|next <cycle>" , pattern = [ regex "(this|current|coming|next|(the( following)?)|last|past|previous|upcoming)" , dimension TimeGrain ] , prod = \case ( Token RegexMatch (GroupMatch (match:_)): Token TimeGrain grain: _) -> case Text.toLower match of "this" -> tt $ cycleNth grain 0 "coming" -> tt $ cycleNth grain 1 "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 "upcoming" -> tt $ cycleNth grain 1 "the following" -> tt $ cycleNth grain 1 "the" -> tt $ cycleNth grain 0 _ -> Nothing _ -> Nothing } ruleDOMOfTimeMonth :: Rule ruleDOMOfTimeMonth = Rule { name = "<day-of-month> (ordinal or number) of <month>" , pattern = [ Predicate isDOMValue , regex "of( the)?" , Predicate $ isGrainOfTime TG.Month ] , prod = \tokens -> case tokens of (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token _ -> 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 Text.toLower 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 Text.toLower match == "after" then 1 else - 1 in tt $ cycleNthAfter False grain n td _ -> 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 } ruleCycleLastOrdinalOfTime :: Rule ruleCycleLastOrdinalOfTime = Rule { name = "<ordinal> last <cycle> of <time>" , pattern = [ dimension Ordinal , regex "last" , 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) $ cycleNthAfter True (timeGrain td) 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 } ruleCycleTheLastOrdinalOfTime :: Rule ruleCycleTheLastOrdinalOfTime = Rule { name = "the <ordinal> last <cycle> of <time>" , pattern = [ regex "the" , dimension Ordinal , regex "last" , 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) $ cycleNthAfter True (timeGrain td) 1 td _ -> Nothing } ruleCycleTheOfTimeGrain :: Rule ruleCycleTheOfTimeGrain = Rule { name = "the <cycle> of the <time grain>" , pattern = [ regex "the" , dimension TimeGrain , regex "of( the)?" , dimension TimeGrain ] , prod = \tokens -> case tokens of (_:Token TimeGrain grain:_:Token TimeGrain time:_) -> tt $ cycleNthAfter True grain 0 $ cycleNth time 0 _ -> 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 now (inDuration dd) "after" -> tt $ withDirection TTime.After $ inDuration dd "in" -> tt $ inDuration dd _ -> Nothing _ -> Nothing } ruleDurationLastNext :: Rule ruleDurationLastNext = Rule { name = "last|past|next <duration>" , pattern = [ regex "([lp]ast|next)" , dimension Duration ] , prod = \tokens -> case tokens of (Token RegexMatch (GroupMatch (match:_)): Token Duration DurationData{TDuration.grain, TDuration.value}: _) -> case Text.toLower match of "next" -> tt $ cycleN True grain value "last" -> tt $ cycleN True grain (- value) "past" -> tt $ cycleN True grain (- value) _ -> Nothing _ -> Nothing } ruleNDOWago :: Rule ruleNDOWago = Rule { name = "<integer> <named-day> ago|back" , pattern = [ Predicate isNatural , Predicate isADayOfWeek , regex "ago|back" ] , prod = \tokens -> case tokens of (Token Numeral NumeralData{TNumeral.value = v}:Token Time td:_) -> tt $ predNth (- (floor v)) False td _ -> 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 } ruleDayDurationHenceAgo :: Rule ruleDayDurationHenceAgo = Rule { name = "<day> <duration> hence|ago" , pattern = [ Predicate $ or . sequence [isGrainOfTime TG.Day, isGrainOfTime TG.Month] , dimension Duration , regex "(from now|hence|ago)" ] , prod = \tokens -> case tokens of (Token Time td: Token Duration dd: Token RegexMatch (GroupMatch (match:_)): _) -> case Text.toLower match of "ago" -> Token Time <$> intersect td (durationIntervalAgo dd) _ -> Token Time <$> intersect td (inDurationInterval dd) _ -> Nothing } ruleDayInDuration :: Rule ruleDayInDuration = Rule { name = "<day> in <duration>" , pattern = [ Predicate $ or . sequence [isGrainOfTime TG.Day, isGrainOfTime TG.Month] , regex "in" , Predicate $ isDurationGreaterThan TG.Hour ] , prod = \tokens -> case tokens of (Token Time td:_:Token Duration dd:_) -> Token Time <$> intersect td (inDurationInterval dd) _ -> Nothing } ruleInDurationAtTime :: Rule ruleInDurationAtTime = Rule { name = "in <duration> at <time-of-day>" , pattern = [ regex "in" , Predicate $ isDurationGreaterThan TG.Hour , regex "at" , Predicate isATimeOfDay ] , prod = \tokens -> case tokens of (_:Token Duration dd:_:Token Time td:_) -> Token Time <$> intersect td (inDurationInterval 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|past <time>" , pattern = [ dimension Duration , regex "(after|before|from|past)" , 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 } ruleIntervalForDurationFrom :: Rule ruleIntervalForDurationFrom = Rule { name = "for <duration> from <time>" , pattern = [ regex "for" , dimension Duration , regex "(from|starting|beginning|after|starting from)" , dimension Time ] , prod = \tokens -> case tokens of (_:Token Duration dd:_:Token Time td1:_) -> Token Time <$> interval TTime.Closed td1 (durationAfter dd td1) _ -> Nothing } ruleIntervalTimeForDuration :: Rule ruleIntervalTimeForDuration = Rule { name = "<time> for <duration>" , pattern = [ Predicate isNotLatent , regex "for" , dimension Duration ] , prod = \tokens -> case tokens of (Token Time td1:_:Token Duration dd:_) -> Token Time <$> interval TTime.Closed td1 (durationAfter dd td1) _ -> Nothing } ruleIntervalFromTimeForDuration :: Rule ruleIntervalFromTimeForDuration = Rule { name = "from <time> for <duration>" , pattern = [ regex "(from|starting|beginning|after|starting from)" , Predicate isNotLatent , regex "for" , dimension Duration ] , prod = \tokens -> case tokens of (_:Token Time td1:_:Token Duration dd:_) -> Token Time <$> interval TTime.Closed td1 (durationAfter dd td1) _ -> Nothing } timezoneName :: String timezoneName = "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" ruleTimezone :: Rule ruleTimezone = Rule { name = "<time> timezone" , pattern = [ Predicate $ and . sequence [isNotLatent, isATimeOfDay, hasNoTimezone] , regex $ "\\b(" ++ timezoneName ++ ")\\b" ] , prod = \tokens -> case tokens of (Token Time td: Token RegexMatch (GroupMatch (tz:_)): _) -> Token Time <$> inTimezone (Text.toUpper tz) td _ -> Nothing } ruleTimezoneBracket :: Rule ruleTimezoneBracket = Rule { name = "<time> (timezone)" , pattern = [ Predicate $ and . sequence [isNotLatent, isATimeOfDay, hasNoTimezone] , regex $ "\\((" ++ timezoneName ++ ")\\)" ] , prod = \tokens -> case tokens of (Token Time td: Token RegexMatch (GroupMatch (tz:_)): _) -> Token Time <$> inTimezone (Text.toUpper tz) td _ -> Nothing } ruleIntervalDashTimezone :: Rule ruleIntervalDashTimezone = Rule { name = "<datetime> - <datetime> (interval) timezone" , pattern = [ Predicate $ and . sequence [isATimeOfDay, hasNoTimezone] , regex "\\-|to|th?ru|through|(un)?til(l)?" , Predicate $ and . sequence [isATimeOfDay, hasNoTimezone] , regex $ "\\b(" ++ timezoneName ++ ")\\b" ] , prod = \tokens -> case tokens of (Token Time td1: _: Token Time td2: Token RegexMatch (GroupMatch (tz:_)): _) -> do tdz1 <- inTimezone (Text.toUpper tz) td1 tdz2 <- inTimezone (Text.toUpper tz) td2 Token Time <$> interval TTime.Closed tdz1 tdz2 _ -> Nothing } ruleMidDay :: Rule ruleMidDay = Rule { name = "Mid-day" , pattern = [ regex "(the )?mid(\\s)?day" ] , prod = \_ -> tt $ hour False 12 } ruleUpcomingGrain :: Rule ruleUpcomingGrain = Rule { name = "upcoming <integer> <cycle>" , pattern = [ regex "upcoming" , Predicate isNatural , dimension TimeGrain ] , prod = \case ( _: Token Numeral NumeralData{ TNumeral.value = numOfTimeGrain }: Token TimeGrain grain: _) -> tt $ cycleNth grain $ floor numOfTimeGrain _ -> Nothing } ruleUpcomingGrainAlt :: Rule ruleUpcomingGrainAlt = Rule { name = "<integer> upcoming <cycle>" , pattern = [ Predicate isNatural , regex "upcoming" , dimension TimeGrain ] , prod = \case ( Token Numeral NumeralData{ TNumeral.value = numOfTimeGrain }: _: Token TimeGrain grain: _) -> tt $ cycleNth grain $ floor numOfTimeGrain _ -> Nothing } ruleMinutesToHOD :: Rule ruleMinutesToHOD = Rule { name = "<integer> minutes to|till|before <hour-of-day>" , pattern = [ Predicate $ isIntegerBetween 1 59 , Predicate $ isGrain TG.Minute , regex "to|till|before|of" , Predicate isAnHourOfDay ] , prod = \tokens -> case tokens of (token:_:_:Token Time td:_) -> do n <- getIntValue token Token Time <$> minutesBefore n td _ -> Nothing } rules :: [Rule] rules = [ ruleIntersect , ruleIntersectOf , ruleIntersectYear , ruleAbsorbOnDay , ruleAbsorbOnADOW , ruleAbsorbInMonthYear , ruleAbsorbCommaTOD , ruleNextDOW , ruleNextTime , ruleThisTime , ruleLastTime , ruleTimeBeforeLastAfterNext , ruleOrdinalDOWOfTime , ruleLastDOWOfTime , ruleLastCycleOfTime , ruleLastNight , ruleLastWeekendOfMonth , ruleNthTimeOfTime , ruleTheNthTimeOfTime , ruleNthTimeAfterTime , ruleTheNthTimeAfterTime , ruleNDOWFromTime , ruleYearLatent , ruleYearADBC , ruleTheDOMNumeral , ruleTheDOMOrdinal , ruleDOMLatent , ruleNamedDOMOrdinal , ruleMonthDOMNumeral , ruleDOMMonth , ruleDOMOfMonth , ruleTheDOMOfMonth , ruleDOMOrdinalMonthYear , ruleDOMMonthYear , ruleIdesOfMonth , ruleTODLatent , ruleAtTOD , ruleTODOClock , rulePODatTOD , rulePODintersectTODlatent , ruleHHMM , ruleHHhMM , ruleHHMMLatent , ruleHHMMSS , ruleMilitaryAMPM , ruleTODAMPM , ruleHONumeral , ruleHONumeralDash , ruleHONumeralAlt , ruleHONumeralAltDash , ruleHODHalf , ruleHODQuarter , ruleNumeralToHOD , ruleHalfToHOD , ruleQuarterToHOD , ruleNumeralAfterHOD , ruleHalfAfterHOD , ruleQuarterAfterHOD , ruleHalfHOD , ruleYYYYQQ , ruleYYYYMM , ruleYYYYMMDD , ruleMMYYYY , ruleNoonMidnightEOD , rulePartOfDays , ruleEarlyMorning , rulePODIn , rulePODThis , ruleTonight , ruleAfterPartofday , ruleTimePOD , rulePODofTime , ruleWeekend , ruleWeek , ruleTODPrecision , ruleTODPOD , rulePrecisionTOD , ruleIntervalFromMonthDDDD , ruleIntervalFromDDDDMonth , ruleIntervalFromDDDDOfMonth , ruleIntervalMonthDDDD , ruleIntervalDDDDMonth , ruleIntervalYearLatent , ruleIntervalDash , ruleIntervalSlash , ruleIntervalFrom , ruleIntervalBetween , ruleIntervalTODDash , ruleIntervalTODFrom , ruleIntervalTODAMPM , ruleIntervalTODBetween , ruleIntervalBy , ruleIntervalByTheEndOf , ruleIntervalUntilTime , ruleIntervalAfterFromSinceTime , ruleCycleTheAfterBeforeTime , ruleCycleThisLastNext , ruleDOMOfTimeMonth , ruleCycleAfterBeforeTime , ruleCycleOrdinalOfTime , ruleCycleLastOrdinalOfTime , ruleCycleTheOrdinalOfTime , ruleCycleTheLastOrdinalOfTime , ruleCycleTheOfTimeGrain , ruleCycleTheOfTime , ruleCycleOrdinalAfterTime , ruleCycleTheOrdinalAfterTime , ruleCycleOrdinalQuarter , ruleCycleTheOrdinalQuarter , ruleCycleOrdinalQuarterYear , ruleDurationInWithinAfter , ruleDurationLastNext , ruleNDOWago , ruleDurationHenceAgo , ruleDayDurationHenceAgo , ruleDayInDuration , ruleInDurationAtTime , ruleDurationAfterBeforeTime , ruleIntervalForDurationFrom , ruleIntervalFromTimeForDuration , ruleIntervalTimeForDuration , ruleInNumeral , ruleTimezone , ruleTimezoneBracket , ruleIntervalDashTimezone , rulePartOfMonth , ruleEndOrBeginningOfMonth , ruleEndOrBeginningOfYear , ruleEndOrBeginningOfWeek , ruleNow , ruleASAP , ruleSeason , ruleEndOfMonth , ruleBeginningOfMonth , ruleEndOfYear , ruleBeginningOfYear , ruleClosest , ruleNthClosest , ruleMidDay , ruleUpcomingGrain , ruleUpcomingGrainAlt , ruleMinutesToHOD ] ++ ruleInstants ++ ruleDaysOfWeek ++ ruleMonths ++ ruleSeasons ++ ruleComputedHolidays ++ ruleComputedHolidays' ++ rulePeriodicHolidays