Duckling/Time/KA/Rules.hs (1,563 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.KA.Rules ( rules ) where import Data.Maybe import Data.Text (Text) 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.Types (TimeData(..)) 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 -- Georgian weekend geoWeekend :: TimeData geoWeekend = case interval TTime.Open fri mon of Just td -> td Nothing -> weekend where fri = dayOfWeek 6 mon = dayOfWeek 7 ruleIntersect :: Rule ruleIntersect = Rule { name = "intersect" , pattern = [ Predicate $ or . sequence [isNotLatent, isGrainOfTime TG.Year] , Predicate $ or . sequence [isNotLatent, isGrainOfTime TG.Year] ] , prod = \case (Token Time td1:Token Time td2:_) | (not $ TTime.latent td1) || (not $ TTime.latent td2) -> Token Time . notLatent <$> intersect td1 td2 _ -> Nothing } ruleAbsorbInYear :: Rule ruleAbsorbInYear = Rule { name = "in year" , pattern = [ Predicate $ isGrainOfTime TG.Year , regex " ?წელს| ?წელი| ?წლის|-ში|ში" ] , prod = \case (Token Time td:_) -> tt $ notLatent td _ -> Nothing } ruleYearInterval :: Rule ruleYearInterval = Rule { name = "<integer> year" , pattern = [ Predicate isNatural , regex "წელიწად(ის|ი|ში)?|წლ(ის)?|წელ(შ?ი|ს)" ] , prod = \case (token:_) -> do n <- getIntValue token Token Time <$> interval TTime.Open (year n) (year $ n + 1) _ -> Nothing } ruleInstants :: [Rule] ruleInstants = mkRuleInstants [ ("ახლა", TG.Second, 0, "ახლავე|ეხლავე|ეხლა|ახლა|ამ წამს|ამ წუთას|ამ მომენტისთვი") , ("დღეს", TG.Day, 0, "დღეს") , ("ხვალ", TG.Day, 1, "ხვალე?") , ("ზეგ", TG.Day, 2, "ზეგ") , ("მაზეგ", TG.Day, 3, "მაზეგ") , ("გუშინ", TG.Day, -1, "გუშინ?") , ("გუშინწინ", TG.Day, -2, "გუშინ ?წინ") ] ruleNow :: Rule ruleNow = Rule { name = "now" , pattern = [ regex "ახლავე|ახლა|ამწუთას|ამ მომენტისთვის" ] , prod = const $ tt now } ruleNextDOW :: Rule ruleNextDOW = Rule { name = "this|next <day-of-week>" , pattern = [ regex "შემდეგი|მომავალი" , Predicate isADayOfWeek ] , prod = \case (_:Token Time td:_) -> tt $ predNth 1 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 1 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 "ბოლო უიქ?კ? ?ენდი|ვიქ?კ? ?ენდი|შაბ?ფ?ათკვირა|შაბ?ფ?ათ-კვირა|უქმეები" , Predicate isAMonth ] , prod = \case (_:Token Time td2:_) -> tt $ predLastOf geoWeekend td2 _ -> Nothing } ruleLastWorkweekOfMonth :: Rule ruleLastWorkweekOfMonth = Rule { name = "last workweek of <named-month>" , pattern = [ regex "ბოლო სამუშაო ?კვირას?(ში)?" , Predicate isAMonth ] , prod = \case (_:Token Time td2:_) -> tt $ predLastOf workweek td2 _ -> Nothing } ruleLastWeekendOfMonth1 :: Rule ruleLastWeekendOfMonth1 = Rule { name = "last weekend of <named-month>" , pattern = [ Predicate isAMonth , regex "(ს? ?ბოლო უიქ?კ? ?ენდი ?|ს? ბოლო ?ვიქ?კ? ?ენდი ?|ს? ბოლო ?შაბ?ფ?ათკვირა ?|ს? ბოლო ?შაბ?ფ?ათ-კვირა ?)| ს?უქმეები ?" ] , prod = \case (Token Time td2:_) -> tt $ predLastOf geoWeekend td2 _ -> Nothing } ruleLastWorkweekOfMonth1 :: Rule ruleLastWorkweekOfMonth1 = Rule { name = "last workweek of <named-month>" , pattern = [ Predicate isAMonth , regex "(ს? ბოლო სამუშაო ?კვირას?(ში)?)" ] , prod = \case (Token Time td2:_) -> tt $ predLastOf workweek td2 _ -> Nothing } ruleTimeAfterNext :: Rule ruleTimeAfterNext = Rule { name = "<time> after next" , pattern = [ regex "(შემდეგის შემდეგი? ?)" , dimension Time ] , prod = \case (Token RegexMatch (GroupMatch (match:_)):Token Time td:_) -> tt $ predNth 2 (Text.toLower match == "შემდეგის შემდეგ") td _ -> Nothing } ruleTimeBeforeLast :: Rule ruleTimeBeforeLast = Rule { name = "<time> before last" , pattern = [ regex "(ბოლოს წინა ?|წინის წინა ?)" , dimension Time ] , prod = \case (Token RegexMatch (GroupMatch (match:_)):Token Time td:_) -> tt $ predNth (-2) (Text.toLower match == "ბოლოს წინა") td _ -> Nothing } ruleNthTimeOfTime :: Rule ruleNthTimeOfTime = Rule { name = "nth <time> of <time>" , pattern = [ dimension Time , dimension Ordinal , dimension Time ] , prod = \case (Token Time td1:Token Ordinal od: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 Time , regex "დან" , dimension Ordinal , dimension Time ] , prod = \case (Token Ordinal od:Token Time td1:_:Token Time td2:_) -> tt $ predNthAfter (TOrdinal.value od - 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 } 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 } ruleMonthDOMNumeral1 :: Rule ruleMonthDOMNumeral1 = Rule { name = "<named-month> <day-of-month> (non ordinal)" , pattern = [ Predicate isAMonth , Predicate isDOMOrdinal , regex "( დღეს?)?" ] , prod = \case (Token Time td:token:_) -> Token Time <$> intersectDOM td token _ -> 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 True n _ -> Nothing } ruleMMDD :: Rule ruleMMDD = Rule { name = "dd/mm" , pattern = [ regex "(3[01]|[12]\\d|0?[1-9])\\s?[/-]\\s?(1[0-2]|0?[1-9])" ] , prod = \case (Token RegexMatch (GroupMatch (dd:mm:_)):_) -> do m <- parseInt mm d <- parseInt dd tt $ monthDay m d _ -> Nothing } ruleMMDDYYYY :: Rule ruleMMDDYYYY = Rule { name = "dd/mm/yyyy" , pattern = [ regex "(3[01]|[12]\\d|0?[1-9])[-/\\s](1[0-2]|0?[1-9])[-/\\s](\\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 } ruleMMDDYYYYDot :: Rule ruleMMDDYYYYDot = Rule { name = "mm.dd.yyyy" , pattern = [ regex "(1[0-2]|0?[1-9])\\.(3[01]|[12]\\d|0?[1-9])\\.(\\d{4})" ] , prod = \case (Token RegexMatch (GroupMatch (mm:dd:yy:_)):_) -> do y <- parseInt yy m <- parseInt mm d <- parseInt dd tt $ yearMonthDay y m d _ -> Nothing } ruleAtTOD :: Rule ruleAtTOD = Rule { name = "at <time-of-day>" , pattern = [ Predicate isATimeOfDay , regex "ს|-ს|-ზე|ზე| ზე" ] , 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 True 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 True h m _ -> Nothing } ruleHHMMSS :: Rule ruleHHMMSS = Rule { name = "hh:mm:ss" , pattern = [regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)[:.]([0-5]\\d)"] , prod = \case (Token RegexMatch (GroupMatch (hh:mm:ss:_)):_) -> do h <- parseInt hh m <- parseInt mm s <- parseInt ss tt $ hourMinuteSecond True h m s _ -> Nothing } ruleMilitaryAMPM :: Rule ruleMilitaryAMPM = Rule { name = "hhmm (military) am|pm" , pattern = [ regex "((?:1[012]|0?\\d))([0-5]\\d)" , regex "([ap])\\.?m?\\.?" ] , prod = \case (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 } ruleMilitarySpelledOutAMPM :: Rule ruleMilitarySpelledOutAMPM = Rule { name = "military spelled out numbers am|pm" , pattern = [ Predicate $ isIntegerBetween 10 12 , Predicate $ isIntegerBetween 1 59 , regex "([ap])(\\s|\\.)?m?\\.?" ] , prod = \case (h:m:Token RegexMatch (GroupMatch (_:ap:_)):_) -> do hh <- getIntValue h mm <- getIntValue m tt . timeOfDayAMPM (Text.toLower ap == "a") $ hourMinute True hh mm _ -> Nothing } ruleMilitarySpelledOutAMPM2 :: Rule ruleMilitarySpelledOutAMPM2 = Rule { name = "six thirty six a.m." , pattern = [ Predicate $ isIntegerBetween 110 999 , regex "([ap])(\\s|\\.)?m?\\.?" ] , prod = \case (token:Token RegexMatch (GroupMatch (_:ap:_)):_) -> do n <- getIntValue token m <- case mod n 100 of v | v < 60 -> Just v _ -> Nothing let h = quot n 100 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 = \case (Token Time td@TimeData {TTime.latent = True}: Token RegexMatch (GroupMatch (_:ap:_:"":_)):_) -> tt . mkLatent $ timeOfDayAMPM (Text.toLower ap == "a") td (Token Time td:Token RegexMatch (GroupMatch (_:ap:_)):_) -> tt $ timeOfDayAMPM (Text.toLower ap == "a") td _ -> Nothing } ruleNumeralToHOD :: Rule ruleNumeralToHOD = Rule { name = "<integer> to|till|before <hour-of-day>" , pattern = [ Predicate isAnHourOfDay , Predicate $ isIntegerBetween 1 59 , regex "(წუთი ?)აკლ(ია)?(და)?|(წუთი ?)უკლია" ] , prod = \case (Token Time td:token:_) -> do n <- getIntValue token t <- minutesBefore n td Just $ Token Time t _ -> Nothing } ruleNumeralToHOD1 :: Rule ruleNumeralToHOD1 = Rule { name = "<integer> to|till|before <hour-of-day>" , pattern = [ Predicate isAnHourOfDay , regex "რო" , Predicate $ isIntegerBetween 1 59 , regex "(წუთი ?)აკლ(ია)?(და)?|(წუთი ?)უკლია" ] , prod = \case (Token Time td:_:token:_) -> do n <- getIntValue token t <- minutesBefore n td Just $ Token Time t _ -> Nothing } ruleHalfToHOD :: Rule ruleHalfToHOD = Rule { name = "half to|till|before <hour-of-day>" , pattern = [ Predicate isAnHourOfDay , regex "ის ნახევა?რი?(ზე)?|-ის ნახევა?რი?(ზე)?" ] , prod = \case (Token Time td:_) -> Token Time <$> minutesBefore 30 td _ -> Nothing } ruleNumeralAfterHOD :: Rule ruleNumeralAfterHOD = Rule { name = "integer after|past <hour-of-day>" , pattern = [ Predicate isAnHourOfDay , Predicate $ isIntegerBetween 1 59 , regex "წუთი?(ზე)?(სთვის)?" ] , prod = \case (Token Time td:token:_) -> do n <- getIntValue token t <- minutesBefore (60-n) td Just $ Token Time t _ -> Nothing } ruleNumeralAfterHOD1 :: Rule ruleNumeralAfterHOD1 = Rule { name = "integer after|past <hour-of-day>" , pattern = [ Predicate isAnHourOfDay , regex "-ის|ის|საათ(სა)?(ზე)?(და )?" , Predicate $ isIntegerBetween 1 59 , regex "წუთი?(ზე)?(სთვის)?" ] , prod = \case (Token Time td:_:token:_) -> do n <- getIntValue token t <- minutesBefore (60-n) td Just $ Token Time t _ -> Nothing } ruleNumeralAfterHOD2 :: Rule ruleNumeralAfterHOD2 = Rule { name = "integer after|past <hour-of-day>" , pattern = [ Predicate isAnHourOfDay , regex "საათი?(სა)?(ზე)?( ?და ?)?" , Predicate $ isIntegerBetween 1 59 , regex "წუთი?(ზე)?(სთვის)?" ] , prod = \case (Token Time td:_:token:_) -> do n <- getIntValue token t <- minutesAfter n td Just $ Token Time t _ -> 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 $ yearMonthDay y m 1 _ -> 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 } 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 4, hour False 12) "დილა" -> (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 18, hour False 0) "შუაღამით" -> (hour False 18, hour False 0) "შუაღამისას" -> (hour False 18, hour False 0) "შუაღამის" -> (hour False 18, hour False 0) "შუაღამე" -> (hour False 18, hour False 0) "ღამით" -> (hour False 18, hour False 0) "ღამის" -> (hour False 18, hour False 0) "ღამე" -> (hour False 18, hour False 0) "გვიან ღამე" -> (hour False 21, hour False 0) "გვიან ღამით" -> (hour False 21, hour False 0) "შუადღე" -> (hour False 12, hour False 18) "შუადღის" -> (hour False 12, hour False 18) "შუადღით" -> (hour False 12, hour False 18) "დღისით" -> (hour False 12, hour False 18) "დღის" -> (hour False 12, hour False 18) _ -> (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) } rulePODIn :: Rule rulePODIn = Rule { name = "in|during the <part-of-day>" , pattern = [ Predicate isAPartOfDay , regex "(-ის )?(ის )?განმავლობაში" ] , prod = \case (Token Time td:_) -> tt $ notLatent td _ -> Nothing } rulePODThis :: Rule rulePODThis = Rule { name = "this <part-of-day>" , pattern = [ regex "დღეს ?|ამ ?" , Predicate isAPartOfDay ] , prod = \case (_:Token Time td:_) -> Token Time . partOfDay . notLatent <$> intersect (cycleNth TG.Day 0) td _ -> Nothing } ruleTonight :: Rule ruleTonight = Rule { name = "tonight" , pattern = [regex "(დღეს )?(გვიან )?ღამე?(ით)?(ისას)?|(დღეს )?(შუა )?ღამე?(ით)?ი?ს?(ისას)?|საღამოს"] , prod = \case (Token RegexMatch (GroupMatch (match:_)):_) -> do let today = cycleNth TG.Day 0 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 "((სკოლის|ლანჩის|სამსახურის) (მერე|შემდეგ))" ] , 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 17, hour False 21) "სკოლის მერე" -> Just (hour False 15, hour False 21) "სკოლის შემდეგ" -> Just (hour False 15, hour False 21) _ -> Nothing td <- interval TTime.Open start end Token Time . partOfDay . notLatent <$> intersect (cycleNth TG.Day 0) td _ -> Nothing } -- Since part of days are latent, general time intersection is blocked ruleTimePOD :: Rule ruleTimePOD = Rule { name = "<time> <part-of-day>" , pattern = [ dimension Time , Predicate isAPartOfDay ] , prod = \case (Token Time td:Token Time pod:_) -> Token Time <$> intersect pod td _ -> Nothing } rulePODofTime :: Rule rulePODofTime = Rule { name = "<part-of-day> <time>" , pattern = [ Predicate isAPartOfDay , 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 = const $ tt $ mkOkForThisNext geoWeekend } ruleWorkweek :: Rule ruleWorkweek = Rule { name = "work-week" , pattern = [ regex "(მიმდინარე )?სამუშაო ?კვირას?(ში)?" ] , prod = const $ tt $ mkOkForThisNext workweek } ruleSeason :: Rule ruleSeason = Rule { name = "last|this|next <season>" , pattern = [ regex "(ამ|ეს|ახლანდელი|მიმდინარე|შემდეგი|მომავალი|წინა|ბოლო|გასული?) სეზონი?(ზე)?" ] , 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 _ -> Nothing tt $ predNth n False season _ -> Nothing } ruleSeasons :: [Rule] ruleSeasons = mkRuleSeasons [ ("ზაფხული", "ზაფხული?(ში)?ს?", monthDay 6 1, monthDay 8 31) , ("შემოდგომა", "შემოდგომა?(ში)?ს?", monthDay 9 1, monthDay 11 30) , ("ზამთარი", "ზამთარი?(ში)?ს?", monthDay 12 1, monthDay 2 28) , ("გაზაფხული", "გაზაფხული?(ში)?ს?", monthDay 3 1, monthDay 5 31) ] 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 , regex "-მდე|მდე| მდე" ] , prod = \case (Token Time td:token1:_:token2:_) -> do dom1 <- intersectDOM td token1 dom2 <- intersectDOM td token2 Token Time <$> interval TTime.Closed dom1 dom2 _ -> Nothing } ruleIntervalMonthDDDD1 :: Rule ruleIntervalMonthDDDD1 = 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 , regex "-მდე|მდე| მდე" , 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 } ruleIntervalDDDDMonth1 :: Rule ruleIntervalDDDDMonth1 = 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 } ruleIntervalBetweenMM :: Rule ruleIntervalBetweenMM = Rule { name = "between <time> and <time>" , pattern = [ Predicate isDOMValue , regex "-ი?დან ?|ი?დან ?| ი?დან ?" , Predicate isDOMValue , Predicate isAMonth , regex "-ა?მდე ?|ა?მდე ?| ა?მდე ?" ] , prod = \case (token1:_:token2:Token Time td:_) -> do start <- intersectDOM td token1 end <- intersectDOM td token2 Token Time <$> interval TTime.Closed start end _ -> Nothing } ruleIntervalBetween :: Rule ruleIntervalBetween = Rule { name = "between <time> and <time>" , pattern = [ dimension Time , regex "-ი?დან ?|ი?დან ?| ი?დან ?" , dimension Time , regex "-ა?მდე ?|ა?მდე ?| ა?მდე ?" ] , prod = \case (Token Time td1:_:Token Time td2:_) -> Token Time <$> interval TTime.Closed td1 td2 _ -> Nothing } ruleIntervalBetween1 :: Rule ruleIntervalBetween1 = Rule { name = "between <time> and <time> 1" , pattern = [ dimension Time , regex "-ი?დან ?|ი?დან ?| ი?დან ?" , regex "დღემდე|ამ წუთამდე|ახლამდე|ამ მომენტამდე" ] , prod = \case (Token Time td1:_) -> Token Time <$> interval TTime.Closed td1 now _ -> Nothing } ruleIntervalBetween2 :: Rule ruleIntervalBetween2 = Rule { name = "between <time-of-day> and <time-of-day>" , pattern = [ dimension Time , Predicate isAPartOfDay , regex "დან|-ი?დან ?|ი?დან ?| ი?დან ?" , dimension Time , Predicate isAPartOfDay , regex "მდე|-ა?მდე ?|ა?მდე ?| ა?მდე ?" ] , prod = \case (Token Time td1:Token Time pod1:_:Token Time td2:Token Time pod2:_) -> do dom1 <- intersect pod1 td1 dom2 <- intersect pod2 td2 Token Time <$> interval TTime.Closed dom1 dom2 _ -> 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 , regex "მდე|-ა?მდე ?|ა?მდე ?| ა?მდე ?" ] , 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 = [ Predicate isATimeOfDay , regex "დან|-ი?დან ?|ი?დან ?| ი?დან ?" , Predicate isATimeOfDay , regex "მდე|-ა?მდე ?|ა?მდე ?| ა?მდე ?" ] , 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 = [ Predicate isATimeOfDay , regex " ?და ?" , Predicate isATimeOfDay , regex "შორის" ] , prod = \case (Token Time td1:_:Token Time td2:_) -> Token Time <$> interval TTime.Closed td1 td2 _ -> Nothing } ruleIntervalBy :: Rule ruleIntervalBy = Rule { name = "by <time>" , pattern = [ dimension Time , regex "მდე|-ა?მდე ?|ა?მდე ?| ა?მდე ?" ] , prod = \case (_:Token Time td:_) -> Token Time <$> interval TTime.Open now td _ -> Nothing } ruleDaysOfWeek :: [Rule] ruleDaysOfWeek = mkRuleDaysOfWeek [ ("ორშაბათი", "ორშაბათი?ს?") , ("სამშაბათი", "სამშაბათი?ს?") , ("ოთხშაბათი", "ოთხშაბათი?ს?") , ("ხუთშაბათი", "ხუთშაბათი?ს?") , ("პარასკევი", "პარასკევი?ს?") , ("შაბათი", "შაბათი?ს?") , ("კვირა", "კვირას?|კვირის") ] ruleMonths :: [Rule] ruleMonths = mkRuleMonthsWithLatent [ ("იანვარი", "იანვა?რი?ს?(ის)?", False) , ("თებერვალი", "თებერვა?ლი?ს?(ის)?", False) , ("მარტი", "მარტი?ს?(ის)?", False) , ("აპრილი", "აპრილი?ს?(ის)?", False) , ("მაისი", "მაისი?ს?(ის)?", False) , ("ივნისი", "ივნისი?ს?(ის)?", False) , ("ივლისი", "ივლისი?ს?(ის)?", False) , ("აგვისტო", "აგვისტო?ს?", False) , ("სექტემბერი", "სექტემბე?რის|სექტემბე?რი?ს?", False) , ("ოქტომბერი", "ოქტომბე?რის|ოქტომბე?რი?ს?", False) , ("ნოემბერი", "ნოემბე?რის|ნოემბე?რი?ს?", False) , ("დეკემბერი", "დეკემბრის?|დეკემბე?რი?ს?", False) ] ruleNamedMonth :: Rule ruleNamedMonth = Rule { name = "in <named-month>" , pattern = [ Predicate isAMonth , regex "ში" ] , prod = \case (Token Time td2:_) -> Token Time <$> interval TTime.Closed (cycleNthAfter False TG.Month 0 td2) (cycleNthAfter False TG.Month 0 td2) _ -> Nothing } rulePartOfMonth :: Rule rulePartOfMonth = Rule { name = "part of <named-month>" , pattern = [ Predicate $ or . sequence [isAMonth, isGrainOfTime TG.Month] , regex "(ის )?(დასაწყის(ი|ში|ისას|ისკენ)|შუ(ა|აში|ისკენ)|ბოლო(ს|მდე|ში|სკენ)?)" ] , prod = \case (Token Time td:Token RegexMatch (GroupMatch (_:match:_)):_) -> do (sd, ed) <- case Text.toLower match of "დასაწყისისკენ" -> Just (1, 10) "დასაწყისიდან" -> Just (1, 10) "დასაწყისას" -> Just (1, 10) "დასაწყისში" -> Just (1, 10) "დასაწყისი" -> Just (1, 10) "შუისკენ" -> Just (11, 20) "შუაში" -> Just (11, 20) "შუა" -> Just (11, 20) "ბოლომდე" -> Just (21, -1) "ბოლოსკენ" -> Just (21, -1) "ბოლოში" -> Just (21, -1) "ბოლოს" -> Just (21, -1) "ბოლო" -> 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 = [ Predicate $ or . sequence [isAMonth, isGrainOfTime TG.Month] , regex "(ის )?(დასაწყისშ?ი|ბოლო(ს?|ში))" ] , prod = \case (Token Time td:Token RegexMatch (GroupMatch (_:match:_)):_) -> do (sd, ed) <- case Text.toLower match of "დასაწყისი" -> Just (1, 10) "დასაწყისში" -> Just (1, 10) "ბოლოს" -> Just (21, -1) "ბოლოში" -> Just (21, -1) "ბოლო" -> 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 "(ამ )?(მიმდინარე )?თვის ბოლოს?(კენ)?(თვის)?" ] , prod = \case (Token RegexMatch _:_) | (Just start, Just end) <- parsed -> Token Time <$> interval TTime.Open start end where cycleMonth = cycleNth TG.Month parsed = ( intersect (dayOfMonth 21) $ cycleMonth 0 , Just $ cycleLastOf TG.Day $ cycleMonth 0) _ -> Nothing } ruleBeginningOfMonth :: Rule ruleBeginningOfMonth = Rule { name = "beginning of month" , pattern = [ regex "(ამ )?(მიმდინარე )?თვის დასაწყისი?(ში)?(სკენ)?(სთვის)?" ] , 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 = [ Predicate $ isGrainOfTime TG.Year , regex "(წლის )?დასაწყისი?(ისკენ)?(ში)? |ბოლოს?(კენ)?(ში)? " ] , prod = \case (Token Time td:Token RegexMatch (GroupMatch (_:match:_)):_) -> do (sd, ed) <- case Text.toLower match of "დასაწყისისკენ" -> Just (1, 4) "დასაწყისში" -> Just (1, 4) "ბოლოსკენ" -> Just (9, -1) "ბოლოს" -> Just (9, -1) "ბოლოში" -> 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 "((მიმდინარე )?(ამ )?წლის ბოლოს?(კენ)?(ში)?(თვის)?)" ] , prod = \_ -> do start <- intersect (month 9) $ cycleNth TG.Year 0 end <- intersect (month 1) $ cycleNth TG.Year 1 Token Time <$> interval TTime.Open start end } ruleBeginningOfYear :: Rule ruleBeginningOfYear = Rule { name = "beginning of year" , pattern = [ regex "((ამ )?(მიმდინარე )?წლის დასაწყისი?(ში)?(სკენ)?(სთვის)?)" ] , 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 = [ Predicate $ isGrainOfTime TG.Week , regex "(დასაწყისი|დასაწყისში|ბოლოში|ბოლოს)" ] , prod = \case (Token Time td:Token RegexMatch (GroupMatch (_:match:_)):_) -> do (sd, ed) <- case Text.toLower match of "დასაწყისი" -> Just (1, 3) "დასაწყისში" -> Just (1, 3) "ბოლოში" -> Just (5, 7) "ბოლოს" -> 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 [ ("ახალი წელი", "ახალი წე?ლი?ს?", monthDay 1 1) , ("შობა", "შობა?(ი)?ს?", monthDay 1 7 ) , ("ნათლისღება", "ნათლისღება?(ი)?ს?", monthDay 1 19 ) , ("დედის დღე", "დედის დღე?ს?", monthDay 3 3) , ("ქალთა საერთაშორისო დღე", "ქალთა (საერთაშორისო )?დღე?ს?", monthDay 3 8 ) , ("საქართველოს დამოუკიდებლობის აღდგენის დღე", "საქართველოს (თავისუფლებისა და ერთიანობისთვის დაღუპულთა მოხსენიების)?(დამოუკიდებლობის აღდგენის )?დღეს?", monthDay 4 9 ) , ("გამარჯვების დღე", "(ფაშიზმზე)?გამარჯვების დღეს?", monthDay 5 9 ) , ("წმინდა ანდრია პირველწოდებულის ხსენების დღე", "(წმინდა)?ანდრია პირველწოდებულის ხსენების დღე?ს?", monthDay 5 12 ) , ("დამოუკიდებლობის დღე", "(საქართველოს)?დამოუკიდებლობის დღე?ს?", monthDay 5 26 ) , ("მცხეთობა", "მცხეთობა?ს?", monthDay 10 14) , ("გიორგობა", "გიორგობა?ს?", monthDay 11 26) ] ruleComputedHolidays :: [Rule] ruleComputedHolidays = mkRuleHolidays [ ("Orthodox Easter Monday", "orthodox\\s+easter\\s+mon(day)?" , cycleNthAfter False TG.Day 1 orthodoxEaster) , ("აღდგომა", "აღდგომი?ა?ს?(ისას)?" , orthodoxEaster) , ("დიდი შაბათი", "დიდი? შაბათი?ს?(ისას)?" , cycleNthAfter False TG.Day (-1) orthodoxEaster) , ("წითელი პარასკევი", "წითელი? პარასკევი?ს?(სას)?" , cycleNthAfter False TG.Day (-2) orthodoxEaster) , ("დიდი ხუთშაბათი", "დიდი? ხუთშაბათი?ს?(ისას)?" , cycleNthAfter False TG.Day (-7) orthodoxEaster) ] 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 $ - 1 "გასული" -> tt . cycleNth grain $ - 1 "გასულ" -> tt . cycleNth grain $ - 1 "წინის წინ" -> tt . cycleNth grain $ - 2 "წინის წინა" -> tt . cycleNth grain $ - 2 "შემდეგ" -> tt $ cycleNth grain 1 "შემდეგი" -> tt $ cycleNth grain 1 "მომავალ" -> tt $ cycleNth grain 1 "მომავალი" -> tt $ cycleNth grain 1 _ -> Nothing _ -> Nothing } ruleCycleThisLastNextInterval :: Rule ruleCycleThisLastNextInterval = Rule { name = "this|last|next <cycle>" , pattern = [ regex "(ეს|ამ|მიმდინარე|შემდეგი?|მომავალი?|წინა|წინის წინა?|გასული?|მომდევნო)" , dimension TimeGrain ] , prod = \case (Token RegexMatch (GroupMatch (match:_)): Token TimeGrain grain: _) -> let n = case Text.toLower match of "ეს" -> 0 "ამ" -> 0 "მიმდინარე" -> 0 "წინა" -> -1 "გასული" -> -1 "გასულ" -> -1 "წინის წინ" -> -2 "წინის წინა" -> -2 _ -> 1 in Token Time <$> interval TTime.Closed (cycleNth grain n) (cycleNth grain n) _ -> Nothing } ruleDOMOfTimeMonth :: Rule ruleDOMOfTimeMonth = Rule { name = "<day-of-month> (ordinal or number) of <month>" , pattern = [ Predicate isDOMValue , Predicate $ isGrainOfTime TG.Month ] , prod = \case (token:Token Time td:_) -> Token Time <$> intersectDOM td token _ -> Nothing } ruleCycleTheAfterBeforeTime :: Rule ruleCycleTheAfterBeforeTime = Rule { name = "the <cycle> after|before <time>" , pattern = [ dimension Time , regex "(დან|ა?მდე)" , dimension TimeGrain ] , prod = \case ( Token Time td : Token RegexMatch (GroupMatch (match:_)) : Token TimeGrain grain : _) -> let n = if Text.toLower match == "დან" then 1 else - 1 in tt $ cycleNthAfter False grain n td _ -> Nothing } ruleCycleTheAfterBeforeTime1 :: Rule ruleCycleTheAfterBeforeTime1 = Rule { name = "the <cycle> after <time>" , pattern = [ dimension Time , regex "დან" , Predicate $ isIntegerBetween 0 99999 , dimension TimeGrain , regex "ში|ის შემდეგ" ] , prod = \case ( Token Time td : _ : token : Token TimeGrain grain : _) -> do n <- getIntValue token tt $ cycleNthAfter False grain n td _ -> Nothing } ruleCycleOrdinalOfTime :: Rule ruleCycleOrdinalOfTime = Rule { name = "<ordinal> <cycle> of <time>" , pattern = [ dimension Time , regex "დან" , dimension Ordinal , dimension TimeGrain ] , prod = \case (Token Time td:_:token:Token TimeGrain grain:_) -> do n <- getIntValue token tt $ cycleNthAfter True grain (n - 1) td _ -> Nothing } ruleCycleLastOrdinalOfTime :: Rule ruleCycleLastOrdinalOfTime = Rule { name = "<ordinal> last <cycle> of <time>" , pattern = [ dimension Time , regex "ბოლოდან" , dimension Ordinal , dimension TimeGrain ] , prod = \case (Token Time td:_:token:Token TimeGrain grain:_) -> do n <- getIntValue token tt . cycleNthAfter True grain (-n) . cycleNthAfter True (timeGrain td) 1 $ td _ -> Nothing } ruleDurationInWithinAfter :: Rule ruleDurationInWithinAfter = Rule { name = "in|within|after <duration>" , pattern = [ dimension Duration , regex "(განმავლობაში|შემდეგ)" ] , prod = \case (Token Duration dd: Token RegexMatch (GroupMatch (match:_)): _) -> case Text.toLower match of "განმავლობაში" -> Token Time <$> interval TTime.Open now (inDuration dd) "შემდეგ" -> tt . withDirection TTime.After $ inDuration dd _ -> Nothing _ -> Nothing } ruleDurationLastNext :: Rule ruleDurationLastNext = Rule { name = "last|past|next <duration>" , pattern = [ regex "(წინა|მომდევნო|გასული?|მომავალ)" , dimension Duration ] , prod = \case (Token RegexMatch (GroupMatch (match:_)): Token Duration DurationData{TDuration.grain, TDuration.value}: _) -> case Text.toLower match of "მომდევნო" -> tt $ cycleN True grain value "მომავალ" -> tt $ cycleN True grain value "წინა" -> tt $ cycleN True grain (- value) "გასული" -> tt $ cycleN True grain (- value) "გასულ" -> tt $ cycleN True grain (- value) _ -> Nothing _ -> Nothing } ruleNDOWago :: Rule ruleNDOWago = Rule { name = "<integer> <named-day> ago|back" , pattern = [ Predicate isNatural , Predicate isADayOfWeek , regex " ?დღის წინ| ?დღის უკან" ] , 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 = [ dimension Duration , regex "წინ|უკან" ] , prod = \case (Token Duration dd: Token RegexMatch (GroupMatch (match:_)): _) -> case Text.toLower match of "წინ" -> tt $ durationAgo dd "უკან" -> 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 "(წინ|უკან)" ] , prod = \case (Token Time td: Token Duration dd: Token RegexMatch (GroupMatch (match:_)): _) -> case Text.toLower match of "უკან" -> Token Time <$> intersect td (durationIntervalAgo dd) _ -> Token Time <$> intersect td (inDurationInterval dd) _ -> Nothing } ruleInNumeral :: Rule ruleInNumeral = Rule { name = "in <number> (implicit minutes)" , pattern = [ Predicate $ isIntegerBetween 0 60 , regex " ?წუთში" ] , prod = \case (Token Numeral NumeralData{TNumeral.value = v}:_) -> tt . inDuration . duration TG.Minute $ floor v _ -> Nothing } ruleDurationAfterBeforeTime :: Rule ruleDurationAfterBeforeTime = Rule { name = "<duration> after|before|from <time>" , pattern = [ dimension Time , dimension Duration , regex "(წინ)" ] , prod = \case (Token Time td: Token Duration dd: Token RegexMatch (GroupMatch (match:_)): _) -> case Text.toLower match of "წინ" -> tt $ durationBefore dd td _ -> tt $ durationAfter dd td _ -> Nothing } ruleDurationAfterBeforeTime1 :: Rule ruleDurationAfterBeforeTime1 = Rule { name = "<duration> after|before|from <time>" , pattern = [ dimension Duration , regex "(წინ)" ] , prod = \case (Token Duration dd: Token RegexMatch (GroupMatch (match:_)): _) -> case Text.toLower match of "წინ" -> tt $ durationBefore dd now _ -> tt $ durationAfter dd now _ -> Nothing } ruleDurationLastNext1 :: Rule ruleDurationLastNext1 = Rule { name = "last|past|next <duration>" , pattern = [ regex "ბოლო|უკანასკნელი?" , dimension Duration ] , prod = \case (_: Token Duration dd: _) -> do Token Time <$> interval TTime.Closed (durationAgo dd) now _ -> Nothing } ruleCycleOrdinalQuarter :: Rule ruleCycleOrdinalQuarter = Rule { name = "<ordinal> quarter" , pattern = [ dimension Ordinal , Predicate $ isGrain TG.Quarter ] , prod = \case (token:_) -> do n <- getIntValue token let start = cycleNthAfter True TG.Quarter (n - 1) $ cycleNth TG.Year 0 end = cycleNthAfter True TG.Quarter n $ cycleNth TG.Year 0 Token Time <$> interval TTime.Open start end _ -> Nothing } -- For example, Today(დღეს), This year(წელს) ruleGrainOfTime :: Rule ruleGrainOfTime = Rule { name = "<TimeGrain>" , pattern = [ Predicate $ isGrainCoarserThan TG.Day ] , prod = \case (Token TimeGrain grain:_) -> do Token Time <$> interval TTime.Closed (cycleNth grain 0) now _ -> Nothing } ruleYear :: Rule ruleYear = Rule { name = "this|last|next year" , pattern = [ regex "(შარშან ?წინის? ?წინ|შარშან ?წინ|შარშან|გასული? წელი?ს?|წელს)" ] , prod = \case (Token RegexMatch (GroupMatch (match:_)):_) -> let n = case Text.toLower match of "წელს" -> 0 "შარშან" -> -1 "გასული წელი" -> -1 "გასულ წელს" -> -1 "შარშან წინ" -> -2 "შარშანწინ" -> -2 _ -> -3 in Token Time <$> interval TTime.Open (cycleNth TG.Year n) (cycleNth TG.Year $ n + 1) _ -> Nothing } rules :: [Rule] rules = [ ruleIntersect , ruleCycleOrdinalQuarter , ruleDurationLastNext1 , ruleAbsorbInYear , ruleNextDOW , ruleNextTime , ruleThisTime , ruleLastTime , ruleTimeAfterNext , ruleTimeBeforeLast , ruleLastWeekendOfMonth , ruleLastWeekendOfMonth1 , ruleLastWorkweekOfMonth , ruleLastWorkweekOfMonth1 , ruleNthTimeOfTime , ruleNthTimeAfterTime , ruleYearLatent , ruleTheDOMNumeral , ruleMonthDOMNumeral1 , ruleTODLatent , ruleAtTOD , ruleTODOClock , ruleHHMM , ruleHHMMLatent , ruleHHMMSS , ruleMilitaryAMPM , ruleMilitarySpelledOutAMPM , ruleMilitarySpelledOutAMPM2 , ruleTODAMPM , ruleHalfToHOD , ruleNumeralAfterHOD , ruleNumeralAfterHOD1 , ruleNumeralAfterHOD2 , ruleNumeralToHOD , ruleNumeralToHOD1 , rulePODIn , rulePODThis , ruleYYYYMMDD , ruleMMYYYY , rulePartOfDays , ruleEarlyMorning , ruleTonight , ruleAfterPartofday , ruleTimePOD , rulePODofTime , ruleWeekend , ruleTODPrecision , rulePrecisionTOD , ruleIntervalMonthDDDD , ruleIntervalMonthDDDD1 , ruleIntervalDDDDMonth , ruleIntervalDDDDMonth1 , ruleIntervalBetween , ruleIntervalBetween1 , ruleIntervalBetween2 , ruleIntervalTODDash , ruleIntervalTODBetween , ruleIntervalTODFrom , ruleIntervalBy , ruleCycleTheAfterBeforeTime , ruleCycleTheAfterBeforeTime1 , ruleCycleThisLastNext , ruleDOMOfTimeMonth , ruleCycleOrdinalOfTime , ruleCycleLastOrdinalOfTime , ruleDurationInWithinAfter , ruleNDOWago , ruleDurationLastNext , ruleDurationHenceAgo , ruleDayDurationHenceAgo , ruleDurationAfterBeforeTime , ruleDurationAfterBeforeTime1 , ruleInNumeral , ruleCycleThisLastNextInterval , rulePartOfMonth , ruleEndOrBeginningOfYear , ruleEndOrBeginningOfMonth , ruleEndOrBeginningOfWeek , ruleNow , ruleSeason , ruleEndOfMonth , ruleBeginningOfMonth , ruleEndOfYear , ruleBeginningOfYear , ruleGrainOfTime , ruleYear , ruleIntervalBetweenMM , ruleWorkweek , ruleNamedMonth , ruleMMDD , ruleMMDDYYYY , ruleMMDDYYYYDot , ruleYearInterval ] ++ ruleInstants ++ ruleDaysOfWeek ++ ruleMonths ++ ruleSeasons ++ ruleComputedHolidays ++ rulePeriodicHolidays