Duckling/Time/BG/Rules.hs (1,677 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.BG.Rules ( rules ) where import Control.Applicative ((<|>)) import Data.Maybe import Data.Text (Text) import Prelude import qualified Data.Text as Text import Duckling.Dimensions.Types import Duckling.Duration.Helpers (duration) 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.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 $ and . sequence [isNotLatent, isGrainFinerThan TG.Year] , Predicate $ or . sequence [isNotLatent, isGrainOfTime TG.Year] ] , prod = \case (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 "от|за|на|," , Predicate isNotLatent ] , prod = \case (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 "на|," , Predicate $ isGrainOfTime TG.Year ] , prod = \case (Token Time td1:_:Token Time td2:_) -> Token Time . notLatent <$> intersect td1 td2 _ -> Nothing } ruleAbsorbOnDay :: Rule ruleAbsorbOnDay = Rule { name = "on <day>" , pattern = [ regex "на" , Predicate $ isGrainOfTime TG.Day ] , prod = \case (_:token:_) -> Just token _ -> Nothing } ruleAbsorbOnADOW :: Rule ruleAbsorbOnADOW = Rule { name = "on a <named-day>" , pattern = [ regex "в" , Predicate isADayOfWeek ] , prod = \case (_:token:_) -> Just token _ -> Nothing } ruleAbsorbInMonthYear :: Rule ruleAbsorbInMonthYear = Rule { name = "in|during <named-month>|year" , pattern = [ regex "в|през" , Predicate $ or . sequence [isAMonth, isGrainOfTime TG.Year] ] , prod = \case (_:Token Time td:_) -> tt $ notLatent td _ -> Nothing } ruleAbsorbCommaTOD :: Rule ruleAbsorbCommaTOD = Rule { name = "absorption of , after named day" , pattern = [ Predicate isADayOfWeek , regex "," ] , prod = \case (token:_) -> Just token _ -> Nothing } ruleInstants :: [Rule] ruleInstants = mkRuleInstants [ ("right now" , TG.Second, 0 , "((точно\\s+)?сега)|веднага") , ("today" , TG.Day , 0 , "днес|(по това време)" ) , ("tomorrow" , TG.Day , 1 , "утре" ) , ("yesterday" , TG.Day , - 1, "вчера" ) , ( "after tomorrow" , TG.Day , 2 , "(в\\s*)?другиден" ) , ( "before yesterday", TG.Day , - 2, "(оня ден)|завчера" ) ] ruleNow :: Rule ruleNow = Rule { name = "now" , pattern = [ regex "сега" ] , prod = \_ -> tt now } ruleNextDOW :: Rule ruleNextDOW = Rule { name = "this|next <day-of-week>" , pattern = [ regex "(т(о|а)зи)|следващ((ия(т)?)|ата)" , Predicate isADayOfWeek ] , prod = \case (_:Token Time td:_) -> tt $ predNth 0 True td _ -> Nothing } ruleThisTime :: Rule ruleThisTime = Rule { name = "this <time>" , pattern = [ regex "(т(а|о)зи)|това" , Predicate isOkWithThisNext ] , prod = \case (_:Token Time td:_) -> tt $ predNth 0 False td _ -> Nothing } ruleNextTime :: Rule ruleNextTime = Rule { name = "next <time>" , pattern = [ regex "следващ((ия(т)?)|ата|ото)" , Predicate isOkWithThisNext ] , prod = \case (_:Token Time td:_) -> tt $ predNth 0 True td _ -> Nothing } ruleLastTime :: Rule ruleLastTime = Rule { name = "last <time>" , pattern = [ regex "((пред(н|ишн))|минал)((ия(т)?)|ата|ото)" , Predicate isOkWithThisNext ] , prod = \case (_:Token Time td:_) -> tt $ predNth (- 1) False td _ -> Nothing } ruleLastWeekendOfMonth :: Rule ruleLastWeekendOfMonth = Rule { name = "last weekend of <named-month>" , pattern = [ regex "последния(т)? уикенд\\s+(през|на)" , Predicate isAMonth ] , prod = \case (_:Token Time td2:_) -> tt $ predLastOf weekend td2 _ -> Nothing } ruleTimeBeforeLast :: Rule ruleTimeBeforeLast = Rule { name = "<time> before last" , pattern = [ regex "по(\\-|\\s+)((пред(н|ишн))|минал)((ия(т)?)|ата|ото)" , dimension Time ] , prod = \case (_:Token Time td:_) -> tt $ predNth (- 2) False td _ -> Nothing } ruleTimeAfterNext :: Rule ruleTimeAfterNext = Rule { name = "<time> after next" , pattern = [ regex "по(\\-|\\s+)следващ((ия(т)?)|ата|ото)" , dimension Time ] , prod = \case (_:Token Time td:_) -> tt $ predNth 1 True td _ -> Nothing } ruleLastDOWOfTime :: Rule ruleLastDOWOfTime = Rule { name = "last <day-of-week> of <time>" , pattern = [ regex "последн((ия(т)?)|ата)" , Predicate isADayOfWeek , regex "на" , 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 "последн((ия(т)?)|ото|ата)" , dimension TimeGrain , regex "на|в|през" , dimension Time ] , prod = \case (_:Token TimeGrain grain:_:Token Time td:_) -> tt $ cycleLastOf grain td _ -> Nothing } ruleLastNight :: Rule ruleLastNight = Rule { name = "last night" , pattern = [ regex "(късно )?снощи" ] , prod = \case (Token RegexMatch (GroupMatch (match:_)):_) -> let hours = if Text.toLower match == "късно " 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 <time> of <time>" , pattern = [ dimension Ordinal , dimension Time , regex "на|в|през" , dimension Time ] , prod = \case (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 "след|от" , dimension Time ] , prod = \case (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 "от" , dimension Time ] , prod = \case (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 "(пр|сл)\\.?\\s+(Хр\\.?|н\\.?\\e.?)" ] , prod = \case (token:Token RegexMatch (GroupMatch (ab:_)):_) -> do y <- getIntValue token tt . yearADBC $ if Text.head (Text.toLower ab) == 'п' then -y else y _ -> Nothing } ruleDOMLatent :: Rule ruleDOMLatent = Rule { name = "<day-of-month> (ordinal)" , pattern = [Predicate isDOMOrdinal] , prod = \case (token:_) -> do n <- getIntValue token tt . mkLatent $ dayOfMonth n _ -> Nothing } ruleTheDOMNumeral :: Rule ruleTheDOMNumeral = Rule { name = "the <day-of-month> (number)" , pattern = [ Predicate isDOMInteger ] , prod = \case (token:_) -> do n <- getIntValue token tt . mkLatent $ dayOfMonth n _ -> Nothing } ruleTheDOMOrdinal :: Rule ruleTheDOMOrdinal = Rule { name = "the <day-of-month> (ordinal)" , pattern = [ Predicate isDOMOrdinal ] , prod = \case (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 = \case (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 = \case (Token Time td:token:_) -> 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 = \case (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{4})" ] , prod = \case (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 = \case (token:Token Time td:Token RegexMatch (GroupMatch (match:_)):_) -> do intVal <- parseInt match dom <- intersectDOM td token Token Time <$> intersect dom (year intVal) _ -> Nothing } ruleMonthYear :: Rule ruleMonthYear = Rule { name = "<named-month> year" , pattern = [ Predicate isAMonth , regex "(\\d{2,4})" ] , prod = \case (Token Time td:Token RegexMatch (GroupMatch (match:_)):_) -> do intVal <- parseInt match Token Time <$> intersect td (year intVal) _ -> Nothing } ruleIdesOfMonth :: Rule ruleIdesOfMonth = Rule { name = "the ides of <named-month>" , pattern = [ regex "средата на" , Predicate isAMonth ] , prod = \case (_: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 = \case (token:_) -> do n <- getIntValue token tt . mkLatent $ hour (n < 13) n _ -> Nothing } ruleAtTOD :: Rule ruleAtTOD = Rule { name = "at <time-of-day>" , pattern = [ regex "в" , Predicate isATimeOfDay ] , prod = \case (_:Token Time td:_) -> tt $ notLatent td _ -> Nothing } ruleTODOClock :: Rule ruleTODOClock = Rule { name = "<time-of-day> o'clock" , pattern = [ Predicate isATimeOfDay , regex "часа" ] , prod = \case (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 = \case (Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do h <- parseInt hh m <- parseInt mm tt $ hourMinute False h m _ -> Nothing } ruleHHhMM :: Rule ruleHHhMM = Rule { name = "hhhmm" , pattern = [ regex "(?<!/)((?:[01]?\\d)|(?:2[0-3]))ч(([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 = \case (Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do h <- parseInt hh m <- parseInt mm tt . mkLatent $ hourMinute False 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 = \case (Token RegexMatch (GroupMatch (hh:mm:ss:_)):_) -> do h <- parseInt hh m <- parseInt mm s <- parseInt ss tt $ hourMinuteSecond False h m s _ -> Nothing } ruleTODAM :: Rule ruleTODAM = Rule { name = "<time-of-day> am" , pattern = [ Predicate isATimeOfDay , regex "сутрин(та)?" ] , prod = \case (Token Time td:_) -> tt $ timeOfDayAMPM True td _ -> Nothing } ruleTODPM :: Rule ruleTODPM = Rule { name = "<time-of-day> pm" , pattern = [ Predicate isATimeOfDay , regex "вечер(та)?|след об(е|я)д" ] , prod = \case (Token Time td:_) -> tt $ timeOfDayAMPM False td _ -> Nothing } ruleHONumeral :: Rule ruleHONumeral = Rule { name = "<hour-of-day> <integer>" , pattern = [ Predicate isAnHourOfDay , Predicate $ isIntegerBetween 1 59 ] , 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 } ruleHODHalf :: Rule ruleHODHalf = Rule { name = "<hour-of-day> half" , pattern = [ Predicate isAnHourOfDay , regex "и половина" ] , prod = \case (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 "и (четвърт|(петнайсе(т)?))" ] , prod = \case (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 isAnHourOfDay , regex "без" , Predicate $ isIntegerBetween 1 59 ] , prod = \case (Token Time td:_:token:_) -> do n <- getIntValue token Token Time <$> minutesBefore n td _ -> Nothing } ruleQuarterToHOD :: Rule ruleQuarterToHOD = Rule { name = "quarter to|till|before <hour-of-day>" , pattern = [ Predicate isAnHourOfDay , regex "без (четвърт|(петнайсе(т)?))" ] , prod = \case (Token Time td:_:_) -> Token Time <$> minutesBefore 15 td _ -> Nothing } ruleNumeralAfterHOD :: Rule ruleNumeralAfterHOD = Rule { name = "integer after|past <hour-of-day>" , pattern = [ Predicate isAnHourOfDay , regex "и" , Predicate $ isIntegerBetween 1 59 ] , prod = \case (Token Time td:_:token:_) -> do n <- getIntValue token Token Time <$> minutesAfter n 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 } ruleDDMMYYYYDot :: Rule ruleDDMMYYYYDot = Rule { name = "dd.mm.yyyy" , pattern = [ regex "(3[01]|[12]\\d|0?[1-9])\\.(1[0-2]|0?[1-9])\\.(\\d{2,4})" ] , prod = \case (Token RegexMatch (GroupMatch (dd:mm:yy:_)):_) -> 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 "(обед|обяд|полунощ|((в )?(края на деня)))" ] , prod = \case (Token RegexMatch (GroupMatch (match:_)):_) -> tt . hour False $ if (Text.toLower match == "обед" || Text.toLower match == "обяд") then 12 else 0 _ -> Nothing } rulePartOfDays :: Rule rulePartOfDays = Rule { name = "part of days" , pattern = [ regex "(сутрин(та)?|(по|след) ?об(е|я)д|вечер(та)?|(през )?нощ(та)?)" ] , prod = \case (Token RegexMatch (GroupMatch (match:_)):_) -> do let (start, end) = case Text.toLower match of "сутрин" -> (hour False 4, hour False 12) "сутринта" -> (hour False 4, hour False 12) "вечер" -> (hour False 18, hour False 0) "вечерта" -> (hour False 18, hour False 0) "нощ" -> (hour False 0, hour False 4) "нощта" -> (hour False 0, hour False 4) "през нощта" -> (hour False 0, hour False 4) "обед" -> (hour False 12, hour False 14) "по обед" -> (hour False 12, hour False 14) "обяд" -> (hour False 12, hour False 14) "по обяд" -> (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 "рано сутрин(та)?" ] , prod = \_ -> Token Time . partOfDay . mkLatent <$> interval TTime.Open (hour False 4) (hour False 9) } rulePODThis :: Rule rulePODThis = Rule { name = "this <part-of-day>" , pattern = [ regex "(т(о|а)зи)" , Predicate isAPartOfDay ] , prod = \case (_:Token Time td:_) -> Token Time . partOfDay . notLatent <$> intersect today td _ -> Nothing } ruleTonight :: Rule ruleTonight = Rule { name = "tonight" , pattern = [ regex "(късно )?вечер(та)?" ] , prod = \case (Token RegexMatch (GroupMatch (match:_)):_) -> do let h = if Text.toLower match == "късно " 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 "след[\\s*]?((об(е|я)д)|работа|училище)" ] , prod = \case (Token RegexMatch (GroupMatch (match:_)):_) -> do (start, end) <- case Text.toLower match of "обед" -> Just (hour False 13, hour False 17) "обяд" -> Just (hour False 13, hour False 17) "работа" -> Just (hour False 17, hour False 21) "училище" -> 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 = \case (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 "на" , dimension Time ] , prod = \case (Token Time pod:_:Token Time td:_) -> Token Time <$> intersect pod td _ -> Nothing } ruleWeekend :: Rule ruleWeekend = Rule { name = "week-end" , pattern = [ regex "(края на седмицата)|(уикенд(а)?)" ] , prod = \_ -> tt $ mkOkForThisNext weekend } ruleWeek :: Rule ruleWeek = Rule { name = "week" , pattern = [regex "(цяла|(остатъка на( тази)?)) седмица(та)?"] , prod = \case (Token RegexMatch (GroupMatch (match:_)):_) -> let end = cycleNthAfter True TG.Day (-2) $ cycleNth TG.Week 1 period = case Text.toLower match of "цяла" -> interval Closed (cycleNth TG.Week 0) end "остатъка на" -> interval Open today end "остатъка на тази" -> interval Open today end _ -> Nothing in case Text.toLower match of "тази" -> Token Time . mkLatent <$> period _ -> Token Time <$> period _ -> Nothing } ruleSeason :: Rule ruleSeason = Rule { name = "last|this|next <season>" , pattern = [ regex "(това|тази|(по\\-)?(по\\-)?((настоящ|следващ|друг|последн|минал|предн)(ото|ата))) seasons?" ] , prod = \case (Token RegexMatch (GroupMatch (match:_)):_) -> do n <- case Text.toLower match of "това" -> Just 0 "тази" -> Just 0 "настоящото" -> Just 0 "настоящата" -> Just 0 "миналото" -> Just (-1) "миналата" -> Just (-1) "последното" -> Just (-1) "последната" -> Just (-1) "предното" -> Just (-1) "предната" -> Just (-1) "следващото" -> Just 1 "следващата" -> Just 1 "другото" -> Just 1 "другата" -> Just 1 "по-миналото" -> Just (-2) "по-миналата" -> Just (-2) "по-последното" -> Just (-2) "по-последната" -> Just (-2) "по-предното" -> Just (-2) "по-предната" -> Just (-2) "по-следващото" -> Just 2 "по-следващата" -> Just 2 "по-другото" -> Just 2 "по-другата" -> Just 2 "по-по-миналото" -> Just (-3) "по-по-миналата" -> Just (-3) "по-по-последното" -> Just (-3) "по-по-последната" -> Just (-3) "по-по-предното" -> Just (-3) "по-по-предната" -> Just (-3) "по-по-следващото" -> Just 3 "по-по-следващата" -> Just 3 "по-по-другото" -> Just 3 "по-по-другата" -> Just 3 _ -> Nothing tt $ predNth n False season _ -> Nothing } ruleSeasons :: [Rule] ruleSeasons = mkRuleSeasons [ ( "summer", "лято" , monthDay 6 21, monthDay 9 23 ) , ( "fall" , "есен" , monthDay 9 23, monthDay 12 21 ) , ( "winter", "зима" , monthDay 12 21, monthDay 3 20 ) , ( "spring", "пролет" , monthDay 3 20, monthDay 6 21 ) ] ruleTODPrecision :: Rule ruleTODPrecision = Rule { name = "<time-of-day> sharp|exactly" , pattern = [ regex "(в )?точно( в )?" , Predicate isATimeOfDay ] , prod = \case (_:Token Time td:_) -> tt $ notLatent td _ -> Nothing } rulePrecisionTOD :: Rule rulePrecisionTOD = Rule { name = "about|exactly <time-of-day>" , pattern = [ regex "около" , Predicate $ isGrainFinerThan TG.Year ] , prod = \case (_:Token Time td:_) -> tt $ notLatent td _ -> Nothing } ruleIntervalMonthDDDD :: Rule ruleIntervalMonthDDDD = Rule { name = "<month> dd-dd (interval)" , pattern = [ Predicate isAMonth , Predicate isDOMValue , regex "\\-|до" , Predicate isDOMValue ] , prod = \case (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 "\\-|до" , Predicate isDOMValue , Predicate isAMonth ] , prod = \case (token1: _: token2: Token Time td: _) -> 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 "от" , Predicate isDOMValue , regex "\\-|до" , Predicate isDOMValue , Predicate isAMonth ] , prod = \case (_: token1: _: token2: Token Time td: _) -> do dom1 <- intersectDOM td token1 dom2 <- intersectDOM td token2 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 "\\-|до" , 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 = \case (Token Time td1:_:Token Time td2:_) -> if sameGrain td1 td2 then Token Time <$> interval TTime.Closed td1 td2 else Nothing _ -> Nothing } ruleIntervalFrom :: Rule ruleIntervalFrom = Rule { name = "from <datetime> - <datetime> (interval)" , pattern = [ regex "от" , dimension Time , regex "до" , dimension Time ] , prod = \case (_: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 "между" , dimension Time , regex "и" , dimension Time ] , prod = \case (_: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 "\\-|до" , Predicate isATimeOfDay ] , prod = \case (Token Time td1:_:Token Time td2:_) -> Token Time <$> interval TTime.Closed td1 td2 _ -> Nothing } ruleIntervalTODBetween :: Rule ruleIntervalTODBetween = Rule { name = "between <time-of-day> and <time-of-day> (interval)" , pattern = [ regex "между" , Predicate isATimeOfDay , regex "и" , 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 "до" , 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 "до края на" , dimension Time ] , prod = \case (_:Token Time td:_) -> Token Time <$> interval TTime.Closed now td _ -> Nothing } ruleIntervalUntilTime :: Rule ruleIntervalUntilTime = Rule { name = "until <time>" , pattern = [ regex "преди|до" , dimension Time ] , prod = \case (_:Token Time td:_) -> tt . withDirection TTime.Before $ notLatent td _ -> Nothing } ruleIntervalAfterFromSinceTime :: Rule ruleIntervalAfterFromSinceTime = Rule { name = "from|since|after <time>" , pattern = [ regex "след|от" , dimension Time ] , prod = \case (_:Token Time td:_) -> tt . withDirection TTime.After $ notLatent td _ -> Nothing } ruleDaysOfWeek :: [Rule] ruleDaysOfWeek = mkRuleDaysOfWeek [ ( "Monday" , "понеделник|пон\\.?|пн\\.?" ) , ( "Tuesday" , "вторник|вт\\.?" ) , ( "Wednesday", "сряда|ср\\.?" ) , ( "Thursday" , "четвъртък|четв\\.?|чт\\.?" ) , ( "Friday" , "петък|пет\\.?|пт\\.?" ) , ( "Saturday" , "събота|съб\\.?|сб\\.?" ) , ( "Sunday" , "неделя|нед\\.?|нд\\.?" ) ] ruleMonths :: [Rule] ruleMonths = mkRuleMonths [ ( "January" , "януари|ян\\.?" ) , ( "February" , "февруари|февр\\.?" ) , ( "March" , "март" ) , ( "April" , "април|апр\\.?" ) , ( "May" , "май" ) , ( "June" , "юни" ) , ( "July" , "юли" ) , ( "August" , "август|авг\\.?" ) , ( "September", "септември|септ\\.?") , ( "October" , "октомври|окт\\.?" ) , ( "November" , "ноември|ноем\\.?" ) , ( "December" , "декември|дек\\.?" ) ] rulePartOfMonth :: Rule rulePartOfMonth = Rule { name = "part of <named-month>" , pattern = [ regex "(началото|средата|края) на" , Predicate isAMonth ] , prod = \case (Token RegexMatch (GroupMatch (match:_)):Token Time td:_) -> do (sd, ed) <- case Text.toLower match of "началото" -> Just (1, 10) "средата" -> Just (11, 20) "края" -> 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 "(в )?(началото|края) на" , Predicate isAMonth ] , prod = \case (Token RegexMatch (GroupMatch (_:match:_)):Token Time td:_) -> do (sd, ed) <- case Text.toLower match of "началото" -> Just (1, 10) "края" -> 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 "((до|в)\\s+)?края на месеца" ] , prod = \case (Token RegexMatch (GroupMatch (match:_)):_) | (Just start, Just end) <- parsed -> Token Time <$> interval TTime.Open start end where cycleMonth = cycleNth TG.Month parsed = if "до" `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 "(в\\s+ )?началото на месеца" ] , 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 "(в\\s+)?(началото|края) на" , Predicate $ isGrainOfTime TG.Year ] , prod = \case (Token RegexMatch (GroupMatch (_:match:_)):Token Time td:_) -> do (sd, ed) <- case Text.toLower match of "началото" -> Just (1, 4) "края" -> 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 "((до|в)\\s+)?края на годината" ] , prod = \case (Token RegexMatch (GroupMatch (match:_)):_) -> do start <- std end <- intersect (month 1) $ cycleYear 1 Token Time <$> interval TTime.Open start end where std = if "до" `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 "(в\\s+)?началото на годината" ] , 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 "(в\\s+)?(началото|края) на" , Predicate $ isGrainOfTime TG.Week ] , prod = \case (Token RegexMatch (GroupMatch (_:match1:_)):Token Time td:_) -> do (sd, ed) <- case Text.toLower match1 of "началото" -> Just (1, 3) "края" -> Just (5, 7) _ -> Nothing start <- intersect td $ dayOfWeek sd end <- intersect td $ dayOfWeek ed Token Time <$> interval TTime.Open start end _ -> Nothing } rulePeriodicHolidays :: [Rule] rulePeriodicHolidays = mkRuleHolidays -- Fixed dates, year over year [ ( "April Fools", "първи април", monthDay 4 1 ) , ( "Bulgarian Liberation Day", "(трети март)|(ден(я|ят)? на освобождението на българия( от турско робство)?)", monthDay 3 3 ) , ( "Bulgarian Indenpendence Day", "ден(я|ят)? на (обявянето на)? независимостта на българия", monthDay 9 22 ) , ( "Bulgarian Unification Day", "ден(я|ят)? на съединението на (княжество )?българия(и източна румелия)?", monthDay 9 6 ) , ( "Slavonic Litaracy Day", "(ден(я|ят)? на славянската писменост)|(св(\\.|ети)?(св(\\.|ети)?)? кирил и методи(й)?)", monthDay 5 24 ) , ( "Saint Geroge's Day", "(св(\\.|ети)? георги)|гергьовден", monthDay 5 6 ) , ( "Assumption of Mary", "голяма богородица", monthDay 8 15 ) , ( "Christmas", "коледа|(рождество( христово)?)", monthDay 12 25 ) , ( "Christmas Eve", "бъдни вечер", monthDay 12 24 ) , ( "Earth Day", "ден(я|ят) на земята", monthDay 4 22 ) , ( "May Day", "(първи май)|(ден(я|ят)? на труда)", monthDay 5 1 ) , ( "New Year's Day", "нова година", monthDay 1 1 ) , ( "St. George's Day", "гергьовден|(св(\\.|ети)? георги)", monthDay 5 6 ) , ( "Valentine's Day", "св(\\.|ети)? валентин", monthDay 2 14 ) , ( "International Women's Day", "ден(я|ят)? на (майката|жената)", monthDay 3 8 ) ] ruleComputedHolidays :: [Rule] ruleComputedHolidays = mkRuleHolidays [ ( "Orthodox Easter Monday", "велики понеделник" , cycleNthAfter False TG.Day 1 orthodoxEaster ) , ( "Orthodox Easter Sunday", "великден" , orthodoxEaster ) , ( "Orthodox Holy Saturday", "велика събота" , cycleNthAfter False TG.Day (-1) orthodoxEaster ) , ( "Orthodox Great Friday", "(велики|разпети) петък" , cycleNthAfter False TG.Day (-2) orthodoxEaster ) , ( "Orthodox Palm Sunday", "цветница" , cycleNthAfter False TG.Day (-7) orthodoxEaster ) ] ruleComputedHolidays' :: [Rule] ruleComputedHolidays' = mkRuleHolidays' [ ( "Great Lent", "велики(те)? пости" , let start = cycleNthAfter False TG.Day (-48) orthodoxEaster end = cycleNthAfter False TG.Day (-9) orthodoxEaster in interval TTime.Open start end ) -- Other -- Last Saturday of March unless it falls on Holy Saturday -- In which case it's the Saturday before , ( "Earth Hour", "час(а|ът) на земята" , let holySaturday = cycleNthAfter False TG.Day (-1) easterSunday tentative = predLastOf (dayOfWeek 6) (month 3) alternative = cycleNthAfter False TG.Day (-7) tentative in do day <- intersectWithReplacement holySaturday tentative alternative start <- intersect day $ hourMinute True 20 30 interval TTime.Closed start $ cycleNthAfter False TG.Minute 60 start ) ] ruleCycleThisLastNext :: Rule ruleCycleThisLastNext = Rule { name = "this|last|next <cycle>" , pattern = [ regex "(т(о|а)зи|това|((по\\-)*(настоящ|идн|следващ|минал|последн|предишн)(ия(т)?|ата|ото)))" , dimension TimeGrain ] , prod = \case (Token RegexMatch (GroupMatch (match:_)):Token TimeGrain grain:_) -> case Text.toLower match of "този" -> tt $ cycleNth grain 0 "тази" -> tt $ cycleNth grain 0 "това" -> tt $ cycleNth grain 0 "настоящия" -> tt $ cycleNth grain 0 "настоящият" -> tt $ cycleNth grain 0 "настоящата" -> tt $ cycleNth grain 0 "настоящото" -> tt $ cycleNth grain 0 "последния" -> tt . cycleNth grain $ - 1 "последният" -> tt . cycleNth grain $ - 1 "последната" -> tt . cycleNth grain $ - 1 "последното" -> tt . cycleNth grain $ - 1 "миналия" -> tt . cycleNth grain $ - 1 "миналият" -> tt . cycleNth grain $ - 1 "миналата" -> tt . cycleNth grain $ - 1 "миналото" -> tt . cycleNth grain $ - 1 "предишния" -> tt . cycleNth grain $ - 1 "предишният" -> tt . cycleNth grain $ - 1 "предишната" -> tt . cycleNth grain $ - 1 "предишното" -> tt . cycleNth grain $ - 1 "следващия" -> tt $ cycleNth grain 1 "следващият" -> tt $ cycleNth grain 1 "следващата" -> tt $ cycleNth grain 1 "следващото" -> tt $ cycleNth grain 1 "идния" -> tt $ cycleNth grain 1 "идният" -> tt $ cycleNth grain 1 "идната" -> tt $ cycleNth grain 1 "идното" -> tt $ cycleNth grain 1 "по-последния" -> tt . cycleNth grain $ - 2 "по-последният" -> tt . cycleNth grain $ - 2 "по-последната" -> tt . cycleNth grain $ - 2 "по-последното" -> tt . cycleNth grain $ - 2 "по-миналия" -> tt . cycleNth grain $ - 2 "по-миналият" -> tt . cycleNth grain $ - 2 "по-миналата" -> tt . cycleNth grain $ - 2 "по-миналото" -> tt . cycleNth grain $ - 2 "по-предишния" -> tt . cycleNth grain $ - 2 "по-предишният" -> tt . cycleNth grain $ - 2 "по-предишната" -> tt . cycleNth grain $ - 2 "по-предишното" -> tt . cycleNth grain $ - 2 "по-следващия" -> tt $ cycleNth grain 2 "по-следващият" -> tt $ cycleNth grain 2 "по-следващата" -> tt $ cycleNth grain 2 "по-следващото" -> tt $ cycleNth grain 2 "по-по-последния" -> tt . cycleNth grain $ - 3 "по-по-последният" -> tt . cycleNth grain $ - 3 "по-по-последната" -> tt . cycleNth grain $ - 3 "по-по-последното" -> tt . cycleNth grain $ - 3 "по-по-миналия" -> tt . cycleNth grain $ - 3 "по-по-миналият" -> tt . cycleNth grain $ - 3 "по-по-миналата" -> tt . cycleNth grain $ - 3 "по-по-миналото" -> tt . cycleNth grain $ - 3 "по-по-предишния" -> tt . cycleNth grain $ - 3 "по-по-предишният" -> tt . cycleNth grain $ - 3 "по-по-предишната" -> tt . cycleNth grain $ - 3 "по-по-предишното" -> tt . cycleNth grain $ - 3 "по-по-следващия" -> tt $ cycleNth grain 3 "по-по-следващият" -> tt $ cycleNth grain 3 "по-по-следващата" -> tt $ cycleNth grain 3 "по-по-следващото" -> tt $ cycleNth grain 3 _ -> Nothing _ -> Nothing } ruleCycleAfterBeforeTime :: Rule ruleCycleAfterBeforeTime = Rule { name = "<cycle> after|before <time>" , pattern = [ dimension TimeGrain , regex "след|преди" , dimension Time ] , prod = \case (Token TimeGrain grain: Token RegexMatch (GroupMatch (match:_)): Token Time td: _) -> let n = if Text.toLower match == "след" then 1 else - 1 in tt $ cycleNthAfter False grain n td _ -> Nothing } ruleDayInDuration :: Rule ruleDayInDuration = Rule { name = "<day> in <duration>" , pattern = [ Predicate $ or . sequence [isGrainOfTime TG.Day, isGrainOfTime TG.Month] , regex "в|през" , Predicate $ isDurationGreaterThan TG.Hour ] , prod = \case (Token Time td:_:Token Duration dd:_) -> Token Time <$> intersect td (inDurationInterval dd) _ -> Nothing } ruleDurationInWithinAfter :: Rule ruleDurationInWithinAfter = Rule { name = "in|within|after <duration>" , pattern = [ regex "(за|в рамките на|след)" , dimension Duration ] , prod = \case (Token RegexMatch (GroupMatch (match:_)): Token Duration dd: _) -> case Text.toLower match of "в рамките на" -> Token Time <$> interval TTime.Open now (inDuration dd) "след" -> tt . withDirection TTime.After $ inDuration dd "за" -> tt $ inDuration dd _ -> Nothing _ -> Nothing } ruleNDOWago :: Rule ruleNDOWago = Rule { name = "<integer> <named-day> ago|back" , pattern = [ regex "преди" , Predicate isNatural , Predicate isADayOfWeek ] , prod = \case (_: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 = [ regex "(от|преди)" , dimension Duration ] , prod = \case (Token RegexMatch (GroupMatch (match:_)):Token Duration dd:_) -> case Text.toLower match of "от" -> tt $ durationAgo dd _ -> tt $ inDuration dd _ -> Nothing } ruleInNumeral :: Rule ruleInNumeral = Rule { name = "in <number> (implicit minutes)" , pattern = [ regex "след" , Predicate $ isIntegerBetween 0 60 ] , prod = \case (_:Token Numeral NumeralData{TNumeral.value = v}:_) -> tt . inDuration . duration TG.Minute $ floor v _ -> Nothing } ruleIntervalForDurationFrom :: Rule ruleIntervalForDurationFrom = Rule { name = "for <duration> from <time>" , pattern = [ regex "за" , dimension Duration , regex "от|след" , dimension Time ] , prod = \case (_: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 "за" , dimension Duration ] , prod = \case (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 "от|след" , Predicate isNotLatent , regex "за" , dimension Duration ] , prod = \case (_: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 = \case (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 = \case (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 "\\-|до" , Predicate $ and . sequence [isATimeOfDay, hasNoTimezone] , regex $ "\\b(" ++ timezoneName ++ ")\\b" ] , prod = \case (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 } rules :: [Rule] rules = [ ruleIntersect , ruleIntersectOf , ruleIntersectYear , ruleAbsorbOnDay , ruleAbsorbInMonthYear , ruleAbsorbCommaTOD , ruleAbsorbOnADOW , ruleNextDOW , ruleNextTime , ruleThisTime , ruleLastTime , ruleTimeBeforeLast , ruleTimeAfterNext , ruleLastDOWOfTime , ruleLastCycleOfTime , ruleLastNight , ruleLastWeekendOfMonth , ruleNthTimeOfTime , ruleNthTimeAfterTime , ruleNDOWFromTime , ruleYearLatent , ruleYearADBC , ruleTheDOMNumeral , ruleTheDOMOrdinal , ruleDOMLatent , ruleNamedDOMOrdinal , ruleMonthDOMNumeral , ruleDOMMonth , ruleDOMOrdinalMonthYear , ruleDOMMonthYear , ruleIdesOfMonth , ruleTODLatent , ruleAtTOD , ruleTODOClock , ruleTODAM , ruleTODPM , ruleHHMM , ruleHHhMM , ruleHHMMLatent , ruleHHMMSS , ruleHONumeral , ruleHODHalf , ruleHODQuarter , ruleNumeralToHOD , ruleQuarterToHOD , ruleNumeralAfterHOD , ruleYYYYQQ , ruleYYYYMM , ruleYYYYMMDD , ruleDDMMYYYYDot , ruleMMYYYY , ruleNoonMidnightEOD , rulePartOfDays , ruleEarlyMorning , rulePODThis , ruleTonight , ruleAfterPartofday , ruleTimePOD , rulePODofTime , ruleWeekend , ruleWeek , ruleTODPrecision , rulePrecisionTOD , ruleIntervalFromDDDDMonth , ruleIntervalMonthDDDD , ruleIntervalDDDDMonth , ruleIntervalDash , ruleIntervalSlash , ruleIntervalFrom , ruleIntervalBetween , ruleIntervalTODDash , ruleIntervalTODBetween , ruleIntervalBy , ruleIntervalByTheEndOf , ruleIntervalUntilTime , ruleIntervalAfterFromSinceTime , ruleCycleThisLastNext , ruleCycleAfterBeforeTime , ruleDayInDuration , ruleDurationInWithinAfter , ruleNDOWago , ruleDurationHenceAgo , ruleIntervalForDurationFrom , ruleIntervalFromTimeForDuration , ruleIntervalTimeForDuration , ruleInNumeral , ruleTimezone , ruleTimezoneBracket , ruleIntervalDashTimezone , rulePartOfMonth , ruleEndOrBeginningOfMonth , ruleEndOrBeginningOfYear , ruleEndOrBeginningOfWeek , ruleNow , ruleSeason , ruleEndOfMonth , ruleBeginningOfMonth , ruleEndOfYear , ruleBeginningOfYear , ruleMonthYear ] ++ ruleInstants ++ ruleDaysOfWeek ++ ruleMonths ++ ruleSeasons ++ ruleComputedHolidays ++ ruleComputedHolidays' ++ rulePeriodicHolidays