diff --git a/Duckling/AmountOfMoney/EN/Corpus.hs b/Duckling/AmountOfMoney/EN/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/EN/Corpus.hs
@@ -0,0 +1,113 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.EN.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (AmountOfMoneyValue Dollar 10)
+             [ "$10"
+             , "$ 10"
+             , "10$"
+             , "10 dollars"
+             , "ten dollars"
+             ]
+  , examples (AmountOfMoneyValue Cent 10)
+             [ "10 cent"
+             , "ten pennies"
+             , "ten cents"
+             , "10 c"
+             , "10¢"
+             ]
+  , examples (AmountOfMoneyValue Dollar 1e4)
+             [ "$10K"
+             , "10k$"
+             , "$10,000"
+             ]
+  , examples (AmountOfMoneyValue USD 3.14)
+             [ "USD3.14"
+             , "3.14US$"
+             , "US$ 3.14"
+             ]
+  , examples (AmountOfMoneyValue EUR 20)
+             [ "20\x20ac"
+             , "20 euros"
+             , "20 Euro"
+             , "20 Euros"
+             , "EUR 20"
+             , "EUR 20.0"
+             , "20€"
+             , "20 €ur"
+             ]
+  , examples (AmountOfMoneyValue Pound 10)
+             [ "\x00a3\&10"
+             , "ten pounds"
+             ]
+  , examples (AmountOfMoneyValue INR 20)
+             [ "Rs. 20"
+             , "Rs 20"
+             , "20 Rupees"
+             , "20Rs"
+             , "Rs20"
+             ]
+  , examples (AmountOfMoneyValue INR 20.43)
+             [ "20 Rupees 43"
+             , "twenty rupees 43"
+             ]
+  , examples (AmountOfMoneyValue Dollar 20.43)
+             [ "$20 and 43c"
+             , "$20 43"
+             , "20 dollar 43c"
+             , "20 dollars 43 cents"
+             , "twenty dollar 43 cents"
+             , "20 dollar 43"
+             , "twenty dollar and 43"
+             ]
+  , examples (AmountOfMoneyValue GBP 3.01)
+             [ "GBP3.01"
+             , "GBP 3.01"
+             , "3 GBP 1 cent"
+             ]
+  , examples (AmountOfMoneyValue Unnamed 42)
+             [ "42 bucks"
+             , "around 42 bucks"
+             , "exactly 42 bucks"
+             ]
+  , examples (AmountOfMoneyValue KWD 42)
+             [ "42 KWD"
+             , "42 kuwaiti Dinar"
+             ]
+  , examples (AmountOfMoneyValue LBP 42)
+             [ "42 LBP"
+             , "42 Lebanese Pounds"
+             ]
+  , examples (AmountOfMoneyValue EGP 42)
+             [ "42 EGP"
+             , "42 egyptianpound"
+             ]
+  , examples (AmountOfMoneyValue QAR 42)
+             [ "42 QAR"
+             , "42 qatari riyals"
+             ]
+  , examples (AmountOfMoneyValue SAR 42)
+             [ "42 SAR"
+             , "42 Saudiriyal"
+             ]
+  ]
diff --git a/Duckling/AmountOfMoney/EN/Rules.hs b/Duckling/AmountOfMoney/EN/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/EN/Rules.hs
@@ -0,0 +1,192 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.EN.Rules
+  ( rules ) where
+
+import Data.Maybe
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Helpers
+import Duckling.AmountOfMoney.Types (Currency(..), AmountOfMoneyData (..))
+import qualified Duckling.AmountOfMoney.Types as TAmountOfMoney
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+rulePounds :: Rule
+rulePounds = Rule
+  { name = "£"
+  , pattern =
+    [ regex "pounds?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Pound
+  }
+
+ruleOtherPounds :: Rule
+ruleOtherPounds = Rule
+  { name = "other pounds"
+  , pattern =
+    [ regex "(egyptian|lebanese) ?pounds?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "egyptian" -> Just . Token AmountOfMoney $ currencyOnly EGP
+        "lebanese" -> Just . Token AmountOfMoney $ currencyOnly LBP
+        _          -> Nothing
+      _ -> Nothing
+  }
+
+ruleRiyals :: Rule
+ruleRiyals = Rule
+  { name = "riyals"
+  , pattern =
+    [ regex "(qatari|saudi) ?riyals?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "qatari" -> Just . Token AmountOfMoney $ currencyOnly QAR
+        "saudi"  -> Just . Token AmountOfMoney $ currencyOnly SAR
+        _        -> Nothing
+      _ -> Nothing
+  }
+
+ruleDinars :: Rule
+ruleDinars = Rule
+  { name = "dinars"
+  , pattern =
+    [ regex "(kuwaiti) ?dinars?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "kuwaiti" -> Just . Token AmountOfMoney $ currencyOnly KWD
+        _        -> Nothing
+      _ -> Nothing
+  }
+
+ruleDirham :: Rule
+ruleDirham = Rule
+  { name = "dirham"
+  , pattern =
+    [ regex "dirhams?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly AED
+  }
+
+ruleCent :: Rule
+ruleCent = Rule
+  { name = "cent"
+  , pattern =
+    [ regex "cents?|penn(y|ies)"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Cent
+  }
+
+ruleBucks :: Rule
+ruleBucks = Rule
+  { name = "bucks"
+  , pattern =
+    [ regex "bucks?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Unnamed
+  }
+
+ruleIntersectAndXCents :: Rule
+ruleIntersectAndXCents = Rule
+  { name = "intersect (and X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "and"
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleIntersectAndNumeral :: Rule
+ruleIntersectAndNumeral = Rule
+  { name = "intersect (and number)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "and"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleIntersectXCents :: Rule
+ruleIntersectXCents = Rule
+  { name = "intersect (X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+rulePrecision :: Rule
+rulePrecision = Rule
+  { name = "about|exactly <amount-of-money>"
+  , pattern =
+    [ regex "exactly|precisely|about|approx(\\.|imately)?|close to|near( to)?|around|almost"
+    , financeWith TAmountOfMoney.value isJust
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleBucks
+  , ruleCent
+  , ruleDinars
+  , ruleDirham
+  , ruleIntersect
+  , ruleIntersectAndNumeral
+  , ruleIntersectAndXCents
+  , ruleIntersectXCents
+  , ruleOtherPounds
+  , rulePounds
+  , rulePrecision
+  , ruleRiyals
+  ]
diff --git a/Duckling/AmountOfMoney/ES/Corpus.hs b/Duckling/AmountOfMoney/ES/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/ES/Corpus.hs
@@ -0,0 +1,67 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.ES.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = ES}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (AmountOfMoneyValue Dollar 10)
+             [ "$10"
+             , "10$"
+             , "diez dollars"
+             , "diez dólares"
+             ]
+  , examples (AmountOfMoneyValue Dollar 10000)
+             [ "$10.000"
+             , "10K$"
+             , "$10k"
+             ]
+  , examples (AmountOfMoneyValue USD 1.23)
+             [ "USD1,23"
+             ]
+  , examples (AmountOfMoneyValue EUR 20)
+             [ "20€"
+             , "20 euros"
+             , "20 Euro"
+             , "20 Euros"
+             , "EUR 20"
+             ]
+  , examples (AmountOfMoneyValue EUR 29.99)
+             [ "EUR29,99"
+             ]
+  , examples (AmountOfMoneyValue Pound 9)
+             [ "£9"
+             , "nueve pounds"
+             , "9 libras"
+             ]
+  , examples (AmountOfMoneyValue GBP 3.01)
+             [ "GBP3,01"
+             , "GBP 3,01"
+             , "3 gbp 1 centavo"
+             , "3 gbp y 1 centavo"
+             ]
+  , examples (AmountOfMoneyValue PTS 15)
+             [ "15 Pt"
+             , "15pta"
+             , "15Ptas"
+             ]
+  ]
diff --git a/Duckling/AmountOfMoney/ES/Rules.hs b/Duckling/AmountOfMoney/ES/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/ES/Rules.hs
@@ -0,0 +1,123 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.ES.Rules
+  ( rules ) where
+
+import Data.Maybe
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Helpers
+import Duckling.AmountOfMoney.Types (Currency(..), AmountOfMoneyData (..))
+import qualified Duckling.AmountOfMoney.Types as TAmountOfMoney
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Types
+
+ruleDollar :: Rule
+ruleDollar = Rule
+  { name = "dollar"
+  , pattern =
+    [ regex "d(\x00f3|o)lar(es)?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Dollar
+  }
+
+ruleCent :: Rule
+ruleCent = Rule
+  { name = "cent"
+  , pattern =
+    [ regex "centavos?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Cent
+  }
+
+rulePounds :: Rule
+rulePounds = Rule
+  { name = "£"
+  , pattern =
+    [ regex "(pound|libra)s?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Pound
+  }
+
+ruleIntersectAndNumeral :: Rule
+ruleIntersectAndNumeral = Rule
+  { name = "intersect (and number)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "y"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleIntersectXCents :: Rule
+ruleIntersectXCents = Rule
+  { name = "intersect (X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleIntersectAndXCents :: Rule
+ruleIntersectAndXCents = Rule
+  { name = "intersect (and X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "y"
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleCent
+  , ruleDollar
+  , ruleIntersect
+  , ruleIntersectAndNumeral
+  , ruleIntersectAndXCents
+  , ruleIntersectXCents
+  , rulePounds
+  ]
diff --git a/Duckling/AmountOfMoney/FR/Corpus.hs b/Duckling/AmountOfMoney/FR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/FR/Corpus.hs
@@ -0,0 +1,65 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.FR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = FR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (AmountOfMoneyValue Dollar 10)
+             [ "$10"
+             , "10$"
+             , "dix dollars"
+             ]
+  , examples (AmountOfMoneyValue Dollar 10000)
+             [ "$10.000"
+             , "10K$"
+             , "$10k"
+             ]
+  , examples (AmountOfMoneyValue USD 1.23)
+             [ "USD1,23"
+             , "1 USD et 23 centimes"
+             , "1 USD 23 cents"
+             ]
+  , examples (AmountOfMoneyValue EUR 20)
+             [ "20€"
+             , "20 euros"
+             , "20 Euro"
+             , "20 Euros"
+             , "EUR 20"
+             ]
+  , examples (AmountOfMoneyValue EUR 29.99)
+             [ "EUR29,99"
+             ]
+  , examples (AmountOfMoneyValue Unnamed 3)
+             [ "3 balles"
+             , "à peu près 3 pouloutes"
+             ]
+  , examples (AmountOfMoneyValue Pound 9)
+             [ "exactement £9"
+             , "neuf pounds"
+             , "quasi neuf livres"
+             ]
+  , examples (AmountOfMoneyValue GBP 3.01)
+             [ "GBP3,01"
+             , "GBP 3,01"
+             ]
+  ]
diff --git a/Duckling/AmountOfMoney/FR/Rules.hs b/Duckling/AmountOfMoney/FR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/FR/Rules.hs
@@ -0,0 +1,136 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.FR.Rules
+  ( rules ) where
+
+import Data.Maybe
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Helpers
+import Duckling.AmountOfMoney.Types (Currency (..), AmountOfMoneyData (..))
+import qualified Duckling.AmountOfMoney.Types as TAmountOfMoney
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Types
+
+ruleIntersectAndNumeral :: Rule
+ruleIntersectAndNumeral = Rule
+  { name = "intersect (and number)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "et"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+rulePrecision :: Rule
+rulePrecision = Rule
+  { name = "precision"
+  , pattern =
+    [ regex "exactement|quasi|plus ou moins|environ|autour de|(a|\x00e0) peu pr(e|\x00e8)s"
+    , financeWith TAmountOfMoney.value isJust
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleCent :: Rule
+ruleCent = Rule
+  { name = "cent"
+  , pattern =
+    [ regex "cent(ime)?s?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Cent
+  }
+
+ruleIntersectXCents :: Rule
+ruleIntersectXCents = Rule
+  { name = "intersect (X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+rulePounds :: Rule
+rulePounds = Rule
+  { name = "£"
+  , pattern =
+    [ regex "(livre|pound)s?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Pound
+  }
+
+ruleIntersectAndXCents :: Rule
+ruleIntersectAndXCents = Rule
+  { name = "intersect (and X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "et"
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleUnnamedCurrency :: Rule
+ruleUnnamedCurrency = Rule
+  { name = "unnamed currencyOnly"
+  , pattern =
+    [ regex "(balle|pouloute)s?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Unnamed
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleCent
+  , ruleIntersect
+  , ruleIntersectAndNumeral
+  , ruleIntersectAndXCents
+  , ruleIntersectXCents
+  , rulePounds
+  , rulePrecision
+  , ruleUnnamedCurrency
+  ]
diff --git a/Duckling/AmountOfMoney/GA/Corpus.hs b/Duckling/AmountOfMoney/GA/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/GA/Corpus.hs
@@ -0,0 +1,86 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.GA.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = GA}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (AmountOfMoneyValue Dollar 10)
+             [ "$10"
+             , "10$"
+             , "deich dollair"
+             ]
+  , examples (AmountOfMoneyValue Cent 10)
+             [ "deich ceinteanna"
+             ]
+  , examples (AmountOfMoneyValue Dollar 10000)
+             [ "$10,000"
+             , "10K$"
+             , "$10k"
+             ]
+  , examples (AmountOfMoneyValue EUR 10000)
+             [ "€10,000"
+             , "10K€"
+             , "€10k"
+             ]
+  , examples (AmountOfMoneyValue USD 1.23)
+             [ "USD1.23"
+             ]
+  , examples (AmountOfMoneyValue Dollar 2.23)
+             [ "2 dhollair agus 23 ceinteanna"
+             , "dhá dhollair 23 ceinteanna"
+             , "2 dhollair 23"
+             , "dhá dhollair agus 23"
+             ]
+  , examples (AmountOfMoneyValue EUR 20)
+             [ "20€"
+             , "20 euros"
+             , "20 Euro"
+             , "20 Euros"
+             , "EUR 20"
+             ]
+  , examples (AmountOfMoneyValue EUR 29.99)
+             [ "EUR29.99"
+             ]
+  , examples (AmountOfMoneyValue INR 20)
+             [ "Rs. 20"
+             , "Rs 20"
+             , "20 Rúpaí"
+             , "20Rs"
+             , "Rs20"
+             ]
+  , examples (AmountOfMoneyValue INR 20.43)
+             [ "20 Rupees 43"
+             , "fiche rúpaí 43"
+             ]
+  , examples (AmountOfMoneyValue INR 33)
+             [ "INR33"
+             ]
+  , examples (AmountOfMoneyValue Pound 9)
+             [ "£9"
+             , "naoi bpunt"
+             ]
+  , examples (AmountOfMoneyValue GBP 3.01)
+             [ "GBP3.01"
+             , "GBP 3.01"
+             ]
+  ]
diff --git a/Duckling/AmountOfMoney/GA/Rules.hs b/Duckling/AmountOfMoney/GA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/GA/Rules.hs
@@ -0,0 +1,159 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.GA.Rules
+  ( rules ) where
+
+import Data.Maybe
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Helpers
+import Duckling.AmountOfMoney.Types (Currency (..), AmountOfMoneyData (..))
+import qualified Duckling.AmountOfMoney.Types as TAmountOfMoney
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Types
+
+ruleDollar :: Rule
+ruleDollar = Rule
+  { name = "$"
+  , pattern =
+    [ regex "n?dh?oll?ai?rs?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Dollar
+  }
+
+ruleCent :: Rule
+ruleCent = Rule
+  { name = "cent"
+  , pattern =
+    [ regex "cents?|g?ch?eint(eanna)?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Cent
+  }
+
+ruleThartArAmountofmoney :: Rule
+ruleThartArAmountofmoney = Rule
+  { name = "thart ar <amount-of-money>"
+  , pattern =
+    [ regex "thart( ar)?|beagnach|breis (is|agus)"
+    , financeWith TAmountOfMoney.value isJust
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleIntersectXCents :: Rule
+ruleIntersectXCents = Rule
+  { name = "intersect (X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+rulePounds :: Rule
+rulePounds = Rule
+  { name = "£"
+  , pattern =
+    [ regex "pounds?|b?ph?unt"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Pound
+  }
+
+ruleIntersectAndXCents :: Rule
+ruleIntersectAndXCents = Rule
+  { name = "intersect (and X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "agus|is"
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleInr :: Rule
+ruleInr = Rule
+  { name = "INR"
+  , pattern =
+    [ regex "r(\x00fa|u)pa(\x00ed|i)"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly INR
+  }
+
+ruleIntersectAgusNumeral :: Rule
+ruleIntersectAgusNumeral = Rule
+  { name = "intersect (agus number)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "agus|is"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleAmountofmoneyGlan :: Rule
+ruleAmountofmoneyGlan = Rule
+  { name = "<amount-of-money> glan"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "glan|baileach|(go )?d(\x00ed|i)reach"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> Just token
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAmountofmoneyGlan
+  , ruleCent
+  , ruleDollar
+  , ruleInr
+  , ruleIntersect
+  , ruleIntersectAgusNumeral
+  , ruleIntersectAndXCents
+  , ruleIntersectXCents
+  , rulePounds
+  , ruleThartArAmountofmoney
+  ]
diff --git a/Duckling/AmountOfMoney/HR/Corpus.hs b/Duckling/AmountOfMoney/HR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/HR/Corpus.hs
@@ -0,0 +1,96 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.HR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.AmountOfMoney.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = HR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (AmountOfMoneyValue Dollar 10)
+             [ "$10"
+             , "10$"
+             , "deset dolara"
+             ]
+  , examples (AmountOfMoneyValue Cent 10)
+             [ "deset centa"
+             ]
+  , examples (AmountOfMoneyValue Dollar 10000)
+             [ "$10.000"
+             , "10K$"
+             , "$10k"
+             ]
+  , examples (AmountOfMoneyValue USD 1.23)
+             [ "USD1,23"
+             ]
+  , examples (AmountOfMoneyValue Dollar 2.23)
+             [ "2 dolara i 23 centa"
+             , "dva dolara 23 centa"
+             , "2 dolara 23"
+             , "dva dolara i 23"
+             ]
+  , examples (AmountOfMoneyValue HRK 2.23)
+             [ "2 kune i 23 lipe"
+             , "dvije kune 23 lipe"
+             , "2 kune 23"
+             , "dvije kune i 23"
+             ]
+  , examples (AmountOfMoneyValue HRK 100)
+             [ "100 kuna"
+             , "sto kuna"
+             ]
+  , examples (AmountOfMoneyValue HRK 200)
+             [ "200 kuna"
+             , "dvije stotine kuna"
+             , "dvjesto kuna"
+             , "dvjesta kuna"
+             ]
+  , examples (AmountOfMoneyValue EUR 20)
+             [ "20€"
+             , "20 euros"
+             , "20 Euro"
+             , "20 Euros"
+             , "EUR 20"
+             ]
+  , examples (AmountOfMoneyValue EUR 29.99)
+             [ "EUR29,99"
+             ]
+  , examples (AmountOfMoneyValue INR 20)
+             [ "Rs. 20"
+             , "Rs 20"
+             , "20 Rupija"
+             , "20Rs"
+             , "Rs20"
+             ]
+  , examples (AmountOfMoneyValue INR 20.43)
+             [ "20 Rupija 43"
+             , "dvadeset rupija 43"
+             ]
+  , examples (AmountOfMoneyValue INR 33)
+             [ "INR33"
+             ]
+  , examples (AmountOfMoneyValue Pound 9)
+             [ "£9"
+             , "devet funti"
+             ]
+  , examples (AmountOfMoneyValue GBP 3.01)
+             [ "GBP3,01"
+             , "GBP 3,01"
+             ]
+  ]
diff --git a/Duckling/AmountOfMoney/HR/Rules.hs b/Duckling/AmountOfMoney/HR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/HR/Rules.hs
@@ -0,0 +1,200 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.HR.Rules
+  ( rules ) where
+
+import Data.Maybe
+import Data.String
+import Data.Text (Text)
+import Prelude
+import qualified Data.Text as Text
+
+import Duckling.AmountOfMoney.Helpers
+import Duckling.AmountOfMoney.Types (Currency(..), AmountOfMoneyData (..))
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData (..))
+import Duckling.Regex.Types
+import Duckling.Types
+import qualified Duckling.AmountOfMoney.Types as TAmountOfMoney
+import qualified Duckling.Numeral.Types as TNumeral
+
+ruleIntersectAndNumber :: Rule
+ruleIntersectAndNumber = Rule
+  { name = "intersect (and number)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "i"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleSar :: Rule
+ruleSar = Rule
+  { name = "SAR"
+  , pattern =
+    [ regex "saudijskirijal|saudi rijal?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly SAR
+  }
+
+ruleDollar :: Rule
+ruleDollar = Rule
+  { name = "$"
+  , pattern =
+    [ regex "dolar(a|i|e)?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Dollar
+  }
+
+ruleCent :: Rule
+ruleCent = Rule
+  { name = "cent"
+  , pattern =
+    [ regex "cent(i|a)?|penij(i|a)?|c|\x00a2|lp|lip(a|e)"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Cent
+  }
+
+ruleIntersectIXLipa :: Rule
+ruleIntersectIXLipa = Rule
+  { name = "intersect (i X lipa)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "i"
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleIntersectXCents :: Rule
+ruleIntersectXCents = Rule
+  { name = "intersect (X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+rulePound :: Rule
+rulePound = Rule
+  { name = "£"
+  , pattern =
+    [ regex "funt(a|e|i)?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Pound
+  }
+
+ruleHrk :: Rule
+ruleHrk = Rule
+  { name = "HRK"
+  , pattern =
+    [ regex "kn|(hrvatsk(a|ih|e) )?kun(a|e)"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly HRK
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleOtherPounds :: Rule
+ruleOtherPounds = Rule
+  { name = "other pounds"
+  , pattern =
+    [ regex "(egipatska|libanonska) ?funta"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "egipatska"  -> Just . Token AmountOfMoney $ currencyOnly EGP
+        "libanonska" -> Just . Token AmountOfMoney $ currencyOnly LBP
+        _            -> Nothing
+      _ -> Nothing
+  }
+
+ruleInr :: Rule
+ruleInr = Rule
+  { name = "INR"
+  , pattern =
+    [ regex "rupija?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly INR
+  }
+
+ruleKwd :: Rule
+ruleKwd = Rule
+  { name = "KWD"
+  , pattern =
+    [ regex "kuvajtski ?dinar"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly KWD
+  }
+
+ruleQar :: Rule
+ruleQar = Rule
+  { name = "QAR"
+  , pattern =
+    [ regex "katarski(i| )rijal"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly QAR
+  }
+
+ruleAed :: Rule
+ruleAed = Rule
+  { name = "AED"
+  , pattern =
+    [ regex "drahma?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly AED
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDollar
+  , rulePound
+  , ruleOtherPounds
+  , ruleAed
+  , ruleCent
+  , ruleHrk
+  , ruleInr
+  , ruleIntersect
+  , ruleIntersectAndNumber
+  , ruleIntersectIXLipa
+  , ruleIntersectXCents
+  , ruleKwd
+  , ruleQar
+  , ruleSar
+  ]
diff --git a/Duckling/AmountOfMoney/Helpers.hs b/Duckling/AmountOfMoney/Helpers.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/Helpers.hs
@@ -0,0 +1,48 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.Helpers
+  ( currencyOnly
+  , financeWith
+  , withCents
+  , withValue
+  )
+  where
+
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.AmountOfMoney.Types (Currency (..), AmountOfMoneyData (..))
+import Duckling.Types hiding (Entity(..))
+
+-- -----------------------------------------------------------------
+-- Patterns
+
+financeWith :: (AmountOfMoneyData -> t) -> (t -> Bool) -> PatternItem
+financeWith f pred = Predicate $ \x ->
+  case x of
+    (Token AmountOfMoney x) -> pred (f x)
+    _ -> False
+
+-- -----------------------------------------------------------------
+-- Production
+
+currencyOnly :: Currency -> AmountOfMoneyData
+currencyOnly c = AmountOfMoneyData {currency = c, value = Nothing}
+
+withValue :: Double -> AmountOfMoneyData -> AmountOfMoneyData
+withValue x fd = fd {value = Just x}
+
+withCents :: Double -> AmountOfMoneyData -> AmountOfMoneyData
+withCents x fd@AmountOfMoneyData {value = Just value} = fd
+  {value = Just $ value + x / 100}
+withCents x AmountOfMoneyData {value = Nothing} = AmountOfMoneyData
+  {value = Just x, currency = Cent}
diff --git a/Duckling/AmountOfMoney/ID/Corpus.hs b/Duckling/AmountOfMoney/ID/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/ID/Corpus.hs
@@ -0,0 +1,82 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.ID.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = ID}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (AmountOfMoneyValue Dollar 10)
+             [ "$10"
+             , "10$"
+             , "sepuluh dolar"
+             ]
+  , examples (AmountOfMoneyValue Dollar 10000)
+             [ "$10.000"
+             , "10K$"
+             , "$10k"
+             ]
+  , examples (AmountOfMoneyValue USD 1.23)
+             [ "USD1,23"
+             ]
+  , examples (AmountOfMoneyValue Dollar 2)
+             [ "2 dola"
+             , "dua dolar"
+             ]
+  , examples (AmountOfMoneyValue EUR 20)
+             [ "20€"
+             , "20 euros"
+             , "20 Euro"
+             , "20 Euros"
+             , "EUR 20"
+             , "dua puluh Euro"
+             ]
+  , examples (AmountOfMoneyValue EUR 29.99)
+             [ "EUR29,99"
+             ]
+  , examples (AmountOfMoneyValue IDR 315)
+             [ "Rp. 315,00"
+             ]
+  , examples (AmountOfMoneyValue IDR 20)
+             [ "Rp 20"
+             , "20 Rupiah"
+             , "20Rp"
+             , "Rp20"
+             ]
+  , examples (AmountOfMoneyValue IDR 33000)
+             [ "IDR33000"
+             , "IDR 33.000"
+             ]
+  , examples (AmountOfMoneyValue Pound 9)
+             [ "£9"
+             , "sembilan pound"
+             , "sembilan pound sterling"
+             ]
+  , examples (AmountOfMoneyValue GBP 3.01)
+             [ "GBP3,01"
+             , "GBP 3,01"
+             ]
+  , examples (AmountOfMoneyValue JPY 10)
+             [ "10 yen"
+             , "10¥"
+             , "10 ¥."
+             ]
+  ]
diff --git a/Duckling/AmountOfMoney/ID/Rules.hs b/Duckling/AmountOfMoney/ID/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/ID/Rules.hs
@@ -0,0 +1,84 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.ID.Rules
+  ( rules ) where
+
+import Data.Maybe
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Helpers
+import Duckling.AmountOfMoney.Types (Currency(..))
+import qualified Duckling.AmountOfMoney.Types as TAmountOfMoney
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Types
+
+ruleDollar :: Rule
+ruleDollar = Rule
+  { name = "$"
+  , pattern =
+    [ regex "dolar?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Dollar
+  }
+
+ruleIdr :: Rule
+ruleIdr = Rule
+  { name = "IDR"
+  , pattern =
+    [ regex "rp\\.?|rupiah"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly IDR
+  }
+
+rulePounds :: Rule
+rulePounds = Rule
+  { name = "£"
+  , pattern =
+    [ regex "pound( sterling)?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Pound
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleJpy :: Rule
+ruleJpy = Rule
+  { name = "JPY"
+  , pattern =
+    [ regex "\x00a5\\."
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly JPY
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDollar
+  , ruleIdr
+  , ruleIntersect
+  , ruleJpy
+  , rulePounds
+  ]
diff --git a/Duckling/AmountOfMoney/KO/Corpus.hs b/Duckling/AmountOfMoney/KO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/KO/Corpus.hs
@@ -0,0 +1,78 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.KO.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = KO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (AmountOfMoneyValue Dollar 10)
+             [ "십달러"
+             , "십불"
+             ]
+  , examples (AmountOfMoneyValue Cent 10)
+             [ "십센트"
+             ]
+  , examples (AmountOfMoneyValue Dollar 10000)
+             [ "만달러"
+             , "만불"
+             ]
+  , examples (AmountOfMoneyValue Dollar 1.23)
+             [ "일점이삼달러"
+             , "일쩜이삼달러"
+             , "일점이삼불"
+             , "일쩜이삼불"
+             ]
+  , examples (AmountOfMoneyValue Dollar 2.23)
+             [ "이달러이십삼센트"
+             , "이불이십삼센트"
+             ]
+  , examples (AmountOfMoneyValue EUR 20)
+             [ "이십유로"
+             , "EUR 20"
+             ]
+  , examples (AmountOfMoneyValue EUR 29.99)
+             [ "이십구점구구유로"
+             , "EUR29.99"
+             ]
+  , examples (AmountOfMoneyValue Pound 9)
+             [ "구파운드"
+             ]
+  , examples (AmountOfMoneyValue KRW 27350000)
+             [ "이천칠백삼십오만원"
+             , "27,350,000원"
+             , "27350000원"
+             ]
+  , examples (AmountOfMoneyValue KRW 27000)
+             [ "이만칠천원"
+             , "27,000원"
+             , "27000원"
+             ]
+  , examples (AmountOfMoneyValue KRW 100)
+             [ "백원"
+             , "100원"
+             ]
+  , examples (AmountOfMoneyValue KRW 10)
+             [ "십원"
+             , "10원"
+             , "10₩"
+             ]
+  ]
diff --git a/Duckling/AmountOfMoney/KO/Rules.hs b/Duckling/AmountOfMoney/KO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/KO/Rules.hs
@@ -0,0 +1,161 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.KO.Rules
+  ( rules ) where
+
+import Data.Maybe
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.AmountOfMoney.Helpers
+import Duckling.AmountOfMoney.Types (Currency(..), AmountOfMoneyData (..))
+import qualified Duckling.AmountOfMoney.Types as TAmountOfMoney
+import Duckling.Types
+
+ruleAmountofmoneyAbout :: Rule
+ruleAmountofmoneyAbout = Rule
+  { name = "<amount-of-money> about"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "\xc815\xb3c4|\xcbe4"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleAud :: Rule
+ruleAud = Rule
+  { name = "AUD"
+  , pattern =
+    [ regex "\xd638\xc8fc\xb2ec\xb7ec"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly AUD
+  }
+
+ruleKrw :: Rule
+ruleKrw = Rule
+  { name = "₩"
+  , pattern =
+    [ regex "\x20a9|\xc6d0"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly KRW
+  }
+
+ruleAboutAmountofmoney :: Rule
+ruleAboutAmountofmoney = Rule
+  { name = "about <amount-of-money>"
+  , pattern =
+    [ regex "\xc57d|\xb300\xcda9|\xc5bc\xcd94"
+    , financeWith TAmountOfMoney.value isJust
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleCent :: Rule
+ruleCent = Rule
+  { name = "cent"
+  , pattern =
+    [ regex "cents?|\xc13c(\xd2b8|\xce20)"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Cent
+  }
+
+ruleExactlyAmountofmoney :: Rule
+ruleExactlyAmountofmoney = Rule
+  { name = "exactly <amount-of-money>"
+  , pattern =
+    [ regex "\xb531|\xc815\xd655\xd788"
+    , financeWith TAmountOfMoney.value isJust
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleIntersectXCents :: Rule
+ruleIntersectXCents = Rule
+  { name = "intersect (X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleEuro :: Rule
+ruleEuro = Rule
+  { name = "€"
+  , pattern =
+    [ regex "\x20ac|\xc720\xb85c"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly EUR
+  }
+
+ruleDollar :: Rule
+ruleDollar = Rule
+  { name = "$"
+  , pattern =
+    [ regex "\xb2ec\xb7ec|\xbd88"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Dollar
+  }
+
+ruleInr :: Rule
+ruleInr = Rule
+  { name = "INR"
+  , pattern =
+    [ regex "\xb8e8\xd53c|\xc778\xb3c4\xb8e8\xd53c"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly INR
+  }
+
+rulePounds :: Rule
+rulePounds = Rule
+  { name = "£"
+  , pattern =
+    [ regex "\xd30c\xc6b4\xb4dc|\xc601\xad6d\xd30c\xc6b4\xb4dc"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Pound
+  }
+
+ruleDirham :: Rule
+ruleDirham = Rule
+  { name = "dirham"
+  , pattern =
+    [ regex "dirhams?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly AED
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAboutAmountofmoney
+  , ruleAmountofmoneyAbout
+  , ruleAud
+  , ruleCent
+  , ruleDirham
+  , ruleDollar
+  , ruleEuro
+  , ruleExactlyAmountofmoney
+  , ruleInr
+  , ruleIntersectXCents
+  , ruleKrw
+  , rulePounds
+  ]
diff --git a/Duckling/AmountOfMoney/NB/Corpus.hs b/Duckling/AmountOfMoney/NB/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/NB/Corpus.hs
@@ -0,0 +1,86 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.NB.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = NB}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (AmountOfMoneyValue Dollar 10)
+             [ "$10"
+             , "10$"
+             , "ti dollar"
+             ]
+  , examples (AmountOfMoneyValue Cent 10)
+             [ "ti øre"
+             ]
+  , examples (AmountOfMoneyValue Dollar 10000)
+             [ "$10.000"
+             , "10K$"
+             , "$10k"
+             ]
+  , examples (AmountOfMoneyValue USD 1.23)
+             [ "USD1,23"
+             ]
+  , examples (AmountOfMoneyValue NOK 10)
+             [ "10kroner"
+             , "10kr"
+             , "ti kroner"
+             , "10 NOK"
+             ]
+  , examples (AmountOfMoneyValue NOK 2.23)
+             [ "2 kroner og 23 øre"
+             , "to kroner 23 øre"
+             , "to kroner og 23 øre"
+             , "to kr 23"
+             ]
+  , examples (AmountOfMoneyValue EUR 20)
+             [ "20€"
+             , "20 euro"
+             , "20 Euro"
+             , "20 Euros"
+             , "EUR 20"
+             ]
+  , examples (AmountOfMoneyValue EUR 29.99)
+             [ "EUR29,99"
+             ]
+  , examples (AmountOfMoneyValue INR 20)
+             [ "Rs. 20"
+             , "Rs 20"
+             , "20 Rupees"
+             , "20Rs"
+             , "Rs20"
+             , "INR20"
+             ]
+  , examples (AmountOfMoneyValue INR 20.43)
+             [ "20 Rupees 43"
+             , "tjue rupees 43"
+             , "tjue rupees 43¢"
+             ]
+  , examples (AmountOfMoneyValue Pound 9)
+             [ "£9"
+             , "ni pund"
+             ]
+  , examples (AmountOfMoneyValue GBP 3.01)
+             [ "GBP3,01"
+             , "GBP 3,01"
+             ]
+  ]
diff --git a/Duckling/AmountOfMoney/NB/Rules.hs b/Duckling/AmountOfMoney/NB/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/NB/Rules.hs
@@ -0,0 +1,163 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.NB.Rules
+  ( rules ) where
+
+import Data.Maybe
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Helpers
+import Duckling.AmountOfMoney.Types (Currency (..), AmountOfMoneyData (..))
+import qualified Duckling.AmountOfMoney.Types as TAmountOfMoney
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Types
+
+ruleIntersectAndNumeral :: Rule
+ruleIntersectAndNumeral = Rule
+  { name = "intersect (and number)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "og"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleAboutAmountofmoney :: Rule
+ruleAboutAmountofmoney = Rule
+  { name = "about <amount-of-money>"
+  , pattern =
+    [ regex "omtrent|cirka|rundt|ca"
+    , financeWith TAmountOfMoney.value isJust
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleCent :: Rule
+ruleCent = Rule
+  { name = "cent"
+  , pattern =
+    [ regex "cents?|penn(y|ies)|(\x00f8)re"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Cent
+  }
+
+ruleIntersectXCents :: Rule
+ruleIntersectXCents = Rule
+  { name = "intersect (X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleIntersectXCentsWithAnd :: Rule
+ruleIntersectXCentsWithAnd = Rule
+  { name = "intersect (X cents) with and"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "og"
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleNok :: Rule
+ruleNok = Rule
+  { name = "NOK"
+  , pattern =
+    [ regex "kr(oner)?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly NOK
+  }
+
+rulePound :: Rule
+rulePound = Rule
+  { name = "£"
+  , pattern =
+    [ regex "pund?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Pound
+  }
+
+ruleIntersectAndXCents :: Rule
+ruleIntersectAndXCents = Rule
+  { name = "intersect (and X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "og"
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleDirham :: Rule
+ruleDirham = Rule
+  { name = "AED"
+  , pattern =
+    [ regex "dirhams?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly AED
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAboutAmountofmoney
+  , ruleCent
+  , ruleDirham
+  , ruleIntersect
+  , ruleIntersectAndNumeral
+  , ruleIntersectAndXCents
+  , ruleIntersectXCents
+  , ruleIntersectXCentsWithAnd
+  , ruleNok
+  , rulePound
+  ]
diff --git a/Duckling/AmountOfMoney/PT/Corpus.hs b/Duckling/AmountOfMoney/PT/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/PT/Corpus.hs
@@ -0,0 +1,74 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.PT.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = PT}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (AmountOfMoneyValue Dollar 10)
+             [ "$10"
+             , "10$"
+             , "dez dolares"
+             ]
+  , examples (AmountOfMoneyValue Dollar 10000)
+             [ "$10.000"
+             , "10K$"
+             , "$10k"
+             ]
+  , examples (AmountOfMoneyValue USD 1.23)
+             [ "USD1,23"
+             ]
+  , examples (AmountOfMoneyValue EUR 20)
+             [ "20€"
+             , "20 euros"
+             , "20 Euro"
+             , "20 Euros"
+             , "EUR 20"
+             ]
+  , examples (AmountOfMoneyValue EUR 29.99)
+             [ "EUR29,99"
+             ]
+  , examples (AmountOfMoneyValue Pound 9)
+             [ "£9"
+             , "nove libras"
+             ]
+  , examples (AmountOfMoneyValue GBP 3.01)
+             [ "GBP3,01"
+             , "GBP 3,01"
+             ]
+  , examples (AmountOfMoneyValue PTS 15)
+             [ "15 Pt"
+             , "15pta"
+             , "15Ptas"
+             ]
+  , examples (AmountOfMoneyValue BRL 15)
+             [ "15 reais"
+             , "15reais"
+             , "15 Reais"
+             , "BRL 15"
+             ]
+  , examples (AmountOfMoneyValue BRL 2.0)
+             [ "R$2,00"
+             , "R$ 2,00"
+             , "2,00 reais"
+             ]
+  ]
diff --git a/Duckling/AmountOfMoney/PT/Rules.hs b/Duckling/AmountOfMoney/PT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/PT/Rules.hs
@@ -0,0 +1,133 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.PT.Rules
+  ( rules ) where
+
+import Data.Maybe
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Helpers
+import Duckling.AmountOfMoney.Types (Currency(..), AmountOfMoneyData (..))
+import qualified Duckling.AmountOfMoney.Types as TAmountOfMoney
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Types
+
+ruleIntersectAndNumeral :: Rule
+ruleIntersectAndNumeral = Rule
+  { name = "intersect (and number)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "e"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleDollar :: Rule
+ruleDollar = Rule
+  { name = "$"
+  , pattern =
+    [ regex "dolares?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Dollar
+  }
+
+ruleCent :: Rule
+ruleCent = Rule
+  { name = "cent"
+  , pattern =
+    [ regex "centavos?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Cent
+  }
+
+ruleIntersectXCents :: Rule
+ruleIntersectXCents = Rule
+  { name = "intersect (X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+rulePounds :: Rule
+rulePounds = Rule
+  { name = "£"
+  , pattern =
+    [ regex "libras?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Pound
+  }
+
+ruleIntersectAndXCents :: Rule
+ruleIntersectAndXCents = Rule
+  { name = "intersect (and X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "e"
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleBrl :: Rule
+ruleBrl = Rule
+  { name = "BRL"
+  , pattern =
+    [ regex "reais|r\\$"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly BRL
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleBrl
+  , ruleCent
+  , ruleDollar
+  , ruleIntersect
+  , ruleIntersectAndNumeral
+  , ruleIntersectAndXCents
+  , ruleIntersectXCents
+  , rulePounds
+  ]
diff --git a/Duckling/AmountOfMoney/RO/Corpus.hs b/Duckling/AmountOfMoney/RO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/RO/Corpus.hs
@@ -0,0 +1,120 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.RO.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = RO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (AmountOfMoneyValue RON 10)
+             [ "10 lei"
+             , "10 roni"
+             , "10 RON"
+             ]
+  , examples (AmountOfMoneyValue Cent 50)
+             [ "50 bani"
+             , "50 BANI"
+             ]
+  , examples (AmountOfMoneyValue RON 10.5)
+             [ "10,5 lei"
+             , "10,5 ron"
+             , "10 lei si 50 bani"
+             ]
+  , examples (AmountOfMoneyValue Dollar 10)
+             [ "$10"
+             , "10$"
+             , "zece dolari"
+             , "10 dolari"
+             ]
+  , examples (AmountOfMoneyValue Cent 10)
+             [ "zece centi"
+             , "zece cenți"
+             ]
+  , examples (AmountOfMoneyValue Dollar 10000)
+             [ "$10.000"
+             , "$10000"
+             ]
+  , examples (AmountOfMoneyValue USD 1.23)
+             [ "USD1,23"
+             ]
+  , examples (AmountOfMoneyValue Dollar 1)
+             [ "1 dolar"
+             , "un dolar"
+             , "$1"
+             ]
+  , examples (AmountOfMoneyValue Cent 2)
+             [ "2 centi"
+             ]
+  , examples (AmountOfMoneyValue Cent 23)
+             [ "23 centi"
+             ]
+  , examples (AmountOfMoneyValue Dollar 2.23)
+             [ "2 dolari si 23 centi"
+             , "2 dolari și 23 cenți"
+             , "doi dolari si douăzeci si trei centi"
+             , "doi dolari și douăzeci și trei cenți"
+             , "2 dolari 23 centi"
+             , "doi dolari si 23"
+             , "doi dolari și 23"
+             ]
+  , examples (AmountOfMoneyValue EUR 20)
+             [ "20€"
+             , "20 euro"
+             , "20 Euro"
+             , "EUR 20"
+             ]
+  , examples (AmountOfMoneyValue EUR 29.99)
+             [ "EUR29,99"
+             ]
+  , examples (AmountOfMoneyValue Pound 9)
+             [ "£9"
+             , "noua lir"
+             , "nouă lire"
+             ]
+  , examples (AmountOfMoneyValue GBP 3.01)
+             [ "GBP3,01"
+             , "GBP 3,01"
+             ]
+  , examples (AmountOfMoneyValue QAR 1)
+             [ "un rial qatarian"
+             , "1 rial qataria"
+             ]
+  , examples (AmountOfMoneyValue EGP 10)
+             [ "zece lira egiptiana"
+             ]
+  , examples (AmountOfMoneyValue LBP 1)
+             [ "una liră libaneză"
+             ]
+  , examples (AmountOfMoneyValue INR 42)
+             [ "42 rupii"
+             , "Rs. 42"
+             ]
+  , examples (AmountOfMoneyValue KWD 1)
+             [ "un dinar kuweitian"
+             ]
+  , examples (AmountOfMoneyValue AED 2)
+             [ "2 dirhami"
+             ]
+  , examples (AmountOfMoneyValue SAR 1)
+             [ "1 rial saudit"
+             , "un rial saudi"
+             ]
+  ]
diff --git a/Duckling/AmountOfMoney/RO/Rules.hs b/Duckling/AmountOfMoney/RO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/RO/Rules.hs
@@ -0,0 +1,210 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.RO.Rules
+  ( rules ) where
+
+import Data.Maybe
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Helpers
+import Duckling.AmountOfMoney.Types (Currency(..), AmountOfMoneyData (..))
+import qualified Duckling.AmountOfMoney.Types as TAmountOfMoney
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleIntersectAndNumeral :: Rule
+ruleIntersectAndNumeral = Rule
+  { name = "intersect (and number)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "(s|\x0219)i"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleRiyals :: Rule
+ruleRiyals = Rule
+  { name = "riyals"
+  , pattern =
+    [ regex "rial (saudit?|qatarian?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "saudi"     -> Just . Token AmountOfMoney $ currencyOnly SAR
+        "saudit"    -> Just . Token AmountOfMoney $ currencyOnly SAR
+        "qataria"   -> Just . Token AmountOfMoney $ currencyOnly QAR
+        "qatarian"  -> Just . Token AmountOfMoney $ currencyOnly QAR
+        _           -> Nothing
+      _ -> Nothing
+  }
+
+ruleDollar :: Rule
+ruleDollar = Rule
+  { name = "$"
+  , pattern =
+    [ regex "dolari?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Dollar
+  }
+
+rulePrecisionAmountofmoney :: Rule
+rulePrecisionAmountofmoney = Rule
+  { name = "about/exactly <amount-of-money>"
+  , pattern =
+    [ regex "exact|cam|aprox(\\.|imativ)?|aproape|(i|\x00ee)n jur (de)?"
+    , financeWith TAmountOfMoney.value isJust
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleCent :: Rule
+ruleCent = Rule
+  { name = "cent|bani"
+  , pattern =
+    [ regex "bani?|cen(t|\x021b)i?|c|\x00a2"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Cent
+  }
+
+ruleRon :: Rule
+ruleRon = Rule
+  { name = "RON"
+  , pattern =
+    [ regex "roni|lei"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly RON
+  }
+
+ruleIntersectAndXCents :: Rule
+ruleIntersectAndXCents = Rule
+  { name = "intersect (and X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "(s|\x0219)i"
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleIntersectXCents :: Rule
+ruleIntersectXCents = Rule
+  { name = "intersect (X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+rulePounds :: Rule
+rulePounds = Rule
+  { name = "£"
+  , pattern =
+    [ regex "lire?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Pound
+  }
+
+ruleOtherPounds :: Rule
+ruleOtherPounds = Rule
+  { name = "other pounds"
+  , pattern =
+    [ regex "lir(a|\x0103) (egiptian|libanez)(a|\x0103)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (_:match:_)):_) -> case Text.toLower match of
+        "egiptian" -> Just . Token AmountOfMoney $ currencyOnly EGP
+        "libanez"  -> Just . Token AmountOfMoney $ currencyOnly LBP
+        _          -> Nothing
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleInr :: Rule
+ruleInr = Rule
+  { name = "INR"
+  , pattern =
+    [ regex "rupii?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly INR
+  }
+
+ruleKwd :: Rule
+ruleKwd = Rule
+  { name = "KWD"
+  , pattern =
+    [ regex "dinar kuweitian"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly KWD
+  }
+
+ruleAed :: Rule
+ruleAed = Rule
+  { name = "AED"
+  , pattern =
+    [ regex "dirhami?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly AED
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAed
+  , ruleCent
+  , ruleDollar
+  , ruleInr
+  , ruleIntersect
+  , ruleIntersectAndNumeral
+  , ruleIntersectAndXCents
+  , ruleIntersectXCents
+  , ruleKwd
+  , ruleOtherPounds
+  , rulePounds
+  , rulePrecisionAmountofmoney
+  , ruleRiyals
+  , ruleRon
+  ]
diff --git a/Duckling/AmountOfMoney/Rules.hs b/Duckling/AmountOfMoney/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/Rules.hs
@@ -0,0 +1,129 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.Rules
+  ( rules
+  ) where
+
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.Maybe
+import Data.String
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+
+import Duckling.AmountOfMoney.Helpers
+import Duckling.AmountOfMoney.Types (Currency(..), AmountOfMoneyData (..))
+import qualified Duckling.AmountOfMoney.Types as TAmountOfMoney
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types     as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+currencies :: HashMap Text Currency
+currencies = HashMap.fromList
+  [ ("aed", AED)
+  , ("aud", AUD)
+  , ("brl", BRL)
+  , ("\x00a2", Cent)
+  , ("c", Cent)
+  , ("$", Dollar)
+  , ("dollar", Dollar)
+  , ("dollars", Dollar)
+  , ("egp", EGP)
+  , ("\x20ac", EUR)
+  , ("eur", EUR)
+  , ("euro", EUR)
+  , ("euros", EUR)
+  , ("eurs", EUR)
+  , ("\x20acur", EUR)
+  , ("\x20acuro", EUR)
+  , ("\x20acuros", EUR)
+  , ("\x20acurs", EUR)
+  , ("gbp", GBP)
+  , ("hrk", HRK)
+  , ("idr", IDR)
+  , ("inr", INR)
+  , ("rs", INR)
+  , ("rs.", INR)
+  , ("rupee", INR)
+  , ("rupees", INR)
+  , ("\x00a5", JPY)
+  , ("jpy", JPY)
+  , ("yen", JPY)
+  , ("krw", KRW)
+  , ("kwd", KWD)
+  , ("lbp", LBP)
+  , ("nok", NOK)
+  , ("\x00a3", Pound)
+  , ("pt", PTS)
+  , ("pta", PTS)
+  , ("ptas", PTS)
+  , ("pts", PTS)
+  , ("qar", QAR)
+  , ("ron", RON)
+  , ("sar", SAR)
+  , ("sek", SEK)
+  , ("sgd", SGD)
+  , ("usd", USD)
+  , ("us$", USD)
+  , ("vnd", VND)
+  ]
+
+ruleCurrencies :: Rule
+ruleCurrencies = Rule
+  { name = "currencies"
+  , pattern =
+    [ regex "(aed|aud|brl|\x00a2|c|\\$|dollars?|egp|(e|\x20ac)uro?s?|\x20ac|gbp|hrk|idr|inr|\x00a5|jpy|krw|kwd|lbp|nok|\x00a3|pta?s?|qar|rs\\.?|ron|rupees?|sar|sek|sgb|us(d|\\$)|vnd|yen)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        c <- HashMap.lookup (Text.toLower match) currencies
+        Just . Token AmountOfMoney $ currencyOnly c
+      _ -> Nothing
+  }
+
+ruleAmountUnit :: Rule
+ruleAmountUnit = Rule
+  { name = "<amount> <unit>"
+  , pattern =
+    [ dimension Numeral
+    , financeWith TAmountOfMoney.value isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.currency = c}):
+       _) -> Just . Token AmountOfMoney . withValue v $ currencyOnly c
+      _ -> Nothing
+  }
+
+ruleUnitAmount :: Rule
+ruleUnitAmount = Rule
+  { name = "<unit> <amount>"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isNothing
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.currency = c}):
+       Token Numeral (NumeralData {TNumeral.value = v}):
+       _) -> Just . Token AmountOfMoney . withValue v $ currencyOnly c
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAmountUnit
+  , ruleCurrencies
+  , ruleUnitAmount
+  ]
diff --git a/Duckling/AmountOfMoney/SV/Corpus.hs b/Duckling/AmountOfMoney/SV/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/SV/Corpus.hs
@@ -0,0 +1,91 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.SV.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = SV}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (AmountOfMoneyValue Dollar 10)
+             [ "$10"
+             , "10$"
+             , "tio dollar"
+             ]
+  , examples (AmountOfMoneyValue Cent 10)
+             [ "tio öre"
+             ]
+  , examples (AmountOfMoneyValue Dollar 10000)
+             [ "$10.000"
+             , "10K$"
+             , "$10k"
+             ]
+  , examples (AmountOfMoneyValue USD 1.23)
+             [ "USD1,23"
+             ]
+  , examples (AmountOfMoneyValue SEK 10)
+             [ "10kronor"
+             , "10kr"
+             , "10 kr"
+             , "tio kronor"
+             , "10 SEK"
+             ]
+  , examples (AmountOfMoneyValue SEK 2.23)
+             [ "2 kronor och 23 öre"
+             , "två kronor 23 öre"
+             , "två kronor och 23 öre"
+             ]
+  , examples (AmountOfMoneyValue EUR 20)
+             [ "20€"
+             , "20 euro"
+             , "20 Euro"
+             , "20 Euros"
+             , "EUR 20"
+             ]
+  , examples (AmountOfMoneyValue EUR 29.99)
+             [ "EUR29,99"
+             ]
+  , examples (AmountOfMoneyValue INR 20)
+             [ "Rs. 20"
+             , "Rs 20"
+             , "20 Rupees"
+             , "20Rs"
+             , "Rs20"
+             ]
+  , examples (AmountOfMoneyValue INR 20.43)
+             [ "20 Rupees 43"
+             , "tjugo rupees 43"
+             ]
+  , examples (AmountOfMoneyValue INR 33)
+             [ "INR33"
+             ]
+  , examples (AmountOfMoneyValue Pound 9)
+             [ "£9"
+             , "nio pund"
+             ]
+  , examples (AmountOfMoneyValue GBP 3.01)
+             [ "GBP3,01"
+             , "GBP 3,01"
+             ]
+  , examples (AmountOfMoneyValue NOK 10)
+             [ "10 norska kronor"
+             , "10 nkr"
+             ]
+  ]
diff --git a/Duckling/AmountOfMoney/SV/Rules.hs b/Duckling/AmountOfMoney/SV/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/SV/Rules.hs
@@ -0,0 +1,169 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.SV.Rules
+  ( rules ) where
+
+import Data.Maybe
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Helpers
+import Duckling.AmountOfMoney.Types (Currency(..), AmountOfMoneyData (..))
+import qualified Duckling.AmountOfMoney.Types as TAmountOfMoney
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Types
+
+ruleIntersectAndNumeral :: Rule
+ruleIntersectAndNumeral = Rule
+  { name = "intersect (and number)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "och"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleAboutAmountofmoney :: Rule
+ruleAboutAmountofmoney = Rule
+  { name = "about <amount-of-money>"
+  , pattern =
+    [ regex "omkring|cirka|runt|ca"
+    , financeWith TAmountOfMoney.value isJust
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleCent :: Rule
+ruleCent = Rule
+  { name = "cent"
+  , pattern =
+    [ regex "cents?|penn(y|ies)|\x00f6re"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Cent
+  }
+
+ruleExactlyAmountofmoney :: Rule
+ruleExactlyAmountofmoney = Rule
+  { name = "exactly <amount-of-money>"
+  , pattern =
+    [ regex "exakt|precis"
+    , financeWith TAmountOfMoney.value isJust
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleIntersectXCents :: Rule
+ruleIntersectXCents = Rule
+  { name = "intersect (X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleNok :: Rule
+ruleNok = Rule
+  { name = "NOK"
+  , pattern =
+    [ regex "norska kronor|nkr"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly NOK
+  }
+
+rulePounds :: Rule
+rulePounds = Rule
+  { name = "£"
+  , pattern =
+    [ regex "pund?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Pound
+  }
+
+ruleIntersectAndXCents :: Rule
+ruleIntersectAndXCents = Rule
+  { name = "intersect (and X cents)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "och"
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleSek :: Rule
+ruleSek = Rule
+  { name = "SEK"
+  , pattern =
+    [ regex "kr(onor)?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly SEK
+  }
+
+ruleDirham :: Rule
+ruleDirham = Rule
+  { name = "AED"
+  , pattern =
+    [ regex "dirhams?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly AED
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAboutAmountofmoney
+  , ruleCent
+  , ruleDirham
+  , ruleExactlyAmountofmoney
+  , ruleIntersect
+  , ruleIntersectAndNumeral
+  , ruleIntersectAndXCents
+  , ruleIntersectXCents
+  , ruleNok
+  , rulePounds
+  , ruleSek
+  ]
diff --git a/Duckling/AmountOfMoney/Types.hs b/Duckling/AmountOfMoney/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/Types.hs
@@ -0,0 +1,109 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Duckling.AmountOfMoney.Types where
+
+import Control.DeepSeq
+import Data.Aeson
+import Data.Hashable
+import Data.Text (Text)
+import GHC.Generics
+import Prelude
+
+import Duckling.Resolve (Resolve(..))
+
+data Currency
+  -- ambiguous
+  = Cent
+  | Dollar
+  | Pound
+  | Unnamed -- e.g. bucks
+  -- unambiguous
+  | AED
+  | AUD
+  | BRL
+  | EGP
+  | EUR
+  | GBP
+  | HRK
+  | IDR
+  | INR
+  | JPY
+  | KRW
+  | KWD
+  | LBP
+  | NOK
+  | PTS
+  | QAR
+  | RON
+  | SAR
+  | SEK
+  | SGD
+  | USD
+  | VND
+  deriving (Eq, Generic, Hashable, Show, Ord, NFData)
+
+instance ToJSON Currency where
+  toJSON Cent    = "cent"
+  toJSON Dollar  = "$"
+  toJSON Pound   = "\x00a3"
+  toJSON Unnamed = "unknown"
+  toJSON AED     = "AED"
+  toJSON AUD     = "AUD"
+  toJSON BRL     = "BRL"
+  toJSON EGP     = "EGP"
+  toJSON EUR     = "EUR"
+  toJSON GBP     = "GBP"
+  toJSON HRK     = "HRK"
+  toJSON IDR     = "IDR"
+  toJSON INR     = "INR"
+  toJSON JPY     = "JPY"
+  toJSON KRW     = "KRW"
+  toJSON KWD     = "KWD"
+  toJSON LBP     = "LBP"
+  toJSON NOK     = "NOK"
+  toJSON PTS     = "PTS"
+  toJSON QAR     = "QAR"
+  toJSON RON     = "RON"
+  toJSON SAR     = "SAR"
+  toJSON SEK     = "SEK"
+  toJSON SGD     = "SGD"
+  toJSON USD     = "USD"
+  toJSON VND     = "VND"
+
+data AmountOfMoneyData = AmountOfMoneyData
+  { value    :: Maybe Double
+  , currency :: Currency
+  }
+  deriving (Eq, Generic, Hashable, Show, Ord, NFData)
+
+data AmountOfMoneyValue = AmountOfMoneyValue
+  { vCurrency :: Currency
+  , vValue    :: Double
+  }
+  deriving (Eq, Show)
+
+instance Resolve AmountOfMoneyData where
+  type ResolvedValue AmountOfMoneyData = AmountOfMoneyValue
+  resolve _ AmountOfMoneyData {value = Nothing} = Nothing
+  resolve _ AmountOfMoneyData {value = Just value, currency} =
+    Just AmountOfMoneyValue {vValue = value, vCurrency = currency}
+
+instance ToJSON AmountOfMoneyValue where
+  toJSON AmountOfMoneyValue {vCurrency, vValue} = object
+    [ "type"  .= ("value" :: Text)
+    , "value" .= vValue
+    , "unit"  .= vCurrency
+    ]
diff --git a/Duckling/AmountOfMoney/VI/Corpus.hs b/Duckling/AmountOfMoney/VI/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/VI/Corpus.hs
@@ -0,0 +1,103 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.VI.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = VI}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (AmountOfMoneyValue Dollar 10)
+             [ "$10"
+             , "10$"
+             , "mười đô"
+             , "mười đô la"
+             , "mười đô mỹ"
+             ]
+  , examples (AmountOfMoneyValue Cent 10)
+             [ "mười xen"
+             , "mười xu"
+             ]
+  , examples (AmountOfMoneyValue Dollar 10000)
+             [ "$10,000"
+             , "10K$"
+             , "$10k"
+             ]
+  , examples (AmountOfMoneyValue USD 1.23)
+             [ "USD1.23"
+             ]
+  , examples (AmountOfMoneyValue Dollar 2.23)
+             [ "2 đô la và 23 xen"
+             , "hai đô la và 23 xen"
+             , "2 đô 23 xu"
+             , "hai đô 23"
+             , "2 chấm 23 đô la"
+             , "hai phẩy 23 đô"
+             ]
+  , examples (AmountOfMoneyValue VND 10)
+             [ "mười đồng"
+             ]
+  , examples (AmountOfMoneyValue VND 10000)
+             [ "10,000 đồng"
+             , "10K đồng"
+             , "10k đồng"
+             ]
+  , examples (AmountOfMoneyValue VND 1000)
+             [ "1000 VNĐ"
+             , "VN$1000"
+             ]
+  , examples (AmountOfMoneyValue EUR 20)
+             [ "20€"
+             , "20 euros"
+             , "20 Euro"
+             , "20 Euros"
+             , "EUR 20"
+             ]
+  , examples (AmountOfMoneyValue EUR 29.99)
+             [ "EUR29.99"
+             ]
+  , examples (AmountOfMoneyValue INR 20)
+             [ "Rs. 20"
+             , "Rs 20"
+             , "20 Rupees"
+             , "20Rs"
+             , "Rs20"
+             ]
+  , examples (AmountOfMoneyValue INR 20.43)
+             [ "20 Rupees 43"
+             , "hai mươi rupees 43"
+             , "hai mươi rupees 43 xen"
+             ]
+  , examples (AmountOfMoneyValue INR 33)
+             [ "INR33"
+             ]
+  , examples (AmountOfMoneyValue Pound 9)
+             [ "£9"
+             , "chín pounds"
+             ]
+  , examples (AmountOfMoneyValue GBP 3.01)
+             [ "GBP3.01"
+             , "GBP 3.01"
+             ]
+  , examples (AmountOfMoneyValue AED 1)
+             [ "1 AED."
+             , "1 dirham"
+             ]
+  ]
diff --git a/Duckling/AmountOfMoney/VI/Rules.hs b/Duckling/AmountOfMoney/VI/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/AmountOfMoney/VI/Rules.hs
@@ -0,0 +1,153 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.AmountOfMoney.VI.Rules
+  ( rules ) where
+
+import Data.Maybe
+import Prelude
+import Data.String
+
+import Duckling.AmountOfMoney.Helpers
+import Duckling.AmountOfMoney.Types (Currency(..), AmountOfMoneyData (..))
+import qualified Duckling.AmountOfMoney.Types as TAmountOfMoney
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Types
+
+ruleNg :: Rule
+ruleNg = Rule
+  { name = "đồng"
+  , pattern =
+    [ regex "\x0111\x1ed3ng?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly VND
+  }
+
+ruleDollar :: Rule
+ruleDollar = Rule
+  { name = "$"
+  , pattern =
+    [ regex "\x0111\x00f4 la|\x0111\x00f4 m\x1ef9|\x0111(\x00f4)?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Dollar
+  }
+
+ruleVnd :: Rule
+ruleVnd = Rule
+  { name = "VNĐ"
+  , pattern =
+    [ regex "vn(\x0110|\\$)"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly VND
+  }
+
+ruleCent :: Rule
+ruleCent = Rule
+  { name = "cent"
+  , pattern =
+    [ regex "xen|xu?|penn(y|ies)"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Cent
+  }
+
+rulePounds :: Rule
+rulePounds = Rule
+  { name = "£"
+  , pattern =
+    [ regex "pounds?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly Pound
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleIntersectAndNumeral :: Rule
+ruleIntersectAndNumeral = Rule
+  { name = "intersect and number"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "v\x00e0"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token Numeral (NumeralData {TNumeral.value = c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleIntersectXXuxen :: Rule
+ruleIntersectXXuxen = Rule
+  { name = "intersect (X xu|xen)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleIntersectVXXuxen :: Rule
+ruleIntersectVXXuxen = Rule
+  { name = "intersect (và X xu|xen)"
+  , pattern =
+    [ financeWith TAmountOfMoney.value isJust
+    , regex "v\x00e0"
+    , financeWith TAmountOfMoney.currency (== Cent)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token AmountOfMoney fd:
+       _:
+       Token AmountOfMoney (AmountOfMoneyData {TAmountOfMoney.value = Just c}):
+       _) -> Just . Token AmountOfMoney $ withCents c fd
+      _ -> Nothing
+  }
+
+ruleDirham :: Rule
+ruleDirham = Rule
+  { name = "AED"
+  , pattern =
+    [ regex "AED\\.|dirhams?"
+    ]
+  , prod = \_ -> Just . Token AmountOfMoney $ currencyOnly AED
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleCent
+  , ruleDirham
+  , ruleDollar
+  , ruleIntersect
+  , ruleIntersectAndNumeral
+  , ruleIntersectVXXuxen
+  , ruleIntersectXXuxen
+  , ruleNg
+  , rulePounds
+  , ruleVnd
+  ]
diff --git a/Duckling/Api.hs b/Duckling/Api.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Api.hs
@@ -0,0 +1,62 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+
+module Duckling.Api
+  ( analyze
+  , formatToken
+  , parse
+  , supportedDimensions
+  ) where
+
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.HashSet (HashSet)
+import qualified Data.HashSet as HashSet
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+import TextShow
+
+import Duckling.Dimensions.Types
+import Duckling.Dimensions
+import Duckling.Engine
+import Duckling.Lang
+import Duckling.Ranking.Classifiers
+import Duckling.Ranking.Rank
+import Duckling.Resolve
+import Duckling.Rules
+import Duckling.Types
+
+-- | Parses `input` and returns a curated list of entities found.
+parse :: Text -> Context -> [Some Dimension] -> [Entity]
+parse input ctx = map (formatToken input) . analyze input ctx . HashSet.fromList
+
+supportedDimensions :: HashMap Lang [Some Dimension]
+supportedDimensions =
+  HashMap.fromList [ (l, allDimensions l) | l <- [minBound..maxBound] ]
+
+-- | Returns a curated list of resolved tokens found
+-- When `targets` is non-empty, returns only tokens of such dimensions.
+analyze :: Text -> Context -> HashSet (Some Dimension) -> [ResolvedToken]
+analyze input context@Context{..} targets =
+  rank (classifiers lang) targets
+  . filter (\(Resolved{node = Node{token = (Token d _)}}) ->
+      HashSet.null targets || HashSet.member (This d) targets
+    )
+  $ parseAndResolve (rulesFor lang targets) input context
+
+-- | Converts the resolved token to the API format
+formatToken :: Text -> ResolvedToken -> Entity
+formatToken sentence (Resolved (Range start end) (Node{token=Token dimension _}) jsonValue) =
+  Entity (toName dimension) body val start end
+  where
+    body = Text.drop start $ Text.take end sentence
+    val = toJText jsonValue
diff --git a/Duckling/Core.hs b/Duckling/Core.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Core.hs
@@ -0,0 +1,58 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE NoRebindableSyntax #-}
+
+-- | Everything needed to run Duckling.
+
+module Duckling.Core
+  ( Context(..)
+  , Dimension(..)
+  , fromName
+  , Entity(..)
+  , Lang(..)
+  , Some(..)
+  , toName
+
+  -- Duckling API
+  , parse
+  , supportedDimensions
+
+  -- Reference time builders
+  , currentReftime
+  , makeReftime
+  ) where
+
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.Maybe
+import Data.Text (Text)
+import Data.Time
+import Data.Time.LocalTime.TimeZone.Series
+import Prelude
+
+import Duckling.Api
+import Duckling.Dimensions.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Types
+
+-- | Builds a `DucklingTime` for timezone `tz` at `utcTime`.
+-- If no `series` found for `tz`, uses UTC.
+makeReftime :: HashMap Text TimeZoneSeries -> Text -> UTCTime -> DucklingTime
+makeReftime series tz utcTime = DucklingTime $ ZoneSeriesTime ducklingTime tzs
+  where
+    tzs = HashMap.lookupDefault (TimeZoneSeries utc []) tz series
+    ducklingTime = toUTC $ utcToLocalTime' tzs utcTime
+
+-- | Builds a `DucklingTime` for timezone `tz` at current time.
+-- If no `series` found for `tz`, uses UTC.
+currentReftime :: HashMap Text TimeZoneSeries -> Text -> IO DucklingTime
+currentReftime series tz = do
+  utcNow <- getCurrentTime
+  return $ makeReftime series tz utcNow
diff --git a/Duckling/Debug.hs b/Duckling/Debug.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Debug.hs
@@ -0,0 +1,85 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Debug
+  ( allParses
+  , debug
+  , debugContext
+  , fullParses
+  , ptree
+  ) where
+
+import qualified Data.HashSet as HashSet
+import Data.Maybe
+import Data.Text (Text)
+import qualified Data.Text as Text
+import qualified Data.Text.IO as Text
+import Prelude
+
+import Duckling.Api
+import Duckling.Dimensions.Types
+import Duckling.Engine
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Rules
+import Duckling.Testing.Types
+import Duckling.Types
+
+-- -----------------------------------------------------------------
+-- API
+
+debug :: Lang -> Text -> [Some Dimension] -> IO [Entity]
+debug l = debugContext testContext {lang = l}
+
+allParses :: Lang -> Text -> [Some Dimension] -> IO [Entity]
+allParses l sentence targets = debugTokens sentence $ parses l sentence targets
+
+fullParses :: Lang -> Text -> [Some Dimension] -> IO [Entity]
+fullParses l sentence targets = debugTokens sentence .
+  filter (\(Resolved {range = Range start end}) -> start == 0 && end == n) $
+  parses l sentence targets
+  where
+    n = Text.length sentence
+
+ptree :: Text -> ResolvedToken -> IO ()
+ptree sentence Resolved {node} = pnode sentence 0 node
+
+-- -----------------------------------------------------------------
+-- Internals
+
+parses :: Lang -> Text -> [Some Dimension] -> [ResolvedToken]
+parses l sentence targets = flip filter tokens $
+  \(Resolved {node = Node{token = (Token d _)}}) ->
+    case targets of
+      [] -> True
+      _ -> elem (This d) targets
+  where
+    tokens = parseAndResolve rules sentence testContext {lang = l}
+    rules = rulesFor l $ HashSet.fromList targets
+
+debugContext :: Context -> Text -> [Some Dimension] -> IO [Entity]
+debugContext context sentence targets =
+  debugTokens sentence . analyze sentence context $ HashSet.fromList targets
+
+debugTokens :: Text -> [ResolvedToken] -> IO [Entity]
+debugTokens sentence tokens = do
+  mapM_ (ptree sentence) tokens
+  return $ map (formatToken sentence) tokens
+
+pnode :: Text -> Int -> Node -> IO ()
+pnode sentence depth Node {children, rule, nodeRange = Range start end} = do
+  Text.putStrLn out
+  mapM_ (pnode sentence (depth + 1)) children
+  where
+    out = Text.concat [ Text.replicate depth "-- ", name, " (", body, ")" ]
+    name = fromMaybe "regex" rule
+    body = Text.drop start $ Text.take end sentence
diff --git a/Duckling/Dimensions.hs b/Duckling/Dimensions.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions.hs
@@ -0,0 +1,104 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+
+module Duckling.Dimensions
+  ( allDimensions
+  , explicitDimensions
+  ) where
+
+import Data.HashSet (HashSet)
+import Prelude
+import qualified Data.HashSet as HashSet
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Dimensions.Common as CommonDimensions
+import qualified Duckling.Dimensions.AR as ARDimensions
+import qualified Duckling.Dimensions.DA as DADimensions
+import qualified Duckling.Dimensions.DE as DEDimensions
+import qualified Duckling.Dimensions.EN as ENDimensions
+import qualified Duckling.Dimensions.ES as ESDimensions
+import qualified Duckling.Dimensions.ET as ETDimensions
+import qualified Duckling.Dimensions.FR as FRDimensions
+import qualified Duckling.Dimensions.GA as GADimensions
+import qualified Duckling.Dimensions.HE as HEDimensions
+import qualified Duckling.Dimensions.HR as HRDimensions
+import qualified Duckling.Dimensions.ID as IDDimensions
+import qualified Duckling.Dimensions.IT as ITDimensions
+import qualified Duckling.Dimensions.JA as JADimensions
+import qualified Duckling.Dimensions.KO as KODimensions
+import qualified Duckling.Dimensions.MY as MYDimensions
+import qualified Duckling.Dimensions.NB as NBDimensions
+import qualified Duckling.Dimensions.NL as NLDimensions
+import qualified Duckling.Dimensions.PL as PLDimensions
+import qualified Duckling.Dimensions.PT as PTDimensions
+import qualified Duckling.Dimensions.RO as RODimensions
+import qualified Duckling.Dimensions.RU as RUDimensions
+import qualified Duckling.Dimensions.SV as SVDimensions
+import qualified Duckling.Dimensions.TR as TRDimensions
+import qualified Duckling.Dimensions.UK as UKDimensions
+import qualified Duckling.Dimensions.VI as VIDimensions
+import qualified Duckling.Dimensions.ZH as ZHDimensions
+import Duckling.Lang
+
+allDimensions :: Lang -> [Some Dimension]
+allDimensions lang = CommonDimensions.allDimensions ++ langDimensions lang
+
+-- | Augments `targets` with all dependent dimensions.
+explicitDimensions :: HashSet (Some Dimension) -> HashSet (Some Dimension)
+explicitDimensions targets = HashSet.union targets deps
+  where
+    deps = HashSet.unions . map dependents $ HashSet.toList targets
+
+-- | Ordinal depends on Numeral for JA, KO, and ZH.
+dependents :: Some Dimension -> HashSet (Some Dimension)
+dependents (This Distance) = HashSet.singleton (This Numeral)
+dependents (This Duration) = HashSet.fromList [This Numeral, This TimeGrain]
+dependents (This Numeral) = HashSet.empty
+dependents (This Email) = HashSet.empty
+dependents (This AmountOfMoney) = HashSet.singleton (This Numeral)
+dependents (This Ordinal) = HashSet.singleton (This Numeral)
+dependents (This PhoneNumber) = HashSet.empty
+dependents (This Quantity) = HashSet.singleton (This Numeral)
+dependents (This RegexMatch) = HashSet.empty
+dependents (This Temperature) = HashSet.singleton (This Numeral)
+dependents (This Time) =
+  HashSet.fromList [This Numeral, This Duration, This Ordinal, This TimeGrain]
+dependents (This TimeGrain) = HashSet.empty
+dependents (This Url) = HashSet.empty
+dependents (This Volume) = HashSet.singleton (This Numeral)
+
+langDimensions :: Lang -> [Some Dimension]
+langDimensions AR = ARDimensions.allDimensions
+langDimensions DA = DADimensions.allDimensions
+langDimensions DE = DEDimensions.allDimensions
+langDimensions EN = ENDimensions.allDimensions
+langDimensions ES = ESDimensions.allDimensions
+langDimensions ET = ETDimensions.allDimensions
+langDimensions FR = FRDimensions.allDimensions
+langDimensions GA = GADimensions.allDimensions
+langDimensions HE = HEDimensions.allDimensions
+langDimensions HR = HRDimensions.allDimensions
+langDimensions ID = IDDimensions.allDimensions
+langDimensions IT = ITDimensions.allDimensions
+langDimensions JA = JADimensions.allDimensions
+langDimensions KO = KODimensions.allDimensions
+langDimensions MY = MYDimensions.allDimensions
+langDimensions NB = NBDimensions.allDimensions
+langDimensions NL = NLDimensions.allDimensions
+langDimensions PL = PLDimensions.allDimensions
+langDimensions PT = PTDimensions.allDimensions
+langDimensions RO = RODimensions.allDimensions
+langDimensions RU = RUDimensions.allDimensions
+langDimensions SV = SVDimensions.allDimensions
+langDimensions TR = TRDimensions.allDimensions
+langDimensions UK = UKDimensions.allDimensions
+langDimensions VI = VIDimensions.allDimensions
+langDimensions ZH = ZHDimensions.allDimensions
diff --git a/Duckling/Dimensions/AR.hs b/Duckling/Dimensions/AR.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/AR.hs
@@ -0,0 +1,19 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.AR
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Numeral
+  , This Ordinal
+  ]
diff --git a/Duckling/Dimensions/Common.hs b/Duckling/Dimensions/Common.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/Common.hs
@@ -0,0 +1,21 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.Common
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Email
+  , This AmountOfMoney
+  , This PhoneNumber
+  , This Url
+  ]
diff --git a/Duckling/Dimensions/DA.hs b/Duckling/Dimensions/DA.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/DA.hs
@@ -0,0 +1,21 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.DA
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Duration
+  , This Numeral
+  , This Ordinal
+  , This Time
+  ]
diff --git a/Duckling/Dimensions/DE.hs b/Duckling/Dimensions/DE.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/DE.hs
@@ -0,0 +1,21 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.DE
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Duration
+  , This Numeral
+  , This Ordinal
+  , This Time
+  ]
diff --git a/Duckling/Dimensions/EN.hs b/Duckling/Dimensions/EN.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/EN.hs
@@ -0,0 +1,25 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.EN
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Distance
+  , This Duration
+  , This Numeral
+  , This Ordinal
+  , This Quantity
+  , This Temperature
+  , This Time
+  , This Volume
+  ]
diff --git a/Duckling/Dimensions/ES.hs b/Duckling/Dimensions/ES.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/ES.hs
@@ -0,0 +1,24 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.ES
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Distance
+  , This Duration
+  , This Numeral
+  , This Ordinal
+  , This Temperature
+  , This Time
+  , This Volume
+  ]
diff --git a/Duckling/Dimensions/ET.hs b/Duckling/Dimensions/ET.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/ET.hs
@@ -0,0 +1,19 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.ET
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Numeral
+  , This Ordinal
+  ]
diff --git a/Duckling/Dimensions/FR.hs b/Duckling/Dimensions/FR.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/FR.hs
@@ -0,0 +1,25 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.FR
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Distance
+  , This Duration
+  , This Numeral
+  , This Ordinal
+  , This Quantity
+  , This Temperature
+  , This Time
+  , This Volume
+  ]
diff --git a/Duckling/Dimensions/GA.hs b/Duckling/Dimensions/GA.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/GA.hs
@@ -0,0 +1,24 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.GA
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Distance
+  , This Duration
+  , This Numeral
+  , This Ordinal
+  , This Temperature
+  , This Time
+  , This Volume
+  ]
diff --git a/Duckling/Dimensions/HE.hs b/Duckling/Dimensions/HE.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/HE.hs
@@ -0,0 +1,21 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.HE
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Duration
+  , This Numeral
+  , This Ordinal
+  , This Time
+  ]
diff --git a/Duckling/Dimensions/HR.hs b/Duckling/Dimensions/HR.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/HR.hs
@@ -0,0 +1,25 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.HR
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Distance
+  , This Duration
+  , This Numeral
+  , This Ordinal
+  , This Quantity
+  , This Temperature
+  , This Time
+  , This Volume
+  ]
diff --git a/Duckling/Dimensions/ID.hs b/Duckling/Dimensions/ID.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/ID.hs
@@ -0,0 +1,19 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.ID
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Numeral
+  , This Ordinal
+  ]
diff --git a/Duckling/Dimensions/IT.hs b/Duckling/Dimensions/IT.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/IT.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.IT
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Duration
+  , This Numeral
+  , This Ordinal
+  , This Temperature
+  , This Time
+  , This Volume
+  ]
diff --git a/Duckling/Dimensions/JA.hs b/Duckling/Dimensions/JA.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/JA.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.JA
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Duration
+  , This Numeral
+  , This Ordinal
+  , This Temperature
+  , This Time
+  ]
diff --git a/Duckling/Dimensions/KO.hs b/Duckling/Dimensions/KO.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/KO.hs
@@ -0,0 +1,25 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.KO
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Distance
+  , This Duration
+  , This Numeral
+  , This Ordinal
+  , This Quantity
+  , This Temperature
+  , This Time
+  , This Volume
+  ]
diff --git a/Duckling/Dimensions/MY.hs b/Duckling/Dimensions/MY.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/MY.hs
@@ -0,0 +1,18 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.MY
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Numeral
+  ]
diff --git a/Duckling/Dimensions/NB.hs b/Duckling/Dimensions/NB.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/NB.hs
@@ -0,0 +1,21 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.NB
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Duration
+  , This Numeral
+  , This Ordinal
+  , This Time
+  ]
diff --git a/Duckling/Dimensions/NL.hs b/Duckling/Dimensions/NL.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/NL.hs
@@ -0,0 +1,21 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.NL
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Distance
+  , This Numeral
+  , This Ordinal
+  , This Volume
+  ]
diff --git a/Duckling/Dimensions/PL.hs b/Duckling/Dimensions/PL.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/PL.hs
@@ -0,0 +1,21 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.PL
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Duration
+  , This Numeral
+  , This Ordinal
+  , This Time
+  ]
diff --git a/Duckling/Dimensions/PT.hs b/Duckling/Dimensions/PT.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/PT.hs
@@ -0,0 +1,25 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.PT
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Distance
+  , This Duration
+  , This Numeral
+  , This Ordinal
+  , This Quantity
+  , This Temperature
+  , This Time
+  , This Volume
+  ]
diff --git a/Duckling/Dimensions/RO.hs b/Duckling/Dimensions/RO.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/RO.hs
@@ -0,0 +1,25 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.RO
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Distance
+  , This Duration
+  , This Numeral
+  , This Ordinal
+  , This Quantity
+  , This Temperature
+  , This Time
+  , This Volume
+  ]
diff --git a/Duckling/Dimensions/RU.hs b/Duckling/Dimensions/RU.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/RU.hs
@@ -0,0 +1,19 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.RU
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Numeral
+  , This Ordinal
+  ]
diff --git a/Duckling/Dimensions/SV.hs b/Duckling/Dimensions/SV.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/SV.hs
@@ -0,0 +1,21 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.SV
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Duration
+  , This Numeral
+  , This Ordinal
+  , This Time
+  ]
diff --git a/Duckling/Dimensions/TR.hs b/Duckling/Dimensions/TR.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/TR.hs
@@ -0,0 +1,19 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.TR
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Numeral
+  , This Ordinal
+  ]
diff --git a/Duckling/Dimensions/Types.hs b/Duckling/Dimensions/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/Types.hs
@@ -0,0 +1,181 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE ExistentialQuantification #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE TypeOperators #-}
+
+
+module Duckling.Dimensions.Types
+  ( Some(..)
+  , Dimension(..)
+
+  , fromName
+  , toName
+  ) where
+
+import Data.GADT.Compare
+import Data.GADT.Show
+import Data.Hashable
+import qualified Data.HashMap.Strict as HashMap
+import Data.Maybe
+import Data.Some
+import Data.Text (Text)
+-- Intentionally limit use of Typeable to avoid casting or typeOf usage
+import Data.Typeable ((:~:)(..))
+import TextShow (TextShow(..))
+import qualified TextShow as TS
+import Prelude
+
+import Duckling.AmountOfMoney.Types (AmountOfMoneyData)
+import Duckling.Distance.Types (DistanceData)
+import Duckling.Duration.Types (DurationData)
+import Duckling.Email.Types (EmailData)
+import Duckling.Numeral.Types (NumeralData)
+import Duckling.Ordinal.Types (OrdinalData)
+import Duckling.PhoneNumber.Types (PhoneNumberData)
+import Duckling.Quantity.Types (QuantityData)
+import Duckling.Regex.Types (GroupMatch)
+import Duckling.Temperature.Types (TemperatureData)
+import Duckling.Time.Types (TimeData)
+import Duckling.TimeGrain.Types (Grain)
+import Duckling.Url.Types (UrlData)
+import Duckling.Volume.Types (VolumeData)
+
+-- -----------------------------------------------------------------
+-- Dimension
+
+-- | GADT for differentiating between dimensions
+-- Each dimension should have its own constructor and provide the data structure
+-- for its parsed data
+data Dimension a where
+  RegexMatch :: Dimension GroupMatch
+  AmountOfMoney :: Dimension AmountOfMoneyData
+  Distance :: Dimension DistanceData
+  Duration :: Dimension DurationData
+  Email :: Dimension EmailData
+  Numeral :: Dimension NumeralData
+  Ordinal :: Dimension OrdinalData
+  PhoneNumber :: Dimension PhoneNumberData
+  Quantity :: Dimension QuantityData
+  Temperature :: Dimension TemperatureData
+  Time :: Dimension TimeData
+  TimeGrain :: Dimension Grain
+  Url :: Dimension UrlData
+  Volume :: Dimension VolumeData
+
+-- Show
+instance Show (Dimension a) where
+  show RegexMatch = "RegexMatch"
+  show Distance = "Distance"
+  show Duration = "Duration"
+  show Email = "Email"
+  show AmountOfMoney = "AmountOfMoney"
+  show Numeral = "Numeral"
+  show Ordinal = "Ordinal"
+  show PhoneNumber = "PhoneNumber"
+  show Quantity = "Quantity"
+  show Temperature = "Temperature"
+  show Time = "Time"
+  show TimeGrain = "TimeGrain"
+  show Url = "Url"
+  show Volume = "Volume"
+instance GShow Dimension where gshowsPrec = showsPrec
+
+-- TextShow
+instance TextShow (Dimension a) where
+  showb d = TS.fromString $ show d
+instance TextShow (Some Dimension) where
+  showb (This d) = showb d
+
+-- Hashable
+instance Hashable (Some Dimension) where
+  hashWithSalt s (This a) = hashWithSalt s a
+instance Hashable (Dimension a) where
+  hashWithSalt s RegexMatch  = hashWithSalt s (0::Int)
+  hashWithSalt s Distance    = hashWithSalt s (1::Int)
+  hashWithSalt s Duration    = hashWithSalt s (2::Int)
+  hashWithSalt s Email       = hashWithSalt s (3::Int)
+  hashWithSalt s AmountOfMoney     = hashWithSalt s (4::Int)
+  hashWithSalt s Numeral     = hashWithSalt s (5::Int)
+  hashWithSalt s Ordinal     = hashWithSalt s (6::Int)
+  hashWithSalt s PhoneNumber = hashWithSalt s (7::Int)
+  hashWithSalt s Quantity    = hashWithSalt s (8::Int)
+  hashWithSalt s Temperature = hashWithSalt s (9::Int)
+  hashWithSalt s Time        = hashWithSalt s (10::Int)
+  hashWithSalt s TimeGrain   = hashWithSalt s (11::Int)
+  hashWithSalt s Url         = hashWithSalt s (12::Int)
+  hashWithSalt s Volume      = hashWithSalt s (13::Int)
+
+
+toName :: Dimension a -> Text
+toName RegexMatch = "regex"
+toName Distance = "distance"
+toName Duration = "duration"
+toName Email = "email"
+toName AmountOfMoney = "amount-of-money"
+toName Numeral = "number"
+toName Ordinal = "ordinal"
+toName PhoneNumber = "phone-number"
+toName Quantity = "quantity"
+toName Temperature = "temperature"
+toName Time = "time"
+toName TimeGrain = "time-grain"
+toName Url = "url"
+toName Volume = "volume"
+
+fromName :: Text -> Maybe (Some Dimension)
+fromName name = HashMap.lookup name m
+  where
+    m = HashMap.fromList
+      [ ("amount-of-money", This AmountOfMoney)
+      , ("distance", This Distance)
+      , ("duration", This Duration)
+      , ("email", This Email)
+      , ("number", This Numeral)
+      , ("ordinal", This Ordinal)
+      , ("phone-number", This PhoneNumber)
+      , ("quantity", This Quantity)
+      , ("temperature", This Temperature)
+      , ("time", This Time)
+      , ("url", This Url)
+      , ("volume", This Volume)
+      ]
+
+instance GEq Dimension where
+  geq RegexMatch RegexMatch = Just Refl
+  geq RegexMatch _ = Nothing
+  geq Distance Distance = Just Refl
+  geq Distance _ = Nothing
+  geq Duration Duration = Just Refl
+  geq Duration _ = Nothing
+  geq Email Email = Just Refl
+  geq Email _ = Nothing
+  geq AmountOfMoney AmountOfMoney = Just Refl
+  geq AmountOfMoney _ = Nothing
+  geq Numeral Numeral = Just Refl
+  geq Numeral _ = Nothing
+  geq Ordinal Ordinal = Just Refl
+  geq Ordinal _ = Nothing
+  geq PhoneNumber PhoneNumber = Just Refl
+  geq PhoneNumber _ = Nothing
+  geq Quantity Quantity = Just Refl
+  geq Quantity _ = Nothing
+  geq Temperature Temperature = Just Refl
+  geq Temperature _ = Nothing
+  geq Time Time = Just Refl
+  geq Time _ = Nothing
+  geq TimeGrain TimeGrain = Just Refl
+  geq TimeGrain _ = Nothing
+  geq Url Url = Just Refl
+  geq Url _ = Nothing
+  geq Volume Volume = Just Refl
+  geq Volume _ = Nothing
diff --git a/Duckling/Dimensions/UK.hs b/Duckling/Dimensions/UK.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/UK.hs
@@ -0,0 +1,19 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.UK
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Numeral
+  , This Ordinal
+  ]
diff --git a/Duckling/Dimensions/VI.hs b/Duckling/Dimensions/VI.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/VI.hs
@@ -0,0 +1,21 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.VI
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Duration
+  , This Numeral
+  , This Ordinal
+  , This Time
+  ]
diff --git a/Duckling/Dimensions/ZH.hs b/Duckling/Dimensions/ZH.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Dimensions/ZH.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.ZH
+  ( allDimensions
+  ) where
+
+import Duckling.Dimensions.Types
+
+allDimensions :: [Some Dimension]
+allDimensions =
+  [ This Duration
+  , This Numeral
+  , This Ordinal
+  , This Temperature
+  , This Time
+  ]
diff --git a/Duckling/Distance/EN/Corpus.hs b/Duckling/Distance/EN/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/EN/Corpus.hs
@@ -0,0 +1,43 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.EN.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Distance.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DistanceValue Kilometre 3)
+             [ "3 kilometers"
+             , "3 km"
+             , "3km"
+             , "3k"
+             , "3.0 km"
+             ]
+  , examples (DistanceValue Mile 8)
+             [ "8 miles"
+             , "eight mile"
+             ]
+  , examples (DistanceValue M 9)
+             [ "9m"
+             ]
+  , examples (DistanceValue Centimetre 2)
+             [ "2cm"
+             , "2 centimeters"
+             ]
+  ]
diff --git a/Duckling/Distance/EN/Rules.hs b/Duckling/Distance/EN/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/EN/Rules.hs
@@ -0,0 +1,86 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.EN.Rules
+  ( rules ) where
+
+
+import Data.String
+import Data.Text (Text)
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Distance.Helpers
+import Duckling.Distance.Types (DistanceData(..))
+import qualified Duckling.Distance.Types as TDistance
+import Duckling.Types
+
+ruleDistanceFeetInch :: Rule
+ruleDistanceFeetInch = Rule
+  { name = "<distance|feet> <distance|inch>"
+  , pattern =
+    [ unitDistance TDistance.Foot
+    , unitDistance TDistance.Inch
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance DistanceData {TDistance.value = feet}:
+       Token Distance DistanceData {TDistance.value = inches}:
+       _) -> Just . Token Distance . withUnit TDistance.Inch . distance $
+        feet * 12 + inches
+      _ -> Nothing
+  }
+
+ruleDistanceFeetAndInch :: Rule
+ruleDistanceFeetAndInch = Rule
+  { name = "<distance|feet> and <distance|inch>"
+  , pattern =
+    [ unitDistance TDistance.Foot
+    , regex "and"
+    , unitDistance TDistance.Inch
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance DistanceData {TDistance.value = feet}:
+       _:
+       Token Distance DistanceData {TDistance.value = inches}:
+       _) -> Just . Token Distance . withUnit TDistance.Inch . distance $
+        feet * 12 + inches
+      _ -> Nothing
+  }
+
+distances :: [(Text, String, TDistance.Unit)]
+distances = [ ("<latent dist> km", "k(ilo)?m?(eter)?s?", TDistance.Kilometre)
+            , ("<latent dist> feet", "('|f(oo|ee)?ts?)", TDistance.Foot)
+            , ("<latent dist> inch", "(''|inch(es)?)", TDistance.Inch)
+            , ("<latent dist> yard", "y(ar)?ds?", TDistance.Yard)
+            , ("<dist> meters", "meters?", TDistance.Metre)
+            , ("<dist> centimeters", "cm|centimeters?", TDistance.Centimetre)
+            , ("<dist> miles", "miles?", TDistance.Mile)
+            , ("<dist> m (ambiguous miles or meters)", "m", TDistance.M)
+            ]
+
+ruleDistances :: [Rule]
+ruleDistances = map go distances
+  where
+    go :: (Text, String, TDistance.Unit) -> Rule
+    go (name, regexPattern, u) = Rule
+      { name = name
+      , pattern = [ dimension Distance, regex regexPattern ]
+      , prod = \tokens -> case tokens of
+          (Token Distance dd:_) -> Just . Token Distance $ withUnit u dd
+          _ -> Nothing
+      }
+
+rules :: [Rule]
+rules =
+  [ ruleDistanceFeetInch
+  , ruleDistanceFeetAndInch
+  ]
+  ++ ruleDistances
diff --git a/Duckling/Distance/ES/Corpus.hs b/Duckling/Distance/ES/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/ES/Corpus.hs
@@ -0,0 +1,48 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.ES.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Distance.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = ES}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DistanceValue Kilometre 3)
+             [ "3 kilómetros"
+             , "3 kilometros"
+             , "3 km"
+             , "3km"
+             , "3k"
+             ]
+  , examples (DistanceValue Kilometre 3.0)
+             [ "3,0 km"
+             ]
+  , examples (DistanceValue Mile 8)
+             [ "8 miles"
+             ]
+  , examples (DistanceValue Metre 9)
+             [ "9m"
+             , "9 metros"
+             ]
+  , examples (DistanceValue Centimetre 2)
+             [ "2cm"
+             , "2 centímetros"
+             ]
+  ]
diff --git a/Duckling/Distance/ES/Rules.hs b/Duckling/Distance/ES/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/ES/Rules.hs
@@ -0,0 +1,81 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.ES.Rules
+  ( rules ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Distance.Helpers
+import qualified Duckling.Distance.Types as TDistance
+import Duckling.Types
+
+ruleLatentDistKm :: Rule
+ruleLatentDistKm = Rule
+  { name = "<latent dist> km"
+  , pattern =
+    [ dimension Distance
+    , regex "k(il(\x00f3|o))?m?(etro)?s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Kilometre dd
+      _ -> Nothing
+  }
+
+ruleDistMeters :: Rule
+ruleDistMeters = Rule
+  { name = "<dist> meters"
+  , pattern =
+    [ dimension Distance
+    , regex "m(etros?)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Metre dd
+      _ -> Nothing
+  }
+
+ruleDistCentimeters :: Rule
+ruleDistCentimeters = Rule
+  { name = "<dist> centimeters"
+  , pattern =
+    [ dimension Distance
+    , regex "(cm|cent(\x00ed|i)m(etros?))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Centimetre dd
+      _ -> Nothing
+  }
+
+ruleDistMiles :: Rule
+ruleDistMiles = Rule
+  { name = "<dist> miles"
+  , pattern =
+    [ dimension Distance
+    , regex "miles?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Mile dd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDistCentimeters
+  , ruleDistMeters
+  , ruleDistMiles
+  , ruleLatentDistKm
+  ]
diff --git a/Duckling/Distance/FR/Corpus.hs b/Duckling/Distance/FR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/FR/Corpus.hs
@@ -0,0 +1,48 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.FR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Distance.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = FR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DistanceValue Kilometre 3)
+             [ "3 kilomètres"
+             , "3 kilometres"
+             , "3 km"
+             , "3km"
+             , "3k"
+             ]
+  , examples (DistanceValue Kilometre 3.0)
+             [ "3,0 km"
+             ]
+  , examples (DistanceValue Mile 8)
+             [ "8 miles"
+             ]
+  , examples (DistanceValue Metre 9)
+             [ "9 metres"
+             , "9m"
+             ]
+  , examples (DistanceValue Centimetre 2)
+             [ "2cm"
+             , "2 centimetres"
+             ]
+  ]
diff --git a/Duckling/Distance/FR/Rules.hs b/Duckling/Distance/FR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/FR/Rules.hs
@@ -0,0 +1,81 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.FR.Rules
+  ( rules ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Distance.Helpers
+import qualified Duckling.Distance.Types as TDistance
+import Duckling.Types
+
+ruleLatentDistKm :: Rule
+ruleLatentDistKm = Rule
+  { name = "<latent dist> km"
+  , pattern =
+    [ dimension Distance
+    , regex "k(ilo)?m?((e|\x00e9|\x00e8)tre)?s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Kilometre dd
+      _ -> Nothing
+  }
+
+ruleDistMeters :: Rule
+ruleDistMeters = Rule
+  { name = "<dist> meters"
+  , pattern =
+    [ dimension Distance
+    , regex "m((e|\x00e9|\x00e8)tres?)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Metre dd
+      _ -> Nothing
+  }
+
+ruleDistCentimeters :: Rule
+ruleDistCentimeters = Rule
+  { name = "<dist> centimeters"
+  , pattern =
+    [ dimension Distance
+    , regex "cm|centim(e|\x00e9|\x00e8)tres?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Centimetre dd
+      _ -> Nothing
+  }
+
+ruleDistMiles :: Rule
+ruleDistMiles = Rule
+  { name = "<dist> miles"
+  , pattern =
+    [ dimension Distance
+    , regex "miles?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Mile dd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDistCentimeters
+  , ruleDistMeters
+  , ruleDistMiles
+  , ruleLatentDistKm
+  ]
diff --git a/Duckling/Distance/GA/Corpus.hs b/Duckling/Distance/GA/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/GA/Corpus.hs
@@ -0,0 +1,47 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.GA.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Distance.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = GA}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DistanceValue Kilometre 3)
+             [ "3 ciliméadair"
+             , "3 km"
+             , "3km"
+             , "3k"
+             ]
+  , examples (DistanceValue Kilometre 3.0)
+             [ "3.0 km"
+             ]
+  , examples (DistanceValue Mile 8)
+             [ "8 mhíle"
+             , "8 míle"
+             ]
+  , examples (DistanceValue M 9)
+             [ "9m"
+             ]
+  , examples (DistanceValue Centimetre 2)
+             [ "2cm"
+             , "2 cheintiméadar"
+             ]
+  ]
diff --git a/Duckling/Distance/GA/Rules.hs b/Duckling/Distance/GA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/GA/Rules.hs
@@ -0,0 +1,123 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.GA.Rules
+  ( rules ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Distance.Helpers
+import qualified Duckling.Distance.Types as TDistance
+import Duckling.Types
+
+ruleDistMeters :: Rule
+ruleDistMeters = Rule
+  { name = "<dist> meters"
+  , pattern =
+    [ dimension Distance
+    , regex "mh?(e|\x00e9)adai?r"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Metre dd
+      _ -> Nothing
+  }
+
+ruleDistCentimeters :: Rule
+ruleDistCentimeters = Rule
+  { name = "<dist> centimeters"
+  , pattern =
+    [ dimension Distance
+    , regex "(c\\.?m\\.?|g?ch?eintimh?(e|\x00e9)adai?r)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Centimetre dd
+      _ -> Nothing
+  }
+
+ruleDistMiles :: Rule
+ruleDistMiles = Rule
+  { name = "<dist> miles"
+  , pattern =
+    [ dimension Distance
+    , regex "mh?(\x00ed|i)lt?e"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Mile dd
+      _ -> Nothing
+  }
+
+ruleLatentDistKm :: Rule
+ruleLatentDistKm = Rule
+  { name = "<latent dist> km"
+  , pattern =
+    [ dimension Distance
+    , regex "(k\\.?(m\\.?)?|g?ch?ilim(e|\x00e9)adai?r)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Kilometre dd
+      _ -> Nothing
+  }
+
+ruleLatentDistTroigh :: Rule
+ruleLatentDistTroigh = Rule
+  { name = "<latent dist> troigh"
+  , pattern =
+    [ dimension Distance
+    , regex "('|d?th?roi[tg]he?|tr\\.?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Foot dd
+      _ -> Nothing
+  }
+
+ruleLatentDistOrlach :: Rule
+ruleLatentDistOrlach = Rule
+  { name = "<latent dist> orlach"
+  , pattern =
+    [ dimension Distance
+    , regex "(''|([nth]-?)?orl(ach|aigh|a(\x00ed|i)|\\.))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Inch dd
+      _ -> Nothing
+  }
+
+ruleDistMAmbiguousMilesOrMeters :: Rule
+ruleDistMAmbiguousMilesOrMeters = Rule
+  { name = "<dist> m (ambiguous miles or meters)"
+  , pattern =
+    [ dimension Distance
+    , regex "m"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.M dd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDistCentimeters
+  , ruleDistMAmbiguousMilesOrMeters
+  , ruleDistMeters
+  , ruleDistMiles
+  , ruleLatentDistKm
+  , ruleLatentDistOrlach
+  , ruleLatentDistTroigh
+  ]
diff --git a/Duckling/Distance/HR/Corpus.hs b/Duckling/Distance/HR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/HR/Corpus.hs
@@ -0,0 +1,45 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.HR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Distance.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = HR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DistanceValue Kilometre 3)
+             [ "3 kilometra"
+             , "3 km"
+             , "3km"
+             , "3k"
+             ]
+  , examples (DistanceValue Kilometre 3.0)
+             [ "3,0 km"
+             ]
+  , examples (DistanceValue Mile 8)
+             [ "8 milja"
+             ]
+  , examples (DistanceValue M 9)
+             [ "9m"
+             ]
+  , examples (DistanceValue Centimetre 2)
+             [ "2cm"
+             , "2 centimetra"
+             ]
+  ]
diff --git a/Duckling/Distance/HR/Rules.hs b/Duckling/Distance/HR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/HR/Rules.hs
@@ -0,0 +1,92 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.HR.Rules
+  ( rules ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Distance.Helpers
+import Duckling.Types
+import qualified Duckling.Distance.Types as TDistance
+
+ruleLatentDistKm :: Rule
+ruleLatentDistKm = Rule
+  { name = "<latent dist> km"
+  , pattern =
+    [ dimension Distance
+    , regex "k(ilo)?m?(eta?r)?a?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Kilometre dd
+      _ -> Nothing
+  }
+
+ruleDistMetar :: Rule
+ruleDistMetar = Rule
+  { name = "<dist> metar"
+  , pattern =
+    [ dimension Distance
+    , regex "metara?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Metre dd
+      _ -> Nothing
+  }
+
+ruleDistCentimetar :: Rule
+ruleDistCentimetar = Rule
+  { name = "<dist> centimetar"
+  , pattern =
+    [ dimension Distance
+    , regex "cm|centimeta?ra?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Centimetre dd
+      _ -> Nothing
+  }
+
+ruleDistMilja :: Rule
+ruleDistMilja = Rule
+  { name = "<dist> milja"
+  , pattern =
+    [ dimension Distance
+    , regex "milja"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) -> Just . Token Distance $ withUnit TDistance.Mile dd
+      _ -> Nothing
+  }
+
+ruleDistMAmbiguousMilesOrMeters :: Rule
+ruleDistMAmbiguousMilesOrMeters = Rule
+  { name = "<dist> m (ambiguous miles or meters)"
+  , pattern =
+    [ dimension Distance
+    , regex "m"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) -> Just . Token Distance $ withUnit TDistance.M dd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDistCentimetar
+  , ruleDistMAmbiguousMilesOrMeters
+  , ruleDistMetar
+  , ruleDistMilja
+  , ruleLatentDistKm
+  ]
diff --git a/Duckling/Distance/Helpers.hs b/Duckling/Distance/Helpers.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/Helpers.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.Helpers
+  ( distance
+  , unitDistance
+  , withUnit
+  ) where
+
+
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Distance.Types (DistanceData(..))
+import qualified Duckling.Distance.Types as TDistance
+import Duckling.Types
+
+-- -----------------------------------------------------------------
+-- Patterns
+
+unitDistance :: TDistance.Unit -> PatternItem
+unitDistance value = Predicate $ \x -> case x of
+  (Token Distance DistanceData {TDistance.unit = Just unit}) -> value == unit
+  _ -> False
+
+-- -----------------------------------------------------------------
+-- Production
+
+distance :: Double -> DistanceData
+distance x = DistanceData {TDistance.value = x, TDistance.unit = Nothing}
+
+withUnit :: TDistance.Unit -> DistanceData -> DistanceData
+withUnit value dd = dd {TDistance.unit = Just value}
diff --git a/Duckling/Distance/KO/Corpus.hs b/Duckling/Distance/KO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/KO/Corpus.hs
@@ -0,0 +1,56 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.KO.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Distance.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = KO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DistanceValue Kilometre 3)
+             [ "3 킬로미터"
+             , "3 킬로"
+             , "3 키로"
+             , "3 km"
+             , "3km"
+             ]
+  , examples (DistanceValue Kilometre 3.0)
+             [ "3.0 km"
+             ]
+  , examples (DistanceValue Mile 8)
+             [ "8 miles"
+             , "8 마일"
+             , "8 마일즈"
+             ]
+  , examples (DistanceValue Metre 9)
+             [ "9m"
+             , "9미터"
+             , "9메터"
+             , "구메터"
+             ]
+  , examples (DistanceValue Centimetre 2)
+             [ "2cm"
+             , "2 센치"
+             , "이센치"
+             , "2 센티"
+             , "2 센티미터"
+             , "2 센치미터"
+             ]
+  ]
diff --git a/Duckling/Distance/KO/Rules.hs b/Duckling/Distance/KO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/KO/Rules.hs
@@ -0,0 +1,154 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.KO.Rules
+  ( rules ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Distance.Helpers
+import qualified Duckling.Distance.Types as TDistance
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Types
+
+ruleLatentDistYard :: Rule
+ruleLatentDistYard = Rule
+  { name = "<latent dist> yard"
+  , pattern =
+    [ dimension Distance
+    , regex "y(ar)?ds?|\xc57c\xb4dc"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Yard dd
+      _ -> Nothing
+  }
+
+ruleDistCentimeters :: Rule
+ruleDistCentimeters = Rule
+  { name = "<dist> centimeters"
+  , pattern =
+    [ dimension Distance
+    , regex "cm|\xc13c(\xd2f0|\xce58)((\xbbf8|\xba54)\xd130)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Centimetre dd
+      _ -> Nothing
+  }
+
+ruleLatentDistFeetAndLatentDistInch :: Rule
+ruleLatentDistFeetAndLatentDistInch = Rule
+  { name = "<latent dist> feet and <latent dist> inch "
+  , pattern =
+    [ dimension Distance
+    , regex "('|f(oo|ee)?ts?)|\xd53c\xd2b8"
+    , dimension Distance
+    , regex "(''|inch(es)?)|\xc778\xce58"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Foot dd
+      _ -> Nothing
+  }
+
+ruleDistMeters :: Rule
+ruleDistMeters = Rule
+  { name = "<dist> meters"
+  , pattern =
+    [ dimension Distance
+    , regex "m|(\xbbf8|\xba54|\xb9e4)\xd130"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Metre dd
+      _ -> Nothing
+  }
+
+ruleLatentDistFeet :: Rule
+ruleLatentDistFeet = Rule
+  { name = "<latent dist> feet"
+  , pattern =
+    [ dimension Distance
+    , regex "('|f(oo|ee)?ts?)|\xd53c\xd2b8"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Foot dd
+      _ -> Nothing
+  }
+
+ruleLatentDistKm :: Rule
+ruleLatentDistKm = Rule
+  { name = "<latent dist> km"
+  , pattern =
+    [ dimension Distance
+    , regex "km|(\xd0ac|\xd0a4)\xb85c((\xbbf8|\xba54)\xd130)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Kilometre dd
+      _ -> Nothing
+  }
+
+ruleHalf :: Rule
+ruleHalf = Rule
+  { name = "half"
+  , pattern =
+    [ regex "\xbc18"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral NumeralData {TNumeral.value = v}:_) ->
+        Just . Token Distance $ distance v
+      _ -> Nothing
+  }
+
+ruleDistMiles :: Rule
+ruleDistMiles = Rule
+  { name = "<dist> miles"
+  , pattern =
+    [ dimension Distance
+    , regex "miles?|\xb9c8\xc77c(\xc988)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Mile dd
+      _ -> Nothing
+  }
+
+ruleLatentDistInch :: Rule
+ruleLatentDistInch = Rule
+  { name = "<latent dist> inch"
+  , pattern =
+    [ dimension Distance
+    , regex "(''|inch(es)?)|\xc778\xce58"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Inch dd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDistCentimeters
+  , ruleDistMeters
+  , ruleDistMiles
+  , ruleHalf
+  , ruleLatentDistFeet
+  , ruleLatentDistFeetAndLatentDistInch
+  , ruleLatentDistInch
+  , ruleLatentDistKm
+  , ruleLatentDistYard
+  ]
diff --git a/Duckling/Distance/NL/Corpus.hs b/Duckling/Distance/NL/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/NL/Corpus.hs
@@ -0,0 +1,50 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.NL.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Distance.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = NL}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DistanceValue Kilometre 3)
+             [ "3 kilometer"
+             , "3 km"
+             , "3km"
+             , "3k"
+             ]
+  , examples (DistanceValue Kilometre 3.0)
+             [ "3,0 km"
+             , "3,0km"
+             ]
+  , examples (DistanceValue Mile 8)
+             [ "8 mijl"
+             ]
+  , examples (DistanceValue Metre 9)
+             [ "9m"
+             , "9 m"
+             , "9 meter"
+             ]
+  , examples (DistanceValue Centimetre 2)
+             [ "2cm"
+             , "2 cm"
+             , "2 centimeter"
+             ]
+  ]
diff --git a/Duckling/Distance/NL/Rules.hs b/Duckling/Distance/NL/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/NL/Rules.hs
@@ -0,0 +1,81 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.NL.Rules
+  ( rules ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Distance.Helpers
+import qualified Duckling.Distance.Types as TDistance
+import Duckling.Types
+
+ruleLatentDistKm :: Rule
+ruleLatentDistKm = Rule
+  { name = "<latent dist> km"
+  , pattern =
+    [ dimension Distance
+    , regex "k(ilo)?m?(eter)?s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Kilometre dd
+      _ -> Nothing
+  }
+
+ruleDistMeters :: Rule
+ruleDistMeters = Rule
+  { name = "<dist> meters"
+  , pattern =
+    [ dimension Distance
+    , regex "m(eter)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Metre dd
+      _ -> Nothing
+  }
+
+ruleDistCentimeters :: Rule
+ruleDistCentimeters = Rule
+  { name = "<dist> centimeters"
+  , pattern =
+    [ dimension Distance
+    , regex "(c(enti)?m(eter)?s?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Centimetre dd
+      _ -> Nothing
+  }
+
+ruleDistMiles :: Rule
+ruleDistMiles = Rule
+  { name = "<dist> miles"
+  , pattern =
+    [ dimension Distance
+    , regex "mijl?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Mile dd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDistCentimeters
+  , ruleDistMeters
+  , ruleDistMiles
+  , ruleLatentDistKm
+  ]
diff --git a/Duckling/Distance/PT/Corpus.hs b/Duckling/Distance/PT/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/PT/Corpus.hs
@@ -0,0 +1,48 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.PT.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Distance.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = PT}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DistanceValue Kilometre 3)
+             [ "3 kilómetros"
+             , "3 kilometros"
+             , "3 km"
+             , "3km"
+             , "3k"
+             ]
+  , examples (DistanceValue Kilometre 3.0)
+             [ "3,0 km"
+             ]
+  , examples (DistanceValue Mile 8)
+             [ "8 milhas"
+             ]
+  , examples (DistanceValue Metre 9)
+             [ "9m"
+             , "9 metros"
+             ]
+  , examples (DistanceValue Centimetre 2)
+             [ "2cm"
+             , "2 centímetros"
+             ]
+  ]
diff --git a/Duckling/Distance/PT/Rules.hs b/Duckling/Distance/PT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/PT/Rules.hs
@@ -0,0 +1,81 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.PT.Rules
+  ( rules ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Distance.Helpers
+import qualified Duckling.Distance.Types as TDistance
+import Duckling.Types
+
+ruleLatentDistKm :: Rule
+ruleLatentDistKm = Rule
+  { name = "<latent dist> km"
+  , pattern =
+    [ dimension Distance
+    , regex "k(il(\x00f3|o))?m?(etro)?s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Kilometre dd
+      _ -> Nothing
+  }
+
+ruleDistMeters :: Rule
+ruleDistMeters = Rule
+  { name = "<dist> meters"
+  , pattern =
+    [ dimension Distance
+    , regex "m(etros?)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Metre dd
+      _ -> Nothing
+  }
+
+ruleDistCentimeters :: Rule
+ruleDistCentimeters = Rule
+  { name = "<dist> centimeters"
+  , pattern =
+    [ dimension Distance
+    , regex "(cm|cent(\x00ed|i)m(etros?))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Centimetre dd
+      _ -> Nothing
+  }
+
+ruleDistMiles :: Rule
+ruleDistMiles = Rule
+  { name = "<dist> miles"
+  , pattern =
+    [ dimension Distance
+    , regex "milhas?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Mile dd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDistCentimeters
+  , ruleDistMeters
+  , ruleDistMiles
+  , ruleLatentDistKm
+  ]
diff --git a/Duckling/Distance/RO/Corpus.hs b/Duckling/Distance/RO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/RO/Corpus.hs
@@ -0,0 +1,47 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.RO.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Distance.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = RO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DistanceValue Kilometre 3)
+             [ "3 kilometri"
+             , "3 km"
+             , "3km"
+             , "3,0 km"
+             ]
+  , examples (DistanceValue Mile 8)
+             [ "8 mile"
+             ]
+  , examples (DistanceValue Metre 9)
+             [ "9m"
+             , "9 m"
+             ]
+  , examples (DistanceValue Centimetre 2)
+             [ "2cm"
+             , "2 centimetri"
+             ]
+  , examples (DistanceValue Foot 10)
+             [ "zece picioare"
+             ]
+  ]
diff --git a/Duckling/Distance/RO/Rules.hs b/Duckling/Distance/RO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/RO/Rules.hs
@@ -0,0 +1,123 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.RO.Rules
+  ( rules ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Distance.Helpers
+import qualified Duckling.Distance.Types as TDistance
+import Duckling.Types
+
+ruleLatentDistKm :: Rule
+ruleLatentDistKm = Rule
+  { name = "<latent dist> km"
+  , pattern =
+    [ dimension Distance
+    , regex "(kilometr[iu]|km)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Kilometre dd
+      _ -> Nothing
+  }
+
+ruleLatentDistPicioare :: Rule
+ruleLatentDistPicioare = Rule
+  { name = "<latent dist> picioare"
+  , pattern =
+    [ dimension Distance
+    , regex "(picio(are|r))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Foot dd
+      _ -> Nothing
+  }
+
+ruleLatentDistInch :: Rule
+ruleLatentDistInch = Rule
+  { name = "<latent dist> inch"
+  , pattern =
+    [ dimension Distance
+    , regex "(inch|inci|inchi)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Inch dd
+      _ -> Nothing
+  }
+
+ruleLatentDistYarzi :: Rule
+ruleLatentDistYarzi = Rule
+  { name = "<latent dist> yarzi"
+  , pattern =
+    [ dimension Distance
+    , regex "y(ar)?(zi|d)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Yard dd
+      _ -> Nothing
+  }
+
+ruleDistMeters :: Rule
+ruleDistMeters = Rule
+  { name = "<dist> meters"
+  , pattern =
+    [ dimension Distance
+    , regex "(metr[ui]|m)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Metre dd
+      _ -> Nothing
+  }
+
+ruleDistCentimeters :: Rule
+ruleDistCentimeters = Rule
+  { name = "<dist> centimeters"
+  , pattern =
+    [ dimension Distance
+    , regex "(centimetr[iu]|cm)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Centimetre dd
+      _ -> Nothing
+  }
+
+ruleDistMiles :: Rule
+ruleDistMiles = Rule
+  { name = "<dist> miles"
+  , pattern =
+    [ dimension Distance
+    , regex "mil(e|a|\x0103)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Distance dd:_) ->
+        Just . Token Distance $ withUnit TDistance.Mile dd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDistCentimeters
+  , ruleDistMeters
+  , ruleDistMiles
+  , ruleLatentDistInch
+  , ruleLatentDistKm
+  , ruleLatentDistPicioare
+  , ruleLatentDistYarzi
+  ]
diff --git a/Duckling/Distance/Rules.hs b/Duckling/Distance/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/Rules.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Distance.Rules
+  ( rules
+  ) where
+
+
+import Data.String
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Distance.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Types
+
+ruleNumeralAsDistance :: Rule
+ruleNumeralAsDistance = Rule
+  { name = "number as distance"
+  , pattern =
+    [ dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral NumeralData {TNumeral.value = v}:_) ->
+        Just . Token Distance $ distance v
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleNumeralAsDistance
+  ]
diff --git a/Duckling/Distance/Types.hs b/Duckling/Distance/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Distance/Types.hs
@@ -0,0 +1,65 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Duckling.Distance.Types where
+
+import Control.DeepSeq
+import Data.Aeson
+import Data.Hashable
+import Data.Text (Text)
+import qualified Data.Text as Text
+import GHC.Generics
+import Prelude
+
+import Duckling.Resolve (Resolve(..))
+
+data Unit
+  = Foot
+  | Centimetre
+  | Kilometre
+  | Inch
+  | M -- ambiguous between Mile and Metre
+  | Metre
+  | Mile
+  | Yard
+  deriving (Eq, Generic, Hashable, Ord, Show, NFData)
+
+instance ToJSON Unit where
+  toJSON = String . Text.toLower . Text.pack . show
+
+data DistanceData = DistanceData
+  { unit :: Maybe Unit
+  , value :: Double
+  }
+  deriving (Eq, Generic, Hashable, Ord, Show, NFData)
+
+instance Resolve DistanceData where
+  type ResolvedValue DistanceData = DistanceValue
+  resolve _ DistanceData {unit = Nothing} = Nothing
+  resolve _ DistanceData {unit = Just unit, value} =
+    Just DistanceValue {vValue = value, vUnit = unit}
+
+data DistanceValue = DistanceValue
+  { vUnit :: Unit
+  , vValue :: Double
+  }
+  deriving (Eq, Ord, Show)
+
+instance ToJSON DistanceValue where
+  toJSON (DistanceValue unit value) = object
+    [ "type" .= ("value" :: Text)
+    , "value" .= value
+    , "unit" .= unit
+    ]
diff --git a/Duckling/Duration/DA/Rules.hs b/Duckling/Duration/DA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/DA/Rules.hs
@@ -0,0 +1,142 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.DA.Rules
+  ( rules ) where
+
+import Control.Monad (join)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Helpers
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleExactlyDuration :: Rule
+ruleExactlyDuration = Rule
+  { name = "exactly <duration>"
+  , pattern =
+    [ regex "pr(\x00e6)cis"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      -- TODO(jodent) +precision exact
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleIntegerAndAnHalfHours :: Rule
+ruleIntegerAndAnHalfHours = Rule
+  { name = "<integer> and an half hours"
+  , pattern =
+    [ Predicate isNatural
+    , regex "og (en )?halv timer?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        Just . Token Duration . duration TG.Minute $ 30 + 60 * floor v
+      _ -> Nothing
+  }
+
+ruleAUnitofduration :: Rule
+ruleAUnitofduration = Rule
+  { name = "a <unit-of-duration>"
+  , pattern =
+    [ regex "en|et?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) -> Just . Token Duration $ duration grain 1
+      _ -> Nothing
+  }
+
+ruleIntegerMoreUnitofduration :: Rule
+ruleIntegerMoreUnitofduration = Rule
+  { name = "<integer> more <unit-of-duration>"
+  , pattern =
+    [ Predicate isNatural
+    , dimension TimeGrain
+    , regex "mere|mindre"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token TimeGrain grain:
+       _) -> Just . Token Duration . duration grain $ floor v
+      _ -> Nothing
+  }
+
+ruleFortnight :: Rule
+ruleFortnight = Rule
+  { name = "fortnight"
+  , pattern =
+    [ regex "(a|one)? fortnight"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Day 14
+  }
+
+ruleAboutDuration :: Rule
+ruleAboutDuration = Rule
+  { name = "about <duration>"
+  , pattern =
+    [ regex "(omkring|cirka|ca.)"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      -- TODO(jodent) +precision approximate
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleNumeralnumberHours :: Rule
+ruleNumeralnumberHours = Rule
+  { name = "number.number hours"
+  , pattern =
+    [ regex "(\\d+)\\,(\\d+)"
+    , regex "timer?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (h:d:_)):_) -> do
+        hh <- parseInt h
+        dec <- parseInt d
+        let divisor = floor $ (fromIntegral (10 :: Integer) :: Float) **
+                        fromIntegral (Text.length d - 1)
+            numerator = fromIntegral $ 6 * dec
+        Just . Token Duration . duration TG.Minute $
+          60 * hh + quot numerator divisor
+      _ -> Nothing
+  }
+
+ruleHalfAnHour :: Rule
+ruleHalfAnHour = Rule
+  { name = "half an hour"
+  , pattern =
+    [ regex "(1/2|en halv) time"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 30
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAUnitofduration
+  , ruleAboutDuration
+  , ruleExactlyDuration
+  , ruleFortnight
+  , ruleHalfAnHour
+  , ruleIntegerAndAnHalfHours
+  , ruleIntegerMoreUnitofduration
+  , ruleNumeralnumberHours
+  ]
diff --git a/Duckling/Duration/DE/Rules.hs b/Duckling/Duration/DE/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/DE/Rules.hs
@@ -0,0 +1,140 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.DE.Rules
+  ( rules ) where
+
+import Control.Monad (join)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Helpers
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleHalfAnHour :: Rule
+ruleHalfAnHour = Rule
+  { name = "half an hour"
+  , pattern =
+    [ regex "(1/2\\s?|(einer )halbe?n? )stunde"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 30
+  }
+
+ruleFortnight :: Rule
+ruleFortnight = Rule
+  { name = "fortnight"
+  , pattern =
+    [ regex "(a|one)? fortnight"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Day 14
+  }
+
+ruleIntegerMoreUnitofduration :: Rule
+ruleIntegerMoreUnitofduration = Rule
+  { name = "<integer> more <unit-of-duration>"
+  , pattern =
+    [ Predicate isNatural
+    , regex "mehr|weniger"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       _:
+       Token TimeGrain grain:
+       _) -> Just . Token Duration . duration grain $ floor v
+      _ -> Nothing
+  }
+
+ruleNumeralnumberHours :: Rule
+ruleNumeralnumberHours = Rule
+  { name = "number.number hours"
+  , pattern =
+    [ regex "(\\d+)\\.(\\d+) stunden?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (h:d:_)):_) -> do
+        hh <- parseInt h
+        dec <- parseInt d
+        let divisor = floor $ (fromIntegral (10 :: Integer) :: Float) **
+                        fromIntegral (Text.length d - 1)
+            numerator = fromIntegral $ 6 * dec
+        Just . Token Duration . duration TG.Minute $
+          60 * hh + quot numerator divisor
+      _ -> Nothing
+  }
+
+ruleIntegerAndAnHalfHours :: Rule
+ruleIntegerAndAnHalfHours = Rule
+  { name = "<integer> and an half hours"
+  , pattern =
+    [ Predicate isNatural
+    , regex "ein ?halb stunden?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        Just . Token Duration . duration TG.Minute $ 30 + 60 * floor v
+      _ -> Nothing
+  }
+
+ruleAUnitofduration :: Rule
+ruleAUnitofduration = Rule
+  { name = "a <unit-of-duration>"
+  , pattern =
+    [ regex "eine?(r|n)?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) -> Just . Token Duration $ duration grain 1
+      _ -> Nothing
+  }
+
+ruleAboutDuration :: Rule
+ruleAboutDuration = Rule
+  { name = "about <duration>"
+  , pattern =
+    [ regex "ungef\x00e4hr|zirka"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleExactlyDuration :: Rule
+ruleExactlyDuration = Rule
+  { name = "exactly <duration>"
+  , pattern =
+    [ regex "genau|exakt"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAUnitofduration
+  , ruleAboutDuration
+  , ruleExactlyDuration
+  , ruleFortnight
+  , ruleHalfAnHour
+  , ruleIntegerAndAnHalfHours
+  , ruleIntegerMoreUnitofduration
+  , ruleNumeralnumberHours
+  ]
diff --git a/Duckling/Duration/EN/Corpus.hs b/Duckling/Duration/EN/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/EN/Corpus.hs
@@ -0,0 +1,65 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.EN.Corpus
+  ( corpus
+  , negativeCorpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Duration.Types
+import Duckling.Testing.Types
+import Duckling.TimeGrain.Types (Grain(..))
+
+corpus :: Corpus
+corpus = (testContext, allExamples)
+
+negativeCorpus :: NegativeCorpus
+negativeCorpus = (testContext, examples)
+  where
+    examples =
+      [ "for months"
+      , "in days"
+      , "secretary"
+      , "minutes"
+      , "I second that"
+      ]
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DurationData 1 Second)
+             [ "one sec"
+             , "1 second"
+             , "1\""
+             ]
+  , examples (DurationData 2 Minute)
+             [ "2 mins"
+             , "two minutes"
+             , "2'"
+             ]
+  , examples (DurationData 30 Day)
+             [ "30 days"
+             ]
+  , examples (DurationData 7 Week)
+             [ "seven weeks"
+             ]
+  , examples (DurationData 1 Month)
+             [ "1 month"
+             , "a month"
+             ]
+  , examples (DurationData 3 Quarter)
+             [ "3 quarters"
+             ]
+  , examples (DurationData 2 Year)
+             [ "2 years"
+             ]
+  ]
diff --git a/Duckling/Duration/EN/Rules.hs b/Duckling/Duration/EN/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/EN/Rules.hs
@@ -0,0 +1,153 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.EN.Rules
+  ( rules ) where
+
+import Control.Monad (join)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Helpers
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Numeral.Types (NumeralData(..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleDurationQuarterOfAnHour :: Rule
+ruleDurationQuarterOfAnHour = Rule
+  { name = "quarter of an hour"
+  , pattern = [ regex "(1/4\\s?h(our)?|(a\\s)?quarter of an hour)" ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 15
+  }
+
+ruleDurationHalfAnHour :: Rule
+ruleDurationHalfAnHour = Rule
+  { name = "half an hour"
+  , pattern = [regex "(1/2\\s?h(our)?|half an? hour)"]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 30
+  }
+
+ruleDurationThreeQuartersOfAnHour :: Rule
+ruleDurationThreeQuartersOfAnHour = Rule
+  { name = "three-quarters of an hour"
+  , pattern = [regex "(3/4\\s?h(our)?|three(\\s|-)quarters of an hour)"]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 45
+  }
+
+ruleDurationFortnight :: Rule
+ruleDurationFortnight = Rule
+  { name = "fortnight"
+  , pattern = [regex "(a|one)? fortnight"]
+  , prod = \_ -> Just . Token Duration $ duration TG.Day 14
+  }
+
+ruleNumeralQuotes :: Rule
+ruleNumeralQuotes = Rule
+  { name = "<integer> + '\""
+  , pattern =
+    [ Predicate isNatural
+    , regex "(['\"])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (x:_)):
+       _) -> case x of
+         "'"  -> Just . Token Duration . duration TG.Minute $ floor v
+         "\"" -> Just . Token Duration . duration TG.Second $ floor v
+         _    -> Nothing
+      _ -> Nothing
+  }
+
+ruleDurationNumeralMore :: Rule
+ruleDurationNumeralMore = Rule
+  { name = "<integer> more <unit-of-duration>"
+  , pattern =
+    [ Predicate isNatural
+    , regex "more|less"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral nd:_:Token TimeGrain grain:_) ->
+        Just . Token Duration . duration grain . floor $ TNumeral.value nd
+      _ -> Nothing
+  }
+
+ruleDurationDotNumeralHours :: Rule
+ruleDurationDotNumeralHours = Rule
+  { name = "number.number hours"
+  , pattern = [regex "(\\d+)\\.(\\d+) *hours?"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (h:d:_)):_) -> do
+        hh <- parseInt h
+        dec <- parseInt d
+        let divisor = floor $ (fromIntegral (10 :: Integer) :: Float) **
+                        fromIntegral (Text.length d - 1)
+            numerator = fromIntegral $ 6 * dec
+        Just . Token Duration . duration TG.Minute $
+          60 * hh + quot numerator divisor
+      _ -> Nothing
+  }
+
+ruleDurationAndHalfHour :: Rule
+ruleDurationAndHalfHour = Rule
+  { name = "<integer> and an half hour"
+  , pattern =
+    [ Predicate isNatural
+    , regex "and (an? )?half hours?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        Just . Token Duration . duration TG.Minute $ 30 + 60 * floor v
+      _ -> Nothing
+  }
+
+ruleDurationA :: Rule
+ruleDurationA = Rule
+  { name = "a <unit-of-duration>"
+  , pattern =
+    [ regex "an?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) -> Just . Token Duration $ duration grain 1
+      _ -> Nothing
+  }
+
+ruleDurationPrecision :: Rule
+ruleDurationPrecision = Rule
+  { name = "about|exactly <duration>"
+  , pattern =
+    [ regex "(about|around|approximately|exactly)"
+    , dimension Duration
+    ]
+    , prod = \tokens -> case tokens of
+        (_:token:_) -> Just token
+        _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDurationQuarterOfAnHour
+  , ruleDurationHalfAnHour
+  , ruleDurationThreeQuartersOfAnHour
+  , ruleDurationFortnight
+  , ruleDurationNumeralMore
+  , ruleDurationDotNumeralHours
+  , ruleDurationAndHalfHour
+  , ruleDurationA
+  , ruleDurationPrecision
+  , ruleNumeralQuotes
+  ]
diff --git a/Duckling/Duration/FR/Corpus.hs b/Duckling/Duration/FR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/FR/Corpus.hs
@@ -0,0 +1,66 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.FR.Corpus
+  ( corpus
+  , negativeCorpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Duration.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+import Duckling.TimeGrain.Types (Grain(..))
+
+corpus :: Corpus
+corpus = (testContext {lang = FR}, allExamples)
+
+negativeCorpus :: NegativeCorpus
+negativeCorpus = (testContext {lang = FR}, examples)
+  where
+    examples =
+      [ "les jours"
+      , "en secondaire"
+      , "minutes"
+      , "pendant des mois"
+      ]
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DurationData 1 Second)
+             [ "une sec"
+             , "1 seconde"
+             , "1\""
+             ]
+  , examples (DurationData 2 Minute)
+             [ "2 mins"
+             , "deux minutes"
+             , "2'"
+             ]
+  , examples (DurationData 30 Day)
+             [ "30 jours"
+             ]
+  , examples (DurationData 7 Week)
+             [ "sept semaines"
+             ]
+  , examples (DurationData 1 Month)
+             [ "1 mois"
+             , "un mois"
+             ]
+  , examples (DurationData 3 Quarter)
+             [ "3 trimestres"
+             ]
+  , examples (DurationData 2 Year)
+             [ "2 ans"
+             ]
+  ]
diff --git a/Duckling/Duration/FR/Rules.hs b/Duckling/Duration/FR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/FR/Rules.hs
@@ -0,0 +1,104 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.FR.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Helpers
+import Duckling.Numeral.Types (NumeralData(..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleNumeralQuotes :: Rule
+ruleNumeralQuotes = Rule
+  { name = "<integer> + '\""
+  , pattern =
+    [ Predicate isNatural
+    , regex "(['\"])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (x:_)):
+       _) -> case x of
+         "'"  -> Just . Token Duration . duration TG.Minute $ floor v
+         "\"" -> Just . Token Duration . duration TG.Second $ floor v
+         _    -> Nothing
+      _ -> Nothing
+  }
+
+ruleUneUnitofduration :: Rule
+ruleUneUnitofduration = Rule
+  { name = "une <unit-of-duration>"
+  , pattern =
+    [ regex "une|la|le?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token TimeGrain grain:
+       _) -> Just . Token Duration $ duration grain 1
+      _ -> Nothing
+  }
+
+ruleUnQuartDHeure :: Rule
+ruleUnQuartDHeure = Rule
+  { name = "un quart d'heure"
+  , pattern =
+    [ regex "(1/4\\s?h(eure)?|(un|1) quart d'heure)"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 15
+  }
+
+ruleUneDemiHeure :: Rule
+ruleUneDemiHeure = Rule
+  { name = "une demi heure"
+  , pattern =
+    [ regex "(1/2\\s?h(eure)?|(1|une) demi(e)?(\\s|-)heure)"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 30
+  }
+
+ruleTroisQuartsDHeure :: Rule
+ruleTroisQuartsDHeure = Rule
+  { name = "trois quarts d'heure"
+  , pattern =
+    [ regex "(3/4\\s?h(eure)?|(3|trois) quart(s)? d'heure)"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 45
+  }
+
+ruleDurationEnviron :: Rule
+ruleDurationEnviron = Rule
+  { name = "environ <duration>"
+  , pattern =
+    [ regex "environ"
+    ]
+    , prod = \tokens -> case tokens of
+      -- TODO(jodent) +precision approximate
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleUneUnitofduration
+  , ruleUnQuartDHeure
+  , ruleUneDemiHeure
+  , ruleTroisQuartsDHeure
+  , ruleDurationEnviron
+  , ruleNumeralQuotes
+  ]
diff --git a/Duckling/Duration/GA/Corpus.hs b/Duckling/Duration/GA/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/GA/Corpus.hs
@@ -0,0 +1,42 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.GA.Corpus
+  ( corpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Duration.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+import Duckling.TimeGrain.Types (Grain(..))
+
+corpus :: Corpus
+corpus = (testContext {lang = GA}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DurationData 1 Second)
+             [ "aon soicind amhain"
+             , "aon soicind"
+             , "1 tshoicindi"
+             , "1 tsoicind"
+             ]
+  , examples (DurationData 30 Minute)
+             [ "leathuair"
+             , "30 noimead"
+             ]
+  , examples (DurationData 14 Day)
+             [ "coicís"
+             ]
+  ]
diff --git a/Duckling/Duration/GA/Rules.hs b/Duckling/Duration/GA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/GA/Rules.hs
@@ -0,0 +1,78 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.GA.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Helpers
+import Duckling.Numeral.Types (NumeralData(..))
+import qualified Duckling.Numeral.Types as TNumeral
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleCoics :: Rule
+ruleCoics = Rule
+  { name = "coicís"
+  , pattern =
+    [ regex "coic(\x00ed|i)s(\x00ed|i|e)?"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Day 14
+  }
+
+ruleLeathuair :: Rule
+ruleLeathuair = Rule
+  { name = "leathuair"
+  , pattern =
+    [ regex "leathuair(e|eanta)?"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 30
+  }
+
+ruleAonDurationAmhain :: Rule
+ruleAonDurationAmhain = Rule
+  { name = "aon X amhain"
+  , pattern =
+    [ isNumeralWith TNumeral.value (== 1)
+    , dimension TimeGrain
+    , isNumeralWith TNumeral.value (== 1)
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) -> Just . Token Duration $ duration grain 1
+      _ -> Nothing
+  }
+
+ruleIntegerUnitofdurationInteger :: Rule
+ruleIntegerUnitofdurationInteger = Rule
+  { name = "<unit-integer> <unit-of-duration> <tens-integer>"
+  , pattern =
+    [ isNumeralWith TNumeral.value (< 10)
+    , dimension TimeGrain
+    , isNumeralWith TNumeral.value (`elem` [10, 20 .. 50])
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token TimeGrain grain:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> Just . Token Duration . duration grain . floor $ v1 + v2
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleCoics
+  , ruleIntegerUnitofdurationInteger
+  , ruleLeathuair
+  , ruleAonDurationAmhain
+  ]
diff --git a/Duckling/Duration/HE/Rules.hs b/Duckling/Duration/HE/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/HE/Rules.hs
@@ -0,0 +1,124 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.HE.Rules
+  ( rules ) where
+
+import Control.Monad (join)
+import Data.Maybe
+import Data.String
+import Data.Text (Text)
+import Prelude
+import qualified Data.Text as Text
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Helpers
+import Duckling.Duration.Types (DurationData (..))
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Numeral.Types (NumeralData(..))
+import Duckling.Regex.Types
+import Duckling.Types
+import qualified Duckling.Duration.Types as TDuration
+import qualified Duckling.Numeral.Types as TNumeral
+import qualified Duckling.TimeGrain.Types as TG
+
+ruleQuarterOfAnHour :: Rule
+ruleQuarterOfAnHour = Rule
+  { name = "quarter of an hour"
+  , pattern =
+    [ regex "(1/4/s \x05e9\x05e2\x05d4|\x05e8\x05d1\x05e2 \x05e9\x05e2\x05d4)"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 15
+  }
+
+ruleHalfAnHour :: Rule
+ruleHalfAnHour = Rule
+  { name = "half an hour"
+  , pattern =
+    [ regex "(1/2/s \x05e9\x05e2\x05d4|\x05d7\x05e6\x05d9 \x05e9\x05e2\x05d4)"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 30
+  }
+
+ruleThreequartersOfAnHour :: Rule
+ruleThreequartersOfAnHour = Rule
+  { name = "three-quarters of an hour"
+  , pattern =
+    [ regex "(3/4/s \x05e9\x05e2\x05d4|\x05e9\x05dc\x05d5\x05e9\x05ea \x05e8\x05d1\x05e2\x05d9 \x05e9\x05e2\x05d4)"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 45
+  }
+
+ruleNumbernumberHours :: Rule
+ruleNumbernumberHours = Rule
+  { name = "number.number hours"
+  , pattern =
+    [ regex "(\\d+)\\.(\\d+)"
+    , regex "\x05e9\x05e2\x05d4|\x05e9\x05e2\x05d5\x05ea"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (h:d:_)):_) -> do
+        hh <- parseInt h
+        dec <- parseInt d
+        let divisor = floor $ (fromIntegral (10 :: Integer) :: Float) **
+                        fromIntegral (Text.length d - 1)
+            numerator = fromIntegral $ 6 * dec
+        Just . Token Duration . duration TG.Minute $
+          60 * hh + quot numerator divisor
+      _ -> Nothing
+  }
+
+ruleIntegerAndAnHalfHours :: Rule
+ruleIntegerAndAnHalfHours = Rule
+  { name = "<integer> and an half hours"
+  , pattern =
+    [ Predicate isNatural
+    , regex "\x05d5\x05d7\x05e6\x05d9 (\x05e9\x05e2\x05d5\x05ea|\x05e9\x05e2\x05d4)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        Just . Token Duration . duration TG.Minute $ 30 + 60 * floor v
+      _ -> Nothing
+  }
+
+ruleAboutDuration :: Rule
+ruleAboutDuration = Rule
+  { name = "about <duration>"
+  , pattern =
+    [ regex "(\x05d1\x05e2\x05e8\x05da|\x05e1\x05d1\x05d9\x05d1\x05d5\x05ea|\x05d1\x05e7\x05d9\x05e8\x05d5\x05d1)"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleExactlyDuration :: Rule
+ruleExactlyDuration = Rule
+  { name = "exactly <duration>"
+  , pattern =
+    [ regex "\x05d1\x05d3\x05d9\x05d5\x05e7"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAboutDuration
+  , ruleExactlyDuration
+  , ruleHalfAnHour
+  , ruleIntegerAndAnHalfHours
+  , ruleNumbernumberHours
+  , ruleQuarterOfAnHour
+  , ruleThreequartersOfAnHour
+  ]
diff --git a/Duckling/Duration/HR/Rules.hs b/Duckling/Duration/HR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/HR/Rules.hs
@@ -0,0 +1,141 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.HR.Rules
+  ( rules ) where
+
+import Control.Monad (join)
+import Data.Maybe
+import Data.String
+import Data.Text (Text)
+import Prelude
+import qualified Data.Text as Text
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Helpers
+import Duckling.Duration.Types (DurationData (..))
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Numeral.Types (NumeralData(..))
+import Duckling.Regex.Types
+import Duckling.Types
+import qualified Duckling.Duration.Types as TDuration
+import qualified Duckling.Numeral.Types as TNumeral
+import qualified Duckling.TimeGrain.Types as TG
+
+ruleExactlyDuration :: Rule
+ruleExactlyDuration = Rule
+  { name = "exactly <duration>"
+  , pattern =
+    [ regex "to(c|\x010d)no"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleIntegerAndAnHalfHours :: Rule
+ruleIntegerAndAnHalfHours = Rule
+  { name = "<integer> and an half hours"
+  , pattern =
+    [ Predicate isNatural
+    , regex "i pol?a?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        Just . Token Duration . duration TG.Minute $ 30 + 60 * floor v
+      _ -> Nothing
+  }
+
+ruleIntegerMoreUnitofduration :: Rule
+ruleIntegerMoreUnitofduration = Rule
+  { name = "<integer> more <unit-of-duration>"
+  , pattern =
+    [ Predicate isNatural
+    , regex "vi(s|\x0161)e|manje"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       _:
+       Token TimeGrain grain:_) ->
+         Just . Token Duration . duration grain $ floor v
+      _ -> Nothing
+  }
+
+ruleQuarterOfAnHour :: Rule
+ruleQuarterOfAnHour = Rule
+  { name = "quarter of an hour"
+  , pattern =
+    [ regex "((1/4|frtalj|kvarat|(c|\x010d)etvrt)\\s?(h|sata)?)"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 15
+  }
+
+ruleAboutDuration :: Rule
+ruleAboutDuration = Rule
+  { name = "about <duration>"
+  , pattern =
+    [ regex "(oko|otprilike|odokativno)"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleNumbernumberHours :: Rule
+ruleNumbernumberHours = Rule
+  { name = "number.number hours"
+  , pattern =
+    [ regex "(\\d+)\\.(\\d+)"
+    , regex "sat(i|a)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (h:d:_)):_) -> do
+        hh <- parseInt h
+        dec <- parseInt d
+        let divisor = floor $ (fromIntegral (10 :: Integer) :: Float) **
+                        fromIntegral (Text.length d - 1)
+            numerator = fromIntegral $ 6 * dec
+        Just . Token Duration . duration TG.Minute $
+          60 * hh + quot numerator divisor
+      _ -> Nothing
+  }
+
+ruleThreequartersOfAnHour :: Rule
+ruleThreequartersOfAnHour = Rule
+  { name = "three-quarters of an hour"
+  , pattern =
+    [ regex "((3/4|tri-?frtalja|tri-?kvarat|tri-?(c|\x010d)etvrt(ine)?)\\s?(h|sata)?)"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 45
+  }
+
+ruleHalfAnHour :: Rule
+ruleHalfAnHour = Rule
+  { name = "half an hour"
+  , pattern =
+    [ regex "(1/2\\s?(h|sata)?|pol?a? sata)"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 30
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAboutDuration
+  , ruleExactlyDuration
+  , ruleHalfAnHour
+  , ruleIntegerAndAnHalfHours
+  , ruleIntegerMoreUnitofduration
+  , ruleNumbernumberHours
+  , ruleQuarterOfAnHour
+  , ruleThreequartersOfAnHour
+  ]
diff --git a/Duckling/Duration/Helpers.hs b/Duckling/Duration/Helpers.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/Helpers.hs
@@ -0,0 +1,50 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.Helpers
+  ( duration
+  , isGrain
+  , isNatural
+  , isNumeralWith
+  ) where
+
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Types (DurationData (DurationData))
+import qualified Duckling.Duration.Types as TDuration
+import Duckling.Numeral.Types (NumeralData (NumeralData))
+import qualified Duckling.Numeral.Types as TNumeral
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+-- -----------------------------------------------------------------
+-- Patterns
+
+isGrain :: TG.Grain -> Predicate
+isGrain value (Token TimeGrain grain) = grain == value
+isGrain _ _ = False
+
+isNatural :: Predicate
+isNatural (Token Numeral NumeralData {TNumeral.value = x}) =
+  TNumeral.isNatural x
+isNatural _ = False
+
+isNumeralWith :: (NumeralData -> t) -> (t -> Bool) -> PatternItem
+isNumeralWith f pred = Predicate $ \x -> case x of
+  (Token Numeral x) -> pred $ f x
+  _ -> False
+
+-- -----------------------------------------------------------------
+-- Production
+
+duration :: TG.Grain -> Int -> DurationData
+duration grain n = DurationData {TDuration.grain = grain, TDuration.value = n}
diff --git a/Duckling/Duration/IT/Rules.hs b/Duckling/Duration/IT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/IT/Rules.hs
@@ -0,0 +1,68 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.IT.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Helpers
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleUneUnitofduration :: Rule
+ruleUneUnitofduration = Rule
+  { name = "une <unit-of-duration>"
+  , pattern =
+    [ regex "un[a']?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) -> Just . Token Duration $ duration grain 1
+      _ -> Nothing
+  }
+
+ruleUnQuartoDora :: Rule
+ruleUnQuartoDora = Rule
+  { name = "un quarto d'ora"
+  , pattern =
+    [ regex "un quarto d['i] ?ora"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 15
+  }
+
+ruleMezzOra :: Rule
+ruleMezzOra = Rule
+  { name = "mezz'ora"
+  , pattern =
+    [ regex "mezz[a'] ?ora"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 30
+  }
+
+ruleTreQuartiDora :: Rule
+ruleTreQuartiDora = Rule
+  { name = "tre quarti d'ora"
+  , pattern =
+    [ regex "(3|tre) quarti d['i] ?ora"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 45
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleUneUnitofduration
+  , ruleUnQuartoDora
+  , ruleMezzOra
+  , ruleTreQuartiDora
+  ]
diff --git a/Duckling/Duration/JA/Corpus.hs b/Duckling/Duration/JA/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/JA/Corpus.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.JA.Corpus
+  ( corpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Duration.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+import Duckling.TimeGrain.Types (Grain(..))
+
+corpus :: Corpus
+corpus = (testContext {lang = JA}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DurationData 1 Second)
+             [ "1 秒"
+             , "一 秒間"
+             ]
+  , examples (DurationData 4 Minute)
+             [ "四 分間"
+             ]
+  , examples (DurationData 100 Day)
+             [ "百 日"
+             , "百 日間"
+             ]
+  ]
diff --git a/Duckling/Duration/KO/Corpus.hs b/Duckling/Duration/KO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/KO/Corpus.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.KO.Corpus
+  ( corpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Duration.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+import Duckling.TimeGrain.Types (Grain(..))
+
+corpus :: Corpus
+corpus = (testContext {lang = KO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DurationData 1 Second)
+             [ "1 초"
+             ]
+  , examples (DurationData 30 Minute)
+             [ "시간반"
+             , "시반"
+             , "서른분"
+             ]
+  , examples (DurationData 66 Minute)
+             [ "1.1 시간"
+             ]
+  ]
diff --git a/Duckling/Duration/KO/Rules.hs b/Duckling/Duration/KO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/KO/Rules.hs
@@ -0,0 +1,112 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.KO.Rules
+  ( rules ) where
+
+import Control.Monad (join)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Helpers
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleHalfAnHour :: Rule
+ruleHalfAnHour = Rule
+  { name = "half an hour"
+  , pattern =
+    [ Predicate $ isGrain TG.Hour
+    , regex "\xbc18"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 30
+  }
+
+ruleADay :: Rule
+ruleADay = Rule
+  { name = "a day - 하루"
+  , pattern =
+    [ regex "\xd558\xb8e8"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Day 1
+  }
+
+ruleNumeralnumberHours :: Rule
+ruleNumeralnumberHours = Rule
+  { name = "number.number hours"
+  , pattern =
+    [ regex "(\\d+)\\.(\\d+)"
+    , regex "\xc2dc\xac04"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        hh <- parseInt m1
+        dec <- parseInt m2
+        let divisor = floor $ (fromIntegral (10 :: Integer) :: Float) **
+                        fromIntegral (Text.length m2 - 1)
+            numerator = fromIntegral $ 6 * dec
+        Just . Token Duration . duration TG.Minute $
+          60 * hh + quot numerator divisor
+      _ -> Nothing
+  }
+
+ruleIntegerAndAnHalfHours :: Rule
+ruleIntegerAndAnHalfHours = Rule
+  { name = "<integer> and an half hours"
+  , pattern =
+    [ Predicate isNatural
+    , regex "\xc2dc\xac04\xbc18"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        Just . Token Duration . duration TG.Minute $ 30 + 60 * floor v
+      _ -> Nothing
+  }
+
+ruleAboutDuration :: Rule
+ruleAboutDuration = Rule
+  { name = "about <duration>"
+  , pattern =
+    [ regex "\xb300\xcda9|\xc57d"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleExactlyDuration :: Rule
+ruleExactlyDuration = Rule
+  { name = "exactly <duration>"
+  , pattern =
+    [ regex "\xc815\xd655\xd788"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleADay
+  , ruleAboutDuration
+  , ruleExactlyDuration
+  , ruleHalfAnHour
+  , ruleIntegerAndAnHalfHours
+  , ruleNumeralnumberHours
+  ]
diff --git a/Duckling/Duration/NB/Corpus.hs b/Duckling/Duration/NB/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/NB/Corpus.hs
@@ -0,0 +1,47 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.NB.Corpus
+  ( corpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Duration.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+import Duckling.TimeGrain.Types (Grain(..))
+
+corpus :: Corpus
+corpus = (testContext {lang = NB}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DurationData 1 Second)
+             [ "1 sek"
+             , "en sek"
+             , "ett sekund"
+             , "e sekunder"
+             ]
+  , examples (DurationData 30 Minute)
+             [ "tredve min"
+             , "30 minutt"
+             , "30 minutter"
+             , "1/2 time"
+             , "en halv time"
+             ]
+  , examples (DurationData 2 Day)
+             [ "et par dager"
+             , "2 dag"
+             , "to dag"
+             ]
+  ]
diff --git a/Duckling/Duration/NB/Rules.hs b/Duckling/Duration/NB/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/NB/Rules.hs
@@ -0,0 +1,129 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.NB.Rules
+  ( rules ) where
+
+import Control.Monad (join)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Helpers
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleHalfAnHour :: Rule
+ruleHalfAnHour = Rule
+  { name = "half an hour"
+  , pattern =
+    [ regex "(1/2|en halv) time"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 30
+  }
+
+ruleIntegerMoreUnitofduration :: Rule
+ruleIntegerMoreUnitofduration = Rule
+  { name = "<integer> more <unit-of-duration>"
+  , pattern =
+    [ Predicate isNatural
+    , dimension TimeGrain
+    , regex "mere?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token TimeGrain grain:
+       _) -> Just . Token Duration . duration grain $ floor v
+      _ -> Nothing
+  }
+
+ruleNumeralnumberHours :: Rule
+ruleNumeralnumberHours = Rule
+  { name = "number.number hours"
+  , pattern =
+    [ regex "(\\d+)\\,(\\d+) *timer?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (h:d:_)):_) -> do
+        hh <- parseInt h
+        dec <- parseInt d
+        let divisor = floor $ (fromIntegral (10 :: Integer) :: Float) **
+                        fromIntegral (Text.length d - 1)
+            numerator = fromIntegral $ 6 * dec
+        Just . Token Duration $ duration TG.Minute $
+          60 * hh + quot numerator divisor
+      _ -> Nothing
+  }
+
+ruleIntegerAndAnHalfHours :: Rule
+ruleIntegerAndAnHalfHours = Rule
+  { name = "<integer> and an half hours"
+  , pattern =
+    [ Predicate isNatural
+    , regex "og (en )?halv time?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        Just . Token Duration . duration TG.Minute $ 30 + 60 * floor v
+      _ -> Nothing
+  }
+
+ruleAUnitofduration :: Rule
+ruleAUnitofduration = Rule
+  { name = "a <unit-of-duration>"
+  , pattern =
+    [ regex "en|ett|et?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) -> Just . Token Duration $ duration grain 1
+      _ -> Nothing
+  }
+
+ruleAboutDuration :: Rule
+ruleAboutDuration = Rule
+  { name = "about <duration>"
+  , pattern =
+    [ regex "(omkring|cirka|ca.|ca)"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleExactlyDuration :: Rule
+ruleExactlyDuration = Rule
+  { name = "exactly <duration>"
+  , pattern =
+    [ regex "(precis|akkurat)"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAUnitofduration
+  , ruleAboutDuration
+  , ruleExactlyDuration
+  , ruleHalfAnHour
+  , ruleIntegerAndAnHalfHours
+  , ruleIntegerMoreUnitofduration
+  , ruleNumeralnumberHours
+  ]
diff --git a/Duckling/Duration/PL/Corpus.hs b/Duckling/Duration/PL/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/PL/Corpus.hs
@@ -0,0 +1,49 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.PL.Corpus
+  ( corpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Duration.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+import Duckling.TimeGrain.Types (Grain(..))
+
+corpus :: Corpus
+corpus = (testContext {lang = PL}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DurationData 1 Second)
+             [ "1s"
+             , "1 sekund"
+             , "jeden sekundzie"
+             , "pojedynczy sekundach"
+             ]
+  , examples (DurationData 30 Minute)
+             [ "pol godziny"
+             , "pół godziny"
+             , "30m"
+             , "30 minut"
+             , "trzydzieści minutami"
+             ]
+  , examples (DurationData 5 Day)
+             [ "pięciu dniach"
+             ]
+  , examples (DurationData 100 Day)
+             [ "sto dzień"
+             , "setki dnią"
+             ]
+  ]
diff --git a/Duckling/Duration/PL/Rules.hs b/Duckling/Duration/PL/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/PL/Rules.hs
@@ -0,0 +1,131 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.PL.Rules
+  ( rules ) where
+
+import Control.Monad (join)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Helpers
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleHalfAnHour :: Rule
+ruleHalfAnHour = Rule
+  { name = "half an hour"
+  , pattern =
+    [ regex "p(o|\x00f3)(l|\x0142) godziny"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 30
+  }
+
+ruleIntegerMoreUnitofduration :: Rule
+ruleIntegerMoreUnitofduration = Rule
+  { name = "<integer> more <unit-of-duration>"
+  , pattern =
+    [ Predicate isNatural
+    , regex "more|less"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       _:
+       Token TimeGrain grain:
+       _) -> Just . Token Duration . duration grain $ floor v
+      _ -> Nothing
+  }
+
+ruleNumeralnumberHours :: Rule
+ruleNumeralnumberHours = Rule
+  { name = "number.number hours"
+  , pattern =
+    [ regex "(\\d+)\\.(\\d+)"
+    , regex "godzin(y)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (h:d:_)):_) -> do
+        hh <- parseInt h
+        dec <- parseInt d
+        let divisor = floor $ (fromIntegral (10 :: Integer) :: Float) **
+                        fromIntegral (Text.length d - 1)
+            numerator = fromIntegral $ 6 * dec
+        Just . Token Duration . duration TG.Minute $
+          60 * hh + quot numerator divisor
+      _ -> Nothing
+  }
+
+ruleIntegerAndAnHalfHours :: Rule
+ruleIntegerAndAnHalfHours = Rule
+  { name = "<integer> and an half hours"
+  , pattern =
+    [ Predicate isNatural
+    , regex "i (p(o|\x00f3)(l|\x0142)) godziny"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        Just . Token Duration . duration TG.Minute $ 30 + 60 * floor v
+      _ -> Nothing
+  }
+
+ruleUnitofdurationAsADuration :: Rule
+ruleUnitofdurationAsADuration = Rule
+  { name = "<unit-of-duration> as a duration"
+  , pattern =
+    [ dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_) ->
+        Just . Token Duration $ duration grain 1
+      _ -> Nothing
+  }
+
+ruleAboutDuration :: Rule
+ruleAboutDuration = Rule
+  { name = "about <duration>"
+  , pattern =
+    [ regex "(oko(l|\x0142)o|miej wi(\x0119|e)cej|jakie(s|\x015b))"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleExactlyDuration :: Rule
+ruleExactlyDuration = Rule
+  { name = "exactly <duration>"
+  , pattern =
+    [ regex "r(o|\x00f3)wno|dok(l|\x0142)adnie"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAboutDuration
+  , ruleExactlyDuration
+  , ruleHalfAnHour
+  , ruleIntegerAndAnHalfHours
+  , ruleIntegerMoreUnitofduration
+  , ruleNumeralnumberHours
+  , ruleUnitofdurationAsADuration
+  ]
diff --git a/Duckling/Duration/PT/Corpus.hs b/Duckling/Duration/PT/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/PT/Corpus.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.PT.Corpus
+  ( corpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Duration.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+import Duckling.TimeGrain.Types (Grain(..))
+
+corpus :: Corpus
+corpus = (testContext {lang = PT}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DurationData 1 Second)
+             [ "um segundo"
+             , "uma seg"
+             ]
+  , examples (DurationData 2 Minute)
+             [ "duas mins"
+             , "dois minutos"
+             ]
+  , examples (DurationData 20 Day)
+             [ "20 dias"
+             , "vinte días"
+             ]
+  ]
diff --git a/Duckling/Duration/RO/Corpus.hs b/Duckling/Duration/RO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/RO/Corpus.hs
@@ -0,0 +1,52 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.RO.Corpus
+  ( corpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Duration.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+import Duckling.TimeGrain.Types (Grain(..))
+
+corpus :: Corpus
+corpus = (testContext {lang = RO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DurationData 1 Second)
+             [ "o sec"
+             , "1 secunda"
+             ]
+  , examples (DurationData 30 Minute)
+             [ "jumatate ora"
+             , "1/2h"
+             , "treizeci minute"
+             ]
+  , examples (DurationData 45 Minute)
+             [ "trei sferturi de oră"
+             , "45min"
+             ]
+  , examples (DurationData 12 Week)
+             [ "doișpe saptamanile"
+             , "doisprezece saptămâni"
+             ]
+  , examples (DurationData 2 Month)
+             [ "2 luni"
+             ]
+  , examples (DurationData 1 Quarter)
+             [ "un trimestru"
+             ]
+  ]
diff --git a/Duckling/Duration/RO/Rules.hs b/Duckling/Duration/RO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/RO/Rules.hs
@@ -0,0 +1,81 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.RO.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Helpers
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleQuarterOfAnHour :: Rule
+ruleQuarterOfAnHour = Rule
+  { name = "quarter of an hour"
+  , pattern =
+    [ regex "(1/4\\s?(h|or(a|\x0103))|sfert de or(a|\x0103))"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 15
+  }
+
+ruleJumatateDeOra :: Rule
+ruleJumatateDeOra = Rule
+  { name = "jumatate de ora"
+  , pattern =
+    [ regex "(1/2\\s?(h|or(a|\x0103))|jum(a|\x0103)tate (de )?or(a|\x0103))"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 30
+  }
+
+ruleTreiSferturiDeOra :: Rule
+ruleTreiSferturiDeOra = Rule
+  { name = "trei sferturi de ora"
+  , pattern =
+    [ regex "(3/4\\s?(h|or(a|\x0103))|trei sferturi de or(a|\x0103))"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 45
+  }
+
+ruleOUnitofduration :: Rule
+ruleOUnitofduration = Rule
+  { name = "o <unit-of-duration>"
+  , pattern =
+    [ regex "o|un"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) -> Just . Token Duration $ duration grain 1
+      _ -> Nothing
+  }
+
+ruleExactInJurDeDuration :: Rule
+ruleExactInJurDeDuration = Rule
+  { name = "exact|in jur de <duration>"
+  , pattern =
+    [ regex "(exact|aproximativ|(i|\x00ee)n jur de)"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleExactInJurDeDuration
+  , ruleJumatateDeOra
+  , ruleOUnitofduration
+  , ruleQuarterOfAnHour
+  , ruleTreiSferturiDeOra
+  ]
diff --git a/Duckling/Duration/Rules.hs b/Duckling/Duration/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/Rules.hs
@@ -0,0 +1,42 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.Rules
+  ( rules
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Helpers
+import Duckling.Numeral.Types (NumeralData(..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Types
+
+ruleIntegerUnitofduration :: Rule
+ruleIntegerUnitofduration = Rule
+  { name = "<integer> <unit-of-duration>"
+  , pattern =
+    [ Predicate isNatural
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token TimeGrain grain:
+       _) -> Just . Token Duration . duration grain $ floor v
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleIntegerUnitofduration
+  ]
diff --git a/Duckling/Duration/SV/Corpus.hs b/Duckling/Duration/SV/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/SV/Corpus.hs
@@ -0,0 +1,48 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.SV.Corpus
+  ( corpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Duration.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+import Duckling.TimeGrain.Types (Grain(..))
+
+corpus :: Corpus
+corpus = (testContext {lang = SV}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DurationData 1 Second)
+             [ "enkel sek"
+             , "1 sekund fler"
+             , "1 sekunder mer"
+             , "en sekunderna"
+             , "et sek"
+             , "ett sek"
+             ]
+  , examples (DurationData 30 Minute)
+             [ "1/2 timme"
+             , "en halv timme"
+             , "0,5 timmar"
+             , "30 minuterna"
+             , "trettio min"
+             ]
+  , examples (DurationData 5 Year)
+             [ "5 år"
+             , "fem år"
+             ]
+  ]
diff --git a/Duckling/Duration/SV/Rules.hs b/Duckling/Duration/SV/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/SV/Rules.hs
@@ -0,0 +1,131 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.SV.Rules
+  ( rules ) where
+
+import Control.Monad (join)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Helpers
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Numeral.Types (NumeralData(..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleHalfAnHour :: Rule
+ruleHalfAnHour = Rule
+  { name = "half an hour"
+  , pattern =
+    [ regex "(1/2|en halv) timme"
+    ]
+  , prod = \_ -> Just . Token Duration $ duration TG.Minute 30
+  }
+
+ruleIntegerMoreUnitofduration :: Rule
+ruleIntegerMoreUnitofduration = Rule
+  { name = "<integer> more <unit-of-duration>"
+  , pattern =
+    [ Predicate isNatural
+    , dimension TimeGrain
+    , regex "fler|mer"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token TimeGrain grain:
+       _) -> Just . Token Duration . duration grain $ floor v
+      _ -> Nothing
+  }
+
+ruleNumeralnumberHours :: Rule
+ruleNumeralnumberHours = Rule
+  { name = "number.number hours"
+  , pattern =
+    [ regex "(\\d+)\\,(\\d+)"
+    , regex "timm(e|ar)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (h:d:_)):_) -> do
+        hh <- parseInt h
+        dec <- parseInt d
+        let divisor = floor $ (fromIntegral (10 :: Integer) :: Float) **
+                        fromIntegral (Text.length d - 1)
+            numerator = fromIntegral $ 6 * dec
+        Just . Token Duration . duration TG.Minute $
+          60 * hh + quot numerator divisor
+      _ -> Nothing
+  }
+
+ruleIntegerAndAnHalfHours :: Rule
+ruleIntegerAndAnHalfHours = Rule
+  { name = "<integer> and an half hours"
+  , pattern =
+    [ Predicate isNatural
+    , regex "och (en )?halv timme?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        Just . Token Duration . duration TG.Minute $ 30 + 60 * floor v
+      _ -> Nothing
+  }
+
+ruleAUnitofduration :: Rule
+ruleAUnitofduration = Rule
+  { name = "a <unit-of-duration>"
+  , pattern =
+    [ regex "en|ett?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        Just . Token Duration $ duration grain 1
+      _ -> Nothing
+  }
+
+ruleAboutDuration :: Rule
+ruleAboutDuration = Rule
+  { name = "about <duration>"
+  , pattern =
+    [ regex "(omkring|cirka|ca.|ca|runt)"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleExactlyDuration :: Rule
+ruleExactlyDuration = Rule
+  { name = "exactly <duration>"
+  , pattern =
+    [ regex "(precis|exakt)"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAUnitofduration
+  , ruleAboutDuration
+  , ruleExactlyDuration
+  , ruleHalfAnHour
+  , ruleIntegerAndAnHalfHours
+  , ruleIntegerMoreUnitofduration
+  , ruleNumeralnumberHours
+  ]
diff --git a/Duckling/Duration/Types.hs b/Duckling/Duration/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/Types.hs
@@ -0,0 +1,48 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Duckling.Duration.Types where
+
+import Control.DeepSeq
+import Data.Aeson
+import Data.Hashable
+import Data.Text (Text)
+import GHC.Generics
+import TextShow (showt)
+import Prelude
+
+import Duckling.Resolve (Resolve(..))
+import Duckling.TimeGrain.Types (Grain(..), inSeconds)
+
+data DurationData = DurationData
+  { value :: Int
+  , grain :: Grain
+  }
+  deriving (Eq, Generic, Hashable, Show, Ord, NFData)
+
+instance Resolve DurationData where
+  type ResolvedValue DurationData = DurationData
+  resolve _ x = Just x
+
+instance ToJSON DurationData where
+  toJSON DurationData {value, grain} = object
+    [ "value"      .= value
+    , "unit"       .= grain
+    , showt grain  .= value
+    , "normalized" .= object
+      [ "unit"  .= ("second" :: Text)
+      , "value" .= inSeconds grain value
+      ]
+    ]
diff --git a/Duckling/Duration/ZH/Corpus.hs b/Duckling/Duration/ZH/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Duration/ZH/Corpus.hs
@@ -0,0 +1,39 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Duration.ZH.Corpus
+  ( corpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Duration.Types
+import Duckling.Testing.Types
+import Duckling.TimeGrain.Types (Grain(..))
+
+corpus :: Corpus
+corpus = (testContext, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (DurationData 1 Second)
+             [ "1 秒钟"
+             , "一 秒鐘"
+             ]
+  , examples (DurationData 5 Day)
+             [ "5 天"
+             , "五 天"
+             ]
+  , examples (DurationData 10 Month)
+             [ "10 月"
+             , "十 月"
+             ]
+  ]
diff --git a/Duckling/Email/Corpus.hs b/Duckling/Email/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Email/Corpus.hs
@@ -0,0 +1,47 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Email.Corpus
+  ( corpus
+  , negativeCorpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Email.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext, allExamples)
+
+negativeCorpus :: NegativeCorpus
+negativeCorpus = (testContext, examples)
+  where
+    examples =
+      [ "hey@6"
+      , "hey@you"
+      ]
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (EmailData "alice@exAmple.io")
+             [ "alice@exAmple.io"
+             ]
+  , examples (EmailData "yo+yo@blah.org")
+             [ "yo+yo@blah.org"
+             ]
+  , examples (EmailData "1234+abc@x.net")
+             [ "1234+abc@x.net"
+             ]
+  , examples (EmailData "jean-jacques@stuff.co.uk")
+             [ "jean-jacques@stuff.co.uk"
+             ]
+  ]
diff --git a/Duckling/Email/EN/Corpus.hs b/Duckling/Email/EN/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Email/EN/Corpus.hs
@@ -0,0 +1,38 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Email.EN.Corpus
+  ( corpus
+  ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Email.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (EmailData "alice@exAmple.io")
+             [ "alice at exAmple.io"
+             ]
+  , examples (EmailData "yo+yo@blah.org")
+             [ "yo+yo at blah.org"
+             ]
+  , examples (EmailData "1234+abc@x.net")
+             [ "1234+abc at x.net"
+             ]
+  , examples (EmailData "jean-jacques@stuff.co.uk")
+             [ "jean-jacques at stuff.co.uk"
+             ]
+  ]
diff --git a/Duckling/Email/EN/Rules.hs b/Duckling/Email/EN/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Email/EN/Rules.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Email.EN.Rules
+  ( rules ) where
+
+import Data.String
+import qualified Data.Text as Text
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Email.Types (EmailData (..))
+import qualified Duckling.Email.Types as TEmail
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleEmailSpelledOut :: Rule
+ruleEmailSpelledOut = Rule
+  { name = "email spelled out"
+  , pattern =
+    [ regex "([\\w\\._+-]+) at ([\\w_-]+(\\.[\\w_-]+)+)"
+    ]
+  , prod = \xs -> case xs of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) ->
+        Just $ Token Email EmailData {TEmail.value = Text.concat [m1, "@", m2]}
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleEmailSpelledOut
+  ]
diff --git a/Duckling/Email/FR/Corpus.hs b/Duckling/Email/FR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Email/FR/Corpus.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Email.FR.Corpus
+  ( corpus
+  ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Email.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = FR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (EmailData "alice@exAmple.io")
+             [ "alice arobase exAmple.io"
+             ]
+  , examples (EmailData "yo+yo@blah.org")
+             [ "yo+yo arobase blah.org"
+             ]
+  , examples (EmailData "1234+abc@x.net")
+             [ "1234+abc arobase x.net"
+             ]
+  , examples (EmailData "jean-jacques@stuff.co.uk")
+             [ "jean-jacques arobase stuff.co.uk"
+             ]
+  ]
diff --git a/Duckling/Email/FR/Rules.hs b/Duckling/Email/FR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Email/FR/Rules.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Email.FR.Rules
+  ( rules ) where
+
+import Data.String
+import qualified Data.Text as Text
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Email.Types (EmailData (..))
+import qualified Duckling.Email.Types as TEmail
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleEmailSpelledOut :: Rule
+ruleEmailSpelledOut = Rule
+  { name = "email spelled out"
+  , pattern =
+    [ regex "([\\w\\._+-]+) arobase ([\\w_-]+(\\.[\\w_-]+)+)"
+    ]
+  , prod = \xs -> case xs of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) ->
+        Just $ Token Email EmailData {TEmail.value = Text.concat [m1, "@", m2]}
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleEmailSpelledOut
+  ]
diff --git a/Duckling/Email/IT/Corpus.hs b/Duckling/Email/IT/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Email/IT/Corpus.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Email.IT.Corpus
+  ( corpus
+  ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Email.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = IT}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (EmailData "alice@exAmple.io")
+             [ "alice chiocciola exAmple.io"
+             ]
+  , examples (EmailData "yo+yo@blah.org")
+             [ "yo+yo chiocciola blah.org"
+             ]
+  , examples (EmailData "1234+abc@x.net")
+             [ "1234+abc chiocciola x.net"
+             ]
+  , examples (EmailData "jean-jacques@stuff.co.uk")
+             [ "jean-jacques chiocciola stuff.co.uk"
+             ]
+  ]
diff --git a/Duckling/Email/IT/Rules.hs b/Duckling/Email/IT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Email/IT/Rules.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Email.IT.Rules
+  ( rules ) where
+
+import Data.String
+import qualified Data.Text as Text
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Email.Types (EmailData (..))
+import qualified Duckling.Email.Types as TEmail
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleEmailSpelledOut :: Rule
+ruleEmailSpelledOut = Rule
+  { name = "email spelled out"
+  , pattern =
+    [ regex "([\\w\\._+-]+) chiocciola ([\\w_-]+(\\.[\\w_-]+)+)"
+    ]
+  , prod = \xs -> case xs of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) ->
+        Just $ Token Email EmailData {TEmail.value = Text.concat [m1, "@", m2]}
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleEmailSpelledOut
+  ]
diff --git a/Duckling/Email/Rules.hs b/Duckling/Email/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Email/Rules.hs
@@ -0,0 +1,39 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Email.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Email.Types (EmailData (..))
+import qualified Duckling.Email.Types as TEmail
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleEmail :: Rule
+ruleEmail = Rule
+  { name = "email"
+  , pattern =
+    [ regex "([\\w\\._+-]+@[\\w_-]+(\\.[\\w_-]+)+)"
+    ]
+  , prod = \xs -> case xs of
+      (Token RegexMatch (GroupMatch (x:_)):_) ->
+        Just $ Token Email EmailData {TEmail.value = x}
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleEmail
+  ]
diff --git a/Duckling/Email/Types.hs b/Duckling/Email/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Email/Types.hs
@@ -0,0 +1,35 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Duckling.Email.Types where
+
+import Control.DeepSeq
+import Data.Aeson
+import Data.Hashable
+import Data.Text (Text)
+import GHC.Generics
+import Prelude
+
+import Duckling.Resolve (Resolve(..))
+
+newtype EmailData = EmailData { value :: Text }
+  deriving (Eq, Generic, Hashable, Ord, Show, NFData)
+
+instance Resolve EmailData where
+  type ResolvedValue EmailData = EmailData
+  resolve _ x = Just x
+
+instance ToJSON EmailData where
+  toJSON EmailData {value} = object [ "value" .= value ]
diff --git a/Duckling/Engine.hs b/Duckling/Engine.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Engine.hs
@@ -0,0 +1,228 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Engine
+  ( parseAndResolve
+  , lookupRegexAnywhere
+  , runDuckling
+  ) where
+
+import Control.DeepSeq
+import Control.Monad.Extra
+import Data.Aeson
+import qualified Data.Array as Array
+import Data.ByteString (ByteString)
+import Data.Functor.Identity
+import qualified Data.Foldable as Foldable
+import Data.Maybe
+import Data.Text (Text)
+import qualified Data.List as L
+import Prelude
+import qualified Text.Regex.PCRE as PCRE
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Engine.Regex as Regex
+import Duckling.Regex.Types
+import Duckling.Resolve
+import Duckling.Types
+import qualified Duckling.Types.Document as Document
+import Duckling.Types.Document (Document)
+import qualified Duckling.Types.Stash as Stash
+import Duckling.Types.Stash (Stash)
+
+-- -----------------------------------------------------------------
+-- Engine
+
+type Duckling a = Identity a
+
+runDuckling :: Duckling a -> a
+runDuckling ma = runIdentity ma
+
+parseAndResolve :: [Rule] -> Text -> Context -> [ResolvedToken]
+parseAndResolve rules input context = mapMaybe (resolveNode context) .
+  force $ Stash.toPosOrderedList $ runDuckling $
+  parseString rules (Document.fromText input)
+
+produce :: Match -> Maybe Node
+produce (_, _, []) = Nothing
+produce (Rule name _ production, _, etuor@(Node {nodeRange = Range _ e}:_)) = do
+  let route = reverse etuor
+  token <- force $ production $ map token route
+  case route of
+    (Node {nodeRange = Range p _}:_) -> Just Node
+      { nodeRange = Range p e
+      , token = token
+      , children = route
+      , rule = Just name
+      }
+    [] -> Nothing
+
+-- | Handle a regex match at a given position
+lookupRegex :: Document -> PCRE.Regex -> Int -> Duckling [Node]
+lookupRegex doc _regex position | position >= Document.length doc = return []
+lookupRegex doc regex position =
+  lookupRegexCommon doc regex position Regex.matchOnce
+
+-- | Handle a regex match anywhere in the text
+lookupRegexAnywhere :: Document -> PCRE.Regex -> Duckling [Node]
+lookupRegexAnywhere doc regex = lookupRegexCommon doc regex 0 Regex.matchAll
+
+{-# INLINE lookupRegexCommon #-}
+-- INLINE bloats the code a bit, but the code is better
+lookupRegexCommon
+  :: Foldable t
+  => Document
+  -> PCRE.Regex
+  -> Int
+  -> (PCRE.Regex -> ByteString -> t PCRE.MatchArray)
+  -> Duckling [Node]
+lookupRegexCommon doc regex position matchFun = return nodes
+  where
+  -- See Note [Regular expressions and Text] to understand what's going
+  -- on here
+  (substring, rangeToText, translateRange) =
+    Document.byteStringFromPos doc position
+  nodes = mapMaybe (f . Array.elems) $ Foldable.toList $
+    matchFun regex substring
+  f :: [(Int, Int)] -> Maybe Node
+  f [] = Nothing
+  f ((0,0):_) = Nothing
+  f ((bsStart, bsLen):groups) =
+    if Document.isRangeValid doc start end
+      then Just node
+      else Nothing
+    where
+    textGroups = map rangeToText groups
+    (start, end) = translateRange bsStart bsLen
+    node = Node
+      { nodeRange = Range start end
+      , token = Token RegexMatch (GroupMatch textGroups)
+      , children = []
+      , rule = Nothing
+      }
+
+-- | Handle one PatternItem at a given position
+lookupItem :: Document -> PatternItem -> Stash -> Int -> Duckling [Node]
+lookupItem doc (Regex re) _ position =
+  filter (isPositionValid position doc) <$>
+  lookupRegex doc re position
+lookupItem doc (Predicate p) stash position =
+  return $
+  filter (p . token) $
+  takeWhile (isPositionValid position doc) $
+  Stash.toPosOrderedListFrom stash position
+
+-- | Handle one PatternItem anywhere in the text
+lookupItemAnywhere :: Document -> PatternItem -> Stash -> Duckling [Node]
+lookupItemAnywhere doc (Regex re) _ = lookupRegexAnywhere doc re
+lookupItemAnywhere _doc (Predicate p) stash =
+  return $ filter (p . token) $ Stash.toPosOrderedList stash
+
+isPositionValid :: Int -> Document -> Node -> Bool
+isPositionValid position sentence (Node {nodeRange = Range start _}) =
+  Document.isAdjacent sentence position start
+
+-- | A match is full if its rule pattern is empty.
+-- (rule, endPosition, reversedRoute)
+type Match = (Rule, Int, [Node])
+
+-- | Recursively augments `matches`.
+-- Discards partial matches stuck by a regex.
+matchAll :: Document -> Stash -> [Match] -> Duckling [Match]
+matchAll sentence stash matches = concatMapM mkNextMatches matches
+  where
+    mkNextMatches :: Match -> Duckling [Match]
+    mkNextMatches match@(Rule {pattern = []}, _, _) = return [ match ]
+    mkNextMatches match@(Rule {pattern = p:_}, _, _) = do
+      nextMatches <- matchAll sentence stash =<< matchFirst sentence stash match
+      return $ case p of
+        Regex _ -> nextMatches
+        Predicate _ -> match:nextMatches
+
+-- | Returns all matches matching the first pattern item of `match`,
+-- resuming from a Match position
+matchFirst :: Document -> Stash -> Match -> Duckling [Match]
+matchFirst _ _ (Rule {pattern = []}, _, _) = return []
+matchFirst sentence stash (rule@(Rule {pattern = p:ps}), position, route) =
+  map (mkMatch route newRule) <$> lookupItem sentence p stash position
+  where
+  newRule = rule { pattern = ps }
+
+-- | Returns all matches matching the first pattern item of `match`,
+-- starting anywhere
+matchFirstAnywhere :: Document -> Stash -> Rule -> Duckling [Match]
+matchFirstAnywhere _sentence _stash Rule {pattern = []} = return []
+matchFirstAnywhere sentence stash rule@(Rule {pattern = p:ps}) =
+  map (mkMatch [] newRule) <$> lookupItemAnywhere sentence p stash
+  where
+  newRule = rule { pattern = ps }
+
+{-# INLINE mkMatch #-}
+mkMatch :: [Node] -> Rule -> Node -> Match
+mkMatch route newRule (node@Node {nodeRange = Range _ pos'}) =
+  newRoute `seq` (newRule, pos', newRoute)
+  where newRoute = node:route
+
+-- | Finds new matches resulting from newly added tokens.
+-- Produces new tokens from full matches.
+parseString1
+  :: [Rule] -> Document -> Stash -> Stash -> [Match]
+  -> Duckling (Stash, [Match])
+parseString1 rules sentence stash new matches = do
+  -- Recursively match patterns.
+  -- Find which `matches` can advance because of `new`.
+  newPartial <- concatMapM (matchFirst sentence new) matches
+
+  -- Find new matches resulting from newly added tokens (`new`)
+  newMatches <- concatMapM (matchFirstAnywhere sentence new) rules
+
+  (full, partial) <- L.partition (\(Rule {pattern}, _, _) -> null pattern)
+    <$> matchAll sentence stash (newPartial ++ newMatches)
+
+  -- Produce full matches as new tokens
+  return ( Stash.fromList $ mapMaybe produce full
+         , partial ++ matches
+         )
+
+-- | Produces all tokens recursively.
+saturateParseString
+  :: [Rule] -> Document -> Stash -> Stash -> [Match] -> Duckling Stash
+saturateParseString rules sentence stash new matches = do
+  (new', matches') <- parseString1 rules sentence stash new matches
+  let stash' = Stash.union stash new'
+  if Stash.null new'
+    then return stash
+    else saturateParseString rules sentence stash' new' matches'
+
+parseString :: [Rule] -> Document -> Duckling Stash
+parseString rules sentence = do
+  (new, partialMatches) <-
+    -- One the first pass we try all the rules
+    parseString1 rules sentence Stash.empty Stash.empty []
+  if Stash.null new
+    then return Stash.empty
+    else
+    -- For subsequent passes, we only try rules starting with a predicate.
+    saturateParseString headPredicateRules sentence new new partialMatches
+  where
+  headPredicateRules =
+    [ rule | rule@(Rule {pattern = (Predicate _:_)}) <- rules ]
+
+resolveNode :: Context -> Node -> Maybe ResolvedToken
+resolveNode context n@Node{token = (Token _ dd), nodeRange = nodeRange} = do
+  val <- resolve context dd
+  Just Resolved
+    { range = nodeRange
+    , node = n
+    , jsonValue = toJSON val
+    }
diff --git a/Duckling/Engine/Regex.hs b/Duckling/Engine/Regex.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Engine/Regex.hs
@@ -0,0 +1,14 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Engine.Regex
+  ( matchAll
+  , matchOnce
+  ) where
+
+import Text.Regex.Base (matchAll, matchOnce)
diff --git a/Duckling/Lang.hs b/Duckling/Lang.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Lang.hs
@@ -0,0 +1,54 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+
+module Duckling.Lang
+  ( Lang(..)
+  ) where
+
+import Prelude
+
+import Data.Hashable
+import GHC.Generics
+import TextShow (TextShow)
+import qualified TextShow as TS
+
+data Lang
+  = AR
+  | DA
+  | DE
+  | EN
+  | ES
+  | ET
+  | FR
+  | GA
+  | HE
+  | HR
+  | ID
+  | IT
+  | JA
+  | KO
+  | MY
+  | NB
+  | NL
+  | PL
+  | PT
+  | RO
+  | RU
+  | SV
+  | TR
+  | UK
+  | VI
+  | ZH
+  deriving (Bounded, Enum, Eq, Generic, Hashable, Ord, Read, Show)
+
+instance TextShow Lang where
+  showb = TS.fromString . show
diff --git a/Duckling/Numeral/AR/Corpus.hs b/Duckling/Numeral/AR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/AR/Corpus.hs
@@ -0,0 +1,117 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.AR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = AR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "صفر"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "واحد"
+             ]
+  , examples (NumeralValue 4)
+             [ "4"
+             , "أربعة"
+             , "أربع"
+             ]
+  , examples (NumeralValue 6)
+             [ "6"
+             , "ستة"
+             , "ست"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "ثلاث و ثلاثون"
+             ]
+  , examples (NumeralValue 11)
+             [ "11"
+             , "إحدى عشرة"
+             , "إحدى عشر"
+             ]
+  , examples (NumeralValue 12)
+             [ "12"
+             , "إثنتى عشر"
+             , "إثنى عشر"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "أربع عشر"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "ستة عشر"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "سبع عشر"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "ثمان عشرة"
+             ]
+  , examples (NumeralValue 525)
+             [ "خمسمائة و خمسة و عشرون"
+             , "525"
+             ]
+  , examples (NumeralValue 21)
+             [ "واحدة و عشرون"
+             , "21"
+             ]
+  , examples (NumeralValue 24)
+             [ "أربعة و عشرون"
+             , "24"
+             ]
+  , examples (NumeralValue 26)
+             [ "ستة و عشرون"
+             , "26"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1.1"
+             , "1.10"
+             , "01.10"
+             , "1 فاصلة 1"
+             , "واحد فاصلة واحد"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0.77"
+             , ".77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100000"
+             , "100 الف"
+             ]
+  , examples (NumeralValue 10000)
+             [ "10000"
+             , "10 آلاف"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3 ملايين"
+             , "3000000"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "-1200000"
+             ]
+  ]
diff --git a/Duckling/Numeral/AR/Rules.hs b/Duckling/Numeral/AR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/AR/Rules.hs
@@ -0,0 +1,371 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.AR.Rules
+  ( rules ) where
+
+import Data.Maybe
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleInteger5 :: Rule
+ruleInteger5 = Rule
+  { name = "integer 4"
+  , pattern =
+    [ regex "(\x0623\x0631\x0628\x0639(\x0629)?)"
+    ]
+  , prod = \_ -> integer 4
+  }
+
+ruleInteger23 :: Rule
+ruleInteger23 = Rule
+  { name = "integer 101..999"
+  , pattern =
+    [ oneOf [100, 200 .. 900]
+    , regex "\x0648"
+    , numberBetween 1 100
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleInteger18 :: Rule
+ruleInteger18 = Rule
+  { name = "integer 12"
+  , pattern =
+    [ regex "(\x0625\x062b\x0646(\x062a)?\x0649 \x0639\x0634\x0631)"
+    ]
+  , prod = \_ -> integer 12
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- parseInt match
+        integer $ toInteger v
+      _ -> Nothing
+  }
+
+ruleInteger19 :: Rule
+ruleInteger19 = Rule
+  { name = "integer (20..90)"
+  , pattern =
+    [ regex "(\x0639\x0634\x0631\x0648\x0646|\x062b\x0644\x0627\x062b\x0648\x0646|\x0623\x0631\x0628\x0639\x0648\x0646|\x062e\x0645\x0633\x0648\x0646|\x0633\x062a\x0648\x0646|\x0633\x0628\x0639\x0648\x0646|\x062b\x0645\x0627\x0646\x0648\x0646|\x062a\x0633\x0639\x0648\x0646)"
+    ]
+  , prod = \tokens -> case tokens of
+      Token RegexMatch (GroupMatch (match:_)):_ -> case match of
+        "\x0639\x0634\x0631\x0648\x0646" -> integer 20
+        "\x062b\x0644\x0627\x062b\x0648\x0646" -> integer 30
+        "\x0623\x0631\x0628\x0639\x0648\x0646" -> integer 40
+        "\x062e\x0645\x0633\x0648\x0646" -> integer 50
+        "\x0633\x062a\x0648\x0646" -> integer 60
+        "\x0633\x0628\x0639\x0648\x0646" -> integer 70
+        "\x062b\x0645\x0627\x0646\x0648\x0646" -> integer 80
+        "\x062a\x0633\x0639\x0648\x0646" -> integer 90
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleInteger22 :: Rule
+ruleInteger22 = Rule
+  { name = "integer 21..99"
+  , pattern =
+    [ numberBetween 1 10
+    , regex "\x0648"
+    , oneOf [20, 30 .. 90]
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleInteger21 :: Rule
+ruleInteger21 = Rule
+  { name = "integer (13..19)"
+  , pattern =
+    [ numberBetween 3 10
+    , numberWith TNumeral.value (== 10)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ v + 10
+      _ -> Nothing
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(,\\d\\d\\d)+\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+ruleMultiply :: Rule
+ruleMultiply = Rule
+  { name = "compose by multiplication"
+  , pattern =
+    [ dimension Numeral
+    , numberWith TNumeral.multipliable id
+    ]
+  , prod = \tokens -> case tokens of
+      (token1:token2:_) -> multiply token1 token2
+      _ -> Nothing
+  }
+
+ruleInteger15 :: Rule
+ruleInteger15 = Rule
+  { name = "integer 11"
+  , pattern =
+    [ regex "(\x0625\x062d\x062f\x0649 \x0639\x0634\x0631(\x0629)?)"
+    ]
+  , prod = \_ -> integer 11
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDecimal True match
+      _ -> Nothing
+  }
+
+rulePowersOfTen :: Rule
+rulePowersOfTen = Rule
+  { name = "powers of tens"
+  , pattern =
+    [ regex "(\x0645\x0627\x0626\x0629|\x0645\x0626\x0627\x062a|\x0623\x0644\x0641|\x0627\x0644\x0641|\x0622\x0644\x0627\x0641|\x0645\x0644\x0627\x064a\x064a(\x0646)?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "\x0645\x0627\x0626\x0629" ->
+          double 1e2 >>= withGrain 2 >>= withMultipliable
+        "\x0645\x0626\x0627\x062a" ->
+          double 1e2 >>= withGrain 2 >>= withMultipliable
+        "\x0623\x0644\x0641" -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "\x0627\x0644\x0641" -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "\x0622\x0644\x0627\x0641" ->
+          double 1e3 >>= withGrain 3 >>= withMultipliable
+        "\x0645\x0644\x0627\x064a\x064a" ->
+          double 1e6 >>= withGrain 6 >>= withMultipliable
+        "\x0645\x0644\x0627\x064a\x064a\x0646" ->
+          double 1e6 >>= withGrain 6 >>= withMultipliable
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer 2"
+  , pattern =
+    [ regex "(\x0627\x062b\x0646\x0627\x0646|\x0627\x062b\x0646\x064a\x0646)"
+    ]
+  , prod = \_ -> integer 2
+  }
+
+ruleInteger13 :: Rule
+ruleInteger13 = Rule
+  { name = "integer 9"
+  , pattern =
+    [ regex "(\x062a\x0633\x0639\x0629|\x062a\x0633\x0639)"
+    ]
+  , prod = \_ -> integer 9
+  }
+
+ruleInteger12 :: Rule
+ruleInteger12 = Rule
+  { name = "integer 8"
+  , pattern =
+    [ regex "(\x062b\x0645\x0627\x0646\x064a\x0629|\x062b\x0645\x0627\x0646)"
+    ]
+  , prod = \_ -> integer 8
+  }
+
+ruleNumeralsPrefixWithMinus :: Rule
+ruleNumeralsPrefixWithMinus = Rule
+  { name = "numbers prefix with -, minus"
+  , pattern =
+    [ regex "-"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        double (v * (- 1))
+      _ -> Nothing
+  }
+
+ruleInteger7 :: Rule
+ruleInteger7 = Rule
+  { name = "integer 5"
+  , pattern =
+    [ regex "(\x062e\x0645\x0633)(\x0629)?"
+    ]
+  , prod = \_ -> integer 5
+  }
+
+ruleInteger14 :: Rule
+ruleInteger14 = Rule
+  { name = "integer 10"
+  , pattern =
+    [ regex "(\x0639\x0634\x0631\x0629|\x0639\x0634\x0631)"
+    ]
+  , prod = \_ -> integer 10
+  }
+
+ruleInteger9 :: Rule
+ruleInteger9 = Rule
+  { name = "integer 6"
+  , pattern =
+    [ regex "(\x0633\x062a(\x0629)?)"
+    ]
+  , prod = \_ -> integer 6
+  }
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer 0"
+  , pattern =
+    [ regex "(\x0635\x0641\x0631)"
+    ]
+  , prod = \_ -> integer 0
+  }
+
+ruleInteger4 :: Rule
+ruleInteger4 = Rule
+  { name = "integer 3"
+  , pattern =
+    [ regex "(\x062b\x0644\x0627\x062b|\x062b\x0644\x0627\x062b\x0629)"
+    ]
+  , prod = \_ -> integer 3
+  }
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer 1"
+  , pattern =
+    [ regex "(\x0648\x0627\x062d\x062f\x0629|\x0648\x0627\x062d\x062f\x0647|\x0648\x0627\x062d\x062f)"
+    ]
+  , prod = \_ -> integer 1
+  }
+
+ruleInteger11 :: Rule
+ruleInteger11 = Rule
+  { name = "integer 7"
+  , pattern =
+    [ regex "(\x0633\x0628\x0639\x0629|\x0633\x0628\x0639)"
+    ]
+  , prod = \_ -> integer 7
+  }
+
+ruleInteger20 :: Rule
+ruleInteger20 = Rule
+  { name = "integer (100..900)"
+  , pattern =
+    [ regex "(\x0645\x0627\x0626\x0629|\x0645\x0627\x0626\x062a\x0627\x0646|\x062b\x0644\x0627\x062b\x0645\x0627\x0626\x0629|\x0623\x0631\x0628\x0639\x0645\x0627\x0626\x0629|\x062e\x0645\x0633\x0645\x0627\x0626\x0629|\x0633\x062a\x0645\x0627\x0626\x0629|\x0633\x0628\x0639\x0645\x0627\x0626\x0629|\x062b\x0645\x0627\x0646\x0645\x0627\x0626\x0629|\x062a\x0633\x0639\x0645\x0627\x0626\x0629)"
+    ]
+  , prod = \tokens -> case tokens of
+      Token RegexMatch (GroupMatch (match:_)):_ -> case match of
+        "\x0645\x0627\x0626\x0629" -> integer 100
+        "\x0633\x0628\x0639\x0645\x0627\x0626\x0629" -> integer 700
+        "\x062e\x0645\x0633\x0645\x0627\x0626\x0629" -> integer 500
+        "\x0623\x0631\x0628\x0639\x0645\x0627\x0626\x0629" -> integer 400
+        "\x0633\x062a\x0645\x0627\x0626\x0629" -> integer 600
+        "\x0645\x0627\x0626\x062a\x0627\x0646" -> integer 200
+        "\x062b\x0644\x0627\x062b\x0645\x0627\x0626\x0629" -> integer 300
+        "\x062b\x0645\x0627\x0646\x0645\x0627\x0626\x0629" -> integer 800
+        "\x062a\x0633\x0639\x0645\x0627\x0626\x0629" -> integer 900
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumeralDotNumeral :: Rule
+ruleNumeralDotNumeral = Rule
+  { name = "number dot number"
+  , pattern =
+    [ dimension Numeral
+    , regex "\x0641\x0627\x0635\x0644\x0629"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + decimalsToDouble v2
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ,"
+  , pattern =
+    [ regex "(\\d{1,3}(,\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleInteger
+  , ruleInteger11
+  , ruleInteger12
+  , ruleInteger13
+  , ruleInteger14
+  , ruleInteger15
+  , ruleInteger18
+  , ruleInteger19
+  , ruleInteger2
+  , ruleInteger20
+  , ruleInteger21
+  , ruleInteger22
+  , ruleInteger23
+  , ruleInteger3
+  , ruleInteger4
+  , ruleInteger5
+  , ruleInteger7
+  , ruleInteger9
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleMultiply
+  , ruleNumeralDotNumeral
+  , ruleNumeralsPrefixWithMinus
+  , rulePowersOfTen
+  ]
diff --git a/Duckling/Numeral/DA/Corpus.hs b/Duckling/Numeral/DA/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/DA/Corpus.hs
@@ -0,0 +1,101 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.DA.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = DA}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "nul"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "én"
+             , "en"
+             , "ét"
+             , "et"
+             ]
+  , examples (NumeralValue 2)
+             [ "2"
+             , "to"
+             , "et par"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "fjorten"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "seksten"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "sytten"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "Atten"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1,1"
+             , "1,10"
+             , "01,10"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0,77"
+             , ",77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100.000"
+             , "100000"
+             , "100K"
+             , "100k"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3M"
+             , "3000K"
+             , "3000000"
+             , "3.000.000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1.200.000"
+             , "1200000"
+             , "1,2M"
+             , "1200K"
+             , ",0012G"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "- 1.200.000"
+             , "-1200000"
+             , "minus 1.200.000"
+             , "negativ 1200000"
+             , "-1,2M"
+             , "-1200K"
+             , "-,0012G"
+             ]
+  , examples (NumeralValue 5000)
+             [ "5 tusind"
+             , "fem tusinde"
+             , "fem Tusind"
+             ]
+  ]
diff --git a/Duckling/Numeral/DA/Rules.hs b/Duckling/Numeral/DA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/DA/Rules.hs
@@ -0,0 +1,327 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.DA.Rules
+  ( rules ) where
+
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.Maybe
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleNumeralsPrefixWithNegativeOrMinus :: Rule
+ruleNumeralsPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|minus\\s?|negativ\\s?"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        double $ v * (-1)
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- parseInt match
+        integer $ toInteger v
+      _ -> Nothing
+  }
+
+ruleFew :: Rule
+ruleFew = Rule
+  { name = "few"
+  , pattern =
+    [ regex "(nogle )?f\x00e5"
+    ]
+  , prod = \_ -> integer 3
+  }
+
+ruleTen :: Rule
+ruleTen = Rule
+  { name = "ten"
+  , pattern =
+    [ regex "ti"
+    ]
+  , prod = \_ -> integer 10 >>= withGrain 1
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(\\.\\d\\d\\d)+\\,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton '.') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDecimal False match
+      _ -> Nothing
+  }
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer 21..99"
+  , pattern =
+    [ oneOf [20, 30 .. 90]
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleSingle :: Rule
+ruleSingle = Rule
+  { name = "single"
+  , pattern =
+    [ regex "enkelt"
+    ]
+  , prod = \_ -> integer 1 >>= withGrain 1
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "([kmg])(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+          "k" -> double $ v * 1e3
+          "m" -> double $ v * 1e6
+          "g" -> double $ v * 1e9
+          _   -> Nothing
+      _ -> Nothing
+  }
+
+rulePowersOfTen :: Rule
+rulePowersOfTen = Rule
+  { name = "powers of tens"
+  , pattern =
+    [ regex "(hundrede?|tusinde?|million(er)?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "hundred"   -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "hundrede"  -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "tusind"    -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "tusinde"   -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "million"   -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        "millioner" -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        _           -> Nothing
+      _ -> Nothing
+  }
+
+ruleAPair :: Rule
+ruleAPair = Rule
+  { name = "a pair"
+  , pattern =
+    [ regex "et par"
+    ]
+  , prod = \_ -> integer 2 >>= withGrain 1
+  }
+
+ruleNumeralsOg :: Rule
+ruleNumeralsOg = Rule
+  { name = "numbers og"
+  , pattern =
+    [ numberBetween 1 10
+    , regex "og"
+    , oneOf [70, 20, 60, 50, 40, 90, 30, 80]
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleDozen :: Rule
+ruleDozen = Rule
+  { name = "dozen"
+  , pattern =
+    [ regex "dusin"
+    ]
+  , prod = \_ -> integer 12 >>= withGrain 1 >>= withMultipliable
+  }
+
+ruleMultiply :: Rule
+ruleMultiply = Rule
+  { name = "compose by multiplication"
+  , pattern =
+    [ dimension Numeral
+    , numberWith TNumeral.multipliable id
+    ]
+  , prod = \tokens -> case tokens of
+      (token1:token2:_) -> multiply token1 token2
+      _ -> Nothing
+  }
+
+zeroNineteenMap :: HashMap Text Integer
+zeroNineteenMap = HashMap.fromList
+  [ ("ingen", 0)
+  , ("nul", 0)
+  , ("intet", 0)
+  , ("en", 1)
+  , ("et", 1)
+  , ("\x00e9n", 1)
+  , ("\x00e9t", 1)
+  , ("to", 2)
+  , ("tre", 3)
+  , ("fire", 4)
+  , ("fem", 5)
+  , ("seks", 6)
+  , ("syv", 7)
+  , ("otte", 8)
+  , ("ni", 9)
+  , ("ti", 10)
+  , ("elleve", 11)
+  , ("tolv", 12)
+  , ("tretten", 13)
+  , ("fjorten", 14)
+  , ("femten", 15)
+  , ("seksten", 16)
+  , ("sytten", 17)
+  , ("atten", 18)
+  , ("nitten", 19)
+  ]
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer (0..19)"
+  , pattern =
+    [ regex "(intet|ingen|nul|en|et|\x00e9n|\x00e9t|to|tretten|tre|fire|femten|fem|seksten|seks|syv|otte|nitten|ni|ti|elleve|tolv|fjorten|sytten|atten)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) zeroNineteenMap >>= integer
+      _ -> Nothing
+  }
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer (20..90)"
+  , pattern =
+    [ regex "(tyve|tredive|fyrre|halvtreds|tres|halvfjerds|firs|halvfems)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "tyve" -> integer 20
+        "tredive" -> integer 30
+        "fyrre" -> integer 40
+        "halvtreds" -> integer 50
+        "tres" -> integer 60
+        "halvfjerds" -> integer 70
+        "firs" -> integer 80
+        "halvfems" -> integer 90
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumeralDotNumeral :: Rule
+ruleNumeralDotNumeral = Rule
+  { name = "number dot number"
+  , pattern =
+    [ dimension Numeral
+    , regex "komma"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + decimalsToDouble v2
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ."
+  , pattern =
+    [ regex "(\\d{1,3}(\\.\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton '.') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAPair
+  , ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleDozen
+  , ruleFew
+  , ruleInteger
+  , ruleInteger2
+  , ruleInteger3
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleIntersect
+  , ruleMultiply
+  , ruleNumeralDotNumeral
+  , ruleNumeralsOg
+  , ruleNumeralsPrefixWithNegativeOrMinus
+  , ruleNumeralsSuffixesKMG
+  , rulePowersOfTen
+  , ruleSingle
+  , ruleTen
+  ]
diff --git a/Duckling/Numeral/DE/Corpus.hs b/Duckling/Numeral/DE/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/DE/Corpus.hs
@@ -0,0 +1,133 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.DE.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = DE}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "null"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "eins"
+             ]
+  , examples (NumeralValue 3)
+             [ "3"
+             , "Drei"
+             ]
+  , examples (NumeralValue 30)
+             [ "30"
+             , "dreissig"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "drei Und dreissig"
+             , "dreiunddreissig"
+             , "0033"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "vierzehn"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "sechzehn"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "Siebzehn"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "achtzehn"
+             ]
+  , examples (NumeralValue 200)
+             [ "200"
+             , "zwei hundert"
+             ]
+  , examples (NumeralValue 102)
+             [ "102"
+             , "Hundert zwei"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1,1"
+             , "1 komma 1"
+             , "1,10"
+             , "01,10"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0,77"
+             , ",77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100.000"
+             , "100000"
+             , "100K"
+             , "100k"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3M"
+             , "3000K"
+             , "3000000"
+             , "3.000.000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1.200.000"
+             , "1200000"
+             , "1,2M"
+             , "1200K"
+             , ",0012G"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "- 1.200.000"
+             , "-1200000"
+             , "minus 1.200.000"
+             , "negativ 1200000"
+             , "-1,2M"
+             , "-1200K"
+             , "-,0012G"
+             ]
+  , examples (NumeralValue 5000)
+             [ "5 tausend"
+             , "fünf tausend"
+             ]
+  , examples (NumeralValue 200000)
+             [ "zwei hundert tausend"
+             ]
+  , examples (NumeralValue 721012)
+             [ "sieben hundert einundzwanzig tausend zwölf"
+             ]
+  , examples (NumeralValue 31256721)
+             [ "ein und dreissig millionen zwei hundert sechs und fünfzig tausend sieben hundert ein und zwanzig"
+             ]
+  , examples (NumeralValue 1416.15)
+             [ "1416,15"
+             ]
+  , examples (NumeralValue 1416.15)
+             [ "1.416,15"
+             ]
+  , examples (NumeralValue 1000000.0)
+             [ "1.000.000,00"
+             ]
+  ]
diff --git a/Duckling/Numeral/DE/Rules.hs b/Duckling/Numeral/DE/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/DE/Rules.hs
@@ -0,0 +1,389 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.DE.Rules
+  ( rules ) where
+
+import Control.Monad (join)
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.Maybe
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleNumeralsPrefixWithNegativeOrMinus :: Rule
+ruleNumeralsPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|minus|negativ"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        double $ v * (- 1)
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- parseInt match
+        integer $ toInteger v
+      _ -> Nothing
+  }
+
+ruleFew :: Rule
+ruleFew = Rule
+  { name = "few"
+  , pattern =
+    [ regex "mehrere"
+    ]
+  , prod = \_ -> integer 3
+  }
+
+ruleTen :: Rule
+ruleTen = Rule
+  { name = "ten"
+  , pattern =
+    [ regex "zehn"
+    ]
+  , prod = \_ -> integer 10 >>= withGrain 1
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(\\.\\d\\d\\d)+\\,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        let dot = Text.singleton '.'
+            comma = Text.singleton ','
+            fmt = Text.replace comma dot $ Text.replace dot Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDecimal False match
+      _ -> Nothing
+  }
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer ([2-9][1-9])"
+  , pattern =
+    [ regex "(ein|zwei|drei|vier|f\x00fcnf|sechs|sieben|acht|neun)und(zwanzig|dreissig|vierzig|f\x00fcnfzig|sechzig|siebzig|achtzig|neunzig)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        v1 <- case Text.toLower m1 of
+          "ein" -> Just 1
+          "zwei" -> Just 2
+          "drei" -> Just 3
+          "vier" -> Just 4
+          "f\x00fcnf" -> Just 5
+          "sechs" -> Just 6
+          "sieben" -> Just 7
+          "acht" -> Just 8
+          "neun" -> Just 9
+          _ -> Nothing
+        v2 <- case Text.toLower m2 of
+          "zwanzig" -> Just 20
+          "dreissig" -> Just 30
+          "vierzig" -> Just 40
+          "f\x00fcnfzig" -> Just 50
+          "sechzig" -> Just 60
+          "siebzig" -> Just 70
+          "achtzig" -> Just 80
+          "neunzig" -> Just 90
+          _ -> Nothing
+        integer $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleNumeralsUnd :: Rule
+ruleNumeralsUnd = Rule
+  { name = "numbers und"
+  , pattern =
+    [ numberBetween 1 10
+    , regex "und"
+    , oneOf [20, 30 .. 90]
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleMultiply :: Rule
+ruleMultiply = Rule
+  { name = "compose by multiplication"
+  , pattern =
+    [ dimension Numeral
+    , numberWith TNumeral.multipliable id
+    ]
+  , prod = \tokens -> case tokens of
+      (token1:token2:_) -> multiply token1 token2
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "([kmg])(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "k" -> double $ v * 1e3
+         "m" -> double $ v * 1e6
+         "g" -> double $ v * 1e9
+         _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleCouple :: Rule
+ruleCouple = Rule
+  { name = "couple"
+  , pattern =
+    [ regex "(ein )?paar"
+    ]
+  , prod = \_ -> integer 2
+  }
+
+ruleDozen :: Rule
+ruleDozen = Rule
+  { name = "dozen"
+  , pattern =
+    [ regex "dutzend"
+    ]
+  , prod = \_ -> integer 12 >>= withGrain 1 >>= withMultipliable
+  }
+
+rulePowersOfTen :: Rule
+rulePowersOfTen = Rule
+  { name = "powers of tens"
+  , pattern =
+    [ regex "(hunderte?|tausende?|million(en)?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "hundert"   -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "hunderte"  -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "tausend"   -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "tausende"  -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "million"   -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        "millionen" -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        _           -> Nothing
+      _ -> Nothing
+  }
+
+zeroNineteenMap :: HashMap Text Integer
+zeroNineteenMap = HashMap.fromList
+  [ ("keine", 0)
+  , ("null", 0)
+  , ("nichts", 0)
+  , ("keiner", 0)
+  , ("kein", 0)
+  , ("keins", 0)
+  , ("keinen", 0)
+  , ("keines", 0)
+  , ("einer", 1)
+  , ("eins", 1)
+  , ("ein", 1)
+  , ("eine", 1)
+  , ("einser", 1)
+  , ("zwei", 2)
+  , ("drei", 3)
+  , ("vier", 4)
+  , ("f\x00fcnf", 5)
+  , ("sechs", 6)
+  , ("sieben", 7)
+  , ("acht", 8)
+  , ("neun", 9)
+  , ("zehn", 10)
+  , ("elf", 11)
+  , ("zw\x00f6lf", 12)
+  , ("dreizehn", 13)
+  , ("vierzehn", 14)
+  , ("f\x00fcnfzehn", 15)
+  , ("sechzehn", 16)
+  , ("siebzehn", 17)
+  , ("achtzehn", 18)
+  , ("neunzehn", 19)
+  ]
+
+ruleToNineteen :: Rule
+ruleToNineteen = Rule
+  { name = "integer (0..19)"
+  -- e.g. fourteen must be before four,
+  -- otherwise four will always shadow fourteen
+  , pattern = [regex "(keine?|keine?s|keiner|keinen|null|nichts|eins?(er)?|zwei|dreizehn|drei|vierzehn|vier|f\x00fcnf|sechzehn|sechs|siebzehn|sieben|achtzehn|acht|neunzehn|neun|elf|zw\x00f6lf|f\x00fcfzehn)"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) zeroNineteenMap >>= integer
+      _ -> Nothing
+  }
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer (0..19)"
+  , pattern =
+    [ regex "(keine?|keine?s|keiner|keinen|null|nichts|eins?(er)?|zwei|dreizehn|drei|vierzehn|vier|f\x00fcnf|sechzehn|sechs|siebzehn|sieben|achtzehn|acht|neunzehn|neun|elf|zw\x00f6lf|f\x00fcfzehn)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "nichts" -> integer 0
+        "keine" -> integer 0
+        "null" -> integer 0
+        "keiner" -> integer 0
+        "kein" -> integer 0
+        "keins" -> integer 0
+        "keinen" -> integer 0
+        "keines" -> integer 0
+        "einer" -> integer 1
+        "eins" -> integer 1
+        "ein" -> integer 1
+        "eine" -> integer 1
+        "zwei" -> integer 2
+        "drei" -> integer 3
+        "vier" -> integer 4
+        "f\x00fcnf" -> integer 5
+        "sechs" -> integer 6
+        "sieben" -> integer 7
+        "acht" -> integer 8
+        "neun" -> integer 9
+        "zehn" -> integer 10
+        "elf" -> integer 11
+        "zw\x00f6lf" -> integer 12
+        "dreizehn" -> integer 13
+        "vierzehn" -> integer 14
+        "f\x00fcnfzehn" -> integer 15
+        "sechzehn" -> integer 16
+        "siebzehn" -> integer 17
+        "achtzehn" -> integer 18
+        "neunzehn" -> integer 19
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer (20..90)"
+  , pattern =
+    [ regex "(zwanzig|dreissig|vierzig|f\x00fcnfzig|sechzig|siebzig|achtzig|neunzig)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "zwanzig" -> integer 20
+        "dreissig" -> integer 30
+        "vierzig" -> integer 40
+        "f\x00fcnfzig" -> integer 50
+        "sechzig" -> integer 60
+        "siebzig" -> integer 70
+        "achtzig" -> integer 80
+        "neunzig" -> integer 90
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumeralDotNumeral :: Rule
+ruleNumeralDotNumeral = Rule
+  { name = "number dot number"
+  , pattern =
+    [ dimension Numeral
+    , regex "komma"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + decimalsToDouble v2
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ."
+  , pattern =
+    [ regex "(\\d{1,3}(\\.\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton '.') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleCouple
+  , ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleDozen
+  , ruleFew
+  , ruleInteger
+  , ruleInteger2
+  , ruleInteger3
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleIntersect
+  , ruleMultiply
+  , ruleNumeralDotNumeral
+  , ruleNumeralsPrefixWithNegativeOrMinus
+  , ruleNumeralsSuffixesKMG
+  , ruleNumeralsUnd
+  , rulePowersOfTen
+  , ruleTen
+  , ruleToNineteen
+  ]
diff --git a/Duckling/Numeral/EN/Corpus.hs b/Duckling/Numeral/EN/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/EN/Corpus.hs
@@ -0,0 +1,157 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.EN.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Numeral.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "naught"
+             , "nought"
+             , "zero"
+             , "nil"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "one"
+             , "single"
+             ]
+  , examples (NumeralValue 2)
+             [ "2"
+             , "two"
+             , "a pair"
+             , "a couple"
+             , "a couple of"
+             ]
+  , examples (NumeralValue 3)
+             [ "3"
+             , "three"
+             , "a few"
+             , "few"
+             ]
+  , examples (NumeralValue 10)
+             [ "10"
+             , "ten"
+             ]
+  , examples (NumeralValue 12)
+             [ "12"
+             , "twelve"
+             , "a dozen"
+             , "a dozen of"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "fourteen"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "sixteen"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "seventeen"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "eighteen"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "thirty three"
+             , "0033"
+             ]
+  , examples (NumeralValue 24)
+             [ "24"
+             , "2 dozens"
+             , "two dozen"
+             , "Two dozen"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1.1"
+             , "1.10"
+             , "01.10"
+             , "1 point 1"
+             ]
+  , examples (NumeralValue 0.77)
+             [ ".77"
+             , "0.77"
+             , "point 77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100,000"
+             , "100,000.0"
+             , "100000"
+             , "100K"
+             , "100k"
+             , "one hundred thousand"
+             ]
+  , examples (NumeralValue 3e6)
+             [ "3M"
+             , "3000K"
+             , "3000000"
+             , "3,000,000"
+             , "3 million"
+             ]
+  , examples (NumeralValue 1.2e6)
+             [ "1,200,000"
+             , "1200000"
+             , "1.2M"
+             , "1200k"
+             , ".0012G"
+             ]
+  , examples (NumeralValue 5000)
+             [ "5 thousand"
+             , "five thousand"
+             ]
+  , examples (NumeralValue (-1.2e6))
+             [ "- 1,200,000"
+             , "-1200000"
+             , "minus 1,200,000"
+             , "negative 1200000"
+             , "-1.2M"
+             , "-1200K"
+             , "-.0012G"
+             ]
+  , examples (NumeralValue 122)
+             [ "one twenty two"
+             , "ONE TwentY tWO"
+             ]
+  , examples (NumeralValue 2e5)
+             [ "two Hundred thousand"
+             ]
+  , examples (NumeralValue 21011)
+             [ "twenty-one thousand Eleven"
+             ]
+  , examples (NumeralValue 721012)
+             [ "seven hundred twenty-one thousand twelve"
+             , "seven hundred twenty-one thousand and twelve"
+             ]
+  , examples (NumeralValue 31256721)
+             [ "thirty-one million two hundred fifty-six thousand seven hundred twenty-one"
+             ]
+  , examples (NumeralValue 2400)
+             [ "two hundred dozens"
+             , "200 dozens"
+             ]
+  , examples (NumeralValue 2200000)
+             [ "two point two million"
+             ]
+  ]
diff --git a/Duckling/Numeral/EN/Rules.hs b/Duckling/Numeral/EN/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/EN/Rules.hs
@@ -0,0 +1,303 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.EN.Rules
+  ( rules ) where
+
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.Maybe
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleIntegers :: Rule
+ruleIntegers = Rule
+  { name = "integer (numeric)"
+  , pattern = [regex "(\\d{1,18})"]
+  , prod = \tokens ->
+      case tokens of
+        (Token RegexMatch (GroupMatch (match:_)):_) -> do
+          v <- parseInt match
+          integer $ toInteger v
+        _ -> Nothing
+  }
+
+ruleDozen :: Rule
+ruleDozen = Rule
+  { name = "a dozen of"
+  , pattern =
+    [ regex "(a )?dozens?( of)?"
+    ]
+  , prod = \_ -> integer 12 >>= withMultipliable
+  }
+
+zeroNineteenMap :: HashMap Text Integer
+zeroNineteenMap = HashMap.fromList
+  [ ( "naught", 0 )
+  , ( "nil"   , 0 )
+  , ( "nought", 0 )
+  , ( "none"  , 0 )
+  , ( "zero"  , 0 )
+  , ( "zilch" , 0 )
+  , ( "one"   , 1 )
+  , ( "single", 1 )
+  , ( "two", 2 )
+  , ( "a couple", 2 )
+  , ( "a couple of", 2 )
+  , ( "couple", 2 )
+  , ( "couples", 2 )
+  , ( "couple of", 2 )
+  , ( "couples of", 2 )
+  , ( "a pair", 2 )
+  , ( "a pair of", 2 )
+  , ( "pair", 2 )
+  , ( "pairs", 2 )
+  , ( "pair of", 2 )
+  , ( "pairs of", 2 )
+  , ( "three", 3 )
+  , ( "a few", 3 )
+  , ( "few", 3 )
+  , ( "four", 4 )
+  , ( "five", 5 )
+  , ( "six", 6 )
+  , ( "seven", 7 )
+  , ( "eight", 8 )
+  , ( "nine", 9 )
+  , ( "ten", 10 )
+  , ( "eleven", 11 )
+  , ( "twelve", 12 )
+  , ( "thirteen", 13 )
+  , ( "fourteen", 14 )
+  , ( "fifteen", 15 )
+  , ( "sixteen", 16 )
+  , ( "seventeen", 17 )
+  , ( "eighteen", 18 )
+  , ( "nineteen", 19 )
+  ]
+
+ruleToNineteen :: Rule
+ruleToNineteen = Rule
+  { name = "integer (0..19)"
+  -- e.g. fourteen must be before four, otherwise four will always shadow fourteen
+  , pattern = [regex "(none|zilch|naught|nought|nil|zero|one|single|two|(a )?(pair|couple)s?( of)?|three|(a )?few|fourteen|four|fifteen|five|sixteen|six|seventeen|seven|eighteen|eight|nineteen|nine|ten|eleven|twelve|thirteen)"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) zeroNineteenMap >>= integer
+      _ -> Nothing
+  }
+
+ruleTens :: Rule
+ruleTens = Rule
+  { name = "integer (20..90)"
+  , pattern = [regex "(twenty|thirty|fou?rty|fifty|sixty|seventy|eighty|ninety)"]
+  , prod = \tokens ->
+      case tokens of
+        (Token RegexMatch (GroupMatch (match : _)) : _) ->
+          case Text.toLower match of
+            "twenty"  -> integer 20
+            "thirty"  -> integer 30
+            "forty"   -> integer 40
+            "fourty"  -> integer 40
+            "fifty"   -> integer 50
+            "sixty"   -> integer 60
+            "seventy" -> integer 70
+            "eighty"  -> integer 80
+            "ninety"  -> integer 90
+            _         -> Nothing
+        _ -> Nothing
+  }
+
+rulePowersOfTen :: Rule
+rulePowersOfTen = Rule
+  { name = "powers of tens"
+  , pattern =
+    [ regex "(hundred|thousand|million|billion)s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "hundred"  -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "thousand" -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "million"  -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        "billion"  -> double 1e9 >>= withGrain 9 >>= withMultipliable
+        _          -> Nothing
+      _ -> Nothing
+  }
+
+ruleCompositeTens :: Rule
+ruleCompositeTens = Rule
+  { name = "integer 21..99"
+  , pattern = [oneOf [20,30..90], numberBetween 1 10]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData { TNumeral.value = tens }) :
+       Token Numeral (NumeralData { TNumeral.value = units }) :
+       _) -> double (tens + units)
+      _ -> Nothing
+  }
+
+ruleSkipHundreds :: Rule
+ruleSkipHundreds = Rule
+  { name = "one twenty two"
+  , pattern = [numberBetween 1 10, numberBetween 10 100]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData { TNumeral.value = hundreds }) :
+       Token Numeral (NumeralData { TNumeral.value = rest }) :
+       _) -> double (hundreds*100 + rest)
+      _ -> Nothing
+  }
+
+ruleDotSpelledOut :: Rule
+ruleDotSpelledOut = Rule
+  { name = "one point 2"
+  , pattern =
+    [ dimension Numeral
+    , regex "point|dot"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral nd1:_:Token Numeral nd2:_) ->
+        double $ TNumeral.value nd1 + decimalsToDouble (TNumeral.value nd2)
+      _ -> Nothing
+  }
+
+ruleLeadingDotSpelledOut :: Rule
+ruleLeadingDotSpelledOut = Rule
+  { name = "point 77"
+  , pattern =
+    [ regex "point|dot"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral nd:_) -> double . decimalsToDouble $ TNumeral.value nd
+      _ -> Nothing
+  }
+
+ruleDecimals :: Rule
+ruleDecimals = Rule
+  { name = "decimal number"
+  , pattern = [regex "(\\d*\\.\\d+)"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> parseDecimal True match
+      _ -> Nothing
+  }
+
+ruleCommas :: Rule
+ruleCommas = Rule
+  { name = "comma-separated numbers"
+  , pattern = [regex "(\\d+(,\\d\\d\\d)+(\\.\\d+)?)"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+ruleSuffixes :: Rule
+ruleSuffixes = Rule
+  { name = "suffixes (K,M,G))"
+  , pattern =
+    [ dimension Numeral
+    , regex "(k|m|g)(?=[\\W$\x20ac\x00a2\x00a3]|$)"
+    ]
+  , prod = \tokens ->
+      case tokens of
+        (Token Numeral nd : Token RegexMatch (GroupMatch (match : _)):_) -> do
+          x <- case Text.toLower match of
+            "k" -> Just 1e3
+            "m" -> Just 1e6
+            "g" -> Just 1e9
+            _ -> Nothing
+          double $ TNumeral.value nd * x
+        _ -> Nothing
+  }
+
+ruleNegative :: Rule
+ruleNegative = Rule
+  { name = "negative numbers"
+  , pattern =
+    [ regex "-|minus\\s?|negative\\s?"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral nd:_) -> double (TNumeral.value nd * (-1))
+      _ -> Nothing
+  }
+
+ruleSum :: Rule
+ruleSum = Rule
+  { name = "intersect 2 numbers"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens ->
+      case tokens of
+        (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+         Token Numeral (NumeralData {TNumeral.value = val2}):
+         _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+        _ -> Nothing
+  }
+
+ruleSumAnd :: Rule
+ruleSumAnd = Rule
+  { name = "intersect 2 numbers (with and)"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , regex "and"
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens ->
+      case tokens of
+        (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+         _:
+         Token Numeral (NumeralData {TNumeral.value = val2}):
+         _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+        _ -> Nothing
+  }
+
+ruleMultiply :: Rule
+ruleMultiply = Rule
+  { name = "compose by multiplication"
+  , pattern =
+    [ dimension Numeral
+    , numberWith TNumeral.multipliable id
+    ]
+  , prod = \tokens -> case tokens of
+      (token1:token2:_) -> multiply token1 token2
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleIntegers
+  , ruleToNineteen
+  , ruleTens
+  , rulePowersOfTen
+  , ruleCompositeTens
+  , ruleSkipHundreds
+  , ruleDotSpelledOut
+  , ruleLeadingDotSpelledOut
+  , ruleDecimals
+  , ruleCommas
+  , ruleSuffixes
+  , ruleNegative
+  , ruleSum
+  , ruleSumAnd
+  , ruleMultiply
+  , ruleDozen
+  ]
diff --git a/Duckling/Numeral/ES/Corpus.hs b/Duckling/Numeral/ES/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/ES/Corpus.hs
@@ -0,0 +1,110 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.ES.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = ES}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 1)
+             [ "1"
+             , "uno"
+             , "una"
+             ]
+  , examples (NumeralValue 11)
+             [ "once"
+             ]
+  , examples (NumeralValue 16)
+             [ "dieciséis"
+             , "dieciseis"
+             , "Diesiseis"
+             , "diez y seis"
+             ]
+  , examples (NumeralValue 21)
+             [ "veintiuno"
+             , "veinte y uno"
+             ]
+  , examples (NumeralValue 23)
+             [ "veintitrés"
+             , "veinte y tres"
+             ]
+  , examples (NumeralValue 70)
+             [ "setenta"
+             ]
+  , examples (NumeralValue 78)
+             [ "Setenta y ocho"
+             ]
+  , examples (NumeralValue 80)
+             [ "ochenta"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "treinta y tres"
+             , "treinta y 3"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1,1"
+             , "1,10"
+             , "01,10"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0,77"
+             , ",77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100.000"
+             , "100000"
+             , "100K"
+             , "100k"
+             ]
+  , examples (NumeralValue 300)
+             [ "trescientos"
+             ]
+  , examples (NumeralValue 243)
+             [ "243"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3M"
+             , "3000K"
+             , "3000000"
+             , "3.000.000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1.200.000"
+             , "1200000"
+             , "1,2M"
+             , "1200K"
+             , ",0012G"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "- 1.200.000"
+             , "-1200000"
+             , "menos 1.200.000"
+             , "-1,2M"
+             , "-1200K"
+             , "-,0012G"
+             ]
+  , examples (NumeralValue 1.5)
+             [ "1 punto cinco"
+             , "una punto cinco"
+             , "1,5"
+             ]
+  ]
diff --git a/Duckling/Numeral/ES/Rules.hs b/Duckling/Numeral/ES/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/ES/Rules.hs
@@ -0,0 +1,315 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.ES.Rules
+  ( rules ) where
+
+import qualified Data.HashMap.Strict as HashMap
+import Data.Maybe
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleNumeralsPrefixWithNegativeOrMinus :: Rule
+ruleNumeralsPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|menos"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        double $ v * (- 1)
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- parseInt match
+        integer $ toInteger v
+      _ -> Nothing
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(\\.\\d\\d\\d)+,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        let dot = Text.singleton '.'
+            comma = Text.singleton ','
+            fmt = Text.replace comma dot $ Text.replace dot Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDecimal False match
+      _ -> Nothing
+  }
+
+byTensMap :: HashMap.HashMap Text.Text Integer
+byTensMap = HashMap.fromList
+  [ ( "veinte" , 20 )
+  , ( "treinta" , 30 )
+  , ( "cuarenta" , 40 )
+  , ( "cincuenta" , 50 )
+  , ( "sesenta" , 60 )
+  , ( "setenta" , 70 )
+  , ( "ochenta" , 80 )
+  , ( "noventa" , 90 )
+  ]
+
+ruleNumeral2 :: Rule
+ruleNumeral2 = Rule
+  { name = "number (20..90)"
+  , pattern =
+    [ regex "(veinte|treinta|cuarenta|cincuenta|sesenta|setenta|ochenta|noventa)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) byTensMap >>= integer
+      _ -> Nothing
+  }
+
+zeroToFifteenMap :: HashMap.HashMap Text.Text Integer
+zeroToFifteenMap = HashMap.fromList
+  [ ( "zero" , 0 )
+  , ( "cero" , 0 )
+  , ( "un" , 1 )
+  , ( "una" , 1 )
+  , ( "uno" , 1 )
+  , ( "dos" , 2 )
+  , ( "tr\x00e9s" , 3 )
+  , ( "tres" , 3 )
+  , ( "cuatro" , 4 )
+  , ( "cinco" , 5 )
+  , ( "seis" , 6 )
+  , ( "s\x00e9is" , 6 )
+  , ( "siete" , 7 )
+  , ( "ocho" , 8 )
+  , ( "nueve" , 9 )
+  , ( "diez" , 10 )
+  , ( "dies" , 10 )
+  , ( "once" , 11 )
+  , ( "doce" , 12 )
+  , ( "trece" , 13 )
+  , ( "catorce" , 14 )
+  , ( "quince" , 15 )
+  ]
+
+ruleNumeral :: Rule
+ruleNumeral = Rule
+  { name = "number (0..15)"
+  , pattern =
+    [ regex "((c|z)ero|un(o|a)?|dos|tr(\x00e9|e)s|cuatro|cinco|s(e|\x00e9)is|siete|ocho|nueve|die(z|s)|once|doce|trece|catorce|quince)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) zeroToFifteenMap >>= integer
+      _ -> Nothing
+  }
+
+sixteenToTwentyNineMap :: HashMap.HashMap Text.Text Integer
+sixteenToTwentyNineMap = HashMap.fromList
+  [ ( "dieciseis" , 16 )
+  , ( "diesis\x00e9is" , 16 )
+  , ( "diesiseis" , 16 )
+  , ( "diecis\x00e9is" , 16 )
+  , ( "diecisiete" , 17 )
+  , ( "dieciocho" , 18 )
+  , ( "diecinueve" , 19 )
+  , ( "veintiuno" , 21 )
+  , ( "veintiuna" , 21 )
+  , ( "veintidos" , 22 )
+  , ( "veintitr\x00e9s" , 23 )
+  , ( "veintitres" , 23 )
+  , ( "veinticuatro" , 24 )
+  , ( "veinticinco" , 25 )
+  , ( "veintis\x00e9is" , 26 )
+  , ( "veintiseis" , 26 )
+  , ( "veintisiete" , 27 )
+  , ( "veintiocho" , 28 )
+  , ( "veintinueve" , 29 )
+  ]
+
+ruleNumeral5 :: Rule
+ruleNumeral5 = Rule
+  { name = "number (16..19 21..29)"
+  , pattern =
+    [ regex "(die(c|s)is(\x00e9|e)is|diecisiete|dieciocho|diecinueve|veintiun(o|a)|veintidos|veintitr(\x00e9|e)s|veinticuatro|veinticinco|veintis(\x00e9|e)is|veintisiete|veintiocho|veintinueve)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) sixteenToTwentyNineMap >>= integer
+      _ -> Nothing
+  }
+
+ruleNumeral3 :: Rule
+ruleNumeral3 = Rule
+  { name = "number (16..19)"
+  , pattern =
+    [ numberWith TNumeral.value (== 10)
+    , regex "y"
+    , numberBetween 6 10
+    ]
+  , prod = \tokens -> case tokens of
+      (_:_:Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        double $ 10 + v
+      _ -> Nothing
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "([kmg])(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+          "k" -> double $ v * 1e3
+          "m" -> double $ v * 1e6
+          "g" -> double $ v * 1e9
+          _ -> Nothing
+      _ -> Nothing
+  }
+
+oneHundredToThousandMap :: HashMap.HashMap Text.Text Integer
+oneHundredToThousandMap = HashMap.fromList
+  [ ( "cien" , 100 )
+  , ( "cientos" , 100 )
+  , ( "ciento" , 100 )
+  , ( "doscientos" , 200 )
+  , ( "trescientos" , 300 )
+  , ( "cuatrocientos" , 400 )
+  , ( "quinientos" , 500 )
+  , ( "seiscientos" , 600 )
+  , ( "setecientos" , 700 )
+  , ( "ochocientos" , 800 )
+  , ( "novecientos" , 900 )
+  , ( "mil" , 1000 )
+  ]
+
+
+ruleNumeral6 :: Rule
+ruleNumeral6 = Rule
+  { name = "number 100..1000 "
+  , pattern =
+    [ regex "(cien(to)?s?|doscientos|trescientos|cuatrocientos|quinientos|seiscientos|setecientos|ochocientos|novecientos|mil)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) oneHundredToThousandMap >>= integer
+      _ -> Nothing
+  }
+
+ruleNumeral4 :: Rule
+ruleNumeral4 = Rule
+  { name = "number (21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99)"
+  , pattern =
+    [ oneOf [70, 20, 60, 50, 40, 90, 30, 80]
+    , regex "y"
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleNumerals :: Rule
+ruleNumerals = Rule
+  { name = "numbers 200..999"
+  , pattern =
+    [ numberBetween 2 10
+    , numberWith TNumeral.value (== 100)
+    , numberBetween 0 100
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ 100 * v1 + v2
+      _ -> Nothing
+  }
+
+ruleNumeralDotNumeral :: Rule
+ruleNumeralDotNumeral = Rule
+  { name = "number dot number"
+  , pattern =
+    [ dimension Numeral
+    , regex "punto"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + decimalsToDouble v2
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ."
+  , pattern =
+    [ regex "(\\d{1,3}(\\.\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton '.') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleNumeral
+  , ruleNumeral2
+  , ruleNumeral3
+  , ruleNumeral4
+  , ruleNumeral5
+  , ruleNumeral6
+  , ruleNumeralDotNumeral
+  , ruleNumerals
+  , ruleNumeralsPrefixWithNegativeOrMinus
+  , ruleNumeralsSuffixesKMG
+  ]
diff --git a/Duckling/Numeral/ET/Corpus.hs b/Duckling/Numeral/ET/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/ET/Corpus.hs
@@ -0,0 +1,109 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.ET.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = ET}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "null"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "üks"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "Kolmkümmend kolm"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "neliteist"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "kuusteist"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "Seitseteist"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "kaheksateist"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1.1"
+             , "1.10"
+             , "01.10"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0.77"
+             , ".77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100,000"
+             , "100000"
+             , "100K"
+             , "100k"
+             , "100 000"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3M"
+             , "3000K"
+             , "3000000"
+             , "3,000,000"
+             , "3 000 000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1,200,000"
+             , "1200000"
+             , "1.2M"
+             , "1200K"
+             , ".0012G"
+             , "1 200 000"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "- 1,200,000"
+             , "-1200000"
+             , "miinus 1,200,000"
+             , "-1.2M"
+             , "-1200K"
+             , "-.0012G"
+             ]
+  , examples (NumeralValue 5000)
+             [ "viis tuhat"
+             ]
+  , examples (NumeralValue 200000)
+             [ "kakssada tuhat"
+             ]
+  , examples (NumeralValue 21011)
+             [ "kakskümmend üks Tuhat üksteist"
+             ]
+  , examples (NumeralValue 721012)
+             [ "seitsesada kakskümmend üks tuhat kaksteist"
+             ]
+  , examples (NumeralValue 31256721)
+             [ "kolmkümmend üks miljonit kakssada viiskümmend kuus tuhat seitsesada kakskümmend üks"
+             ]
+  ]
diff --git a/Duckling/Numeral/ET/Rules.hs b/Duckling/Numeral/ET/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/ET/Rules.hs
@@ -0,0 +1,306 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.ET.Rules
+  ( rules ) where
+
+import Data.Maybe
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleIntegerWithThousandsSeparatorSpace :: Rule
+ruleIntegerWithThousandsSeparatorSpace = Rule
+  { name = "integer with thousands separator space"
+  , pattern =
+    [ regex "(\\d{1,3}(\\s\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ' ') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+ruleNumeralsPrefixWithNegativeOrMinus :: Rule
+ruleNumeralsPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|miinus|negatiivne"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Numeral (NumeralData {TNumeral.value = v}):
+       _) -> double $ v * (-1)
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> do
+         v <- parseInt match
+         integer $ toInteger v
+      _ -> Nothing
+  }
+
+ruleACoupleOf :: Rule
+ruleACoupleOf = Rule
+  { name = "a couple of"
+  , pattern =
+    [ regex "paar"
+    ]
+  , prod = \_ -> integer 2
+  }
+
+ruleTen :: Rule
+ruleTen = Rule
+  { name = "ten"
+  , pattern =
+    [ regex "k\x00fcmme"
+    ]
+  , prod = \_ -> integer 10 >>= withGrain 1
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(,\\d\\d\\d)+\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> parseDecimal True match
+      _ -> Nothing
+  }
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer 21..99"
+  , pattern =
+    [ oneOf [70, 20, 60, 50, 40, 90, 30, 80]
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):_) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleAFew :: Rule
+ruleAFew = Rule
+  { name = "(a )?few"
+  , pattern =
+    [ regex "m\x00f5ni"
+    ]
+  , prod = \_ -> integer 3
+  }
+
+rulePowersOfTen :: Rule
+rulePowersOfTen = Rule
+  { name = "powers of tens"
+  , pattern = [regex "(sada|tuhat|miljoni?t?)"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "sada"  -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "tuhat" -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        _       -> double 1e6 >>= withGrain 6 >>= withMultipliable
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "([kmg])(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+          "k" -> double $ v * 1e3
+          "m" -> double $ v * 1e6
+          "g" -> double $ v * 1e9
+          _   -> Nothing
+      _ -> Nothing
+  }
+
+ruleMultiply :: Rule
+ruleMultiply = Rule
+  { name = "compose by multiplication"
+  , pattern =
+    [ dimension Numeral
+    , numberWith TNumeral.multipliable id
+    ]
+  , prod = \tokens -> case tokens of
+      (token1:token2:_) -> multiply token1 token2
+      _ -> Nothing
+  }
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer (0..19)"
+  , pattern =
+    [ regex "(null|\x00fcksteist|\x00fcks|kaksteist|kaks|kolmteist|kolm|neliteist|neli|viisteist|viis|kuusteist|kuus|seitseteist|seitse|kaheksateist|kaheksa|\x00fcheksateist|\x00fcheksa|k\x00fcmme)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "null" -> integer 0
+        "\x00fcks" -> integer 1
+        "kaks" -> integer 2
+        "kolm" -> integer 3
+        "neli" -> integer 4
+        "viis" -> integer 5
+        "kuus" -> integer 6
+        "seitse" -> integer 7
+        "kaheksa" -> integer 8
+        "\x00fcheksa" -> integer 9
+        "k\x00fcmme" -> integer 10
+        "\x00fcksteist" -> integer 11
+        "kaksteist" -> integer 12
+        "kolmteist" -> integer 13
+        "neliteist" -> integer 14
+        "viisteist" -> integer 15
+        "kuusteist" -> integer 16
+        "seitseteist" -> integer 17
+        "kaheksateist" -> integer 18
+        "\x00fcheksateist" -> integer 19
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleInteger4 :: Rule
+ruleInteger4 = Rule
+  { name = "integer (200..900)"
+  , pattern =
+    [ regex "(kakssada|kolmsada|nelisada|viissada|kuussada|seitsesada|kaheksasada|\x00fcheksasada)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "kakssada"        -> integer 200 >>= withGrain 2 >>= withMultipliable
+        "kolmsada"        -> integer 300 >>= withGrain 2 >>= withMultipliable
+        "nelisada"        -> integer 400 >>= withGrain 2 >>= withMultipliable
+        "viissada"        -> integer 500 >>= withGrain 2 >>= withMultipliable
+        "kuussada"        -> integer 600 >>= withGrain 2 >>= withMultipliable
+        "seitsesada"      -> integer 700 >>= withGrain 2 >>= withMultipliable
+        "kaheksasada"     -> integer 800 >>= withGrain 2 >>= withMultipliable
+        "\x00fcheksasada" -> integer 900 >>= withGrain 2 >>= withMultipliable
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer (20..90)"
+  , pattern =
+    [ regex "((kaks|kolm|neli|viis|kuus|seitse|kaheksa|(\x00fc)heksa)k(\x00fc)mmend)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "kaksk\x00fcmmend" -> integer 20
+        "kolmk\x00fcmmend" -> integer 30
+        "nelik\x00fcmmend" -> integer 40
+        "viisk\x00fcmmend" -> integer 50
+        "kuusk\x00fcmmend" -> integer 60
+        "seitsek\x00fcmmend" -> integer 70
+        "kaheksak\x00fcmmend" -> integer 80
+        "\x00fcheksak\x00fcmmend" -> integer 90
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumeralDotNumeral :: Rule
+ruleNumeralDotNumeral = Rule
+  { name = "number dot number"
+  , pattern =
+    [ dimension Numeral
+    , regex "dot|point"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + decimalsToDouble v2
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ,"
+  , pattern =
+    [ regex "(\\d{1,3}(,\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleACoupleOf
+  , ruleAFew
+  , ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleInteger
+  , ruleInteger2
+  , ruleInteger3
+  , ruleInteger4
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleIntegerWithThousandsSeparatorSpace
+  , ruleIntersect
+  , ruleMultiply
+  , ruleNumeralDotNumeral
+  , ruleNumeralsPrefixWithNegativeOrMinus
+  , ruleNumeralsSuffixesKMG
+  , rulePowersOfTen
+  , ruleTen
+  ]
diff --git a/Duckling/Numeral/FR/Corpus.hs b/Duckling/Numeral/FR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/FR/Corpus.hs
@@ -0,0 +1,127 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.FR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = FR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "zero"
+             , "zéro"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "un"
+             , "une"
+             ]
+  , examples (NumeralValue 11)
+             [ "onze"
+             ]
+  , examples (NumeralValue 17)
+             [ "dix sept"
+             , "dix-sept"
+             ]
+  , examples (NumeralValue 21)
+             [ "vingt et un"
+             , "vingt-et-un"
+             ]
+  , examples (NumeralValue 23)
+             [ "vingt trois"
+             , "vingt-trois"
+             ]
+  , examples (NumeralValue 70)
+             [ "soixante dix"
+             ]
+  , examples (NumeralValue 71)
+             [ "soixante onze"
+             ]
+  , examples (NumeralValue 78)
+             [ "soixante dix huit"
+             ]
+  , examples (NumeralValue 73)
+             [ "soixante treize"
+             ]
+  , examples (NumeralValue 80)
+             [ "quatre vingt"
+             ]
+  , examples (NumeralValue 81)
+             [ "quatre vingt un"
+             ]
+  , examples (NumeralValue 82)
+             [ "quatre vingt deux"
+             ]
+  , examples (NumeralValue 90)
+             [ "quatre vingt dix"
+             ]
+  , examples (NumeralValue 91)
+             [ "quatre vingt onze"
+             ]
+  , examples (NumeralValue 92)
+             [ "quatre vingt douze"
+             ]
+  , examples (NumeralValue 99)
+             [ "quatre vingt dix neuf"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "trente trois"
+             , "trente-trois"
+             , "trente 3"
+             ]
+  , examples (NumeralValue 118)
+             [ "cent dix-huit"
+             ]
+  , examples (NumeralValue 4020)
+             [ "quatre mille vingt"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100.000"
+             , "100000"
+             , "100K"
+             , "100k"
+             , "cent mille"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3M"
+             , "3000K"
+             , "3000000"
+             , "3.000.000"
+             , "trois millions"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1.200.000"
+             , "1200000"
+             , "1,2M"
+             , "1200K"
+             , ",0012G"
+             , "un million deux cent mille"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "- 1.200.000"
+             , "-1200000"
+             , "moins 1200000"
+             , "-1,2M"
+             , "-1200K"
+             , "-,0012G"
+             ]
+  ]
diff --git a/Duckling/Numeral/FR/Rules.hs b/Duckling/Numeral/FR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/FR/Rules.hs
@@ -0,0 +1,330 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.FR.Rules
+  ( rules ) where
+
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+import Data.Maybe
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleNumerals4 :: Rule
+ruleNumerals4 = Rule
+  { name = "numbers 81"
+  , pattern =
+    [ numberWith TNumeral.value (== 80)
+    , numberWith TNumeral.value (== 1)
+    ]
+  , prod = \_ -> integer 81
+  }
+
+ruleNumeralsPrefixWithNegativeOrMinus :: Rule
+ruleNumeralsPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|moins"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Numeral (NumeralData {TNumeral.value = v}):
+       _) -> double $ v * (-1)
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> do
+         v <- parseInt match
+         integer $ toInteger v
+      _ -> Nothing
+  }
+
+ruleNumerals2 :: Rule
+ruleNumerals2 = Rule
+  { name = "numbers 22..29 32..39 .. 52..59"
+  , pattern =
+    [ oneOf [20, 50, 40, 30]
+    , numberBetween 2 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(\\.\\d\\d\\d)+,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        let dot = Text.singleton '.'
+            comma = Text.singleton ','
+            fmt = Text.replace comma dot $ Text.replace dot Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> parseDecimal False match
+      _ -> Nothing
+  }
+
+ruleNumeral2Map :: HashMap Text Integer
+ruleNumeral2Map = HashMap.fromList
+  [ ( "vingt"    , 20 )
+  , ( "trente"   , 30 )
+  , ( "quarante" , 40 )
+  , ( "cinquante", 50 )
+  , ( "soixante" , 60 )
+  ]
+
+ruleNumeral2 :: Rule
+ruleNumeral2 = Rule
+  { name = "number (20..60)"
+  , pattern =
+    [ regex "(vingt|trente|quarante|cinquante|soixante)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) ruleNumeral2Map >>= integer
+      _ -> Nothing
+  }
+
+ruleNumerals5 :: Rule
+ruleNumerals5 = Rule
+  { name = "numbers 62..69 .. 92..99"
+  , pattern =
+    [ oneOf [60, 80]
+    , numberBetween 2 20
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleNumeralMap :: HashMap Text Integer
+ruleNumeralMap = HashMap.fromList
+  [ ( "zero"     , 0 )
+  , ( "z\x00e9ro", 0 )
+  , ( "un"       , 1 )
+  , ( "une"      , 1 )
+  , ( "deux"     , 2 )
+  , ( "trois"    , 3 )
+  , ( "quatre"   , 4 )
+  , ( "cinq"     , 5 )
+  , ( "six"      , 6 )
+  , ( "sept"     , 7 )
+  , ( "huit"     , 8 )
+  , ( "neuf"     , 9 )
+  , ( "dix"      , 10 )
+  , ( "onze"     , 11 )
+  , ( "douze"    , 12 )
+  , ( "treize"   , 13 )
+  , ( "quatorze" , 14 )
+  , ( "quinze"   , 15 )
+  , ( "seize"    , 16 )
+  ]
+
+ruleNumeral :: Rule
+ruleNumeral = Rule
+  { name = "number (0..16)"
+  , pattern =
+    [ regex "(z(e|\x00e9)ro|une?|deux|trois|quatre|cinq|six|sept|huit|neuf|dix|onze|douze|treize|quatorze|quinze|seize)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) ruleNumeralMap >>= integer
+      _ -> Nothing
+  }
+
+ruleNumeral3 :: Rule
+ruleNumeral3 = Rule
+  { name = "number (17..19)"
+  , pattern =
+    [ numberWith TNumeral.value (== 10)
+    , numberBetween 7 10
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Numeral (NumeralData {TNumeral.value = v}):
+       _) -> double $ 10 + v
+      _ -> Nothing
+  }
+
+ruleNumerals3 :: Rule
+ruleNumerals3 = Rule
+  { name = "numbers 61 71"
+  , pattern =
+    [ numberWith TNumeral.value (== 60)
+    , regex "-?et-?"
+    , oneOf [1, 11]
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "([kmg])(?=[\\W$\x20ac\x00a2\x00a3]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "k" -> double $ v * 1e3
+         "m" -> double $ v * 1e6
+         "g" -> double $ v * 1e9
+         _   -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumeral4 :: Rule
+ruleNumeral4 = Rule
+  { name = "number 80"
+  , pattern =
+    [ regex "quatre"
+    , regex "vingts?"
+    ]
+  , prod = \_ -> integer 80
+  }
+
+ruleNumerals :: Rule
+ruleNumerals = Rule
+  { name = "numbers 21 31 41 51"
+  , pattern =
+    [ oneOf [20, 50, 40, 30]
+    , regex "et"
+    , numberWith TNumeral.value (== 1)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ."
+  , pattern =
+    [ regex "(\\d{1,3}(\\.\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> let fmt = Text.replace (Text.singleton '.') Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+rulePowersOfTen :: Rule
+rulePowersOfTen = Rule
+  { name = "powers of tens"
+  , pattern =
+    [ regex "(cent|mille|millions?|milliards?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "cent"      -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "mille"     -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "million"   -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        "millions"  -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        "milliard"  -> double 1e9 >>= withGrain 9 >>= withMultipliable
+        "milliards" -> double 1e9 >>= withGrain 9 >>= withMultipliable
+        _           -> Nothing
+      _ -> Nothing
+  }
+
+ruleSum :: Rule
+ruleSum = Rule
+  { name = "intersect 2 numbers"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleMultiply :: Rule
+ruleMultiply = Rule
+  { name = "compose by multiplication"
+  , pattern =
+    [ dimension Numeral
+    , numberWith TNumeral.multipliable id
+    ]
+  , prod = \tokens -> case tokens of
+      (token1:token2:_) -> multiply token1 token2
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleNumeral
+  , ruleNumeral2
+  , ruleNumeral3
+  , ruleNumeral4
+  , ruleNumerals
+  , ruleNumerals2
+  , ruleNumerals3
+  , ruleNumerals4
+  , ruleNumerals5
+  , ruleNumeralsPrefixWithNegativeOrMinus
+  , ruleNumeralsSuffixesKMG
+  , rulePowersOfTen
+  , ruleSum
+  , ruleMultiply
+  ]
diff --git a/Duckling/Numeral/GA/Corpus.hs b/Duckling/Numeral/GA/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/GA/Corpus.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.GA.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = GA}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "a náid"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "aon"
+             , "a haon"
+             , "Amháin"
+             ]
+  , examples (NumeralValue 20)
+             [ "20"
+             , "Fiche"
+             ]
+  ]
diff --git a/Duckling/Numeral/GA/Rules.hs b/Duckling/Numeral/GA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/GA/Rules.hs
@@ -0,0 +1,256 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.GA.Rules
+  ( rules ) where
+
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleNumeralsPrefixWithNegativeOrMinus :: Rule
+ruleNumeralsPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|m(\x00ed|i)neas(\\sa)?\\s?"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Numeral (NumeralData {TNumeral.value = v}):
+       _) -> double $ v * (-1)
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- toInteger <$> parseInt match
+        integer v
+      _ -> Nothing
+  }
+
+ruleNumerals2 :: Rule
+ruleNumerals2 = Rule
+  { name = "numbers, 1-10"
+  , pattern =
+    [ regex "(aon|dh(\x00e1|a)|tr(\x00ed|i)|ceithre|c(\x00fa|u)ig|seacht|s(\x00e9|e)|ocht|naoi|deich)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "aon" -> integer 1
+        "dha" -> integer 2
+        "dh\x00e1" -> integer 2
+        "tr\x00ed" -> integer 3
+        "tri" -> integer 3
+        "ceithre" -> integer 4
+        "cuig" -> integer 5
+        "c\x00faig" -> integer 5
+        "s\x00e9" -> integer 6
+        "se" -> integer 6
+        "seacht" -> integer 7
+        "ocht" -> integer 8
+        "naoi" -> integer 9
+        "deich" -> integer 10
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(,\\d\\d\\d)+\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> parseDecimal True match
+      _ -> Nothing
+  }
+
+ruleDag :: Rule
+ruleDag = Rule
+  { name = "déag"
+  , pattern =
+    [ regex "d(\x00e9|e)ag"
+    ]
+  , prod = \_ -> integer 10
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "([kmg])(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "k" -> double $ v * 1e3
+         "m" -> double $ v * 1e6
+         "g" -> double $ v * 1e9
+         _   -> Nothing
+      _ -> Nothing
+  }
+
+ruleOldVigesimalNumeralsS :: Rule
+ruleOldVigesimalNumeralsS = Rule
+  { name = "old vigesimal numbers, 20s"
+  , pattern =
+    [ regex "is (dh?(\x00e1|a) fhichead|tr(\x00ed|i) fichid|ceithre fichid)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "d\x00e1 fhichead" -> integer 40
+        "da fhichead" -> integer 40
+        "dh\x00e1 fhichead" -> integer 40
+        "dha fhichead" -> integer 40
+        "tr\x00ed fichid" -> integer 60
+        "tri fichid" -> integer 60
+        "ceithre fichid" -> integer 80
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleOldVigesimalNumeralsS2 :: Rule
+ruleOldVigesimalNumeralsS2 = Rule
+  { name = "old vigesimal numbers, 20s + 10"
+  , pattern =
+    [ regex "d(\x00e9|e)ag is (fiche|dh?(\x00e1|a) fhichead|tr(\x00ed|i) fichid|ceithre fichid)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "fiche" -> integer 30
+        "d\x00e1 fhichead" -> integer 50
+        "da fhichead" -> integer 50
+        "dh\x00e1 fhichead" -> integer 50
+        "dha fhichead" -> integer 50
+        "tr\x00ed fichid" -> integer 70
+        "tri fichid" -> integer 70
+        "ceithre fichid" -> integer 90
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleAmhin :: Rule
+ruleAmhin = Rule
+  { name = "amháin"
+  , pattern =
+    [ regex "amh(\x00e1|a)in"
+    ]
+  , prod = \_ -> integer 1
+  }
+
+ruleNumerals :: Rule
+ruleNumerals = Rule
+  { name = "numbers, 20-90"
+  , pattern =
+    [ regex "(fiche|tr(\x00ed|i)ocha|daichead|caoga|seasca|seacht(\x00f3|o)|ocht(\x00f3|o)|n(\x00f3|o)cha)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "fiche" -> integer 20
+        "triocha" -> integer 30
+        "tr\x00edocha" -> integer 30
+        "daichead" -> integer 40
+        "caoga" -> integer 50
+        "seasca" -> integer 60
+        "seachto" -> integer 70
+        "seacht\x00f3" -> integer 70
+        "ochto" -> integer 80
+        "ocht\x00f3" -> integer 80
+        "n\x00f3cha" -> integer 90
+        "nocha" -> integer 90
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ,"
+  , pattern =
+    [ regex "(\\d{1,3}(,\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+ruleCountNumerals :: Rule
+ruleCountNumerals = Rule
+  { name = "count numbers"
+  , pattern =
+    [ regex "a (n(\x00e1|a)id|haon|d(\x00f3|o)|tr(\x00ed|i)|ceathair|c(\x00fa|u)ig|s(\x00e9|e)|seacht|hocht|naoi|deich)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "naid" -> integer 0
+        "n\x00e1id" -> integer 0
+        "haon" -> integer 1
+        "d\x00f3" -> integer 2
+        "do" -> integer 2
+        "tr\x00ed" -> integer 3
+        "tri" -> integer 3
+        "ceathair" -> integer 4
+        "cuig" -> integer 5
+        "c\x00faig" -> integer 5
+        "s\x00e9" -> integer 6
+        "se" -> integer 6
+        "seacht" -> integer 7
+        "hocht" -> integer 8
+        "naoi" -> integer 9
+        "deich" -> integer 10
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAmhin
+  , ruleCountNumerals
+  , ruleDag
+  , ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleNumerals
+  , ruleNumerals2
+  , ruleNumeralsPrefixWithNegativeOrMinus
+  , ruleNumeralsSuffixesKMG
+  , ruleOldVigesimalNumeralsS
+  , ruleOldVigesimalNumeralsS2
+  ]
diff --git a/Duckling/Numeral/HE/Corpus.hs b/Duckling/Numeral/HE/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/HE/Corpus.hs
@@ -0,0 +1,94 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.HE.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = HE}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "אפס"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "אחד"
+             , "אחת"
+             , "יחיד"
+             ]
+  , examples (NumeralValue 2)
+             [ "2"
+             , "שתיים"
+             , "שניים"
+             , "זוג"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "שלושים ושלוש"
+             , "שלושים ושלושה"
+             , "0033"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "ארבעה עשר"
+             , "ארבע עשרה"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "ששה עשר"
+             , "שש עשרה"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "שבעה עשר"
+             , "שבע עשרה"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "שמונה עשר"
+             , "שמונה עשרה"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1.1"
+             , "1.10"
+             , "01.10"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0.77"
+             , ".77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100,000"
+             , "100000"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3000000"
+             , "3,000,000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1,200,000"
+             , "1200000"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "- 1,200,000"
+             , "-1200000"
+             , "מינוס 1,200,000"
+             ]
+  ]
diff --git a/Duckling/Numeral/HE/Rules.hs b/Duckling/Numeral/HE/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/HE/Rules.hs
@@ -0,0 +1,389 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.HE.Rules
+  ( rules ) where
+
+import Data.Maybe
+import Data.String
+import Data.Text (Text)
+import Prelude
+import qualified Data.Text as Text
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import Duckling.Regex.Types
+import Duckling.Types
+import qualified Duckling.Numeral.Types as TNumeral
+
+ruleInteger5 :: Rule
+ruleInteger5 = Rule
+  { name = "integer 4"
+  , pattern =
+    [ regex "(\x05d0\x05e8\x05d1\x05e2(\x05d4)?)"
+    ]
+  , prod = \_ -> integer 4
+  }
+
+ruleIntersectNumerals :: Rule
+ruleIntersectNumerals = Rule
+  { name = "intersect numbers"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleIntersectWithAnd :: Rule
+ruleIntersectWithAnd = Rule
+  { name = "intersect (with and)"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , regex "\x05d5"
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleCompositeTens :: Rule
+ruleCompositeTens = Rule
+  { name = "integer 21..99"
+  , pattern =
+    [ oneOf [ 20, 30..90 ]
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = tens}):
+       Token Numeral (NumeralData {TNumeral.value = units}):
+       _) -> double $ tens + units
+      _ -> Nothing
+  }
+
+ruleCompositeTensWithAnd :: Rule
+ruleCompositeTensWithAnd = Rule
+  { name = "integer 21..99 (with and)"
+  , pattern =
+    [ oneOf [ 20, 30..90 ]
+    , regex "\x05d5"
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = tens}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = units}):
+       _) -> double $ tens + units
+      _ -> Nothing
+  }
+
+ruleNumeralsPrefixWithNegativeOrMinus :: Rule
+ruleNumeralsPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|\x05de\x05d9\x05e0\x05d5\x05e1"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral nd:_) -> double (TNumeral.value nd * (-1))
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- toInteger <$> parseInt match
+        integer v
+      _ -> Nothing
+  }
+
+ruleInteger10 :: Rule
+ruleInteger10 = Rule
+  { name = "integer 9"
+  , pattern =
+    [ regex "(\x05ea\x05e9\x05e2(\x05d4)?)"
+    ]
+  , prod = \_ -> integer 9
+  }
+
+ruleInteger15 :: Rule
+ruleInteger15 = Rule
+  { name = "integer (20..90)"
+  , pattern =
+    [ regex "(\x05e2\x05e9\x05e8\x05d9\x05dd|\x05e9\x05dc\x05d5\x05e9\x05d9\x05dd|\x05d0\x05e8\x05d1\x05e2\x05d9\x05dd|\x05d7\x05de\x05d9\x05e9\x05d9\x05dd|\x05e9\x05d9\x05e9\x05d9\x05dd|\x05e9\x05d1\x05e2\x05d9\x05dd|\x05e9\x05de\x05d5\x05e0\x05d9\x05dd|\x05ea\x05e9\x05e2\x05d9\x05dd)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case match of
+        "\x05e2\x05e9\x05e8\x05d9\x05dd" -> integer 20
+        "\x05e9\x05dc\x05d5\x05e9\x05d9\x05dd" -> integer 30
+        "\x05d0\x05e8\x05d1\x05e2\x05d9\x05dd" -> integer 40
+        "\x05d7\x05de\x05d9\x05e9\x05d9\x05dd" -> integer 50
+        "\x05e9\x05d9\x05e9\x05d9\x05dd" -> integer 60
+        "\x05e9\x05d1\x05e2\x05d9\x05dd" -> integer 70
+        "\x05e9\x05de\x05d5\x05e0\x05d9\x05dd" -> integer 80
+        "\x05ea\x05e9\x05e2\x05d9\x05dd" -> integer 90
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> parseDecimal True match
+      _ -> Nothing
+  }
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer 2"
+  , pattern =
+    [ regex "(\x05e9\x05ea\x05d9\x05d9\x05dd|\x05e9\x05e0\x05d9\x05d9\x05dd)"
+    ]
+  , prod = \_ -> integer 2
+  }
+
+ruleSingle :: Rule
+ruleSingle = Rule
+  { name = "single"
+  , pattern =
+    [ regex "\x05d9\x05d7\x05d9\x05d3"
+    ]
+  , prod = \_ -> integer 1
+  }
+
+ruleInteger13 :: Rule
+ruleInteger13 = Rule
+  { name = "integer 12"
+  , pattern =
+    [ regex "(\x05e9\x05e0\x05d9\x05d9\x05dd \x05e2\x05e9\x05e8|\x05ea\x05e8\x05d9 \x05e2\x05e9\x05e8)"
+    ]
+  , prod = \_ -> integer 12
+  }
+
+ruleMultiply :: Rule
+ruleMultiply = Rule
+  { name = "compose by multiplication"
+  , pattern =
+    [ dimension Numeral
+    , numberWith TNumeral.multipliable id
+    ]
+  , prod = \tokens -> case tokens of
+      (token1:token2:_) -> multiply token1 token2
+      _ -> Nothing
+  }
+
+ruleInteger6 :: Rule
+ruleInteger6 = Rule
+  { name = "integer 5"
+  , pattern =
+    [ regex "(\x05d7\x05de(\x05e9|\x05d9\x05e9\x05d4))"
+    ]
+  , prod = \_ -> integer 5
+  }
+
+rulePowersOfTen :: Rule
+rulePowersOfTen = Rule
+  { name = "powers of tens"
+  , pattern =
+    [ regex "(\x05de\x05d0(\x05d4|\x05d5\x05ea)|\x05d0\x05dc(\x05e3|\x05e4\x05d9\x05dd)|\x05de\x05d9\x05dc\x05d9\x05d5(\x05df|\x05e0\x05d9\x05dd))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "\x05de\x05d0\x05d4"                               ->
+          double 1e2 >>= withGrain 2 >>= withMultipliable
+        "\x05de\x05d0\x05d5\x05ea"                         ->
+          double 1e2 >>= withGrain 2 >>= withMultipliable
+        "\x05d0\x05dc\x05e3"                               ->
+          double 1e3 >>= withGrain 3 >>= withMultipliable
+        "\x05d0\x05dc\x05e4\x05d9\x05dd"                   ->
+          double 1e3 >>= withGrain 3 >>= withMultipliable
+        "\x05de\x05d9\x05dc\x05d9\x05d5\x05df"             ->
+          double 1e6 >>= withGrain 6 >>= withMultipliable
+        "\x05de\x05d9\x05dc\x05d9\x05d5\x05e0\x05d9\x05dd" ->
+          double 1e6 >>= withGrain 6 >>= withMultipliable
+        _          -> Nothing
+      _ -> Nothing
+  }
+
+ruleInteger7 :: Rule
+ruleInteger7 = Rule
+  { name = "integer 6"
+  , pattern =
+    [ regex "(\x05e9\x05e9(\x05d4)?)"
+    ]
+  , prod = \_ -> integer 6
+  }
+
+ruleInteger14 :: Rule
+ruleInteger14 = Rule
+  { name = "integer 11..19"
+  , pattern =
+    [ numberBetween 1 10
+    , numberWith TNumeral.value (== 10)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleInteger8 :: Rule
+ruleInteger8 = Rule
+  { name = "integer 7"
+  , pattern =
+    [ regex "(\x05e9\x05d1\x05e2(\x05d4)?)"
+    ]
+  , prod = \_ -> integer 7
+  }
+
+ruleCouple :: Rule
+ruleCouple = Rule
+  { name = "couple"
+  , pattern =
+    [ regex "\x05d6\x05d5\x05d2( \x05e9\x05dc)?"
+    ]
+  , prod = \_ -> integer 2
+  }
+
+ruleInteger16 :: Rule
+ruleInteger16 = Rule
+  { name = "integer 101..999"
+  , pattern =
+    [ oneOf [300, 600, 500, 100, 800, 200, 900, 700, 400]
+    , numberBetween 1 100
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleInteger9 :: Rule
+ruleInteger9 = Rule
+  { name = "integer 8"
+  , pattern =
+    [ regex "(\x05e9\x05de\x05d5\x05e0\x05d4)"
+    ]
+  , prod = \_ -> integer 8
+  }
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer 0"
+  , pattern =
+    [ regex "(\x05d0\x05e4\x05e1|\x05db\x05dc\x05d5\x05dd)"
+    ]
+  , prod = \_ -> integer 0
+  }
+
+ruleInteger4 :: Rule
+ruleInteger4 = Rule
+  { name = "integer 3"
+  , pattern =
+    [ regex "(\x05e9\x05dc\x05d5\x05e9(\x05d4)?)"
+    ]
+  , prod = \_ -> integer 3
+  }
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer 1"
+  , pattern =
+    [ regex "(\x05d0\x05d7\x05d3|\x05d0\x05d7\x05ea)"
+    ]
+  , prod = \_ -> integer 1
+  }
+
+ruleInteger11 :: Rule
+ruleInteger11 = Rule
+  { name = "integer 10"
+  , pattern =
+    [ regex "(\x05e2\x05e9\x05e8(\x05d4)?)"
+    ]
+  , prod = \_ -> integer 10
+  }
+
+ruleNumeralDotNumeral :: Rule
+ruleNumeralDotNumeral = Rule
+  { name = "number dot number"
+  , pattern =
+    [ dimension Numeral
+    , regex "\x05e0\x05e7\x05d5\x05d3\x05d4"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral nd1:_:Token Numeral nd2:_) ->
+        double $ TNumeral.value nd1 + decimalsToDouble (TNumeral.value nd2)
+      _ -> Nothing
+  }
+
+ruleCommas :: Rule
+ruleCommas = Rule
+  { name = "comma-separated numbers"
+  , pattern =
+    [ regex "(\\d+(,\\d\\d\\d)+(\\.\\d+)?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleCommas
+  , ruleCompositeTens
+  , ruleCompositeTensWithAnd
+  , ruleCouple
+  , ruleDecimalNumeral
+  , ruleInteger
+  , ruleInteger10
+  , ruleInteger11
+  , ruleInteger13
+  , ruleInteger14
+  , ruleInteger15
+  , ruleInteger16
+  , ruleInteger2
+  , ruleInteger3
+  , ruleInteger4
+  , ruleInteger5
+  , ruleInteger6
+  , ruleInteger7
+  , ruleInteger8
+  , ruleInteger9
+  , ruleIntegerNumeric
+  , ruleIntersectNumerals
+  , ruleIntersectWithAnd
+  , ruleMultiply
+  , ruleNumeralDotNumeral
+  , ruleNumeralsPrefixWithNegativeOrMinus
+  , rulePowersOfTen
+  , ruleSingle
+  ]
diff --git a/Duckling/Numeral/HR/Corpus.hs b/Duckling/Numeral/HR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/HR/Corpus.hs
@@ -0,0 +1,127 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.HR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = HR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "nula"
+             , "nista"
+             , "ništa"
+             , "nistica"
+             , "ništica"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "jedan"
+             , "sam"
+             ]
+  , examples (NumeralValue 2)
+             [ "2"
+             , "dva"
+             , "par"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "trideset i tri"
+             , "trideset tri"
+             , "0033"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "cetrnaest"
+             , "četrnaest"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "šesnaest"
+             , "sesnaest"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "sedamnaest"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "osamnaest"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1,1"
+             , "jedan cijela jedan"
+             , "1,10"
+             , "01,10"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0,77"
+             , ",77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100.000"
+             , "100000"
+             , "100K"
+             , "100k"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3M"
+             , "3000K"
+             , "3000000"
+             , "3.000.000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1.200.000"
+             , "1200000"
+             , "1,2M"
+             , "1200K"
+             , ",0012G"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "- 1.200.000"
+             , "-1200000"
+             , "minus 1.200.000"
+             , "-1,2M"
+             , "-1200K"
+             , "-,0012G"
+             ]
+  , examples (NumeralValue 5000)
+             [ "5 tisuća"
+             , "pet tisuća"
+             , "pet tisuca"
+             ]
+  , examples (NumeralValue 122)
+             [ "stotinu dvadeset dva"
+             ]
+  , examples (NumeralValue 200000)
+             [ "dvjesto tisuća"
+             , "dvjesto tisuca"
+             , "dvije stotine tisuca"
+             , "dvije stotine tisuća"
+             ]
+  , examples (NumeralValue 21011)
+             [ "dvadeset i jedna tisuca jedanaest"
+             , "dvadeset i jedna tisuća jedanaest"
+             ]
+  , examples (NumeralValue 721012)
+             [ "sedam stotina dvadeset jedna tisuća dvanaest"
+             , "sedam stotina dvadeset jedna tisuca dvanaest"
+             ]
+  ]
diff --git a/Duckling/Numeral/HR/Rules.hs b/Duckling/Numeral/HR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/HR/Rules.hs
@@ -0,0 +1,383 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.HR.Rules
+  ( rules ) where
+
+import Data.Maybe
+import Data.String
+import Data.Text (Text)
+import Prelude
+import qualified Data.Text as Text
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import Duckling.Regex.Types
+import Duckling.Types
+import qualified Duckling.Numeral.Types as TNumeral
+
+ruleNumbersPrefixWithNegativeOrMinus :: Rule
+ruleNumbersPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|minus|negativ"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral nd:_) -> double (TNumeral.value nd * (-1))
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- toInteger <$> parseInt match
+        integer v
+      _ -> Nothing
+  }
+
+ruleFew :: Rule
+ruleFew = Rule
+  { name = "few"
+  , pattern =
+    [ regex "nekoliko"
+    ]
+  , prod = \_ -> integer 3
+  }
+
+ruleTen :: Rule
+ruleTen = Rule
+  { name = "ten"
+  , pattern =
+    [ regex "deset|cener"
+    ]
+  , prod = \_ -> integer 10 >>= withGrain 1
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(\\.\\d\\d\\d)+\\,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        let dot = Text.singleton '.'
+            comma = Text.singleton ','
+            fmt = Text.replace comma dot $ Text.replace dot Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+ruleDecimalNumber :: Rule
+ruleDecimalNumber = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> parseDecimal False match
+      _ -> Nothing
+  }
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer (100..900)"
+  , pattern =
+    [ regex "(sto|dvjest(o|a)|tristo|(c|\x010d)etiristo|petsto|(\x0161|s)esto|sedamsto|osamsto|devetsto)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "sto" -> integer 100
+        "dvjesta" -> integer 200
+        "dvjesto" -> integer 200
+        "tristo" -> integer 300
+        "cetiristo" -> integer 400
+        "\269etiristo" -> integer 400
+        "petsto" -> integer 500
+        "\353esto" -> integer 600
+        "sesto" -> integer 600
+        "sedamsto" -> integer 700
+        "osamsto" -> integer 800
+        "devetsto" -> integer 900
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleSingle :: Rule
+ruleSingle = Rule
+  { name = "single"
+  , pattern =
+    [ regex "sam"
+    ]
+  , prod = \_ -> integer 1 >>= withGrain 1
+  }
+
+rulePowersOfTen :: Rule
+rulePowersOfTen = Rule
+  { name = "powers of tens"
+  , pattern =
+    [ regex "(stotin(u|a|e)|tisu(c|\x0107)(a|u|e)|milij(u|o)na?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "stotinu" -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "stotina" -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "stotine" -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "tisuca" -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "tisucu" -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "tisuce" -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "tisu\263a" -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "tisu\263u" -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "tisu\263e" -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "milijun" -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        "milijuna" -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        "milijon" -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        "milijona" -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumbersI :: Rule
+ruleNumbersI = Rule
+  { name = "numbers i"
+  , pattern =
+    [ oneOf [70, 20, 60, 50, 40, 90, 30, 80]
+    , regex "i"
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleSum :: Rule
+ruleSum = Rule
+  { name = "intersect"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleNumbersSuffixesKMG :: Rule
+ruleNumbersSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "([kmg])(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "k" -> double $ v * 1e3
+         "m" -> double $ v * 1e6
+         "g" -> double $ v * 1e9
+         _   -> Nothing
+      _ -> Nothing
+  }
+
+ruleAPair :: Rule
+ruleAPair = Rule
+  { name = "a pair"
+  , pattern =
+    [ regex "par"
+    ]
+  , prod = \_ -> integer 2 >>= withGrain 1
+  }
+
+ruleDozen :: Rule
+ruleDozen = Rule
+  { name = "dozen"
+  , pattern =
+    [ regex "tucet?"
+    ]
+  , prod = \_ -> integer 12 >>= withGrain 1
+  }
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer (0..19)"
+  , pattern =
+    [ regex "(ni(s|\x0161)ta|ni(s|\x0161)tica|nula|jedanaest|dvanaest|trinaest|jeda?n(a|u|o(ga?)?)?|dv(i?je)?(a|o)?(ma)?|tri(ma)?|(\x010d|c)etiri|(\x010d|c)etrnaest|petnaest|pet|(s|\x0161)esnaest|(\x0161|s)est|sedamnaest|sedam|osamnaest|osam|devetnaest|devet)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "ni\353ta" -> integer 0
+        "ni\353tica" -> integer 0
+        "nistica" -> integer 0
+        "nista" -> integer 0
+        "nula" -> integer 0
+        "jednoga" -> integer 1
+        "jedna" -> integer 1
+        "jednog" -> integer 1
+        "jednu" -> integer 1
+        "jedan" -> integer 1
+        "dvoma" -> integer 2
+        "dvije" -> integer 2
+        "dvje" -> integer 2
+        "dva" -> integer 2
+        "dvama" -> integer 2
+        "trima" -> integer 3
+        "tri" -> integer 3
+        "\269etiri" -> integer 4
+        "cetiri" -> integer 4
+        "pet" -> integer 5
+        "\353est" -> integer 6
+        "sedam" -> integer 7
+        "osam" -> integer 8
+        "devet" -> integer 9
+        "jedanaest" -> integer 11
+        "dvanaest" -> integer 12
+        "trinaest" -> integer 13
+        "cetrnaest" -> integer 14
+        "\269etrnaest" -> integer 14
+        "petnaest" -> integer 15
+        "\353esnaest" -> integer 16
+        "sesnaest" -> integer 16
+        "sedamnaest" -> integer 17
+        "osamnaest" -> integer 18
+        "devetnaest" -> integer 19
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleInteger4 :: Rule
+ruleInteger4 = Rule
+  { name = "integer 21..99"
+  , pattern =
+    [ oneOf [70, 20, 60, 50, 40, 90, 30, 80]
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer (20..90)"
+  , pattern =
+    [ regex "(dvadeset|trideset|(c|\x010d)etrdeset|pedeset|(\x0161|s)esdeset|sedamdeset|osamdeset|devedeset)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "dvadeset" -> integer 20
+        "trideset" -> integer 30
+        "cetrdeset" -> integer 40
+        "\269etrdeset" -> integer 40
+        "pedeset" -> integer 50
+        "sesdeset" -> integer 60
+        "\353esdeset" -> integer 60
+        "sedamdeset" -> integer 70
+        "osamdeset" -> integer 80
+        "devedeset" -> integer 90
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumbers :: Rule
+ruleNumbers = Rule
+  { name = "numbers 100..999"
+  , pattern =
+    [ numberBetween 1 10
+    , numberWith TNumeral.value (== 100)
+    , numberBetween 0 100
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ 100 * v1 + v2
+      _ -> Nothing
+  }
+
+ruleNumberDotNumber :: Rule
+ruleNumberDotNumber = Rule
+  { name = "number dot number"
+  , pattern =
+    [ dimension Numeral
+    , regex "cijela|to(c|\x010d)ka|zarez"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + decimalsToDouble v2
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ."
+  , pattern =
+    [ regex "(\\d{1,3}(\\.\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> let fmt = Text.replace (Text.singleton '.') Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+ruleMultiply :: Rule
+ruleMultiply = Rule
+  { name = "compose by multiplication"
+  , pattern =
+    [ dimension Numeral
+    , numberWith TNumeral.multipliable id
+    ]
+  , prod = \tokens -> case tokens of
+      (token1:token2:_) -> multiply token1 token2
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAPair
+  , ruleDecimalNumber
+  , ruleDecimalWithThousandsSeparator
+  , ruleDozen
+  , ruleFew
+  , ruleInteger
+  , ruleInteger2
+  , ruleInteger3
+  , ruleInteger4 , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleMultiply
+  , ruleNumberDotNumber
+  , ruleNumbers
+  , ruleNumbersI
+  , ruleNumbersPrefixWithNegativeOrMinus
+  , ruleNumbersSuffixesKMG
+  , rulePowersOfTen
+  , ruleSingle
+  , ruleSum
+  , ruleTen
+  ]
diff --git a/Duckling/Numeral/Helpers.hs b/Duckling/Numeral/Helpers.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/Helpers.hs
@@ -0,0 +1,128 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.Helpers
+  ( decimalsToDouble
+  , double
+  , integer
+  , multiply
+  , numberBetween
+  , numberWith
+  , oneOf
+  , parseDouble
+  , parseInt
+  , withGrain
+  , withMultipliable
+  , parseDecimal,
+  ) where
+
+import qualified Data.Attoparsec.Text as Atto
+import Data.Maybe
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types
+import Duckling.Types hiding (value)
+
+zeroT :: Text
+zeroT = Text.singleton '0'
+
+dot :: Text
+dot = Text.singleton '.'
+
+comma :: Text
+comma = Text.singleton ','
+
+parseInt :: Text -> Maybe Int
+parseInt =
+  either (const Nothing) Just . Atto.parseOnly (Atto.signed Atto.decimal)
+
+-- | Add leading 0 when leading . for double parsing to succeed
+parseDouble :: Text -> Maybe Double
+parseDouble s
+  | Text.head s == '.' = go $ Text.append zeroT s
+  | otherwise = go s
+  where go = either (const Nothing) Just . Atto.parseOnly Atto.double
+
+-- | 77 -> .77
+-- | Find the first power of ten larger that the actual number
+-- | Use it to divide x
+decimalsToDouble :: Double -> Double
+decimalsToDouble x =
+  let xs = filter (\y -> x - y < 0)
+         . take 10
+         . iterate (*10) $ 1 in
+    case xs of
+      [] -> 0
+      (multiplier : _) -> x / multiplier
+
+-- -----------------------------------------------------------------
+-- Patterns
+
+numberWith :: (NumeralData -> t) -> (t -> Bool) -> PatternItem
+numberWith f pred = Predicate $ \x ->
+  case x of
+    (Token Numeral x@NumeralData{}) -> pred (f x)
+    _ -> False
+
+numberBetween :: Double -> Double -> PatternItem
+numberBetween low up = Predicate $ \x ->
+  case x of
+    (Token Numeral NumeralData {value = v, multipliable = False}) ->
+      low <= v && v < up
+    _ -> False
+
+oneOf :: [Double] -> PatternItem
+oneOf vs = Predicate $ \x ->
+  case x of
+    (Token Numeral NumeralData {value = v}) -> elem v vs
+    _ -> False
+
+-- -----------------------------------------------------------------
+-- Production
+
+withMultipliable :: Token -> Maybe Token
+withMultipliable (Token Numeral x@NumeralData{}) =
+  Just . Token Numeral $ x {multipliable = True}
+withMultipliable _ = Nothing
+
+withGrain :: Int -> Token -> Maybe Token
+withGrain g (Token Numeral x@NumeralData{}) =
+  Just . Token Numeral $ x {grain = Just g}
+withGrain _ _ = Nothing
+
+double :: Double -> Maybe Token
+double x = Just . Token Numeral $ NumeralData
+  { value = x
+  , grain = Nothing
+  , multipliable = False
+  }
+
+integer :: Integer -> Maybe Token
+integer = double . fromIntegral
+
+multiply :: Token -> Token -> Maybe Token
+multiply
+  (Token Numeral (NumeralData {value = v1}))
+  (Token Numeral (NumeralData {value = v2, grain = g})) = case g of
+  Nothing -> double $ v1 * v2
+  Just grain | v2 > v1 -> double (v1 * v2) >>= withGrain grain
+             | otherwise -> Nothing
+multiply _ _ = Nothing
+
+parseDecimal :: Bool -> Text -> Maybe Token
+parseDecimal isDot match
+  | isDot = parseDouble match >>= double
+  | otherwise =
+    parseDouble (Text.replace comma dot match)
+    >>= double
diff --git a/Duckling/Numeral/ID/Corpus.hs b/Duckling/Numeral/ID/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/ID/Corpus.hs
@@ -0,0 +1,118 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.ID.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = ID}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "nol"
+             , "kosong"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "satu"
+             ]
+  , examples (NumeralValue 2)
+             [ "2"
+             , "Dua"
+             ]
+  , examples (NumeralValue 10)
+             [ "10"
+             , "sepuluh"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "tiga puluh tiga"
+             , "0033"
+             ]
+  , examples (NumeralValue 100)
+             [ "100"
+             , "seratus"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "tujuh belas"
+             ]
+  , examples (NumeralValue 28)
+             [ "28"
+             , "dua puluh delapan"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1,1"
+             , "1,10"
+             , "01,10"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0,77"
+             , ",77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100.000"
+             , "100000"
+             , "100K"
+             , "100k"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3M"
+             , "3000K"
+             , "3000000"
+             , "3.000.000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1.200.000"
+             , "1200000"
+             , "1,2M"
+             , "1200K"
+             , ",0012G"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "- 1.200.000"
+             , "-1200000"
+             , "minus 1.200.000"
+             , "negatif 1200000"
+             , "-1,2M"
+             , "-1200K"
+             , "-,0012G"
+             , "-0,0012G"
+             ]
+  , examples (NumeralValue 5000)
+             [ "5 ribu"
+             , "lima ribu"
+             ]
+  , examples (NumeralValue 122)
+             [ "seratus dua puluh dua"
+             ]
+  , examples (NumeralValue 200000)
+             [ "dua ratus ribu"
+             ]
+  , examples (NumeralValue 10011)
+             [ "sepuluh ribu sebelas"
+             ]
+  , examples (NumeralValue 721012)
+             [ "tujuh ratus dua puluh satu ribu dua belas"
+             ]
+  , examples (NumeralValue 31256721)
+             [ "tiga puluh satu juta dua ratus lima puluh enam ribu tujuh ratus dua puluh satu"
+             ]
+  ]
diff --git a/Duckling/Numeral/ID/Rules.hs b/Duckling/Numeral/ID/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/ID/Rules.hs
@@ -0,0 +1,274 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.ID.Rules
+  ( rules ) where
+
+import Data.Maybe
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleTeen :: Rule
+ruleTeen = Rule
+  { name = "teen"
+  , pattern =
+    [ numberBetween 2 10
+    , regex "belas"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ v + 10
+      _ -> Nothing
+  }
+
+ruleNumeralCommaNumeral :: Rule
+ruleNumeralCommaNumeral = Rule
+  { name = "number comma number"
+  , pattern =
+    [ dimension Numeral
+    , regex "koma"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral nd1:_:Token Numeral nd2:_) ->
+        double $ TNumeral.value nd1 + decimalsToDouble (TNumeral.value nd2)
+      _ -> Nothing
+  }
+
+ruleNumeralsPrefixWithNegativeOrMinus :: Rule
+ruleNumeralsPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|minus\\s?|negatif\\s?"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral nd:_) -> double (TNumeral.value nd * (-1))
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- toInteger <$> parseInt match
+        integer v
+      _ -> Nothing
+  }
+
+ruleTen :: Rule
+ruleTen = Rule
+  { name = "ten"
+  , pattern =
+    [ regex "(se)?puluh"
+    ]
+  , prod = \_ -> integer 10 >>= withGrain 1
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(\\.\\d\\d\\d)+,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> let dot = Text.singleton '.'
+                 comma = Text.singleton ','
+                 fmt = Text.replace comma dot $ Text.replace dot Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+ruleMultiply :: Rule
+ruleMultiply = Rule
+  { name = "compose by multiplication"
+  , pattern =
+    [ dimension Numeral
+    , numberWith TNumeral.multipliable id
+    ]
+  , prod = \tokens -> case tokens of
+      (token1:token2:_) -> multiply token1 token2
+      _ -> Nothing
+  }
+
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> parseDecimal False match
+      _ -> Nothing
+  }
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer 21..99"
+  , pattern =
+    [ oneOf [70, 20, 60, 50, 40, 90, 30, 80]
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleSomefewcouple :: Rule
+ruleSomefewcouple = Rule
+  { name = "some/few/couple"
+  , pattern =
+    [ regex "beberapa"
+    ]
+  , prod = \_ -> integer 3
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "([kmg])(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "k" -> double $ v * 1e3
+         "m" -> double $ v * 1e6
+         "g" -> double $ v * 1e9
+         _   -> Nothing
+      _ -> Nothing
+  }
+
+rulePowersOfTen :: Rule
+rulePowersOfTen = Rule
+  { name = "powers of tens"
+  , pattern =
+    [ regex "(se)?(ratus|ribu|juta)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (_:match:_)):_) -> case Text.toLower match of
+        "ratus"-> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "ribu" -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "juta" -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        _      -> Nothing
+      _ -> Nothing
+  }
+
+ruleDozen :: Rule
+ruleDozen = Rule
+  { name = "dozen"
+  , pattern =
+    [ regex "(se)?lusin"
+    ]
+  , prod = \_ -> integer 12 >>= withGrain 1 >>= withMultipliable
+  }
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer (0..9 11)"
+  , pattern =
+    [ regex "(kosong|nol|satu|dua|tiga|empat|lima|enam|tujuh|delapan|sembilan|sebelas)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "kosong" -> integer 0
+        "nol" -> integer 0
+        "satu" -> integer 1
+        "dua" -> integer 2
+        "tiga" -> integer 3
+        "empat" -> integer 4
+        "lima" -> integer 5
+        "enam" -> integer 6
+        "tujuh" -> integer 7
+        "delapan" -> integer 8
+        "sembilan" -> integer 9
+        "sebelas" -> integer 11
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer 20..90"
+  , pattern =
+    [ numberBetween 2 10
+    , numberWith TNumeral.value (== 10)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2, TNumeral.grain = Just g}):
+       _) -> double (v1 * v2) >>= withGrain g
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ."
+  , pattern =
+    [ regex "(\\d{1,3}(\\.\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton '.') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleDozen
+  , ruleInteger
+  , ruleInteger2
+  , ruleInteger3
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleIntersect
+  , ruleMultiply
+  , ruleNumeralCommaNumeral
+  , ruleNumeralsPrefixWithNegativeOrMinus
+  , ruleNumeralsSuffixesKMG
+  , rulePowersOfTen
+  , ruleSomefewcouple
+  , ruleTeen
+  , ruleTen
+  ]
diff --git a/Duckling/Numeral/IT/Corpus.hs b/Duckling/Numeral/IT/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/IT/Corpus.hs
@@ -0,0 +1,155 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.IT.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = IT}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "nulla"
+             , "zero"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "uno"
+             , "Un"
+             ]
+  , examples (NumeralValue 2)
+             [ "2"
+             , "due"
+             ]
+  , examples (NumeralValue 3)
+             [ "3"
+             , "tre"
+             ]
+  , examples (NumeralValue 4)
+             [ "4"
+             , "quattro"
+             ]
+  , examples (NumeralValue 5)
+             [ "5"
+             , "cinque"
+             ]
+  , examples (NumeralValue 6)
+             [ "6"
+             , "sei"
+             ]
+  , examples (NumeralValue 7)
+             [ "7"
+             , "sette"
+             ]
+  , examples (NumeralValue 8)
+             [ "8"
+             , "otto"
+             ]
+  , examples (NumeralValue 9)
+             [ "9"
+             , "nove"
+             ]
+  , examples (NumeralValue 10)
+             [ "10"
+             , "dieci"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "trentatré"
+             , "0033"
+             ]
+  , examples (NumeralValue 11)
+             [ "11"
+             , "Undici"
+             ]
+  , examples (NumeralValue 12)
+             [ "12"
+             , "dodici"
+             ]
+  , examples (NumeralValue 13)
+             [ "13"
+             , "tredici"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "quattordici"
+             ]
+  , examples (NumeralValue 15)
+             [ "15"
+             , "quindici"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "sedici"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "diciassette"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "diciotto"
+             ]
+  , examples (NumeralValue 19)
+             [ "19"
+             , "diciannove"
+             ]
+  , examples (NumeralValue 20)
+             [ "20"
+             , "venti"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1,1"
+             , "1,10"
+             , "01,10"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0,77"
+             , ",77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100.000"
+             , "100000"
+             , "100K"
+             , "100k"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3M"
+             , "3000K"
+             , "3000000"
+             , "3.000.000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1.200.000"
+             , "1200000"
+             , "1,2M"
+             , "1200K"
+             , ",0012G"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "- 1.200.000"
+             , "-1200000"
+             , "meno 1.200.000"
+             , "negativo 1200000"
+             , "-1,2M"
+             , "-1200K"
+             , "-,0012G"
+             ]
+  ]
diff --git a/Duckling/Numeral/IT/Rules.hs b/Duckling/Numeral/IT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/IT/Rules.hs
@@ -0,0 +1,324 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.IT.Rules
+  ( rules ) where
+
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleNumeralsPrefixWithNegativeOrMinus :: Rule
+ruleNumeralsPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|meno|negativo"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral nd:_) -> double (TNumeral.value nd * (-1))
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- toInteger <$> parseInt match
+        integer v
+      _ -> Nothing
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(\\.\\d\\d\\d)+,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        let dot = Text.singleton '.'
+            comma = Text.singleton ','
+            fmt = Text.replace comma dot $ Text.replace dot Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> parseDecimal False match
+      _ -> Nothing
+  }
+
+ruleNumeral2 :: Rule
+ruleNumeral2 = Rule
+  { name = "number (20..90)"
+  , pattern =
+    [ regex "(venti|trenta|quaranta|cinquanta|sessanta|settanta|ottanta|novanta)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "venti" -> integer 20
+        "trenta" -> integer 30
+        "quaranta" -> integer 40
+        "cinquanta" -> integer 50
+        "sessanta" -> integer 60
+        "settanta" -> integer 70
+        "ottanta" -> integer 80
+        "novanta" -> integer 90
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumeral :: Rule
+ruleNumeral = Rule
+  { name = "number (0..19)"
+  , pattern =
+    [ regex "(zero|nulla|niente|uno|due|tredici|tre|quattro|cinque|sei|sette|otto|nove|dieci|undici|dodici|quattordici|quindici|sedici|diciassette|diciotto|diciannove|un)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "zero" -> integer 0
+        "niente" -> integer 0
+        "nulla" -> integer 0
+        "un" -> integer 1
+        "uno" -> integer 1
+        "due" -> integer 2
+        "tre" -> integer 3
+        "quattro" -> integer 4
+        "cinque" -> integer 5
+        "sei" -> integer 6
+        "sette" -> integer 7
+        "otto" -> integer 8
+        "nove" -> integer 9
+        "dieci" -> integer 10
+        "undici" -> integer 11
+        "dodici" -> integer 12
+        "tredici" -> integer 13
+        "quattordici" -> integer 14
+        "quindici" -> integer 15
+        "sedici" -> integer 16
+        "diciassette" -> integer 17
+        "diciotto" -> integer 18
+        "diciannove" -> integer 19
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumeral5 :: Rule
+ruleNumeral5 = Rule
+  { name = "number 100..1000 "
+  , pattern =
+    [ regex "(due|tre|quattro|cinque|sei|sette|otto|nove)?cento|mil(a|le)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "cento" -> integer 100
+        "duecento" -> integer 200
+        "trecento" -> integer 300
+        "quattrocento" -> integer 400
+        "cinquecento" -> integer 500
+        "seicento" -> integer 600
+        "settecento" -> integer 700
+        "ottocento" -> integer 800
+        "novecento" -> integer 900
+        " mila" -> integer 1000
+        "mille" -> integer 1000
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumeral3 :: Rule
+ruleNumeral3 = Rule
+  { name = "number (21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99)"
+  , pattern =
+    [ oneOf [70, 20, 60, 50, 40, 90, 30, 80]
+    , regex "e"
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "([kmg])(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "k" -> double $ v * 1e3
+         "m" -> double $ v * 1e6
+         "g" -> double $ v * 1e9
+         _   -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumeral4 :: Rule
+ruleNumeral4 = Rule
+  { name = "number (21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99)"
+  , pattern =
+    [ regex "((venti|trenta|quaranta|cinquanta|sessanta|settanta|ottanta|novanta)(due|tre|tr\x00e9|quattro|cinque|sei|sette|nove))|((vent|trent|quarant|cinquant|sessant|settant|ottant|novant)(uno|otto))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "ventuno" -> integer 21
+        "ventidue" -> integer 22
+        "ventitre" -> integer 23
+        "ventitr\x00e9" -> integer 23
+        "ventiquattro" -> integer 24
+        "venticinque" -> integer 25
+        "ventisei" -> integer 26
+        "ventisette" -> integer 27
+        "ventotto" -> integer 28
+        "ventinove" -> integer 29
+        "trentuno" -> integer 31
+        "trentadue" -> integer 32
+        "trentatre" -> integer 33
+        "trentatr\x00e9" -> integer 33
+        "trentaquattro" -> integer 34
+        "trentacinque" -> integer 35
+        "trentasei" -> integer 36
+        "trentasette" -> integer 37
+        "trentotto" -> integer 38
+        "trentanove" -> integer 39
+        "quarantuno" -> integer 41
+        "quarantadue" -> integer 42
+        "quarantatre" -> integer 43
+        "quarantatr\x00e9" -> integer 43
+        "quarantaquattro" -> integer 44
+        "quarantacinque" -> integer 45
+        "quarantasei" -> integer 46
+        "quarantasette" -> integer 47
+        "quarantotto" -> integer 48
+        "quarantanove" -> integer 49
+        "cinquantuno" -> integer 51
+        "cinquantadue" -> integer 52
+        "cinquantatre" -> integer 53
+        "cinquantatr\x00e9" -> integer 53
+        "cinquantaquattro" -> integer 54
+        "cinquantacinque" -> integer 55
+        "cinquantasei" -> integer 56
+        "cinquantasette" -> integer 57
+        "cinquantotto" -> integer 58
+        "cinquantanove" -> integer 59
+        "sessantuno" -> integer 61
+        "sessantadue" -> integer 62
+        "sessantatr\x00e9" -> integer 63
+        "sessantatre" -> integer 63
+        "sessantaquattro" -> integer 64
+        "sessantacinque" -> integer 65
+        "sessantasei" -> integer 66
+        "sessantasette" -> integer 67
+        "sessantotto" -> integer 68
+        "sessantanove" -> integer 69
+        "settantuno" -> integer 71
+        "settantadue" -> integer 72
+        "settantatr\x00e9" -> integer 73
+        "settantatre" -> integer 73
+        "settantaquattro" -> integer 74
+        "settantacinque" -> integer 75
+        "settantasei" -> integer 76
+        "settantasette" -> integer 77
+        "settantotto" -> integer 78
+        "settantanove" -> integer 79
+        "ottantuno" -> integer 81
+        "ottantadue" -> integer 82
+        "ottantatr\x00e9" -> integer 83
+        "ottantatre" -> integer 83
+        "ottantaquattro" -> integer 84
+        "ottantacinque" -> integer 85
+        "ottantasei" -> integer 86
+        "ottantasette" -> integer 87
+        "ottantotto" -> integer 88
+        "ottantanove" -> integer 89
+        "novantuno" -> integer 91
+        "novantadue" -> integer 92
+        "novantatre" -> integer 93
+        "novantatr\x00e9" -> integer 93
+        "novantaquattro" -> integer 94
+        "novantacinque" -> integer 95
+        "novantasei" -> integer 96
+        "novantasette" -> integer 97
+        "novantotto" -> integer 98
+        "novantanove" -> integer 99
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumerals :: Rule
+ruleNumerals = Rule
+  { name = "numbers 200..999"
+  , pattern =
+    [ numberBetween 2 10
+    , numberWith TNumeral.value (== 100)
+    , numberBetween 0 100
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       Token Numeral (NumeralData {TNumeral.value = v3}):
+       _) -> double $ v1 * v2 + v3
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ."
+  , pattern =
+    [ regex "(\\d{1,3}(\\.\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton '.') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleNumeral
+  , ruleNumeral2
+  , ruleNumeral3
+  , ruleNumeral4
+  , ruleNumeral5
+  , ruleNumerals
+  , ruleNumeralsPrefixWithNegativeOrMinus
+  , ruleNumeralsSuffixesKMG
+  ]
diff --git a/Duckling/Numeral/JA/Corpus.hs b/Duckling/Numeral/JA/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/JA/Corpus.hs
@@ -0,0 +1,109 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.JA.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = JA}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "零"
+             , "ゼロ"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "一"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "三十三"
+             , "0033"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "十四"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "十六"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "十七"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "十八"
+             ]
+  , examples (NumeralValue 100)
+             [ "100"
+             , "百"
+             ]
+  , examples (NumeralValue 101)
+             [ "101"
+             , "百一"
+             ]
+  , examples (NumeralValue 200)
+             [ "200"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1.1"
+             , "1.10"
+             , "01.10"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0.77"
+             , ".77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100,000"
+             , "100000"
+             , "100K"
+             , "100k"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3M"
+             , "3000K"
+             , "3000000"
+             , "3,000,000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1,200,000"
+             , "1200000"
+             , "1.2M"
+             , "1200K"
+             , ".0012G"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "- 1,200,000"
+             , "-1200000"
+             , "-1.2M"
+             , "-1200K"
+             , "-.0012G"
+             ]
+  , examples (NumeralValue 5000)
+             [ "5千"
+             ]
+  , examples (NumeralValue 20000)
+             [ "2万"
+             ]
+  ]
diff --git a/Duckling/Numeral/JA/Rules.hs b/Duckling/Numeral/JA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/JA/Rules.hs
@@ -0,0 +1,373 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.JA.Rules
+  ( rules ) where
+
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleInteger5 :: Rule
+ruleInteger5 = Rule
+  { name = "integer (100)"
+  , pattern =
+    [ regex "\x767e"
+    ]
+  , prod = \_ -> integer 100
+  }
+
+ruleNumeralsPrefixWithNegativeOrMinus :: Rule
+ruleNumeralsPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|\x30de\x30a4\x30ca\x30b9\\s?|\x8ca0\\s?"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral nd:_) -> double (TNumeral.value nd * (-1))
+      _ -> Nothing
+  }
+
+ruleInteger17 :: Rule
+ruleInteger17 = Rule
+  { name = "integer (0..10)"
+  , pattern =
+    [ regex "(\x30bc\x30ed|\x96f6|\x4e00|\x4e8c|\x4e09|\x56db|\x4e94|\x516d|\x4e03|\x516b|\x4e5d|\x5341)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case match of
+        "\x30bc\x30ed" -> integer 0
+        "\x96f6" -> integer 0
+        "\x4e00" -> integer 1
+        "\x4e8c" -> integer 2
+        "\x4e09" -> integer 3
+        "\x56db" -> integer 4
+        "\x4e94" -> integer 5
+        "\x516d" -> integer 6
+        "\x4e03" -> integer 7
+        "\x516b" -> integer 8
+        "\x4e5d" -> integer 9
+        "\x5341" -> integer 10
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- toInteger <$> parseInt match
+        integer v
+      _ -> Nothing
+  }
+
+ruleInteger10 :: Rule
+ruleInteger10 = Rule
+  { name = "integer (1000..1999)"
+  , pattern =
+    [ regex "\x5343"
+    , numberBetween 1 1000
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        double $ v + 1000
+      _ -> Nothing
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(,\\d\\d\\d)+\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+ruleInteger15 :: Rule
+ruleInteger15 = Rule
+  { name = "integer (20000..90000)"
+  , pattern =
+    [ numberBetween 2 10
+    , regex "\x4e07"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ v * 10000
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> parseDecimal True match
+      _ -> Nothing
+  }
+
+ruleNumeral :: Rule
+ruleNumeral = Rule
+  { name = "<number>个"
+  , pattern =
+    [ dimension Numeral
+    , regex "\x4e2a"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer (20..90)"
+  , pattern =
+    [ numberBetween 2 10
+    , regex "\x5341"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ v * 10
+      _ -> Nothing
+  }
+
+ruleInteger13 :: Rule
+ruleInteger13 = Rule
+  { name = "integer (10000)"
+  , pattern =
+    [ regex "\x4e07"
+    ]
+  , prod = \_ -> integer 10000
+  }
+
+ruleInteger6 :: Rule
+ruleInteger6 = Rule
+  { name = "integer (100..199)"
+  , pattern =
+    [ regex "\x767e"
+    , numberBetween 1 100
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ v + 100
+      _ -> Nothing
+  }
+
+ruleInteger12 :: Rule
+ruleInteger12 = Rule
+  { name = "integer 2001..9999"
+  , pattern =
+    [ oneOf [3000, 9000, 7000, 8000, 2000, 4000, 6000, 5000]
+    , numberBetween 1 1000
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G, 千, 万)"
+  , pattern =
+    [ dimension Numeral
+    , regex "(k|m|g|\x5343|\x4e07)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+          "k"      -> double $ v * 1e3
+          "\x5343" -> double $ v * 1e3
+          "\x4e07" -> double $ v * 1e4
+          "m"      -> double $ v * 1e6
+          "g"      -> double $ v * 1e9
+          _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleInteger7 :: Rule
+ruleInteger7 = Rule
+  { name = "integer (200..900)"
+  , pattern =
+    [ numberBetween 2 10
+    , regex "\x767e"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ v * 100
+      _ -> Nothing
+  }
+
+ruleInteger14 :: Rule
+ruleInteger14 = Rule
+  { name = "integer (10000..19999)"
+  , pattern =
+    [ regex "\x4e07"
+    , numberBetween 1 10000
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        double $ v + 10000
+      _ -> Nothing
+  }
+
+ruleInteger8 :: Rule
+ruleInteger8 = Rule
+  { name = "integer 201..999"
+  , pattern =
+    [ oneOf [300, 600, 500, 800, 200, 900, 700, 400]
+    , numberBetween 1 100
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleInteger16 :: Rule
+ruleInteger16 = Rule
+  { name = "integer 20001..99999"
+  , pattern =
+    [ oneOf [20000, 40000, 80000, 60000, 30000, 70000, 90000, 50000]
+    , numberBetween 1 10000
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleInteger9 :: Rule
+ruleInteger9 = Rule
+  { name = "integer (1000)"
+  , pattern =
+    [ regex "\x5343"
+    ]
+  , prod = \_ -> integer 1000
+  }
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer (0..10)"
+  , pattern =
+    [ regex "\x30bc\x30ed|\x96f6|\x4e00|\x4e8c|\x4e09|\x56db|\x4e94|\x516d|\x4e03|\x516b|\x4e5d|\x5341"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case match of
+        "\x96f6" -> integer 0
+        "\x30bc\x30ed" -> integer 0
+        "\x4e00" -> integer 1
+        "\x4e8c" -> integer 2
+        "\x4e09" -> integer 3
+        "\x56db" -> integer 4
+        "\x4e94" -> integer 5
+        "\x516d" -> integer 6
+        "\x4e03" -> integer 7
+        "\x516b" -> integer 8
+        "\x4e5d" -> integer 9
+        "\x5341" -> integer 10
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleInteger4 :: Rule
+ruleInteger4 = Rule
+  { name = "integer 21..99"
+  , pattern =
+    [ oneOf [70, 20, 60, 50, 40, 90, 30, 80]
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer (11..19)"
+  , pattern =
+    [ regex "\x5341"
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ v + 10
+      _ -> Nothing
+  }
+
+ruleInteger11 :: Rule
+ruleInteger11 = Rule
+  { name = "integer (2000..9000)"
+  , pattern =
+    [ numberBetween 2 10
+    , regex "\x5343"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ v * 1000
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ,"
+  , pattern =
+    [ regex "(\\d{1,3}(,\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleInteger
+  , ruleInteger10
+  , ruleInteger11
+  , ruleInteger12
+  , ruleInteger13
+  , ruleInteger14
+  , ruleInteger15
+  , ruleInteger16
+  , ruleInteger17
+  , ruleInteger2
+  , ruleInteger3
+  , ruleInteger4
+  , ruleInteger5
+  , ruleInteger6
+  , ruleInteger7
+  , ruleInteger8
+  , ruleInteger9
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleNumeral
+  , ruleNumeralsPrefixWithNegativeOrMinus
+  , ruleNumeralsSuffixesKMG
+  ]
diff --git a/Duckling/Numeral/KO/Corpus.hs b/Duckling/Numeral/KO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/KO/Corpus.hs
@@ -0,0 +1,194 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.KO.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = KO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "영"
+             , "빵"
+             , "공"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "일"
+             , "하나"
+             , "한"
+             ]
+  , examples (NumeralValue 10)
+             [ "10"
+             , "십"
+             , "열"
+             ]
+  , examples (NumeralValue 11)
+             [ "11"
+             , "십일"
+             , "열하나"
+             , "십하나"
+             , "열한"
+             ]
+  , examples (NumeralValue 20)
+             [ "20"
+             , "이십"
+             , "스물"
+             ]
+  , examples (NumeralValue 35)
+             [ "35"
+             , "삼십오"
+             , "서른다섯"
+             ]
+  , examples (NumeralValue 47)
+             [ "47"
+             , "사십칠"
+             , "마흔일곱"
+             ]
+  , examples (NumeralValue 52)
+             [ "52"
+             , "오십이"
+             , "쉰둘"
+             , "쉰두"
+             ]
+  , examples (NumeralValue 69)
+             [ "69"
+             , "육십구"
+             , "예순아홉"
+             ]
+  , examples (NumeralValue 71)
+             [ "71"
+             , "칠십일"
+             , "일흔하나"
+             , "일흔한"
+             ]
+  , examples (NumeralValue 84)
+             [ "84"
+             , "팔십사"
+             , "여든넷"
+             ]
+  , examples (NumeralValue 93)
+             [ "93"
+             , "구십삼"
+             , "아흔셋"
+             ]
+  , examples (NumeralValue 100)
+             [ "100"
+             , "백"
+             ]
+  , examples (NumeralValue 123)
+             [ "123"
+             , "백이십삼"
+             ]
+  , examples (NumeralValue 579)
+             [ "579"
+             , "오백칠십구"
+             ]
+  , examples (NumeralValue 1000)
+             [ "1000"
+             , "천"
+             ]
+  , examples (NumeralValue 1723)
+             [ "1723"
+             , "천칠백이십삼"
+             ]
+  , examples (NumeralValue 5619)
+             [ "5619"
+             , "오천육백십구"
+             ]
+  , examples (NumeralValue 10000)
+             [ "10000"
+             , "만"
+             , "일만"
+             ]
+  , examples (NumeralValue 12345)
+             [ "12345"
+             , "만이천삼백사십오"
+             , "일만이천삼백사십오"
+             ]
+  , examples (NumeralValue 58194)
+             [ "58194"
+             , "오만팔천백구십사"
+             ]
+  , examples (NumeralValue 581900)
+             [ "581900"
+             , "오십팔만천구백"
+             ]
+  , examples (NumeralValue 5819014)
+             [ "5819014"
+             , "오백팔십일만구천십사"
+             ]
+  , examples (NumeralValue 58190148)
+             [ "58190148"
+             , "오천팔백십구만백사십팔"
+             ]
+  , examples (NumeralValue 100000000)
+             [ "100000000"
+             , "일억"
+             ]
+  , examples (NumeralValue 274500000000)
+             [ "274500000000"
+             , "이천칠백사십오억"
+             ]
+  , examples (NumeralValue 100000002)
+             [ "100000002"
+             , "일억이"
+             ]
+  , examples (NumeralValue 27350000)
+             [ "27350000"
+             , "이천칠백삼십오만"
+             ]
+  , examples (NumeralValue 3235698120)
+             [ "3235698120"
+             , "삼십이억삼천오백육십구만팔천백이십"
+             ]
+  , examples (NumeralValue 40234985729)
+             [ "40234985729"
+             , "사백이억삼천사백구십팔만오천칠백이십구"
+             ]
+  , examples (NumeralValue 701239801123)
+             [ "701239801123"
+             , "칠천십이억삼천구백팔십만천백이십삼"
+             ]
+  , examples (NumeralValue 3.4)
+             [ "3.4"
+             , "삼점사"
+             ]
+  , examples (NumeralValue 4123.3)
+             [ "4123.3"
+             , "사천백이십삼점삼"
+             ]
+  , examples (NumeralValue 1.23)
+             [ "일점이삼"
+             ]
+  , examples (NumeralValue (-3))
+             [ "-3"
+             , "마이너스3"
+             , "마이너스삼"
+             , "마이너스 3"
+             , "마이나스3"
+             , "마이나스 3"
+             ]
+  , examples (NumeralValue 0.75)
+             [ "3/4"
+             , "사분의삼"
+             ]
+  ]
diff --git a/Duckling/Numeral/KO/Rules.hs b/Duckling/Numeral/KO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/KO/Rules.hs
@@ -0,0 +1,334 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.KO.Rules
+  ( rules ) where
+
+import Data.Maybe
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleIntegerForOrdinals :: Rule
+ruleIntegerForOrdinals = Rule
+  { name = "integer (1..4) - for ordinals"
+  , pattern =
+    [ regex "(\xd55c|\xccab|\xb450|\xc138|\xb124)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case match of
+        "\xd55c" -> integer 1
+        "\xccab" -> integer 1
+        "\xb450" -> integer 2
+        "\xc138" -> integer 3
+        "\xb124" -> integer 4
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- parseInt match
+        integer $ toInteger v
+      _ -> Nothing
+  }
+
+ruleFew :: Rule
+ruleFew = Rule
+  { name = "few 몇"
+  , pattern =
+    [ regex "\xba87"
+    ]
+  , prod = \_ -> integer 3
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(,\\d\\d\\d)+\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> parseDecimal True match
+      _ -> Nothing
+  }
+
+ruleFraction2 :: Rule
+ruleFraction2 = Rule
+  { name = "fraction"
+  , pattern =
+    [ dimension Numeral
+    , regex "/"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 / v2
+      _ -> Nothing
+  }
+
+ruleNumeralsPrefixWithOr :: Rule
+ruleNumeralsPrefixWithOr = Rule
+  { name = "numbers prefix with -, 마이너스, or 마이나스"
+  , pattern =
+    [ regex "-|\xb9c8\xc774\xb108\xc2a4\\s?|\xb9c8\xc774\xb098\xc2a4\\s?"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral nd:_) -> double (TNumeral.value nd * (-1))
+      _ -> Nothing
+  }
+
+ruleHalf :: Rule
+ruleHalf = Rule
+  { name = "half - 반"
+  , pattern =
+    [ regex "\xbc18"
+    ]
+  , prod = \_ -> double 0.5
+  }
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer 0"
+  , pattern =
+    [ regex "\xc601|\xacf5|\xbe75"
+    ]
+  , prod = \_ -> integer 0
+  }
+
+ruleIntegerTypeAndOrdinals :: Rule
+ruleIntegerTypeAndOrdinals = Rule
+  { name = "integer (20..90) - TYPE 2 and ordinals"
+  , pattern =
+    [ regex "(\xc5f4|\xc2a4\xbb3c|\xc11c\xb978|\xb9c8\xd754|\xc270|\xc608\xc21c|\xc77c\xd754|\xc5ec\xb4e0|\xc544\xd754)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case match of
+        "\xc5f4" -> integer 10
+        "\xc2a4\xbb3c" -> integer 20
+        "\xc11c\xb978" -> integer 30
+        "\xb9c8\xd754" -> integer 40
+        "\xc270" -> integer 50
+        "\xc608\xc21c" -> integer 60
+        "\xc77c\xd754" -> integer 70
+        "\xc5ec\xb4e0" -> integer 80
+        "\xc544\xd754" -> integer 90
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleIntegerType1 :: Rule
+ruleIntegerType1 = Rule
+  { name = "integer - TYPE 1"
+  , pattern =
+    [ regex "(\xc601|\xc77c|\xc774|\xc0bc|\xc0ac|\xc624|\xc721|\xce60|\xd314|\xad6c)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case match of
+        "\xc601" -> integer 0
+        "\xc77c" -> integer 1
+        "\xc774" -> integer 2
+        "\xc0bc" -> integer 3
+        "\xc0ac" -> integer 4
+        "\xc624" -> integer 5
+        "\xc721" -> integer 6
+        "\xce60" -> integer 7
+        "\xd314" -> integer 8
+        "\xad6c" -> integer 9
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleIntegerType1PowersOfTen :: Rule
+ruleIntegerType1PowersOfTen = Rule
+  { name = "integer - TYPE 1: powers of ten"
+  , pattern =
+    [ regex "(\xc2ed|\xbc31|\xcc9c|\xb9cc|\xc5b5|\xc870)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case match of
+        "\xc2ed" -> double 10   >>= withGrain 1  >>= withMultipliable
+        "\xbc31" -> double 1e2  >>= withGrain 2  >>= withMultipliable
+        "\xcc9c" -> double 1e3  >>= withGrain 3  >>= withMultipliable
+        "\xb9cc" -> double 1e4  >>= withGrain 4  >>= withMultipliable
+        "\xc5b5" -> double 1e8  >>= withGrain 8  >>= withMultipliable
+        "\xc870" -> double 1e12 >>= withGrain 12 >>= withMultipliable
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleSum :: Rule
+ruleSum = Rule
+  { name = "intersect 2 numbers"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens ->
+      case tokens of
+        (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+         Token Numeral (NumeralData {TNumeral.value = val2}):
+         _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+        _ -> Nothing
+  }
+
+ruleMultiply :: Rule
+ruleMultiply = Rule
+  { name = "compose by multiplication"
+  , pattern =
+    [ dimension Numeral
+    , numberWith TNumeral.multipliable id
+    ]
+  , prod = \tokens -> case tokens of
+      (token1:token2:_) -> multiply token1 token2
+      _ -> Nothing
+  }
+
+ruleIntegerType2 :: Rule
+ruleIntegerType2 = Rule
+  { name = "integer (1..10) - TYPE 2"
+  , pattern =
+    [ regex "(\xd558\xb098|\xb458|\xc14b|\xb137|\xb2e4\xc12f|\xc5ec\xc12f|\xc77c\xacf1|\xc5ec\xb35f|\xc544\xd649)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case match of
+        "\xd558\xb098" -> integer 1
+        "\xb458" -> integer 2
+        "\xc14b" -> integer 3
+        "\xb137" -> integer 4
+        "\xb2e4\xc12f" -> integer 5
+        "\xc5ec\xc12f" -> integer 6
+        "\xc77c\xacf1" -> integer 7
+        "\xc5ec\xb35f" -> integer 8
+        "\xc544\xd649" -> integer 9
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleFraction :: Rule
+ruleFraction = Rule
+  { name = "fraction"
+  , pattern =
+    [ dimension Numeral
+    , regex "\xbd84(\xc758|\xc5d0)"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v2 / v1
+      _ -> Nothing
+  }
+
+ruleNumeralDotNumeral :: Rule
+ruleNumeralDotNumeral = Rule
+  { name = "number dot number - 삼점사"
+  , pattern =
+    [ dimension Numeral
+    , regex "(\xc810|\xca5c)((\xc601|\xc77c|\xc774|\xc0bc|\xc0ac|\xc624|\xc721|\xce60|\xd314|\xad6c)+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token RegexMatch (GroupMatch (_:match:_)):
+       _) -> do
+        let getDigit '\xc601' = Just "0"
+            getDigit '\xc77c' = Just "1"
+            getDigit '\xc774' = Just "2"
+            getDigit '\xc0bc' = Just "3"
+            getDigit '\xc0ac' = Just "4"
+            getDigit '\xc624' = Just "5"
+            getDigit '\xc721' = Just "6"
+            getDigit '\xce60' = Just "7"
+            getDigit '\xd314' = Just "8"
+            getDigit '\xad6c' = Just "9"
+            getDigit _ = Nothing
+        v2 <- parseDouble . Text.concat . mapMaybe getDigit $ Text.unpack match
+        double $ v1 + decimalsToDouble v2
+      _ -> Nothing
+  }
+
+ruleIntegerType3 :: Rule
+ruleIntegerType3 = Rule
+  { name = "integer (21..99) - TYPE 2"
+  , pattern =
+    [ oneOf [10, 20 .. 90]
+    , oneOf [1 .. 9]
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ,"
+  , pattern =
+    [ regex "(\\d{1,3}(,\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleFew
+  , ruleFraction
+  , ruleFraction2
+  , ruleHalf
+  , ruleInteger
+  , ruleIntegerForOrdinals
+  , ruleIntegerNumeric
+  , ruleIntegerType1
+  , ruleIntegerType1PowersOfTen
+  , ruleSum
+  , ruleMultiply
+  , ruleIntegerType2
+  , ruleIntegerType3
+  , ruleIntegerTypeAndOrdinals
+  , ruleIntegerWithThousandsSeparator
+  , ruleNumeralDotNumeral
+  , ruleNumeralsPrefixWithOr
+  ]
diff --git a/Duckling/Numeral/MY/Corpus.hs b/Duckling/Numeral/MY/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/MY/Corpus.hs
@@ -0,0 +1,71 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.MY.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = MY}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "၀"
+             , "သုံည"
+             , "မရှိ"
+             ]
+  , examples (NumeralValue 1)
+             [ "၁"
+             , "တစ်"
+             , "ပထမ"
+             ]
+  , examples (NumeralValue 2)
+             [ "၂"
+             , "နှစ်"
+             , "ဒုတိယ"
+             ]
+  , examples (NumeralValue 3)
+             [ "၃"
+             , "သုံး"
+             , "တတိယ"
+             ]
+  , examples (NumeralValue 30)
+             [ "သုံးဆယ်"
+             ]
+  , examples (NumeralValue 33)
+             [ "သုံးဆယ့်သုံး"
+             ]
+  , examples (NumeralValue 14)
+             [ "ဆယ့်လေး"
+             ]
+  , examples (NumeralValue 17)
+             [ "ဆယ့်ခုနှစ်"
+             ]
+  , examples (NumeralValue 200)
+             [ "နှစ်ရာ"
+             ]
+  , examples (NumeralValue 900)
+             [ "ကိုးရာ"
+             ]
+  , examples (NumeralValue 5000)
+             [ "ငါးထောင်"
+             ]
+  , examples (NumeralValue 80000)
+             [ "ရှစ်သောင်း"
+             ]
+  ]
diff --git a/Duckling/Numeral/MY/Rules.hs b/Duckling/Numeral/MY/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/MY/Rules.hs
@@ -0,0 +1,181 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.MY.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleInteger5 :: Rule
+ruleInteger5 = Rule
+  { name = "integer (11..99) "
+  , pattern =
+    [ numberBetween 1 10
+    , regex "\x1006\x101a\x103a\x1037"
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2 * 10
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (0..9) - numeric"
+  , pattern =
+    [ regex "(\x1040|\x1041|\x1042|\x1043|\x1044|\x1045|\x1046|\x1047|\x1048|\x1049)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case match of
+        "\x1040" -> integer 0
+        "\x1041" -> integer 1
+        "\x1042" -> integer 2
+        "\x1043" -> integer 3
+        "\x1044" -> integer 4
+        "\x1045" -> integer 5
+        "\x1046" -> integer 6
+        "\x1047" -> integer 7
+        "\x1048" -> integer 8
+        "\x1049" -> integer 9
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer (11..19) "
+  , pattern =
+    [ regex "\x1006\x101a\x103a\x1037"
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ v + 10
+      _ -> Nothing
+  }
+
+ruleIntegerPali :: Rule
+ruleIntegerPali = Rule
+  { name = "integer (1..3) - pali"
+  , pattern =
+    [ regex "(\x1015\x1011\x1019|\x1012\x102f\x1010\x102d\x101a|\x1010\x1010\x102d\x101a)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case match of
+        "\x1015\x1011\x1019" -> integer 1
+        "\x1012\x102f\x1010\x102d\x101a" -> integer 2
+        "\x1010\x1010\x102d\x101a" -> integer 3
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleInteger6 :: Rule
+ruleInteger6 = Rule
+  { name = "integer (100..900)"
+  , pattern =
+    [ numberBetween 1 10
+    , regex "\x101b\x102c"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ v * 100
+      _ -> Nothing
+  }
+
+ruleInteger7 :: Rule
+ruleInteger7 = Rule
+  { name = "integer (1000..9000)"
+  , pattern =
+    [ numberBetween 1 10
+    , regex "\x1011\x1031\x102c\x1004\x103a"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ v * 1000
+      _ -> Nothing
+  }
+
+ruleInteger8 :: Rule
+ruleInteger8 = Rule
+  { name = "integer (10000..90000)"
+  , pattern =
+    [ numberBetween 1 10
+    , regex "\x101e\x1031\x102c\x1004\x103a\x1038"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ v * 10000
+      _ -> Nothing
+  }
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer 0"
+  , pattern =
+    [ regex "\x101e\x102f\x1036\x100a|\x1019\x101b\x103e\x102d"
+    ]
+  , prod = \_ -> integer 0
+  }
+
+ruleInteger4 :: Rule
+ruleInteger4 = Rule
+  { name = "integer (10..90)"
+  , pattern =
+    [ numberBetween 1 10
+    , regex "\x1006\x101a\x103a"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ v * 10
+      _ -> Nothing
+  }
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer (1..10)"
+  , pattern =
+    [ regex "(\x1010\x1005\x103a|\x1014\x103e\x1005\x103a|\x101e\x102f\x1036\x1038|\x101c\x1031\x1038|\x1004\x102b\x1038|\x1001\x103c\x1031\x102b\x1000\x103a|\x1001\x102f\x1014\x103e\x1005\x103a|\x101b\x103e\x1005\x103a|\x1000\x102d\x102f\x1038|\x1010\x1005\x103a\x1006\x101a\x103a)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case match of
+        "\x1010\x1005\x103a" -> integer 1
+        "\x1014\x103e\x1005\x103a" -> integer 2
+        "\x101e\x102f\x1036\x1038" -> integer 3
+        "\x101c\x1031\x1038" -> integer 4
+        "\x1004\x102b\x1038" -> integer 5
+        "\x1001\x103c\x1031\x102b\x1000\x103a" -> integer 6
+        "\x1001\x102f\x1014\x103e\x1005\x103a" -> integer 7
+        "\x101b\x103e\x1005\x103a" -> integer 8
+        "\x1000\x102d\x102f\x1038" -> integer 9
+        "\x1010\x1005\x103a\x1006\x101a\x103a" -> integer 10
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleInteger
+  , ruleInteger2
+  , ruleInteger3
+  , ruleInteger4
+  , ruleInteger5
+  , ruleInteger6
+  , ruleInteger7
+  , ruleInteger8
+  , ruleIntegerNumeric
+  , ruleIntegerPali
+  ]
diff --git a/Duckling/Numeral/NB/Corpus.hs b/Duckling/Numeral/NB/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/NB/Corpus.hs
@@ -0,0 +1,126 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.NB.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = NB}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "null"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "én"
+             , "en"
+             , "Ett"
+             ]
+  , examples (NumeralValue 2)
+             [ "2"
+             , "to"
+             , "et par"
+             ]
+  , examples (NumeralValue 7)
+             [ "7"
+             , "syv"
+             , "sju"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "fjorten"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "seksten"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "sytten"
+             , "søtten"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "atten"
+             ]
+  , examples (NumeralValue 20)
+             [ "20"
+             , "tyve"
+             , "Tjue"
+             ]
+  , examples (NumeralValue 30)
+             [ "30"
+             , "tretti"
+             , "tredve"
+             ]
+  , examples (NumeralValue 70)
+             [ "70"
+             , "søtti"
+             , "sytti"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1,1"
+             , "1,10"
+             , "01,10"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0,77"
+             , ",77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100.000"
+             , "100000"
+             , "100K"
+             , "100k"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3M"
+             , "3000K"
+             , "3000000"
+             , "3.000.000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1.200.000"
+             , "1200000"
+             , "1,2M"
+             , "1200K"
+             , ",0012G"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "- 1.200.000"
+             , "-1200000"
+             , "minus 1.200.000"
+             , "negativ 1200000"
+             , "-1,2M"
+             , "-1200K"
+             , "-,0012G"
+             ]
+  , examples (NumeralValue 5000)
+             [ "5 tusen"
+             , "fem tusen"
+             ]
+  , examples (NumeralValue 100)
+             [ "hundre"
+             ]
+  , examples (NumeralValue 5020)
+             [ "fem tusen og tjue"
+             ]
+  ]
diff --git a/Duckling/Numeral/NB/Rules.hs b/Duckling/Numeral/NB/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/NB/Rules.hs
@@ -0,0 +1,325 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.NB.Rules
+  ( rules ) where
+
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.Maybe
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleIntersectWithAnd :: Rule
+ruleIntersectWithAnd = Rule
+  { name = "intersect (with and)"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , regex "og"
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleNumeralsPrefixWithNegativeOrMinus :: Rule
+ruleNumeralsPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|minus\\s?|negativ\\s?"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral nd:_) -> double (TNumeral.value nd * (-1))
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- toInteger <$> parseInt match
+        integer v
+      _ -> Nothing
+  }
+
+ruleFew :: Rule
+ruleFew = Rule
+  { name = "few"
+  , pattern =
+    [ regex "(noen )?f\x00e5"
+    ]
+  , prod = \_ -> integer 3
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(\\.\\d\\d\\d)+\\,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> let dot = Text.singleton '.'
+                 comma = Text.singleton ','
+                 fmt = Text.replace comma dot $ Text.replace dot Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+ruleMultiply :: Rule
+ruleMultiply = Rule
+  { name = "compose by multiplication"
+  , pattern =
+    [ dimension Numeral
+    , numberWith TNumeral.multipliable id
+    ]
+  , prod = \tokens -> case tokens of
+      (token1:token2:_) -> multiply token1 token2
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> parseDecimal False match
+      _ -> Nothing
+  }
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer 21..99"
+  , pattern =
+    [ oneOf [70, 20, 60, 50, 40, 90, 30, 80]
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleSingle :: Rule
+ruleSingle = Rule
+  { name = "single"
+  , pattern =
+    [ regex "enkelt"
+    ]
+  , prod = \_ -> integer 1 >>= withGrain 1
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "([kmg])(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "k" -> double $ v * 1e3
+         "m" -> double $ v * 1e6
+         "g" -> double $ v * 1e9
+         _   -> Nothing
+      _ -> Nothing
+  }
+
+rulePowersOfTen :: Rule
+rulePowersOfTen = Rule
+  { name = "powers of tens"
+  , pattern =
+    [ regex "(hundre(de)?|tusen?|million(er)?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "hundre"    -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "hundrede"  -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "tuse"      -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "tusen"     -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "million"   -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        "millioner" -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        _           -> Nothing
+      _ -> Nothing
+  }
+
+ruleAPair :: Rule
+ruleAPair = Rule
+  { name = "a pair"
+  , pattern =
+    [ regex "et par"
+    ]
+  , prod = \_ -> integer 2 >>= withGrain 1
+  }
+
+ruleDozen :: Rule
+ruleDozen = Rule
+  { name = "dozen"
+  , pattern =
+    [ regex "dusin"
+    ]
+  , prod = \_ -> integer 12 >>= withGrain 1 >>= withMultipliable
+  }
+
+zeroToNineteenMap :: HashMap Text Integer
+zeroToNineteenMap = HashMap.fromList
+  [ ( "null" , 0 )
+  , ( "ingen" , 0 )
+  , ( "intet" , 0 )
+  , ( "en" , 1 )
+  , ( "ett" , 1 )
+  , ( "\x00e9n" , 1 )
+  , ( "to" , 2 )
+  , ( "tre" , 3 )
+  , ( "fire" , 4 )
+  , ( "fem" , 5 )
+  , ( "seks" , 6 )
+  , ( "sju" , 7 )
+  , ( "syv" , 7 )
+  , ( "otte" , 8 )
+  , ( "ni" , 9 )
+  , ( "ti" , 10 )
+  , ( "elleve" , 11 )
+  , ( "tolv" , 12 )
+  , ( "tretten" , 13 )
+  , ( "fjorten" , 14 )
+  , ( "femten" , 15 )
+  , ( "seksten" , 16 )
+  , ( "s\x00f8tten" , 17 )
+  , ( "sytten" , 17 )
+  , ( "atten" , 18 )
+  , ( "nitten" , 19 )
+  ]
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer (0..19)"
+  , pattern =
+    [ regex "(intet|ingen|null|en|ett|\x00e9n|to|tretten|tre|fire|femten|fem|seksten|seks|syv|sju|\x00e5tte|nitten|ni|ti|elleve|tolv|fjorten|sytten|s\x00f8tten|atten)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) zeroToNineteenMap >>= integer
+      _ -> Nothing
+  }
+
+dozensMap :: HashMap Text Integer
+dozensMap = HashMap.fromList
+  [ ( "tyve" , 20 )
+  , ( "tjue" , 20 )
+  , ( "tredve" , 30 )
+  , ( "tretti" , 30 )
+  , ( "f\x00f8rti" , 40 )
+  , ( "femti" , 50 )
+  , ( "seksti" , 60 )
+  , ( "sytti" , 70 )
+  , ( "s\x00f8tti" , 70 )
+  , ( "\x00e5tti" , 80 )
+  , ( "nitti" , 90 )
+  ]
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer (20..90)"
+  , pattern =
+    [ regex "(tyve|tjue|tredve|tretti|f\x00f8rti|femti|seksti|sytti|s\x00f8tti|\x00e5tti|nitti)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) dozensMap >>= integer
+      _ -> Nothing
+  }
+
+ruleNumeralDotNumeral :: Rule
+ruleNumeralDotNumeral = Rule
+  { name = "number dot number"
+  , pattern =
+    [ dimension Numeral
+    , regex "komma"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral nd1:_:Token Numeral nd2:_) ->
+        double $ TNumeral.value nd1 + decimalsToDouble (TNumeral.value nd2)
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ."
+  , pattern =
+    [ regex "(\\d{1,3}(\\.\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton '.') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAPair
+  , ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleDozen
+  , ruleFew
+  , ruleInteger
+  , ruleInteger2
+  , ruleInteger3
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleIntersect
+  , ruleIntersectWithAnd
+  , ruleMultiply
+  , ruleNumeralDotNumeral
+  , ruleNumeralsPrefixWithNegativeOrMinus
+  , ruleNumeralsSuffixesKMG
+  , rulePowersOfTen
+  , ruleSingle
+  ]
diff --git a/Duckling/Numeral/NL/Corpus.hs b/Duckling/Numeral/NL/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/NL/Corpus.hs
@@ -0,0 +1,91 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.NL.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = NL}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "nul"
+             , "geen"
+             , "niks"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "een"
+             , "één"
+             ]
+  , examples (NumeralValue 2)
+             [ "2"
+             , "twee"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "3 en 30"
+             , "drieendertig"
+             , "drieëndertig"
+             , "drie en dertig"
+             , "0033"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "veertien"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "zestien"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "zeventien"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "achtien"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1,1"
+             , "1,10"
+             , "01,10"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0,77"
+             , ",77"
+             ]
+  , examples (NumeralValue 300)
+             [ "3 honderd"
+             , "drie honderd"
+             ]
+  , examples (NumeralValue 5000)
+             [ "5 duizend"
+             , "vijf duizend"
+             ]
+  , examples (NumeralValue 122)
+             [ "honderd tweeëntwintig"
+             , "honderd tweeentwintig"
+             , "honderd twee en twintig"
+             ]
+  , examples (NumeralValue 20000)
+             [ "twintig duizend"
+             ]
+  ]
diff --git a/Duckling/Numeral/NL/Rules.hs b/Duckling/Numeral/NL/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/NL/Rules.hs
@@ -0,0 +1,289 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.NL.Rules
+  ( rules ) where
+
+import Control.Monad (join)
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.Maybe
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleNumeralsPrefixWithNegativeOrMinus :: Rule
+ruleNumeralsPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|min|minus|negatief"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral nd:_) -> double (TNumeral.value nd * (-1))
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- toInteger <$> parseInt match
+        integer v
+      _ -> Nothing
+  }
+
+ruleFew :: Rule
+ruleFew = Rule
+  { name = "few"
+  , pattern =
+    [ regex "meerdere"
+    ]
+  , prod = \_ -> integer 3
+  }
+
+ruleTen :: Rule
+ruleTen = Rule
+  { name = "ten"
+  , pattern =
+    [ regex "tien"
+    ]
+  , prod = \_ -> integer 10 >>= withGrain 1
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(\\.\\d\\d\\d)+,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> let dot = Text.singleton '.'
+                 comma = Text.singleton ','
+                 fmt = Text.replace comma dot $ Text.replace dot Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> parseDecimal False match
+      _ -> Nothing
+  }
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer ([2-9][1-9])"
+  , pattern =
+    [ regex "(een|twee|drie|vier|vijf|zes|zeven|acht|negen)(?:e|\x00eb)n(twintig|dertig|veertig|vijftig|zestig|zeventig|tachtig|negentig)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        v1 <- HashMap.lookup (Text.toLower m1) zeroNineteenMap
+        v2 <- HashMap.lookup (Text.toLower m2) dozenMap
+        integer $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleMultiply :: Rule
+ruleMultiply = Rule
+  { name = "compose by multiplication"
+  , pattern =
+    [ dimension Numeral
+    , numberWith TNumeral.multipliable id
+    ]
+  , prod = \tokens -> case tokens of
+      (token1:token2:_) -> multiply token1 token2
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "([kmg])(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "k" -> double $ v * 1e3
+         "m" -> double $ v * 1e6
+         "g" -> double $ v * 1e9
+         _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumeralsEn :: Rule
+ruleNumeralsEn = Rule
+  { name = "numbers en"
+  , pattern =
+    [ numberBetween 1 10
+    , regex "en"
+    , oneOf [20, 30 .. 90]
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+rulePowersOfTen :: Rule
+rulePowersOfTen = Rule
+  { name = "powers of tens"
+  , pattern =
+    [ regex "(honderd|duizend|miljoen)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "honderd" -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "duizend" -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "miljoen" -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        _         -> Nothing
+      _ -> Nothing
+  }
+
+ruleCouple :: Rule
+ruleCouple = Rule
+  { name = "couple"
+  , pattern =
+    [ regex "(een )?paar"
+    ]
+  , prod = \_ -> integer 2
+  }
+
+ruleDozen :: Rule
+ruleDozen = Rule
+  { name = "dozen"
+  , pattern =
+    [ regex "dozijn"
+    ]
+  , prod = \_ -> integer 12 >>= withGrain 1
+  }
+
+zeroNineteenMap :: HashMap Text Integer
+zeroNineteenMap = HashMap.fromList
+  [ ("niks", 0)
+  , ("nul", 0)
+  , ("geen", 0)
+  , ("\x00e9\x00e9n", 1)
+  , ("een", 1)
+  , ("twee", 2)
+  , ("drie", 3)
+  , ("vier", 4)
+  , ("vijf", 5)
+  , ("zes", 6)
+  , ("zeven", 7)
+  , ("acht", 8)
+  , ("negen", 9)
+  , ("tien", 10)
+  , ("elf", 11)
+  , ("twaalf", 12)
+  , ("dertien", 13)
+  , ("veertien", 14)
+  , ("vijftien", 15)
+  , ("zestien", 16)
+  , ("zeventien", 17)
+  , ("achtien", 18)
+  , ("negentien", 19)
+  ]
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer (0..19)"
+  , pattern =
+    [ regex "(geen|nul|niks|een|\x00e9\x00e9n|twee|drie|vier|vijftien|vijf|zestien|zes|zeventien|zeven|achtien|acht|negentien|negen|tien|elf|twaalf|dertien|veertien)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) zeroNineteenMap >>= integer
+      _ -> Nothing
+  }
+
+dozenMap :: HashMap Text Integer
+dozenMap = HashMap.fromList
+  [ ("twintig", 20)
+  , ("dertig", 30)
+  , ("veertig", 40)
+  , ("vijftig", 50)
+  , ("zestig", 60)
+  , ("zeventig", 70)
+  , ("tachtig", 80)
+  , ("negentig", 90)
+  ]
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer (20..90)"
+  , pattern =
+    [ regex "(twintig|dertig|veertig|vijftig|zestig|zeventig|tachtig|negentig)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) dozenMap >>= integer
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleCouple
+  , ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleDozen
+  , ruleFew
+  , ruleInteger
+  , ruleInteger2
+  , ruleInteger3
+  , ruleIntegerNumeric
+  , ruleIntersect
+  , ruleMultiply
+  , ruleNumeralsEn
+  , ruleNumeralsPrefixWithNegativeOrMinus
+  , ruleNumeralsSuffixesKMG
+  , rulePowersOfTen
+  , ruleTen
+  ]
diff --git a/Duckling/Numeral/PL/Corpus.hs b/Duckling/Numeral/PL/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/PL/Corpus.hs
@@ -0,0 +1,125 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.PL.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = PL}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "nic"
+             , "zero"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "jeden"
+             , "pojedynczy"
+             ]
+  , examples (NumeralValue 2)
+             [ "2"
+             , "dwa"
+             , "para"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "trzydzieści trzy"
+             , "0033"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "czternaście"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "szesnaście"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "siedemnaście"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "osiemnaście"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1.1"
+             , "1.10"
+             , "01.10"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0.77"
+             , ".77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100,000"
+             , "100000"
+             , "100K"
+             , "100k"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3M"
+             , "3000K"
+             , "3000000"
+             , "3,000,000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1,200,000"
+             , "1200000"
+             , "1.2M"
+             , "1200K"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "- 1,200,000"
+             , "-1200000"
+             , "minus 1,200,000"
+             , "-1.2M"
+             , "-1200K"
+             , "-.0012G"
+             ]
+  , examples (NumeralValue 5000)
+             [ "5 tysięcy"
+             , "pięć tysięcy"
+             ]
+  , examples (NumeralValue 122)
+             [ "sto dwadzieścia dwa"
+             ]
+  , examples (NumeralValue 200000)
+             [ "dwieście tysięcy"
+             ]
+  , examples (NumeralValue 21011)
+             [ "dwadzieścia jeden tysięcy i jedenaście"
+             , "dwadzieścia jeden tysięcy jedenaście"
+             ]
+  , examples (NumeralValue 721012)
+             [ "siedemset dwadzieścia jeden tysięcy dwanaście"
+             , "siedemset dwadzieścia jeden tysięcy i dwanaście"
+             ]
+  , examples (NumeralValue 65000000)
+             [ "sześćdziesiąt pięć milionów"
+             ]
+  , examples (NumeralValue 31256721)
+             [ "trzydzieści jeden milionów dwieście pięćdziesiąt sześć tysięcy siedemset dwadzieścia jeden"
+             ]
+  , examples (NumeralValue 15)
+             [ "piętnasta"
+             ]
+  ]
diff --git a/Duckling/Numeral/PL/Rules.hs b/Duckling/Numeral/PL/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/PL/Rules.hs
@@ -0,0 +1,613 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.PL.Rules
+  ( rules ) where
+
+import Data.Maybe
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleSixteen :: Rule
+ruleSixteen = Rule
+  { name = "sixteen"
+  , pattern =
+    [ regex "szesna(s|\x015b)(tu|cie|toma)"
+    ]
+  , prod = \_ -> integer 16
+  }
+
+ruleFourteen :: Rule
+ruleFourteen = Rule
+  { name = "fourteen"
+  , pattern =
+    [ regex "czterna(s|\x015b)(tu|cie|toma)"
+    ]
+  , prod = \_ -> integer 14
+  }
+
+ruleTwo :: Rule
+ruleTwo = Rule
+  { name = "two"
+  , pattern =
+    [ regex "dw(a|(o|\x00f3)(ch|m)|oma|iema|ie)"
+    ]
+  , prod = \_ -> integer 2
+  }
+
+ruleSixty :: Rule
+ruleSixty = Rule
+  { name = "sixty"
+  , pattern =
+    [ regex "sze(\x015b\x0107)dziesi(\x0105)t|sze(\x015b\x0107)dziesi(\x0119)ci(u|oma)"
+    ]
+  , prod = \_ -> integer 60
+  }
+
+ruleIntersectWithAnd :: Rule
+ruleIntersectWithAnd = Rule
+  { name = "intersect (with and)"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , regex "i|a"
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleNumeralsPrefixWithNegativeOrMinus :: Rule
+ruleNumeralsPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|minus\\s?|negative\\s?"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral nd:_) -> double (TNumeral.value nd * (-1))
+      _ -> Nothing
+  }
+
+ruleOne :: Rule
+ruleOne = Rule
+  { name = "one"
+  , pattern =
+    [ regex "jed(en|nego|nemu|nym|nej|n(a|\x0105))"
+    ]
+  , prod = \_ -> integer 1
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- toInteger <$> parseInt match
+        integer v
+      _ -> Nothing
+  }
+
+ruleTen :: Rule
+ruleTen = Rule
+  { name = "ten"
+  , pattern =
+    [ regex "dzisi(e|\x0119)(\x0107|c)(iu|ioma)?"
+    ]
+  , prod = \_ -> integer 10
+  }
+
+ruleSpecialCompositionForMissingHundredsLikeInOneTwentyTwo :: Rule
+ruleSpecialCompositionForMissingHundredsLikeInOneTwentyTwo = Rule
+  { name = "special composition for missing hundreds like in one twenty two"
+  , pattern =
+    [ numberBetween 1 10
+    , numberBetween 10 100
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double (v1 * 100 + v2)
+      _ -> Nothing
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(,\\d\\d\\d)+\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+ruleNine :: Rule
+ruleNine = Rule
+  { name = "nine"
+  , pattern =
+    [ regex "dziewi(e|\x0119)(\x0107|c)(iu|ioma)?"
+    ]
+  , prod = \_ -> integer 9
+  }
+
+ruleNumeral8 :: Rule
+ruleNumeral8 = Rule
+  { name = "number 800"
+  , pattern =
+    [ regex "(osiem(set| setek))"
+    ]
+  , prod = \_ -> integer 800 >>= withGrain 2
+  }
+
+ruleTwelve :: Rule
+ruleTwelve = Rule
+  { name = "twelve"
+  , pattern =
+    [ regex "dwunast(u|oma)|dwana(\x015b|s)cie"
+    ]
+  , prod = \_ -> integer 12
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> parseDecimal True match
+      _ -> Nothing
+  }
+
+ruleFifteen :: Rule
+ruleFifteen = Rule
+  { name = "fifteen"
+  , pattern =
+    [ regex "pi(\x0119)tna(s|\x015b)(ta|tu|cie|toma)"
+    ]
+  , prod = \_ -> integer 15
+  }
+
+ruleEleven :: Rule
+ruleEleven = Rule
+  { name = "eleven"
+  , pattern =
+    [ regex "jedena(stu|(s|\x015b)cie|stoma)"
+    ]
+  , prod = \_ -> integer 11
+  }
+
+ruleThirteen :: Rule
+ruleThirteen = Rule
+  { name = "thirteen"
+  , pattern =
+    [ regex "trzyna(\x015b|s)(tu|cie|toma)"
+    ]
+  , prod = \_ -> integer 13
+  }
+
+ruleThirty :: Rule
+ruleThirty = Rule
+  { name = "thirty"
+  , pattern =
+    [ regex "trzydzie(\x015b)ci|trzydziest(u|oma)"
+    ]
+  , prod = \_ -> integer 30
+  }
+
+ruleNumeral2 :: Rule
+ruleNumeral2 = Rule
+  { name = "number 200"
+  , pattern =
+    [ regex "dwie((\x015b)cie| setki)"
+    ]
+  , prod = \_ -> integer 200 >>= withGrain 2
+  }
+
+ruleSeventeen :: Rule
+ruleSeventeen = Rule
+  { name = "seventeen"
+  , pattern =
+    [ regex "siedemna(s|\x015b)(tu|cie|toma)"
+    ]
+  , prod = \_ -> integer 17
+  }
+
+ruleNumeral :: Rule
+ruleNumeral = Rule
+  { name = "number 100"
+  , pattern =
+    [ regex "(sto|setki)"
+    ]
+  , prod = \_ -> integer 100 >>= withGrain 2
+  }
+
+ruleNumeral9 :: Rule
+ruleNumeral9 = Rule
+  { name = "number 900"
+  , pattern =
+    [ regex "dziewi(\x0119\x0107)(set| setek)"
+    ]
+  , prod = \_ -> integer 900 >>= withGrain 2
+  }
+
+ruleSingle :: Rule
+ruleSingle = Rule
+  { name = "single"
+  , pattern =
+    [ regex "pojedynczy"
+    ]
+  , prod = \_ -> integer 1
+  }
+
+ruleTwenty :: Rule
+ruleTwenty = Rule
+  { name = "twenty"
+  , pattern =
+    [ regex "dwadzie(\x015b|s)cia|dwudziest(u|oma)"
+    ]
+  , prod = \_ -> integer 20
+  }
+
+ruleAFew :: Rule
+ruleAFew = Rule
+  { name = "a few"
+  , pattern =
+    [ regex "kilk(a|u)"
+    ]
+  , prod = \_ -> integer 3
+  }
+
+ruleEight :: Rule
+ruleEight = Rule
+  { name = "eight"
+  , pattern =
+    [ regex "o(s|\x015b)(iem|miu|mioma)"
+    ]
+  , prod = \_ -> integer 8
+  }
+
+ruleNumeral5 :: Rule
+ruleNumeral5 = Rule
+  { name = "number 500"
+  , pattern =
+    [ regex "pi(\x0119\x0107)(set| setek)"
+    ]
+  , prod = \_ -> integer 500 >>= withGrain 2
+  }
+
+ruleNumeral3 :: Rule
+ruleNumeral3 = Rule
+  { name = "number 300"
+  , pattern =
+    [ regex "(trzy(sta| setki))"
+    ]
+  , prod = \_ -> integer 300 >>= withGrain 2
+  }
+
+ruleZero :: Rule
+ruleZero = Rule
+  { name = "zero"
+  , pattern =
+    [ regex "(zero|nic)"
+    ]
+  , prod = \_ -> integer 0
+  }
+
+ruleThousand :: Rule
+ruleThousand = Rule
+  { name = "thousand"
+  , pattern =
+    [ regex "ty(s|\x015b)i(a|\x0105|\x0119)c(e|y)?"
+    ]
+  , prod = \_ -> integer 1000 >>= withGrain 3 >>= withMultipliable
+  }
+
+ruleMillion :: Rule
+ruleMillion = Rule
+  { name = "million"
+  , pattern =
+    [ regex "milion(y|(\x00f3)w)?"
+    ]
+  , prod = \_ -> integer 1000000 >>= withGrain 6 >>= withMultipliable
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleMultiply :: Rule
+ruleMultiply = Rule
+  { name = "compose by multiplication"
+  , pattern =
+    [ dimension Numeral
+    , numberWith TNumeral.multipliable id
+    ]
+  , prod = \tokens -> case tokens of
+      (token1:token2:_) -> multiply token1 token2
+      _ -> Nothing
+  }
+
+ruleThree :: Rule
+ruleThree = Rule
+  { name = "three"
+  , pattern =
+    [ regex "trz(y|ema|ech)"
+    ]
+  , prod = \_ -> integer 3
+  }
+
+ruleFour :: Rule
+ruleFour = Rule
+  { name = "four"
+  , pattern =
+    [ regex "czter(ej|y|ech|em|ema)"
+    ]
+  , prod = \_ -> integer 4
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "([kmg])(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "k" -> double $ v * 1e3
+         "m" -> double $ v * 1e6
+         "g" -> double $ v * 1e9
+         _   -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumeral7 :: Rule
+ruleNumeral7 = Rule
+  { name = "number 700"
+  , pattern =
+    [ regex "(siedem(set| setek))"
+    ]
+  , prod = \_ -> integer 700 >>= withGrain 2
+  }
+
+ruleAPair :: Rule
+ruleAPair = Rule
+  { name = "a pair"
+  , pattern =
+    [ regex "para?"
+    ]
+  , prod = \_ -> integer 2
+  }
+
+ruleCouple :: Rule
+ruleCouple = Rule
+  { name = "couple"
+  , pattern =
+    [ regex "pare"
+    ]
+  , prod = \_ -> integer 2
+  }
+
+ruleSix :: Rule
+ruleSix = Rule
+  { name = "six"
+  , pattern =
+    [ regex "sze(s|\x015b)(c|\x0107)(iu|oma|u)?"
+    ]
+  , prod = \_ -> integer 6
+  }
+
+ruleNumeral6 :: Rule
+ruleNumeral6 = Rule
+  { name = "number 600"
+  , pattern =
+    [ regex "(sze\x015b\x0107(set| setek))"
+    ]
+  , prod = \_ -> integer 600 >>= withGrain 2
+  }
+
+ruleNumeral4 :: Rule
+ruleNumeral4 = Rule
+  { name = "number 400"
+  , pattern =
+    [ regex "(cztery(sta| setki))"
+    ]
+  , prod = \_ -> integer 400 >>= withGrain 2
+  }
+
+ruleFive :: Rule
+ruleFive = Rule
+  { name = "five"
+  , pattern =
+    [ regex "pi(e|\x0119)(c|\x0107)(iu|oma|u)?"
+    ]
+  , prod = \_ -> integer 5
+  }
+
+ruleFourty :: Rule
+ruleFourty = Rule
+  { name = "fou?rty"
+  , pattern =
+    [ regex "czterdzie(\x015b)ci|czterdziest(u|oma)"
+    ]
+  , prod = \_ -> integer 40
+  }
+
+ruleDozen :: Rule
+ruleDozen = Rule
+  { name = "dozen"
+  , pattern =
+    [ regex "tuzin"
+    ]
+  , prod = \_ -> integer 12 >>= withMultipliable
+  }
+
+ruleSeven :: Rule
+ruleSeven = Rule
+  { name = "seven"
+  , pattern =
+    [ regex "sied(miu|em|mioma)"
+    ]
+  , prod = \_ -> integer 7
+  }
+
+ruleNineteen :: Rule
+ruleNineteen = Rule
+  { name = "nineteen"
+  , pattern =
+    [ regex "dziewietna(s|\x015b)(tu|cie|toma)"
+    ]
+  , prod = \_ -> integer 19
+  }
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer 21..99"
+  , pattern =
+    [ oneOf [20, 30 .. 90]
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleEighteen :: Rule
+ruleEighteen = Rule
+  { name = "eighteen"
+  , pattern =
+    [ regex "osiemna(s|\x015b)(tu|cie|toma)"
+    ]
+  , prod = \_ -> integer 18
+  }
+
+ruleNumeralDotNumeral :: Rule
+ruleNumeralDotNumeral = Rule
+  { name = "number dot number"
+  , pattern =
+    [ dimension Numeral
+    , regex "dot|point"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral nd1:_:Token Numeral nd2:_) ->
+        double $ TNumeral.value nd1 + decimalsToDouble (TNumeral.value nd2)
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ,"
+  , pattern =
+    [ regex "(\\d{1,3}(,\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+ruleFifty :: Rule
+ruleFifty = Rule
+  { name = "fifty"
+  , pattern =
+    [ regex "pi(\x0119\x0107)dziesi(\x0105)t|pi(\x0119\x0107)dziesi(\x0119)ci(u|oma)"
+    ]
+  , prod = \_ -> integer 50
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAFew
+  , ruleAPair
+  , ruleCouple
+  , ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleDozen
+  , ruleEight
+  , ruleEighteen
+  , ruleEleven
+  , ruleFifteen
+  , ruleFifty
+  , ruleFive
+  , ruleFour
+  , ruleFourteen
+  , ruleFourty
+  , ruleInteger2
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleIntersect
+  , ruleIntersectWithAnd
+  , ruleMillion
+  , ruleMultiply
+  , ruleNine
+  , ruleNineteen
+  , ruleNumeral
+  , ruleNumeral2
+  , ruleNumeral3
+  , ruleNumeral4
+  , ruleNumeral5
+  , ruleNumeral6
+  , ruleNumeral7
+  , ruleNumeral8
+  , ruleNumeral9
+  , ruleNumeralDotNumeral
+  , ruleNumeralsPrefixWithNegativeOrMinus
+  , ruleNumeralsSuffixesKMG
+  , ruleOne
+  , ruleSeven
+  , ruleSeventeen
+  , ruleSingle
+  , ruleSix
+  , ruleSixteen
+  , ruleSixty
+  , ruleSpecialCompositionForMissingHundredsLikeInOneTwentyTwo
+  , ruleTen
+  , ruleThirteen
+  , ruleThirty
+  , ruleThousand
+  , ruleThree
+  , ruleTwelve
+  , ruleTwenty
+  , ruleTwo
+  , ruleZero
+  ]
diff --git a/Duckling/Numeral/PT/Corpus.hs b/Duckling/Numeral/PT/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/PT/Corpus.hs
@@ -0,0 +1,166 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.PT.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = PT}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 1)
+             [ "1"
+             , "um"
+             , "Uma"
+             ]
+  , examples (NumeralValue 2)
+             [ "2"
+             , "dois"
+             , "duas"
+             ]
+  , examples (NumeralValue 3)
+             [ "3"
+             , "três"
+             , "tres"
+             ]
+  , examples (NumeralValue 6)
+             [ "6"
+             , "seis"
+             ]
+  , examples (NumeralValue 11)
+             [ "11"
+             , "onze"
+             ]
+  , examples (NumeralValue 12)
+             [ "12"
+             , "doze"
+             , "uma dúzia"
+             , "uma duzia"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "Catorze"
+             , "quatorze"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "dezesseis"
+             , "dezasseis"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "dezessete"
+             , "dezassete"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "dezoito"
+             ]
+  , examples (NumeralValue 19)
+             [ "19"
+             , "dezenove"
+             , "dezanove"
+             ]
+  , examples (NumeralValue 21)
+             [ "21"
+             , "vinte e um"
+             ]
+  , examples (NumeralValue 23)
+             [ "23"
+             , "vinte e tres"
+             , "vinte e três"
+             ]
+  , examples (NumeralValue 24)
+             [ "24"
+             , "vinte e quatro"
+             , "duas dúzias"
+             , "duas duzias"
+             ]
+  , examples (NumeralValue 50)
+             [ "50"
+             , "cinquenta"
+             , "cinqüenta"
+             , "cincoenta"
+             ]
+  , examples (NumeralValue 70)
+             [ "setenta"
+             ]
+  , examples (NumeralValue 78)
+             [ "setenta e oito"
+             ]
+  , examples (NumeralValue 80)
+             [ "oitenta"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "trinta e três"
+             , "trinta e tres"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1,1"
+             , "1,10"
+             , "01,10"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0,77"
+             , ",77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100.000"
+             , "100000"
+             , "100K"
+             , "100k"
+             ]
+  , examples (NumeralValue 100)
+             [ "100"
+             , "Cem"
+             ]
+  , examples (NumeralValue 243)
+             [ "243"
+             ]
+  , examples (NumeralValue 300)
+             [ "trezentos"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3M"
+             , "3000K"
+             , "3000000"
+             , "3.000.000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1.200.000"
+             , "1200000"
+             , "1,2M"
+             , "1200K"
+             , ",0012G"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "- 1.200.000"
+             , "-1200000"
+             , "menos 1.200.000"
+             , "-1,2M"
+             , "-1200K"
+             , "-,0012G"
+             ]
+  , examples (NumeralValue 1.5)
+             [ "1 ponto cinco"
+             , "um ponto cinco"
+             , "1,5"
+             ]
+  ]
diff --git a/Duckling/Numeral/PT/Rules.hs b/Duckling/Numeral/PT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/PT/Rules.hs
@@ -0,0 +1,306 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.PT.Rules
+  ( rules ) where
+
+import Data.Maybe
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleNumeralsPrefixWithNegativeOrMinus :: Rule
+ruleNumeralsPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|menos"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral nd:_) -> double (TNumeral.value nd * (-1))
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- parseInt match
+        integer $ toInteger v
+      _ -> Nothing
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(\\.\\d\\d\\d)+,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> let dot = Text.singleton '.'
+                 comma = Text.singleton ','
+                 fmt = Text.replace comma dot $ Text.replace dot Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> parseDecimal False match
+      _ -> Nothing
+  }
+
+ruleNumeral2 :: Rule
+ruleNumeral2 = Rule
+  { name = "number (20..90)"
+  , pattern =
+    [ regex "(vinte|trinta|quarenta|cincoenta|cinq(\x00fc)enta|cinquenta|sessenta|setenta|oitenta|noventa)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "vinte" -> integer 20
+        "trinta" -> integer 30
+        "quarenta" -> integer 40
+        "cinq\252enta" -> integer 50
+        "cincoenta" -> integer 50
+        "cinquenta" -> integer 50
+        "sessenta" -> integer 60
+        "setenta" -> integer 70
+        "oitenta" -> integer 80
+        "noventa" -> integer 90
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumeral :: Rule
+ruleNumeral = Rule
+  { name = "number (0..15)"
+  , pattern =
+    [ regex "(zero|uma?|d(oi|ua)s|tr(\x00ea|e)s|quatro|cinco|seis|sete|oito|nove|dez|onze|doze|treze|(ca|qua)torze|quinze)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "zero" -> integer 0
+        "uma" -> integer 1
+        "um" -> integer 1
+        "dois" -> integer 2
+        "duas" -> integer 2
+        "tr\x00eas" -> integer 3
+        "tres" -> integer 3
+        "quatro" -> integer 4
+        "cinco" -> integer 5
+        "seis" -> integer 6
+        "sete" -> integer 7
+        "oito" -> integer 8
+        "nove" -> integer 9
+        "dez" -> integer 10
+        "onze" -> integer 11
+        "doze" -> integer 12
+        "treze" -> integer 13
+        "catorze" -> integer 14
+        "quatorze" -> integer 14
+        "quinze" -> integer 15
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumeral5 :: Rule
+ruleNumeral5 = Rule
+  { name = "number (16..19)"
+  , pattern =
+    [ regex "(dez[ea]sseis|dez[ea]ssete|dezoito|dez[ea]nove)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "dezesseis" -> integer 16
+        "dezasseis" -> integer 16
+        "dezessete" -> integer 17
+        "dezassete" -> integer 17
+        "dezoito" -> integer 18
+        "dezenove" -> integer 19
+        "dezanove" -> integer 19
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumeral3 :: Rule
+ruleNumeral3 = Rule
+  { name = "number (16..19)"
+  , pattern =
+    [ numberWith TNumeral.value (== 10)
+    , regex "e"
+    , numberBetween 6 10
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ 10 + v
+      _ -> Nothing
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "([kmg])(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "k" -> double $ v * 1e3
+         "m" -> double $ v * 1e6
+         "g" -> double $ v * 1e9
+         _   -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumeral6 :: Rule
+ruleNumeral6 = Rule
+  { name = "number 100..1000 "
+  , pattern =
+    [ regex "(ce(m|to)|duzentos|trezentos|quatrocentos|quinhentos|seiscentos|setecentos|oitocentos|novecentos|mil)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "cento" -> integer 100
+        "cem" -> integer 100
+        "duzentos" -> integer 200
+        "trezentos" -> integer 300
+        "quatrocentos" -> integer 400
+        "quinhentos" -> integer 500
+        "seiscentos" -> integer 600
+        "setecentos" -> integer 700
+        "oitocentos" -> integer 800
+        "novecentos" -> integer 900
+        "mil" -> integer 1000
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumeral4 :: Rule
+ruleNumeral4 = Rule
+  { name = "number (21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99)"
+  , pattern =
+    [ oneOf [70, 20, 60, 50, 40, 90, 30, 80]
+    , regex "e"
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleDozen :: Rule
+ruleDozen = Rule
+  { name = "dozen"
+  , pattern =
+    [ regex "d(\x00fa|u)zias?"
+    ]
+  , prod = \_ -> integer 12 >>= withGrain 1 >>= withMultipliable
+  }
+
+ruleNumeralDozen :: Rule
+ruleNumeralDozen = Rule
+  { name = "number dozen"
+  , pattern =
+    [ numberBetween 1 11
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2, TNumeral.grain = Just g}):
+       _) -> double (v1 * v2) >>= withGrain g
+      _ -> Nothing
+  }
+
+ruleNumerals :: Rule
+ruleNumerals = Rule
+  { name = "numbers 200..999"
+  , pattern =
+    [ numberBetween 2 10
+    , numberWith TNumeral.value (== 100)
+    , numberBetween 0 100
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 * 100 + v2
+      _ -> Nothing
+  }
+
+ruleNumeralDotNumeral :: Rule
+ruleNumeralDotNumeral = Rule
+  { name = "number dot number"
+  , pattern =
+    [ dimension Numeral
+    , regex "ponto"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral nd1:_:Token Numeral nd2:_) ->
+        double $ TNumeral.value nd1 + decimalsToDouble (TNumeral.value nd2)
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ."
+  , pattern =
+    [ regex "(\\d{1,3}(\\.\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> let fmt = Text.replace (Text.singleton '.') Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleDozen
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleNumeral
+  , ruleNumeral2
+  , ruleNumeral3
+  , ruleNumeral4
+  , ruleNumeral5
+  , ruleNumeral6
+  , ruleNumeralDotNumeral
+  , ruleNumeralDozen
+  , ruleNumerals
+  , ruleNumeralsPrefixWithNegativeOrMinus
+  , ruleNumeralsSuffixesKMG
+  ]
diff --git a/Duckling/Numeral/RO/Corpus.hs b/Duckling/Numeral/RO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/RO/Corpus.hs
@@ -0,0 +1,192 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.RO.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = RO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "zero"
+             , "nici unul"
+             , "nici unu"
+             , "nici una"
+             , "nici o"
+             , "nicio"
+             , "nimic"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "unu"
+             , "unul"
+             , "un"
+             , "o"
+             , "intai"
+             , "întâi"
+             ]
+  , examples (NumeralValue 2)
+             [ "2"
+             , "doi"
+             , "doua"
+             , "două"
+             ]
+  , examples (NumeralValue 3)
+             [ "3"
+             , "trei"
+             ]
+  , examples (NumeralValue 4)
+             [ "4"
+             , "patru"
+             ]
+  , examples (NumeralValue 5)
+             [ "5"
+             , "cinci"
+             ]
+  , examples (NumeralValue 6)
+             [ "6"
+             , "sase"
+             , "șase"
+             ]
+  , examples (NumeralValue 7)
+             [ "7"
+             , "sapte"
+             , "șapte"
+             ]
+  , examples (NumeralValue 8)
+             [ "8"
+             , "opt"
+             ]
+  , examples (NumeralValue 9)
+             [ "9"
+             , "noua"
+             , "nouă"
+             ]
+  , examples (NumeralValue 10)
+             [ "10"
+             , "zece"
+             , "zeci"
+             ]
+  , examples (NumeralValue 10)
+             [ "10"
+             , "zece"
+             ]
+  , examples (NumeralValue 11)
+             [ "11"
+             , "unsprezece"
+             , "unspe"
+             , "unșpe"
+             ]
+  , examples (NumeralValue 19)
+             [ "19"
+             , "nouasprezece"
+             , "nouaspe"
+             , "nouăsprezece"
+             , "nouășpe"
+             ]
+  , examples (NumeralValue 70)
+             [ "70"
+             , "sapte zeci"
+             , "șapte zeci"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1,1"
+             , "1,10"
+             , "01,10"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0,77"
+             , ",77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100.000"
+             , "100000"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3000000"
+             , "3.000.000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1.200.000"
+             , "1200000"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "-1.200.000"
+             , "-1200000"
+             , "minus 1.200.000"
+             ]
+  , examples (NumeralValue (-3))
+             [ "-3"
+             , "3 negativ"
+             ]
+  , examples (NumeralValue 5000)
+             [ "5 mii"
+             , "cinci mii"
+             ]
+  , examples (NumeralValue 1000)
+             [ "o mie"
+             , "1 mie"
+             ]
+  , examples (NumeralValue 100)
+             [ "o suta"
+             , "o sută"
+             , "1 suta"
+             , "1 sută"
+             ]
+  , examples (NumeralValue 300)
+             [ "3 sute"
+             , "trei sute"
+             ]
+  , examples (NumeralValue 1000000)
+             [ "un milion"
+             , "1 milion"
+             ]
+  , examples (NumeralValue 7000000)
+             [ "7 milioane"
+             , "sapte milioane"
+             , "șapte milioane"
+             ]
+  , examples (NumeralValue 1000000000)
+             [ "un miliard"
+             , "1 miliard"
+             ]
+  , examples (NumeralValue 9000000000)
+             [ "9 miliarde"
+             , "noua miliarde"
+             , "nouă miliarde"
+             ]
+  , examples (NumeralValue 20)
+             [ "20"
+             , "douazeci"
+             , "doua zeci"
+             , "douăzeci"
+             , "două zeci"
+             ]
+  , examples (NumeralValue 23)
+             [ "23"
+             , "20 3"
+             , "douazeci 3"
+             , "douăzeci 3"
+             , "douăzeci trei"
+             , "douazeci si trei"
+             , "douăzeci și trei"
+             ]
+  ]
diff --git a/Duckling/Numeral/RO/Rules.hs b/Duckling/Numeral/RO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/RO/Rules.hs
@@ -0,0 +1,316 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.RO.Rules
+  ( rules ) where
+
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.Maybe
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleNumeralsPrefixWithOrMinus :: Rule
+ruleNumeralsPrefixWithOrMinus = Rule
+  { name = "numbers prefix with - or minus"
+  , pattern =
+    [ regex "-|minus\\s?"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral nd:_) -> double (TNumeral.value nd * (-1))
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- toInteger <$> parseInt match
+        integer v
+      _ -> Nothing
+  }
+
+ruleSpecialCompositionForMissingHundredsLikeInOneTwentyTwo :: Rule
+ruleSpecialCompositionForMissingHundredsLikeInOneTwentyTwo = Rule
+  { name = "special composition for missing hundreds like in one twenty two"
+  , pattern =
+    [ numberBetween 1 10
+    , numberBetween 11 100
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = hundreds}):
+       Token Numeral (NumeralData {TNumeral.value = rest}):
+       _) -> double $ hundreds * 100 + rest
+      _ -> Nothing
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(\\.\\d\\d\\d)+,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> let dot = Text.singleton '.'
+                 comma = Text.singleton ','
+                 fmt = Text.replace comma dot $ Text.replace dot Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> parseDecimal False match
+      _ -> Nothing
+  }
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer 21..99"
+  , pattern =
+    [ oneOf [20, 30 .. 90]
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleMultiply :: Rule
+ruleMultiply = Rule
+  { name = "compose by multiplication"
+  , pattern =
+    [ dimension Numeral
+    , numberWith TNumeral.multipliable id
+    ]
+  , prod = \tokens -> case tokens of
+      (token1:token2:_) -> multiply token1 token2
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleIntersectCuI :: Rule
+ruleIntersectCuI = Rule
+  { name = "intersect (cu și)"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , regex "(s|\x0219)i"
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleNumeralsSuffixesWithNegativ :: Rule
+ruleNumeralsSuffixesWithNegativ = Rule
+  { name = "numbers suffixes with (negativ)"
+  , pattern =
+    [ dimension Numeral
+    , regex "(negativ|neg)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       _) -> double $ v * (-1)
+      _ -> Nothing
+  }
+
+rulePowersOfTen :: Rule
+rulePowersOfTen = Rule
+  { name = "powers of tens"
+  , pattern =
+    [ regex "(sut(a|e|\x0103)?|milio(n|ane)?|miliar(de?)?|mi[ei]?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "suta"      -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "sute"      -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "sut\x0103" -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "mi"        -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "mie"       -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "mii"       -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "milio"     -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        "milion"    -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        "milioane"  -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        "miliar"    -> double 1e9 >>= withGrain 9 >>= withMultipliable
+        "miliard"   -> double 1e9 >>= withGrain 9 >>= withMultipliable
+        "miliarde"  -> double 1e9 >>= withGrain 9 >>= withMultipliable
+        _           -> Nothing
+      _ -> Nothing
+  }
+
+zeroTenMap :: HashMap Text Integer
+zeroTenMap = HashMap.fromList
+  [ ("zero", 0)
+  , ("nimic", 0)
+  , ("nicio", 0)
+  , ("nici o", 0)
+  , ("nici una", 0)
+  , ("nici unu", 0)
+  , ("nici unul", 0)
+  , ("un", 1)
+  , ("una", 1)
+  , ("unu", 1)
+  , ("unul", 1)
+  , ("intai", 1)
+  , ("\x00eentai", 1)
+  , ("int\x00e2i", 1)
+  , ("\x00eent\x00e2i", 1)
+  , ("o", 1)
+  , ("doi", 2)
+  , ("doua", 2)
+  , ("dou\x0103", 2)
+  , ("trei", 3)
+  , ("patru", 4)
+  , ("cinci", 5)
+  , ("sase", 6)
+  , ("\537ase", 6)
+  , ("sapte", 7)
+  , ("\537apte", 7)
+  , ("opt", 8)
+  , ("noua", 9)
+  , ("nou\x0103", 9)
+  , ("zece", 10)
+  , ("zeci", 10)
+  ]
+
+ruleIntegerZeroTen :: Rule
+ruleIntegerZeroTen = Rule
+  { name = "integer (0..10)"
+  , pattern =
+    [ regex "(zero|nimic|nici(\\s?o|\\sun(a|ul?))|una|unul?|doi|dou(a|\x0103)|trei|patru|cinci|(s|\x0219)ase|(s|\x0219)apte|opt|nou(a|\x0103)|zec[ei]|(i|\x00ee)nt(a|\x00e2)i|un|o)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) zeroTenMap >>= integer
+      _ -> Nothing
+  }
+
+elevenNineteenMap :: HashMap Text Integer
+elevenNineteenMap = HashMap.fromList
+  [ ("un", 11)
+  , ("doi", 12)
+  , ("trei", 13)
+  , ("pai", 14)
+  , ("cin", 15)
+  , ("cinci", 15)
+  , ("sai", 16)
+  , ("\537ai", 16)
+  , ("sapti", 17)
+  , ("\537apti", 17)
+  , ("sapte", 17)
+  , ("\537apte", 17)
+  , ("opti", 18)
+  , ("opt", 18)
+  , ("noua", 19)
+  , ("nou\x0103", 19)
+  ]
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer (11..19)"
+  , pattern =
+    [ regex "(cin|sapti|opti)(s|\x0219)pe|(cinci|(s|\x0219)apte|opt)sprezece|(un|doi|trei|pai|(s|\x0219)ai|nou(a|\x0103))((s|\x0219)pe|sprezece)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (e1:_:e2:_:r:_)):_) -> do
+        match <- case () of
+          _ | not $ Text.null e1 -> Just e1
+            | not $ Text.null e2 -> Just e2
+            | not $ Text.null r  -> Just r
+            | otherwise          -> Nothing
+        HashMap.lookup (Text.toLower match) elevenNineteenMap >>= integer
+      _ -> Nothing
+  }
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer (20..90)"
+  , pattern =
+    [ regex "(dou(a|\x0103)|trei|patru|cinci|(s|\x0219)ai|(s|\x0219)apte|opt|nou(a|\x0103))\\s?zeci"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        unit <- HashMap.lookup (Text.toLower match) zeroTenMap
+        integer (unit * 10) >>= withGrain 2 >>= withMultipliable
+      _ -> Nothing
+  }
+
+ruleIntegerCuSeparatorDeMiiDot :: Rule
+ruleIntegerCuSeparatorDeMiiDot = Rule
+  { name = "integer cu separator de mii dot"
+  , pattern =
+    [ regex "(\\d{1,3}(\\.\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> let fmt = Text.replace (Text.singleton '.') Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleInteger
+  , ruleIntegerZeroTen
+  , ruleInteger2
+  , ruleInteger3
+  , ruleIntegerCuSeparatorDeMiiDot
+  , ruleIntegerNumeric
+  , ruleIntersect
+  , ruleIntersectCuI
+  , ruleMultiply
+  , ruleNumeralsPrefixWithOrMinus
+  , ruleNumeralsSuffixesWithNegativ
+  , rulePowersOfTen
+  , ruleSpecialCompositionForMissingHundredsLikeInOneTwentyTwo
+  ]
diff --git a/Duckling/Numeral/RU/Corpus.hs b/Duckling/Numeral/RU/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/RU/Corpus.hs
@@ -0,0 +1,114 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.RU.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = RU}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "ноль"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "один"
+             ]
+  , examples (NumeralValue 2)
+             [ "2"
+             , "02"
+             , "два"
+             ]
+  , examples (NumeralValue 3)
+             [ "3"
+             , "три"
+             , "03"
+             ]
+  , examples (NumeralValue 4)
+             [ "4"
+             , "четыре"
+             , "04"
+             ]
+  , examples (NumeralValue 5)
+             [ "пять"
+             , "5"
+             , "05"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "тридцать три"
+             , "0033"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "четырнадцать"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "шестнадцать"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "семнадцать"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "восемнадцать"
+             ]
+  , examples (NumeralValue 525)
+             [ "пятьсот двадцать пять"
+             , "525"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1.1"
+             , "1.10"
+             , "01.10"
+             , "1 точка 1"
+             , "один точка один"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0.77"
+             , ".77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100000"
+             , "100к"
+             , "100К"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3М"
+             , "3000К"
+             , "3000000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1200000"
+             , "1.2М"
+             , "1200К"
+             , ".0012Г"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "-1200000"
+             , "минус 1200000"
+             , "-1.2М"
+             , "-1200К"
+             , "-.0012Г"
+             ]
+  ]
diff --git a/Duckling/Numeral/RU/Rules.hs b/Duckling/Numeral/RU/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/RU/Rules.hs
@@ -0,0 +1,279 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.RU.Rules
+  ( rules ) where
+
+import qualified Data.HashMap.Strict as HashMap
+import Data.HashMap.Strict (HashMap)
+import Data.Maybe
+import qualified Data.Text as Text
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+dozensMap :: HashMap Text Integer
+dozensMap = HashMap.fromList
+  [ ( "\x0434\x0432\x0430\x0434\x0446\x0430\x0442\x044c", 20)
+  , ( "\x0442\x0440\x0438\x0434\x0446\x0430\x0442\x044c", 30)
+  , ( "\x0441\x043e\x0440\x043e\x043a", 40)
+  , ( "\x043f\x044f\x0442\x044c\x0434\x0435\x0441\x044f\x0442", 50)
+  , ( "\x0448\x0435\x0441\x0442\x044c\x0434\x0435\x0441\x044f\x0442", 60)
+  , ( "\x0441\x0435\x043c\x044c\x0434\x0435\x0441\x044f\x0442", 70)
+  , ( "\x0432\x043e\x0441\x0435\x043c\x044c\x0434\x0435\x0441\x044f\x0442", 80)
+  , ( "\x0434\x0435\x0432\x044f\x043d\x043e\x0441\x0442\x043e", 90)
+  ]
+
+ruleInteger5 :: Rule
+ruleInteger5 = Rule
+  { name = "integer (20..90)"
+  , pattern =
+    [ regex "(\x0434\x0432\x0430\x0434\x0446\x0430\x0442\x044c|\x0442\x0440\x0438\x0434\x0446\x0430\x0442\x044c|\x0441\x043e\x0440\x043e\x043a|\x043f\x044f\x0442\x044c\x0434\x0435\x0441\x044f\x0442|\x0448\x0435\x0441\x0442\x044c\x0434\x0435\x0441\x044f\x0442|\x0441\x0435\x043c\x044c\x0434\x0435\x0441\x044f\x0442|\x0432\x043e\x0441\x0435\x043c\x044c\x0434\x0435\x0441\x044f\x0442|\x0434\x0435\x0432\x044f\x043d\x043e\x0441\x0442\x043e)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) dozensMap >>= integer
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- toInteger <$> parseInt match
+        integer v
+      _ -> Nothing
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(,\\d\\d\\d)+\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> parseDecimal True match
+      _ -> Nothing
+  }
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer 2"
+  , pattern =
+    [ regex "(\x0434\x0432\x0430|\x0434\x0432\x0435|\x0434\x0432\x043e\x0435|\x043f\x0430\x0440\x0430|\x043f\x0430\x0440\x0443|\x043f\x0430\x0440\x043e\x0447\x043a\x0443|\x043f\x0430\x0440\x043e\x0447\x043a\x0430)"
+    ]
+  , prod = \_ -> integer 2
+  }
+
+hundredsMap :: HashMap Text Integer
+hundredsMap = HashMap.fromList
+  [ ( "\x0441\x0442\x043e", 100)
+  , ( "\x0434\x0432\x0435\x0441\x0442\x0438", 200)
+  , ( "\x0442\x0440\x0438\x0441\x0442\x043e", 300)
+  , ( "\x0447\x0435\x0442\x044b\x0440\x0435\x0441\x0442\x043e", 400)
+  , ( "\x043f\x044f\x0442\x044c\x0441\x043e\x0442", 500)
+  , ( "\x0448\x0435\x0441\x0442\x044c\x0441\x043e\x0442", 600)
+  , ( "\x0441\x0435\x043c\x044c\x0441\x043e\x0442", 700)
+  , ( "\x0432\x043e\x0441\x0435\x043c\x044c\x0441\x043e\x0442", 800)
+  , ( "\x0434\x0435\x0432\x044f\x0442\x044c\x0441\x043e\x0442", 900)
+  ]
+
+ruleInteger6 :: Rule
+ruleInteger6 = Rule
+  { name = "integer (100..900)"
+  , pattern =
+    [ regex "(\x0441\x0442\x043e|\x0434\x0432\x0435\x0441\x0442\x0438|\x0442\x0440\x0438\x0441\x0442\x043e|\x0447\x0435\x0442\x044b\x0440\x0435\x0441\x0442\x043e|\x043f\x044f\x0442\x044c\x0441\x043e\x0442|\x0448\x0435\x0441\x0442\x044c\x0441\x043e\x0442|\x0441\x0435\x043c\x044c\x0441\x043e\x0442|\x0432\x043e\x0441\x0435\x043c\x044c\x0441\x043e\x0442|\x0434\x0435\x0432\x044f\x0442\x044c\x0441\x043e\x0442)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) hundredsMap >>= integer
+      _ -> Nothing
+  }
+
+ruleNumeralsPrefixWithMinus :: Rule
+ruleNumeralsPrefixWithMinus = Rule
+  { name = "numbers prefix with -, minus"
+  , pattern =
+    [ regex "-|\x043c\x0438\x043d\x0443\x0441\\s?"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral nd:_) -> double (TNumeral.value nd * (-1))
+      _ -> Nothing
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "((\x043a|\x043c|\x0433)|(\x041a|\x041c|\x0413))(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "\x043a" -> double $ v * 1e3
+         "\x041a" -> double $ v * 1e3
+         "\x043c" -> double $ v * 1e6
+         "\x041c" -> double $ v * 1e6
+         "\x0433" -> double $ v * 1e9
+         "\x0413" -> double $ v * 1e9
+         _   -> Nothing
+      _ -> Nothing
+  }
+
+ruleInteger7 :: Rule
+ruleInteger7 = Rule
+  { name = "integer 21..99"
+  , pattern =
+    [ oneOf [70, 20, 60, 50, 40, 90, 30, 80]
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleInteger8 :: Rule
+ruleInteger8 = Rule
+  { name = "integer 101..999"
+  , pattern =
+    [ oneOf [300, 600, 500, 100, 800, 200, 900, 700, 400]
+    , numberBetween 1 100
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer 0"
+  , pattern =
+    [ regex "(\x043d\x043e\x043b\x044c)"
+    ]
+  , prod = \_ -> integer 0
+  }
+
+threeToNineteenMap:: HashMap Text Integer
+threeToNineteenMap = HashMap.fromList
+  [ ( "\x0442\x0440\x0438", 3)
+  , ( "\x0447\x0435\x0442\x044b\x0440\x0435", 4)
+  , ( "\x043f\x044f\x0442\x044c", 5)
+  , ( "\x0448\x0435\x0441\x0442\x044c", 6)
+  , ( "\x0441\x0435\x043c\x044c", 7)
+  , ( "\x0432\x043e\x0441\x0435\x043c\x044c", 8)
+  , ( "\x0434\x0435\x0432\x044f\x0442\x044c", 9)
+  , ( "\x0434\x0435\x0441\x044f\x0442\x044c", 10)
+  , ( "\x043e\x0434\x0438\x043d\x0430\x0434\x0446\x0430\x0442\x044c", 11)
+  , ( "\x0434\x0432\x0435\x043d\x0430\x0434\x0446\x0430\x0442\x044c", 12)
+  , ( "\x0442\x0440\x0438\x043d\x0430\x0434\x0446\x0430\x0442\x044c", 13)
+  , ( "\x0447\x0435\x0442\x044b\x0440\x043d\x0430\x0434\x0446\x0430\x0442\x044c", 14)
+  , ( "\x043f\x044f\x0442\x043d\x0430\x0434\x0446\x0430\x0442\x044c", 15)
+  , ( "\x0448\x0435\x0441\x0442\x043d\x0430\x0434\x0446\x0430\x0442\x044c", 16)
+  , ( "\x0441\x0435\x043c\x043d\x0430\x0434\x0446\x0430\x0442\x044c", 17)
+  , ( "\x0432\x043e\x0441\x0435\x043c\x043d\x0430\x0434\x0446\x0430\x0442\x044c", 18)
+  , ( "\x0434\x0435\x0432\x044f\x0442\x043d\x0430\x0434\x0446\x0430\x0442\x044c", 19)
+  ]
+
+ruleInteger4 :: Rule
+ruleInteger4 = Rule
+  { name = "integer (3..19)"
+  , pattern =
+    [ regex "(\x0442\x0440\x0438|\x0447\x0435\x0442\x044b\x0440\x043d\x0430\x0434\x0446\x0430\x0442\x044c|\x0447\x0435\x0442\x044b\x0440\x0435|\x043f\x044f\x0442\x043d\x0430\x0434\x0446\x0430\x0442\x044c|\x043f\x044f\x0442\x044c|\x0448\x0435\x0441\x0442\x043d\x0430\x0434\x0446\x0430\x0442\x044c|\x0448\x0435\x0441\x0442\x044c|\x0441\x0435\x043c\x043d\x0430\x0434\x0446\x0430\x0442\x044c|\x0441\x0435\x043c\x044c|\x0432\x043e\x0441\x0435\x043c\x043d\x0430\x0434\x0446\x0430\x0442\x044c|\x0432\x043e\x0441\x0435\x043c\x044c|\x0434\x0435\x0432\x044f\x0442\x043d\x0430\x0434\x0446\x0430\x0442\x044c|\x0434\x0435\x0432\x044f\x0442\x044c|\x0434\x0435\x0441\x044f\x0442\x044c|\x043e\x0434\x0438\x043d\x0430\x0434\x0446\x0430\x0442\x044c|\x0434\x0432\x0435\x043d\x0430\x0434\x0446\x0430\x0442\x044c|\x0442\x0440\x0438\x043d\x0430\x0434\x0446\x0430\x0442\x044c)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) threeToNineteenMap >>= integer
+      _ -> Nothing
+  }
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer 1"
+  , pattern =
+    [ regex "(\x043e\x0434\x0438\x043d|\x043e\x0434\x043d\x0430|\x043e\x0434\x043d\x0443)"
+    ]
+  , prod = \_ -> integer 1
+  }
+
+ruleNumeralDotNumeral :: Rule
+ruleNumeralDotNumeral = Rule
+  { name = "number dot number"
+  , pattern =
+    [ dimension Numeral
+    , regex "\x0442\x043e\x0447\x043a\x0430"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral nd1:_:Token Numeral nd2:_) ->
+        double $ TNumeral.value nd1 + decimalsToDouble (TNumeral.value nd2)
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ,"
+  , pattern =
+    [ regex "(\\d{1,3}(,\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleInteger
+  , ruleInteger2
+  , ruleInteger3
+  , ruleInteger4
+  , ruleInteger5
+  , ruleInteger6
+  , ruleInteger7
+  , ruleInteger8
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleNumeralDotNumeral
+  , ruleNumeralsPrefixWithMinus
+  , ruleNumeralsSuffixesKMG
+  ]
diff --git a/Duckling/Numeral/SV/Corpus.hs b/Duckling/Numeral/SV/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/SV/Corpus.hs
@@ -0,0 +1,106 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.SV.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = SV}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "noll"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "en"
+             , "ett"
+             ]
+  , examples (NumeralValue 2)
+             [ "2"
+             , "två"
+             , "ett par"
+             ]
+  , examples (NumeralValue 7)
+             [ "7"
+             , "sju"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "Fjorton"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "sexton"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "sjutton"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "arton"
+             ]
+  , examples (NumeralValue 20)
+             [ "20"
+             , "tjugo"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1,1"
+             , "1,10"
+             , "01,10"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0,77"
+             , ",77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100.000"
+             , "100000"
+             , "100K"
+             , "100k"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3M"
+             , "3000K"
+             , "3000000"
+             , "3.000.000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1.200.000"
+             , "1200000"
+             , "1,2M"
+             , "1200K"
+             , ",0012G"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "- 1.200.000"
+             , "-1200000"
+             , "minus 1.200.000"
+             , "negativ 1200000"
+             , "-1,2M"
+             , "-1200K"
+             , "-,0012G"
+             ]
+  , examples (NumeralValue 5000)
+             [ "5 tusen"
+             , "fem tusen"
+             ]
+  ]
diff --git a/Duckling/Numeral/SV/Rules.hs b/Duckling/Numeral/SV/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/SV/Rules.hs
@@ -0,0 +1,323 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.SV.Rules
+  ( rules ) where
+
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.Maybe
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleIntersectWithAnd :: Rule
+ruleIntersectWithAnd = Rule
+  { name = "intersect (with and)"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , regex "och"
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleNumeralsPrefixWithNegativeOrMinus :: Rule
+ruleNumeralsPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|minus\\s?|negativ\\s?"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Numeral (NumeralData {TNumeral.value = v}):
+       _) -> double $ v * (-1)
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> do
+         v <- parseInt match
+         integer $ toInteger v
+      _ -> Nothing
+  }
+
+ruleFew :: Rule
+ruleFew = Rule
+  { name = "few"
+  , pattern =
+    [ regex "(n\x00e5gra )?f\x00e5"
+    ]
+  , prod = \_ -> integer 3
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(\\.\\d\\d\\d)+\\,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        let dot = Text.singleton '.'
+            comma = Text.singleton ','
+            fmt = Text.replace comma dot $ Text.replace dot Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*,\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> parseDecimal False match
+      _ -> Nothing
+  }
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer 21..99"
+  , pattern =
+    [ oneOf [70, 20, 60, 50, 40, 90, 30, 80]
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleSingle :: Rule
+ruleSingle = Rule
+  { name = "single"
+  , pattern =
+    [ regex "enkel"
+    ]
+  , prod = \_ -> integer 1 >>= withGrain 1
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleMultiply :: Rule
+ruleMultiply = Rule
+  { name = "compose by multiplication"
+  , pattern =
+    [ dimension Numeral
+    , numberWith TNumeral.multipliable id
+    ]
+  , prod = \tokens -> case tokens of
+      (token1:token2:_) -> multiply token1 token2
+      _ -> Nothing
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "([kmg])(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "k" -> double $ v * 1e3
+         "m" -> double $ v * 1e6
+         "g" -> double $ v * 1e9
+         _   -> Nothing
+      _ -> Nothing
+  }
+
+rulePowersOfTen :: Rule
+rulePowersOfTen = Rule
+  { name = "powers of tens"
+  , pattern =
+    [ regex "(hundra?|tusen?|miljon(er)?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "hundr"    -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "hundra"   -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "tuse"     -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "tusen"    -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "miljon"   -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        "miljoner" -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        _          -> Nothing
+      _ -> Nothing
+  }
+
+ruleCouple :: Rule
+ruleCouple = Rule
+  { name = "couple, a pair"
+  , pattern =
+    [ regex "ett par"
+    ]
+  , prod = \_ -> integer 2
+  }
+
+ruleDozen :: Rule
+ruleDozen = Rule
+  { name = "dozen"
+  , pattern =
+    [ regex "dussin"
+    ]
+  , prod = \_ -> integer 12 >>= withGrain 1 >>= withMultipliable
+  }
+
+zeroToNineteenMap :: HashMap Text Integer
+zeroToNineteenMap = HashMap.fromList
+  [ ( "inget"    , 0 )
+  , ( "ingen"    , 0 )
+  , ( "noll"     , 0 )
+  , ( "en"       , 1 )
+  , ( "ett"      , 1 )
+  , ( "tv\x00e5" , 2 )
+  , ( "tre"      , 3 )
+  , ( "fyra"     , 4 )
+  , ( "fem"      , 5 )
+  , ( "sex"      , 6 )
+  , ( "sju"      , 7 )
+  , ( "\x00e5tta", 8 )
+  , ( "nio"      , 9 )
+  , ( "tio"      , 10 )
+  , ( "elva"     , 11 )
+  , ( "tolv"     , 12 )
+  , ( "tretton"  , 13 )
+  , ( "fjorton"  , 14 )
+  , ( "femton"   , 15 )
+  , ( "sexton"   , 16 )
+  , ( "sjutton"  , 17 )
+  , ( "arton"    , 18 )
+  , ( "nitton"   , 19 )
+  ]
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer (0..19)"
+  , pattern =
+    [ regex "(inget|ingen|noll|en|ett|tv\x00e5|tretton|tre|fyra|femton|fem|sexton|sex|sjutton|sju|\x00e5tta|nio|tio|elva|tolv|fjorton|arton|nitton)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) zeroToNineteenMap >>= integer
+      _ -> Nothing
+  }
+
+dozenMap :: HashMap Text Integer
+dozenMap = HashMap.fromList
+  [ ( "tjugo"      , 20)
+  , ( "trettio"    , 30)
+  , ( "fyrtio"     , 40)
+  , ( "femtio"     , 50)
+  , ( "sextio"     , 60)
+  , ( "sjuttio"    , 70)
+  , ( "\x00e5ttio" , 80)
+  , ( "nittio"     , 90)
+  ]
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer (20..90)"
+  , pattern =
+    [ regex "(tjugo|trettio|fyrtio|femtio|sextio|sjuttio|\x00e5ttio|nittio)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) dozenMap >>= integer
+      _ -> Nothing
+  }
+
+ruleNumeralDotNumeral :: Rule
+ruleNumeralDotNumeral = Rule
+  { name = "number dot number"
+  , pattern =
+    [ dimension Numeral
+    , regex "komma"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral nd1:_:Token Numeral nd2:_) ->
+        double $ TNumeral.value nd1 + decimalsToDouble (TNumeral.value nd2)
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ."
+  , pattern =
+    [ regex "(\\d{1,3}(\\.\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> let fmt = Text.replace (Text.singleton '.') Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleCouple
+  , ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleDozen
+  , ruleFew
+  , ruleInteger
+  , ruleInteger2
+  , ruleInteger3
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleIntersect
+  , ruleIntersectWithAnd
+  , ruleMultiply
+  , ruleNumeralDotNumeral
+  , ruleNumeralsPrefixWithNegativeOrMinus
+  , ruleNumeralsSuffixesKMG
+  , rulePowersOfTen
+  , ruleSingle
+  ]
diff --git a/Duckling/Numeral/TR/Corpus.hs b/Duckling/Numeral/TR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/TR/Corpus.hs
@@ -0,0 +1,180 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.TR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = TR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "yok"
+             , "hiç"
+             , "sıfır"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "bir"
+             , "tek"
+             , "yek"
+             ]
+  , examples (NumeralValue 2)
+             [ "2"
+             , "iki"
+             , "çift"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "otuzüç"
+             , "otuz üç"
+             , "0033"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "ondört"
+             , "on dört"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "onaltı"
+             , "on altı"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "onyedi"
+             , "on yedi"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "onsekiz"
+             , "on sekiz"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1.1"
+             , "1.10"
+             , "01.10"
+             , "bir virgül bir"
+             , "bir nokta bir"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0.77"
+             , ".77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100,000"
+             , "100000"
+             , "100K"
+             , "100k"
+             , "100b"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3M"
+             , "3000K"
+             , "3000000"
+             , "3,000,000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1,200,000"
+             , "1200000"
+             , "1.2M"
+             , "1200K"
+             , ".0012G"
+             , "1200B"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "- 1,200,000"
+             , "-1200000"
+             , "eksi 1,200,000"
+             , "negatif 1200000"
+             , "-1.2M"
+             , "-1200K"
+             , "-.0012G"
+             , "-1200B"
+             ]
+  , examples (NumeralValue 5000)
+             [ "5 bin"
+             , "beş bin"
+             ]
+  , examples (NumeralValue 50)
+             [ "5 deste"
+             , "beş deste"
+             ]
+  , examples (NumeralValue 200000)
+             [ "iki yüz bin"
+             , "ikiyüzbin"
+             ]
+  , examples (NumeralValue 21011)
+             [ "yirmi bir bin on bir"
+             , "yirmibir bin onbir"
+             ]
+  , examples (NumeralValue 721012)
+             [ "yedi yüz yirmibir bin on iki"
+             , "yedi yüz yirmi bir bin on iki"
+             , "yediyüz yirmibir bin oniki"
+             ]
+  , examples (NumeralValue 300341)
+             [ "üçyüzbin üçyüz kırkbir"
+             , "üç yüz bin üç yüz kırk bir"
+             ]
+  , examples (NumeralValue 40348)
+             [ "kırkbin üçyüz kırksekiz"
+             , "kırk bin üç yüz kırk sekiz"
+             ]
+  , examples (NumeralValue 31256721)
+             [ "otuz bir milyon iki yüz elli altı bin yedi yüz yirmi bir"
+             ]
+  , examples (NumeralValue 107)
+             [ "107"
+             , "yüz yedi"
+             ]
+  , examples (NumeralValue 5.5)
+             [ "beş buçuk"
+             , "beşbuçuk"
+             , "5 buçuk"
+             , "5.5"
+             ]
+  , examples (NumeralValue 3500000)
+             [ "3.5 milyon"
+             , "3500000"
+             , "üç buçuk milyon"
+             , "üçbuçuk milyon"
+             , "3.5M"
+             ]
+  , examples (NumeralValue 0.5)
+             [ "yarım"
+             , "0.5"
+             ]
+  , examples (NumeralValue 2500)
+             [ "2.5 bin"
+             , "2500"
+             , "iki buçuk bin"
+             , "ikibuçuk bin"
+             ]
+  , examples (NumeralValue 2200000)
+             [ "2.2 milyon"
+             , "iki nokta iki milyon"
+             ]
+  , examples (NumeralValue 72.5)
+             [ "yetmişikibuçuk"
+             , "yetmişiki buçuk"
+             , "72.5"
+             ]
+  ]
diff --git a/Duckling/Numeral/TR/Rules.hs b/Duckling/Numeral/TR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/TR/Rules.hs
@@ -0,0 +1,733 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.TR.Rules
+  ( rules ) where
+
+import qualified Data.HashMap.Strict as HashMap
+import Data.HashMap.Strict (HashMap)
+import Data.Maybe
+import qualified Data.Text as Text
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+hundredsMap :: HashMap Text Integer
+hundredsMap = HashMap.fromList
+  [ ( "y\x00fcz", 100)
+  , ( "ikiy\x00fcz", 200)
+  , ( "\x00fc\x00e7y\x00fcz", 300)
+  , ( "d\x00f6rty\x00fcz", 400)
+  , ( "be\x015fy\x00fcz", 500)
+  , ( "alt\x0131y\x00fcz", 600)
+  , ( "yediy\x00fcz", 700)
+  , ( "sekizy\x00fcz", 800)
+  , ( "dokuzy\x00fcz", 900)
+  ]
+
+
+ruleInteger5 :: Rule
+ruleInteger5 = Rule
+  { name = "integer 100..900"
+  , pattern =
+    [ regex "(y\x00fcz|ikiy\x00fcz|\x00fc\x00e7y\x00fcz|d\x00f6rty\x00fcz|be\x015fy\x00fcz|alt\x0131y\x00fcz|yediy\x00fcz|sekizy\x00fcz|dokuzy\x00fcz)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) hundredsMap >>= integer >>= withGrain 2
+      _ -> Nothing
+  }
+
+ruleNumeralsPrefixWithNegativeOrMinus :: Rule
+ruleNumeralsPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|eksi\\s?|negatif\\s?"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        double (v * (- 1))
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- parseInt match
+        integer $ toInteger v
+      _ -> Nothing
+  }
+
+ruleACoupleOf :: Rule
+ruleACoupleOf = Rule
+  { name = "a couple (of)"
+  , pattern =
+    [ regex "(bir )?\x00e7ift"
+    ]
+  , prod = \_ -> integer 2 >>= withGrain 1
+  }
+
+ruleFew :: Rule
+ruleFew = Rule
+  { name = "few"
+  , pattern =
+    [ regex "(bir)?az"
+    ]
+  , prod = \_ -> integer 3
+  }
+
+ruleTen :: Rule
+ruleTen = Rule
+  { name = "ten"
+  , pattern =
+    [ regex "on"
+    ]
+  , prod = \_ -> integer 10 >>= withGrain 1 >>= withMultipliable
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(,\\d\\d\\d)+\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+ruleMultiply :: Rule
+ruleMultiply = Rule
+  { name = "compose by multiplication"
+  , pattern =
+    [ dimension Numeral
+    , numberWith TNumeral.multipliable id
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1}):
+       Token Numeral (NumeralData {TNumeral.value = val2, TNumeral.grain = g2}):
+       _) | isNothing g2 || (isJust g2 && val2 > val1) -> case g2 of
+         Nothing -> double $ val1 * val2
+         Just g  -> double (val1 * val2) >>= withGrain g
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDecimal True match
+      _ -> Nothing
+  }
+
+numeralSuffixesHalfsuffixTextMap :: HashMap Text Double
+numeralSuffixesHalfsuffixTextMap = HashMap.fromList
+  [ ( "birbu\x00e7uk", 1.5)
+  , ( "bibu\x00e7uk", 1.5)
+  , ( "ikibu\x00e7uk", 2.5)
+  , ( "\x00fc\231bu\x00e7uk", 3.5)
+  , ( "d\x00f6rtbu\x00e7uk", 4.5)
+  , ( "be\351bu\x00e7uk", 5.5)
+  , ( "alt\x0131bu\x00e7uk", 6.5)
+  , ( "yedibu\x00e7uk", 7.5)
+  , ( "sekizbu\x00e7uk", 8.5)
+  , ( "dokuzbu\x00e7uk", 9.5)
+  ]
+
+ruleNumeralSuffixesHalfsuffixText :: Rule
+ruleNumeralSuffixesHalfsuffixText = Rule
+  { name = "number suffixes (half-suffix text) (1..9)"
+  , pattern =
+    [ regex "((bir?|iki|\x00fc\x00e7|d\x00f6rt|be\x015f|alt\x0131|yedi|sekiz|dokuz)(bu\x00e7uk))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) numeralSuffixesHalfsuffixTextMap >>= double
+      _ -> Nothing
+  }
+
+tenToNintynineMap :: HashMap Text Integer
+tenToNintynineMap = HashMap.fromList
+  [ ( "onbi", 11)
+  , ( "onbir", 11)
+  , ( "oniki", 12)
+  , ( "on\x00fc\x00e7", 13)
+  , ( "ond\x00f6rt", 14)
+  , ( "onbe\x015f", 15)
+  , ( "onalt\x0131", 16)
+  , ( "onyedi", 17)
+  , ( "onsekiz", 18)
+  , ( "ondokuz", 19)
+  , ( "yirmibi", 21)
+  , ( "yirmibir", 21)
+  , ( "yirmiiki", 22)
+  , ( "yirmi\x00fc\x00e7", 23)
+  , ( "yirmid\x00f6rt", 24)
+  , ( "yirmibe\x015f", 25)
+  , ( "yirmialt\x0131", 26)
+  , ( "yirmiyedi", 27)
+  , ( "yirmisekiz", 28)
+  , ( "yirmidokuz", 29)
+  , ( "otuzbi", 31)
+  , ( "otuzbir", 31)
+  , ( "otuziki", 32)
+  , ( "otuz\x00fc\x00e7", 33)
+  , ( "otuzd\x00f6rt", 34)
+  , ( "otuzbe\x015f", 35)
+  , ( "otuzalt\x0131", 36)
+  , ( "otuzyedi", 37)
+  , ( "otuzsekiz", 38)
+  , ( "otuzdokuz", 39)
+  , ( "k\x0131rkbir", 41)
+  , ( "k\x0131rkbi", 41)
+  , ( "k\x0131rkiki", 42)
+  , ( "k\x0131rk\x00fc\x00e7", 43)
+  , ( "k\x0131rkd\x00f6rt", 44)
+  , ( "k\x0131rkbe\x015f", 45)
+  , ( "k\x0131rkalt\x0131", 46)
+  , ( "k\x0131rkyedi", 47)
+  , ( "k\x0131rksekiz", 48)
+  , ( "k\x0131rkdokuz", 49)
+  , ( "ellibi", 51)
+  , ( "ellibir", 51)
+  , ( "elliiki", 52)
+  , ( "elli\x00fc\x00e7", 53)
+  , ( "ellid\x00f6rt", 54)
+  , ( "ellibe\x015f", 55)
+  , ( "ellialt\x0131", 56)
+  , ( "elliyedi", 57)
+  , ( "ellisekiz", 58)
+  , ( "ellidokuz", 59)
+  , ( "altm\x0131\x015fbir", 61)
+  , ( "atm\x0131\x015fbir", 61)
+  , ( "atm\x0131\x015fiki", 62)
+  , ( "altm\x0131\x015fiki", 62)
+  , ( "atm\x0131\x015f\x00fc\x00e7", 63)
+  , ( "altm\x0131\x015f\x00fc\x00e7", 63)
+  , ( "atm\x0131\x015fd\x00f6rt", 64)
+  , ( "altm\x0131\x015fd\x00f6rt", 64)
+  , ( "atm\x0131\x015fbe\x015f", 65)
+  , ( "altm\x0131\x015fbe\x015f", 65)
+  , ( "atm\x0131\x015falt\x0131", 66)
+  , ( "altm\x0131\x015falt\x0131", 66)
+  , ( "altm\x0131\x015fyedi", 67)
+  , ( "atm\x0131\x015fyedi", 67)
+  , ( "altm\x0131\x015fsekiz", 68)
+  , ( "atm\x0131\x015fsekiz", 68)
+  , ( "atm\x0131\x015fdokuz", 69)
+  , ( "altm\x0131\x015fdokuz", 69)
+  , ( "yetmi\x015fbir", 71)
+  , ( "yetmi\x015fbi", 71)
+  , ( "yetmi\x015fiki", 72)
+  , ( "yetmi\x015f\x00fc\x00e7", 73)
+  , ( "yetmi\x015fd\x00f6rt", 74)
+  , ( "yetmi\x015fbe\x015f", 75)
+  , ( "yetmi\x015falt\x0131", 76)
+  , ( "yetmi\x015fyedi", 77)
+  , ( "yetmi\x015fsekiz", 78)
+  , ( "yetmi\x015fdokuz", 79)
+  , ( "seksenbir", 81)
+  , ( "seksenbi", 81)
+  , ( "sekseniki", 82)
+  , ( "seksen\x00fc\x00e7", 83)
+  , ( "seksend\x00f6rt", 84)
+  , ( "seksenbe\x015f", 85)
+  , ( "seksenalt\x0131", 86)
+  , ( "seksenyedi", 87)
+  , ( "seksensekiz", 88)
+  , ( "seksendokuz", 89)
+  , ( "doksanbi", 91)
+  , ( "doksanbir", 91)
+  , ( "doksaniki", 92)
+  , ( "doksan\x00fc\x00e7", 93)
+  , ( "doksand\x00f6rt", 94)
+  , ( "doksanbe\x015f", 95)
+  , ( "doksanalt\x0131", 96)
+  , ( "doksanyedi", 97)
+  , ( "doksansekiz", 98)
+  , ( "doksandokuz", 99)
+  ]
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer 11..19 21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99"
+  , pattern =
+    [ regex "((on|yirmi|otuz|k\x0131rk|elli|atm\x0131\x015f|altm\x0131\x015f|yetmi\x015f|seksen|doksan)(bir|bi|iki|\x00fc\x00e7|d\x00f6rt|be\x015f|alt\x0131|yedi|sekiz|dokuz))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) tenToNintynineMap >>= integer
+      _ -> Nothing
+  }
+
+
+thousandsMap :: HashMap Text Integer
+thousandsMap = HashMap.fromList
+  [ ( "bin", 1000)
+  , ( "ikibin", 2000)
+  , ( "\x00fc\x00e7bin", 3000)
+  , ( "d\x00f6rtbin", 4000)
+  , ( "be\x015fbin", 5000)
+  , ( "alt\x0131bin", 6000)
+  , ( "yedibin", 7000)
+  , ( "sekizbin", 8000)
+  , ( "dokuzbin", 9000)
+  ]
+
+ruleInteger6 :: Rule
+ruleInteger6 = Rule
+  { name = "integer 1000..9000"
+  , pattern =
+    [ regex "(bin|ikibin|\x00fc\x00e7bin|d\x00f6rtbin|be\x015fbin|alt\x0131bin|yedibin|sekizbin|dokuzbin)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) thousandsMap >>= integer >>= withGrain 3
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "([kmgb])(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+          "k" -> double $ v * 1e3
+          "b" -> double $ v * 1e3
+          "m" -> double $ v * 1e6
+          "g" -> double $ v * 1e9
+          _ -> Nothing
+      _ -> Nothing
+  }
+
+rulePowersOfTen :: Rule
+rulePowersOfTen = Rule
+  { name = "powers of tens"
+  , pattern =
+    [ regex "(y(\x00fc)z|bin|milyon)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "y\x00fcz" -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "bin"      -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "milyon"   -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        _          -> Nothing
+      _ -> Nothing
+  }
+
+tenThousandsMap :: HashMap Text Integer
+tenThousandsMap = HashMap.fromList
+  [  ( "onbin", 10000)
+   , ( "yirmibin", 20000)
+   , ( "otuzbin", 30000)
+   , ( "k\x0131rkbin", 40000)
+   , ( "ellibin", 50000)
+   , ( "altm\x0131\x015fbin", 60000)
+   , ( "atm\x0131\x015fbin", 60000)
+   , ( "yetmi\x015fbin", 70000)
+   , ( "seksenbin", 80000)
+   , ( "doksanbin", 90000)
+  ]
+
+ruleInteger7 :: Rule
+ruleInteger7 = Rule
+  { name = "integer 10000..90000"
+  , pattern =
+    [ regex "(onbin|yirmibin|otuzbin|k\x0131rkbin|ellibin|atm\x0131\x015fbin|altm\x0131\x015fbin|yetmi\x015fbin|seksenbin|doksanbin)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) tenThousandsMap >>= integer >>= withGrain 4
+      _ -> Nothing
+  }
+
+hundredThousandsMap :: HashMap Text Integer
+hundredThousandsMap = HashMap.fromList
+  [ ( "y\x00fczbin", 100000)
+  , ( "ikiy\x00fczbin", 200000)
+  , ( "\x00fc\x00e7y\x00fczbin", 300000)
+  , ( "d\x00f6rty\x00fczbin", 400000)
+  , ( "be\x015fy\x00fczbin", 500000)
+  , ( "alt\x0131y\x00fczbin", 600000)
+  , ( "yediy\x00fczbin", 700000)
+  , ( "sekizy\x00fczbin", 800000)
+  , ( "dokuzy\x00fczbin", 900000)
+  ]
+
+ruleInteger8 :: Rule
+ruleInteger8 = Rule
+  { name = "integer 100000..900000"
+  , pattern =
+    [ regex "(y\x00fczbin|ikiy\x00fczbin|\x00fc\x00e7y\x00fczbin|d\x00f6rty\x00fczbin|be\x015fy\x00fczbin|alt\x0131y\x00fczbin|yediy\x00fczbin|sekizy\x00fczbin|dokuzy\x00fczbin)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) hundredThousandsMap >>= integer >>= withGrain 5
+      _ -> Nothing
+  }
+
+ruleHalf :: Rule
+ruleHalf = Rule
+  { name = "half"
+  , pattern =
+    [ regex "(yar\x0131m)"
+    ]
+  , prod = \_ -> double 0.5
+  }
+
+integer9Map :: HashMap Text Double
+integer9Map = HashMap.fromList
+  [ ( "onbirbu\x00e7uk", 11.5)
+  , ( "onbibu\x00e7uk", 11.5)
+  , ( "onikibu\x00e7uk", 12.5)
+  , ( "on\x00fc\x00e7bu\x00e7uk", 13.5)
+  , ( "ond\x00f6rtbu\x00e7uk", 14.5)
+  , ( "onbe\x015fbu\x00e7uk", 15.5)
+  , ( "onalt\x0131bu\x00e7uk", 16.5)
+  , ( "onyedibu\x00e7uk", 17.5)
+  , ( "onsekizbu\x00e7uk", 18.5)
+  , ( "ondokuzbu\x00e7uk", 19.5)
+  , ( "yirmibibu\x00e7uk", 21.5)
+  , ( "yirmibirbu\x00e7uk", 21.5)
+  , ( "yirmiikibu\x00e7uk", 22.5)
+  , ( "yirmi\x00fc\x00e7bu\x00e7uk", 23.5)
+  , ( "yirmid\x00f6rtbu\x00e7uk", 24.5)
+  , ( "yirmibe\x015fbu\x00e7uk", 25.5)
+  , ( "yirmialt\x0131bu\x00e7uk", 26.5)
+  , ( "yirmiyedibu\x00e7uk", 27.5)
+  , ( "yirmisekizbu\x00e7uk", 28.5)
+  , ( "yirmidokuzbu\x00e7uk", 29.5)
+  , ( "otuzbibu\x00e7uk", 31.5)
+  , ( "otuzbirbu\x00e7uk", 31.5)
+  , ( "otuzikibu\x00e7uk", 32.5)
+  , ( "otuz\x00fc\x00e7bu\x00e7uk", 33.5)
+  , ( "otuzd\x00f6rtbu\x00e7uk", 34.5)
+  , ( "otuzbe\x015fbu\x00e7uk", 35.5)
+  , ( "otuzalt\x0131bu\x00e7uk", 36.5)
+  , ( "otuzyedibu\x00e7uk", 37.5)
+  , ( "otuzsekizbu\x00e7uk", 38.5)
+  , ( "otuzdokuzbu\x00e7uk", 39.5)
+  , ( "k\x0131rkbirbu\x00e7uk", 41.5)
+  , ( "k\x0131rkbibu\x00e7uk", 41.5)
+  , ( "k\x0131rkikibu\x00e7uk", 42.5)
+  , ( "k\x0131rk\x00fc\x00e7bu\x00e7uk", 43.5)
+  , ( "k\x0131rkd\x00f6rtbu\x00e7uk", 44.5)
+  , ( "k\x0131rkbe\x015fbu\x00e7uk", 45.5)
+  , ( "k\x0131rkalt\x0131bu\x00e7uk", 46.5)
+  , ( "k\x0131rkyedibu\x00e7uk", 47.5)
+  , ( "k\x0131rksekizbu\x00e7uk", 48.5)
+  , ( "k\x0131rkdokuzbu\x00e7uk", 49.5)
+  , ( "ellibibu\x00e7uk", 51.5)
+  , ( "ellibirbu\x00e7uk", 51.5)
+  , ( "elliikibu\x00e7uk", 52.5)
+  , ( "elli\x00fc\x00e7bu\x00e7uk", 53.5)
+  , ( "ellid\x00f6rtbu\x00e7uk", 54.5)
+  , ( "ellibe\x015fbu\x00e7uk", 55.5)
+  , ( "ellialt\x0131bu\x00e7uk", 56.5)
+  , ( "elliyedibu\x00e7uk", 57.5)
+  , ( "ellisekizbu\x00e7uk", 58.5)
+  , ( "ellidokuzbu\x00e7uk", 59.5)
+  , ( "altm\x0131\x015fbirbu\x00e7uk", 61.5)
+  , ( "atm\x0131\x015fbirbu\x00e7uk", 61.5)
+  , ( "altm\x0131\x015fikibu\x00e7uk", 62.5)
+  , ( "atm\x0131\x015fikibu\x00e7uk", 62.5)
+  , ( "atm\x0131\x015f\x00fc\x00e7bu\x00e7uk", 63.5)
+  , ( "altm\x0131\x015f\x00fc\x00e7bu\x00e7uk", 63.5)
+  , ( "altm\x0131\x015fd\x00f6rtbu\x00e7uk", 64.5)
+  , ( "atm\x0131\x015fd\x00f6rtbu\x00e7uk", 64.5)
+  , ( "altm\x0131\x015fbe\x015fbu\x00e7uk", 65.5)
+  , ( "atm\x0131\x015fbe\x015fbu\x00e7uk", 65.5)
+  , ( "altm\x0131\x015falt\x0131bu\x00e7uk", 66.5)
+  , ( "atm\x0131\x015falt\x0131bu\x00e7uk", 66.5)
+  , ( "atm\x0131\x015fyedibu\x00e7uk", 67.5)
+  , ( "altm\x0131\x015fyedibu\x00e7uk", 67.5)
+  , ( "altm\x0131\x015fsekizbu\x00e7uk", 68.5)
+  , ( "atm\x0131\x015fsekizbu\x00e7uk", 68.5)
+  , ( "altm\x0131\x015fdokuzbu\x00e7uk", 69.5)
+  , ( "atm\x0131\x015fdokuzbu\x00e7uk", 69.5)
+  , ( "yetmi\x015fbibu\x00e7uk", 71.5)
+  , ( "yetmi\x015fbirbu\x00e7uk", 71.5)
+  , ( "yetmi\x015fikibu\x00e7uk", 72.5)
+  , ( "yetmi\x015f\x00fc\x00e7bu\x00e7uk", 73.5)
+  , ( "yetmi\x015fd\x00f6rtbu\x00e7uk", 74.5)
+  , ( "yetmi\x015fbe\x015fbu\x00e7uk", 75.5)
+  , ( "yetmi\x015falt\x0131bu\x00e7uk", 76.5)
+  , ( "yetmi\x015fyedibu\x00e7uk", 77.5)
+  , ( "yetmi\x015fsekizbu\x00e7uk", 78.5)
+  , ( "yetmi\x015fdokuzbu\x00e7uk", 79.5)
+  , ( "seksenbibu\x00e7uk", 81.5)
+  , ( "seksenbirbu\x00e7uk", 81.5)
+  , ( "seksenikibu\x00e7uk", 82.5)
+  , ( "seksen\x00fc\x00e7bu\x00e7uk", 83.5)
+  , ( "seksend\x00f6rtbu\x00e7uk", 84.5)
+  , ( "seksenbe\x015fbu\x00e7uk", 85.5)
+  , ( "seksenalt\x0131bu\x00e7uk", 86.5)
+  , ( "seksenyedibu\x00e7uk", 87.5)
+  , ( "seksensekizbu\x00e7uk", 88.5)
+  , ( "seksendokuzbu\x00e7uk", 89.5)
+  , ( "doksanbirbu\x00e7uk", 91.5)
+  , ( "doksanbibu\x00e7uk", 91.5)
+  , ( "doksanikibu\x00e7uk", 92.5)
+  , ( "doksan\x00fc\x00e7bu\x00e7uk", 93.5)
+  , ( "doksand\x00f6rtbu\x00e7uk", 94.5)
+  , ( "doksanbe\x015fbu\x00e7uk", 95.5)
+  , ( "doksanalt\x0131bu\x00e7uk", 96.5)
+  , ( "doksanyedibu\x00e7uk", 97.5)
+  , ( "doksansekizbu\x00e7uk", 98.5)
+  , ( "doksandokuzbu\x00e7uk", 99.5)
+  ]
+
+ruleInteger9 :: Rule
+ruleInteger9 = Rule
+  { name = "integer 11..19 21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99"
+  , pattern =
+    [ regex "((on|yirmi|otuz|k\x0131rk|elli|atm\x0131\x015f|altm\x0131\x015f|yetmi\x015f|seksen|doksan)(bir|bi|iki|\x00fc\x00e7|d\x00f6rt|be\x015f|alt\x0131|yedi|sekiz|dokuz)(bu\x00e7uk))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) integer9Map >>= double
+      _ -> Nothing
+  }
+
+ruleDozen :: Rule
+ruleDozen = Rule
+  { name = "dozen"
+  , pattern =
+    [ regex "d\x00fczine"
+    ]
+  , prod = \_ -> integer 12 >>= withGrain 1 >>= withMultipliable
+  }
+
+oneToNineMap :: HashMap Text Integer
+oneToNineMap = HashMap.fromList
+  [ ( "s\305f\305r", 0)
+  , ( "yok", 0)
+  , ( "hi\x00e7", 0)
+  , ( "bir", 1)
+  , ( "bi", 1)
+  , ( "yek", 1)
+  , ( "tek", 1)
+  , ( "iki", 2)
+  , ( "\x00fc\x00e7", 3)
+  , ( "d\x00f6rt", 4)
+  , ( "be\x015f", 5)
+  , ( "alt\x0131", 6)
+  , ( "yedi", 7)
+  , ( "sekiz", 8)
+  , ( "dokuz", 9)
+  ]
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer (0..9)"
+  , pattern =
+    [ regex "(yok|hi(\x00e7)|s(\x0131)f(\x0131)r|bir?|[ty]ek|iki|(\x00fc)(\x00e7)|d(\x00f6)rt|be(\x015f)|alt(\x0131)|yedi|sekiz|dokuz)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) oneToNineMap >>= integer
+      _ -> Nothing
+  }
+
+numeralSuffixesHalfsuffixText2Map :: HashMap Text Double
+numeralSuffixesHalfsuffixText2Map = HashMap.fromList
+  [ ( "onbu\x00e7uk", 10.5)
+  , ( "yirmibu\x00e7uk", 20.5)
+  , ( "otuzbu\x00e7uk", 30.5)
+  , ( "k\x0131rkbu\x00e7uk", 40.5)
+  , ( "ellibu\x00e7uk", 50.5)
+  , ( "atm\x0131\x015fbu\x00e7uk", 60.5)
+  , ( "altm\x0131\x015fbu\x00e7uk", 60.5)
+  , ( "yetmi\x015fbu\x00e7uk", 70.5)
+  , ( "seksenbu\x00e7uk", 80.5)
+  , ( "doksanbu\x00e7uk", 90.5)
+  ]
+
+ruleNumeralSuffixesHalfsuffixText2 :: Rule
+ruleNumeralSuffixesHalfsuffixText2 = Rule
+  { name = "number suffixes (half-suffix text) (10..90)"
+  , pattern =
+    [ regex "((on|yirmi|otuz|k\x0131rk|elli|atm\x0131\x015f|altm\x0131\x015f|yetmi\x015f|seksen|doksan)(bu\x00e7uk))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) numeralSuffixesHalfsuffixText2Map
+        >>= double
+      _ -> Nothing
+  }
+
+ruleNumeralSuffixesHalfSuffix :: Rule
+ruleNumeralSuffixesHalfSuffix = Rule
+  { name = "number suffixes (half-suffix)"
+  , pattern =
+    [ dimension Numeral
+    , regex "(bu\x00e7uk)(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ v + 0.5
+      _ -> Nothing
+  }
+
+ruleInteger4 :: Rule
+ruleInteger4 = Rule
+  { name = "integer 11..99"
+  , pattern =
+    [ oneOf [70, 20, 60, 50, 40, 90, 30, 10, 80]
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleGroupOfTens :: Rule
+ruleGroupOfTens = Rule
+  { name = "group of ten(s)"
+  , pattern =
+    [ regex "deste"
+    ]
+  , prod = \_ -> integer 10 >>= withGrain 1 >>= withMultipliable
+  }
+
+tensMap :: HashMap Text Integer
+tensMap = HashMap.fromList
+  [ ( "on", 10)
+  , ( "yirmi", 20)
+  , ( "otuz", 30)
+  , ( "k\x0131rk", 40)
+  , ( "elli", 50)
+  , ( "altm\x0131\x015f", 60)
+  , ( "atm\x0131\x015f", 60)
+  , ( "yetmi\x015f", 70)
+  , ( "seksen", 80)
+  , ( "doksan", 90)
+  ]
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer (10..90)"
+  , pattern =
+    [ regex "(on|yirmi|otuz|k\x0131rk|elli|atm\x0131\x015f|altm\x0131\x015f|yetmi\x015f|seksen|doksan)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) tensMap >>= integer
+      _ -> Nothing
+  }
+
+ruleQuarter :: Rule
+ruleQuarter = Rule
+  { name = "quarter"
+  , pattern =
+    [ regex "(\x00e7eyrek)"
+    ]
+  , prod = \_ -> double 0.25
+  }
+
+ruleNumeralDotNumeral :: Rule
+ruleNumeralDotNumeral = Rule
+  { name = "number dot number"
+  , pattern =
+    [ dimension Numeral
+    , regex "nokta|virg\x00fcl"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + decimalsToDouble v2
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ,"
+  , pattern =
+    [ regex "(\\d{1,3}(,\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleACoupleOf
+  , ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleDozen
+  , ruleFew
+  , ruleGroupOfTens
+  , ruleHalf
+  , ruleInteger
+  , ruleInteger2
+  , ruleInteger3
+  , ruleInteger4
+  , ruleInteger5
+  , ruleInteger6
+  , ruleInteger7
+  , ruleInteger8
+  , ruleInteger9
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleIntersect
+  , ruleNumeralDotNumeral
+  , ruleNumeralSuffixesHalfSuffix
+  , ruleNumeralSuffixesHalfsuffixText
+  , ruleNumeralSuffixesHalfsuffixText2
+  , ruleNumeralsPrefixWithNegativeOrMinus
+  , ruleNumeralsSuffixesKMG
+  , rulePowersOfTen
+  , ruleQuarter
+  , ruleTen
+  , ruleMultiply
+  ]
diff --git a/Duckling/Numeral/Types.hs b/Duckling/Numeral/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/Types.hs
@@ -0,0 +1,62 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Duckling.Numeral.Types where
+
+import Control.DeepSeq
+import Data.Aeson
+import Data.Hashable
+import Data.Maybe
+import Data.Text (Text)
+import GHC.Generics
+import Prelude
+
+import Duckling.Resolve
+
+data NumeralData = NumeralData
+  { value        :: Double
+  , grain        :: Maybe Int
+  , multipliable :: Bool
+  }
+  deriving (Eq, Generic, Hashable, Ord, Show, NFData)
+
+instance Resolve NumeralData where
+  type ResolvedValue NumeralData = NumeralValue
+  resolve _ NumeralData {value} = Just NumeralValue {vValue = value}
+
+newtype NumeralValue = NumeralValue { vValue :: Double }
+  deriving (Eq, Show)
+
+instance ToJSON NumeralValue where
+  toJSON (NumeralValue value) = object
+    [ "type" .= ("value" :: Text)
+    , "value" .= value
+    ]
+
+getIntValue :: Double -> Maybe Int
+getIntValue x = if rest == 0 then Just int else Nothing
+  where
+    (int, rest) = properFraction x :: (Int, Double)
+
+isInteger :: Double -> Bool
+isInteger = isJust . getIntValue
+
+isNatural :: Double -> Bool
+isNatural x = isInteger x && x > 0
+
+isIntegerBetween :: Double -> Int -> Int -> Bool
+isIntegerBetween x low high = case getIntValue x of
+  Just int -> low <= int && int <= high
+  Nothing -> False
diff --git a/Duckling/Numeral/UK/Corpus.hs b/Duckling/Numeral/UK/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/UK/Corpus.hs
@@ -0,0 +1,114 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.UK.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = UK}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "нуль"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "один"
+             ]
+  , examples (NumeralValue 2)
+             [ "2"
+             , "02"
+             , "два"
+             ]
+  , examples (NumeralValue 3)
+             [ "3"
+             , "три"
+             , "03"
+             ]
+  , examples (NumeralValue 4)
+             [ "4"
+             , "чотири"
+             , "04"
+             ]
+  , examples (NumeralValue 5)
+             [ "п‘ять"
+             , "5"
+             , "05"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "тридцять три"
+             , "0033"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "чотирнадцять"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "шістнадцять"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "сімнадцять"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "вісімнадцять"
+             ]
+  , examples (NumeralValue 525)
+             [ "п‘ятсот двадцять п‘ять"
+             , "525"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1.1"
+             , "1.10"
+             , "01.10"
+             , "1 крапка 1"
+             , "один крапка один"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0.77"
+             , ".77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100000"
+             , "100к"
+             , "100К"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3М"
+             , "3000К"
+             , "3000000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1200000"
+             , "1.2М"
+             , "1200К"
+             , ".0012Г"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "-1200000"
+             , "мінус 1200000"
+             , "-1.2М"
+             , "-1200К"
+             , "-.0012Г"
+             ]
+  ]
diff --git a/Duckling/Numeral/UK/Rules.hs b/Duckling/Numeral/UK/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/UK/Rules.hs
@@ -0,0 +1,278 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.UK.Rules
+  ( rules ) where
+
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.Maybe
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+twentyNinetyMap :: HashMap Text Integer
+twentyNinetyMap = HashMap.fromList
+  [ ( "\x0434\x0432\x0430\x0434\x0446\x044f\x0442\x044c"             , 20 )
+  , ( "\x0442\x0440\x0438\x0434\x0446\x044f\x0442\x044c"             , 30 )
+  , ( "\x0441\x043e\x0440\x043e\x043a"                               , 40 )
+  , ( "\x043f\x2018\x044f\x0442\x0434\x0435\x0441\x044f\x0442"       , 50 )
+  , ( "\x0448\x0456\x0441\x0442\x0434\x0435\x0441\x044f\x0442"       , 60 )
+  , ( "\x0441\x0456\x043c\x0434\x0435\x0441\x044f\x0442"             , 70 )
+  , ( "\x0434\x0435\x0432\x2018\x044f\x043d\x043e\x0441\x0442\x043e" , 90 )
+  , ( "\x0432\x0456\x0441\x0456\x043c\x0434\x0435\x0441\x044f\x0442" , 80 )
+  ]
+
+ruleInteger5 :: Rule
+ruleInteger5 = Rule
+  { name = "integer (20..90)"
+  , pattern =
+    [ regex "(\x0434\x0432\x0430\x0434\x0446\x044f\x0442\x044c|\x0442\x0440\x0438\x0434\x0446\x044f\x0442\x044c|\x0441\x043e\x0440\x043e\x043a|\x043f\x2018\x044f\x0442\x0434\x0435\x0441\x044f\x0442|\x0448\x0456\x0441\x0442\x0434\x0435\x0441\x044f\x0442|\x0441\x0456\x043c\x0434\x0435\x0441\x044f\x0442|\x0432\x0456\x0441\x0456\x043c\x0434\x0435\x0441\x044f\x0442|\x0434\x0435\x0432\x2018\x044f\x043d\x043e\x0441\x0442\x043e)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) twentyNinetyMap >>= integer
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- parseInt match
+        integer $ toInteger v
+      _ -> Nothing
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(,\\d\\d\\d)+\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> parseDecimal True match
+      _ -> Nothing
+  }
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer 2"
+  , pattern =
+    [ regex "(\x0434\x0432\x0430|\x0434\x0432\x0456|\x0434\x0432\x043e\x0454|\x043f\x0430\x0440\x0430|\x043f\x0430\x0440\x0443|\x043f\x0430\x0440\x043e\x0447\x043a\x0443|\x043f\x0430\x0440\x043e\x0447\x043a\x0430)"
+    ]
+  , prod = \_ -> integer 2
+  }
+
+hundredsMap :: HashMap Text Integer
+hundredsMap = HashMap.fromList
+  [ ( "\x0441\x0442\x043e"                                     , 100 )
+  , ( "\x0434\x0432\x0456\x0441\x0442\x0456"                   , 200 )
+  , ( "\x0442\x0440\x0438\x0441\x0442\x0430"                   , 300 )
+  , ( "\x0447\x043e\x0442\x0438\x0440\x0438\x0441\x0442\x0430" , 400 )
+  , ( "\x043f\x2018\x044f\x0442\x0441\x043e\x0442"             , 500 )
+  , ( "\x0448\x0456\x0441\x0442\x0441\x043e\x0442"             , 600 )
+  , ( "\x0441\x0456\x043c\x0441\x043e\x0442"                   , 700 )
+  , ( "\x0432\x0456\x0441\x0456\x043c\x0441\x043e\x0442"       , 800 )
+  , ( "\x0434\x0435\x0432\x2018\x044f\x0442\x0441\x043e\x0442" , 900 )
+  ]
+ruleInteger6 :: Rule
+ruleInteger6 = Rule
+  { name = "integer (100..900)"
+  , pattern =
+    [ regex "(\x0441\x0442\x043e|\x0434\x0432\x0456\x0441\x0442\x0456|\x0442\x0440\x0438\x0441\x0442\x0430|\x0447\x043e\x0442\x0438\x0440\x0438\x0441\x0442\x0430|\x043f\x2018\x044f\x0442\x0441\x043e\x0442|\x0448\x0456\x0441\x0442\x0441\x043e\x0442|\x0441\x0456\x043c\x0441\x043e\x0442|\x0432\x0456\x0441\x0456\x043c\x0441\x043e\x0442|\x0434\x0435\x0432\x2018\x044f\x0442\x0441\x043e\x0442)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) hundredsMap >>= integer
+      _ -> Nothing
+  }
+
+ruleNumeralsPrefixWithMinus :: Rule
+ruleNumeralsPrefixWithMinus = Rule
+  { name = "numbers prefix with -, minus"
+  , pattern =
+    [ regex "-|\x043c\x0456\x043d\x0443\x0441\\s?"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral nd:_) -> double (TNumeral.value nd * (-1))
+      _ -> Nothing
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "((\x043a|\x043c|\x0433)|(\x041a|\x041c|\x0413))(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "\x043a" -> double $ v * 1e3
+         "\x041a" -> double $ v * 1e3
+         "\x043c" -> double $ v * 1e6
+         "\x041c" -> double $ v * 1e6
+         "\x0433" -> double $ v * 1e9
+         "\x0413" -> double $ v * 1e9
+         _   -> Nothing
+      _ -> Nothing
+  }
+
+ruleInteger7 :: Rule
+ruleInteger7 = Rule
+  { name = "integer 21..99"
+  , pattern =
+    [ oneOf [70, 20, 60, 50, 40, 90, 30, 80]
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleInteger8 :: Rule
+ruleInteger8 = Rule
+  { name = "integer 101..999"
+  , pattern =
+    [ oneOf [300, 600, 500, 100, 800, 200, 900, 700, 400]
+    , numberBetween 1 100
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer 0"
+  , pattern =
+    [ regex "(\x043d\x0443\x043b\x044c)"
+    ]
+  , prod = \_ -> integer 0
+  }
+
+threeNineteenMap :: HashMap Text Integer
+threeNineteenMap = HashMap.fromList
+  [ ( "\x0442\x0440\x0438"                                           , 3 )
+  , ( "\x0447\x043e\x0442\x0438\x0440\x0438"                         , 4 )
+  , ( "\x043f\x2018\x044f\x0442\x044c"                               , 5 )
+  , ( "\x0448\x0456\x0441\x0442\x044c"                               , 6 )
+  , ( "\x0441\x0456\x043c"                                           , 7 )
+  , ( "\x0432\x0456\x0441\x0456\x043c"                               , 8 )
+  , ( "\x0434\x0435\x0432\x2018\x044f\x0442\x044c"                   , 9 )
+  , ( "\x0434\x0435\x0441\x044f\x0442\x044c"                         , 10 )
+  , ( "\x043e\x0434\x0438\x043d\x0430\x0434\x0446\x044f\x0442\x044c" , 11 )
+  , ( "\x0434\x0432\x0430\x043d\x0430\x0434\x0446\x044f\x0442\x044c" , 12 )
+  , ( "\x0442\x0440\x0438\x043d\x0430\x0434\x0446\x044f\x0442\x044c" , 13 )
+  , ( "\x0447\x043e\x0442\x0438\x0440\x043d\x0430\x0434\x0446\x044f\x0442\x044c" , 14 )
+  , ( "\x043f\x2018\x044f\x0442\x043d\x0430\x0434\x0446\x044f\x0442\x044c"       , 15 )
+  , ( "\x0448\x0456\x0441\x0442\x043d\x0430\x0434\x0446\x044f\x0442\x044c"       , 16 )
+  , ( "\x0441\x0456\x043c\x043d\x0430\x0434\x0446\x044f\x0442\x044c"             , 17 )
+  , ( "\x0432\x0456\x0441\x0456\x043c\x043d\x0430\x0434\x0446\x044f\x0442\x044c" , 18 )
+  , ( "\x0434\x0435\x0432\x2018\x044f\x0442\x043d\x0430\x0434\x0446\x044f\x0442\x044c" , 19 )
+  ]
+ruleInteger4 :: Rule
+ruleInteger4 = Rule
+  { name = "integer (3..19)"
+  , pattern =
+    [ regex "(\x0442\x0440\x0438|\x0447\x043e\x0442\x0438\x0440\x043d\x0430\x0434\x0446\x044f\x0442\x044c|\x0447\x043e\x0442\x0438\x0440\x0438|\x043f\x2018\x044f\x0442\x043d\x0430\x0434\x0446\x044f\x0442\x044c|\x043f\x2018\x044f\x0442\x044c|\x0448\x0456\x0441\x0442\x043d\x0430\x0434\x0446\x044f\x0442\x044c|\x0448\x0456\x0441\x0442\x044c|\x0441\x0456\x043c\x043d\x0430\x0434\x0446\x044f\x0442\x044c|\x0441\x0456\x043c|\x0432\x0456\x0441\x0456\x043c\x043d\x0430\x0434\x0446\x044f\x0442\x044c|\x0432\x0456\x0441\x0456\x043c|\x0434\x0435\x0432\x2018\x044f\x0442\x043d\x0430\x0434\x0446\x044f\x0442\x044c|\x0434\x0435\x0432\x2018\x044f\x0442\x044c|\x0434\x0435\x0441\x044f\x0442\x044c|\x043e\x0434\x0438\x043d\x0430\x0434\x0446\x044f\x0442\x044c|\x0434\x0432\x0430\x043d\x0430\x0434\x0446\x044f\x0442\x044c|\x0442\x0440\x0438\x043d\x0430\x0434\x0446\x044f\x0442\x044c)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup (Text.toLower match) threeNineteenMap >>= integer
+      _ -> Nothing
+  }
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer 1"
+  , pattern =
+    [ regex "(\x043e\x0434\x0438\x043d|\x043e\x0434\x043d\x0430|\x043e\x0434\x043d\x0443|\x043e\x0434\x043d\x0435|\x043e\x0434\x043d\x043e\x0433\x043e)"
+    ]
+  , prod = \_ -> integer 1
+  }
+
+ruleNumeralDotNumeral :: Rule
+ruleNumeralDotNumeral = Rule
+  { name = "number dot number"
+  , pattern =
+    [ dimension Numeral
+    , regex "\x043a\x0440\x0430\x043f\x043a\x0430"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral nd1:_:Token Numeral nd2:_) ->
+        double $ TNumeral.value nd1 + decimalsToDouble (TNumeral.value nd2)
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ,"
+  , pattern =
+    [ regex "(\\d{1,3}(,\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> let fmt = Text.replace (Text.singleton ',') Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleInteger
+  , ruleInteger2
+  , ruleInteger3
+  , ruleInteger4
+  , ruleInteger5
+  , ruleInteger6
+  , ruleInteger7
+  , ruleInteger8
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleNumeralDotNumeral
+  , ruleNumeralsPrefixWithMinus
+  , ruleNumeralsSuffixesKMG
+  ]
diff --git a/Duckling/Numeral/VI/Corpus.hs b/Duckling/Numeral/VI/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/VI/Corpus.hs
@@ -0,0 +1,111 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.VI.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = VI}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "không"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "một"
+             ]
+  , examples (NumeralValue 2)
+             [ "2"
+             , "hai"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "ba mươi ba"
+             ]
+  , examples (NumeralValue 14)
+             [ "14"
+             , "mười bốn"
+             ]
+  , examples (NumeralValue 16)
+             [ "16"
+             , "mười sáu"
+             ]
+  , examples (NumeralValue 17)
+             [ "17"
+             , "mười bảy"
+             ]
+  , examples (NumeralValue 18)
+             [ "18"
+             , "mười tám"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1.1"
+             , "1.10"
+             , "01.10"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0.77"
+             , ".77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100,000"
+             , "100000"
+             , "100K"
+             , "100k"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3M"
+             , "3000K"
+             , "3000000"
+             , "3,000,000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1,200,000"
+             , "1200000"
+             , "1.2M"
+             , "1200K"
+             , ".0012G"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "- 1,200,000"
+             , "-1200000"
+             , "âm 1,200,000"
+             , "-1.2M"
+             , "-1200K"
+             , "-.0012G"
+             ]
+  , examples (NumeralValue 5000)
+             [ "5 nghìn"
+             , "năm nghìn"
+             ]
+  , examples (NumeralValue 200000)
+             [ "hai trăm nghìn"
+             ]
+  , examples (NumeralValue 1000000000)
+             [ "một tỷ"
+             ]
+  , examples (NumeralValue 21011)
+             [ "hai mươi mốt nghìn không trăm mười một"
+             ]
+  , examples (NumeralValue 721012)
+             [ "bảy trăm hai mươi mốt nghìn không trăm mười hai"
+             ]
+  ]
diff --git a/Duckling/Numeral/VI/Rules.hs b/Duckling/Numeral/VI/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/VI/Rules.hs
@@ -0,0 +1,335 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.VI.Rules
+  ( rules ) where
+
+import Data.Maybe
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+rulePowersOfTen :: Rule
+rulePowersOfTen = Rule
+  { name = "powers of tens"
+  , pattern =
+    [ regex "(tr\x0103m?|ngh\x00ecn?|tri\x1ec7u?|t\x1ef7?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "tr\x0103"   -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "tr\x0103m"  -> double 1e2 >>= withGrain 2 >>= withMultipliable
+        "ngh\x00ec"  -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "ngh\x00ecn" -> double 1e3 >>= withGrain 3 >>= withMultipliable
+        "tri\x1ec7"  -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        "tri\x1ec7u" -> double 1e6 >>= withGrain 6 >>= withMultipliable
+        "t"          -> double 1e9 >>= withGrain 9 >>= withMultipliable
+        "t\x1ef7"    -> double 1e9 >>= withGrain 9 >>= withMultipliable
+        _            -> Nothing
+      _ -> Nothing
+  }
+
+ruleIntersectWithAnd :: Rule
+ruleIntersectWithAnd = Rule
+  { name = "intersect (with and)"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , regex "and"
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       _:
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- parseInt match
+        integer $ toInteger v
+      _ -> Nothing
+  }
+
+ruleNumeralsPrefixWithM :: Rule
+ruleNumeralsPrefixWithM = Rule
+  { name = "numbers prefix with -, âm"
+  , pattern =
+    [ regex "-|\x00e2m\\s?"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral nd:_) -> double (TNumeral.value nd * (-1))
+      _ -> Nothing
+  }
+
+ruleNumerals2 :: Rule
+ruleNumerals2 = Rule
+  { name = "numbers 25 35 45 55 65 75 85 95"
+  , pattern =
+    [ oneOf [20, 30 .. 90]
+    , regex "l\x0103m"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ v + 5
+      _ -> Nothing
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(,\\d\\d\\d)+\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> parseDecimal True match
+      _ -> Nothing
+  }
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer 21..99"
+  , pattern =
+    [ oneOf [20, 30 .. 90]
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleNumeralDot :: Rule
+ruleNumeralDot = Rule
+  { name = "number dot 1 9"
+  , pattern =
+    [ dimension Numeral
+    , regex "ch\x1ea5m|ph\x1ea9y"
+    , numberWith TNumeral.grain isNothing
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral nd1:_:Token Numeral nd2:_) ->
+        double $ TNumeral.value nd1 + decimalsToDouble (TNumeral.value nd2)
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ numberWith (fromMaybe 0 . TNumeral.grain) (>1)
+    , numberWith TNumeral.multipliable not
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = val1, TNumeral.grain = Just g}):
+       Token Numeral (NumeralData {TNumeral.value = val2}):
+       _) | (10 ** fromIntegral g) > val2 -> double $ val1 + val2
+      _ -> Nothing
+  }
+
+ruleMultiply :: Rule
+ruleMultiply = Rule
+  { name = "compose by multiplication"
+  , pattern =
+    [ dimension Numeral
+    , numberWith TNumeral.multipliable id
+    ]
+  , prod = \tokens -> case tokens of
+      (token1:token2:_) -> multiply token1 token2
+      _ -> Nothing
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "([kmg])(?=[\\W\\$\x20ac]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "k" -> double $ v * 1e3
+         "m" -> double $ v * 1e6
+         "g" -> double $ v * 1e9
+         _   -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumeralNghn :: Rule
+ruleNumeralNghn = Rule
+  { name = "number nghìn"
+  , pattern =
+    [ numberBetween 1 1000
+    , numberWith TNumeral.value (== 1000)
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2, TNumeral.grain = Just g}):
+       _) -> double (v1 * v2) >>= withGrain g
+      _ -> Nothing
+  }
+
+ruleInteger :: Rule
+ruleInteger = Rule
+  { name = "integer (0..19)"
+  , pattern =
+    [ regex "(kh\x00f4ng|m\x1ed9t|linh m\x1ed9t|l\x1ebb m\x1ed9t|hai|linh hai|l\x1ebb hai|ba|linh ba|l\x1ebb ba|b\x1ed1n|linh b\x1ed1n|l\x1ebb b\x1ed1n|n\x0103m|linh n\x0103m|l\x1ebb n\x0103m|s\x00e1u|l\x1ebb s\x00e1u|linh s\x00e1u|b\x1ea3y|l\x1ebb b\x1ea3y|linh b\x1ea3y|t\x00e1m|linh t\x00e1m|l\x1ebb t\x00e1m|ch\x00edn|linh ch\x00edn|l\x1ebb ch\x00edn|m\x01b0\x1eddi m\x1ed9t|m\x01b0\x1eddi hai|m\x01b0\x1eddi ba|m\x01b0\x1eddi b\x1ed1n|m\x01b0\x1eddi l\x0103m|m\x01b0\x1eddi s\x00e1u|m\x01b0\x1eddi b\x1ea3y|m\x01b0\x1eddi t\x00e1m|m\x01b0\x1eddi ch\x00edn|m\x01b0\x1eddi|linh m\x01b0\x1eddi)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case match of
+        "kh\x00f4ng" -> integer 0
+        "m\x1ed9t" -> integer 1
+        "linh m\x1ed9t" -> integer 1
+        "l\x1ebb m\x1ed9t" -> integer 1
+        "hai" -> integer 2
+        "l\x1ebb hai" -> integer 2
+        "linh hai" -> integer 2
+        "ba" -> integer 3
+        "l\x1ebb" -> integer 3
+        "linh ba" -> integer 3
+        "l\x1ebb b\x1ed1n" -> integer 4
+        "linh b\x1ed1n" -> integer 4
+        "b\x1ed1n" -> integer 4
+        "n\x0103m" -> integer 5
+        "l\x1ebb n\x0103m" -> integer 5
+        "linh n\x0103m" -> integer 5
+        "linh s\x00e1u" -> integer 6
+        "s\x00e1u" -> integer 6
+        "l\x1ebb s\x00e1u" -> integer 6
+        "linh b\x1ea3y" -> integer 7
+        "l\x1ebb b\x1ea3y" -> integer 7
+        "b\x1ea3y" -> integer 7
+        "l\x1ebb t\x00e1m" -> integer 8
+        "linh t\x00e1m" -> integer 8
+        "t\x00e1m" -> integer 8
+        "l\x1ebb ch\x00edn" -> integer 9
+        "ch\x00edn" -> integer 9
+        "linh ch\x00edn" -> integer 9
+        "linh m\x01b0\x1eddi" -> integer 10
+        "m\x01b0\x1eddi" -> integer 10
+        "l\x1ebb m\x01b0\x1eddi" -> integer 10
+        "m\x01b0\x1eddi m\x1ed9t" -> integer 11
+        "m\x01b0\x1eddi hai" -> integer 12
+        "m\x01b0\x1eddi ba" -> integer 13
+        "m\x01b0\x1eddi b\x1ed1n" -> integer 14
+        "m\x01b0\x1eddi l\x0103m" -> integer 15
+        "m\x01b0\x1eddi s\x00e1u" -> integer 16
+        "m\x01b0\x1eddi b\x1ea3y" -> integer 17
+        "m\x01b0\x1eddi t\x00e1m" -> integer 18
+        "m\x01b0\x1eddi ch\x00edn" -> integer 19
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer (20..90)"
+  , pattern =
+    [ regex "(hai m\x01b0\x01a1i|ba m\x01b0\x01a1i|b\x1ed1n m\x01b0\x01a1i|n\x0103m m\x01b0\x01a1i|s\x00e1u m\x01b0\x01a1i|b\x1ea3y m\x01b0\x01a1i|t\x00e1m m\x01b0\x01a1i|ch\x00edn m\x01b0\x01a1i)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case match of
+        "hai m\x01b0\x01a1i" -> integer 20
+        "ba m\x01b0\x01a1i" -> integer 30
+        "b\x1ed1n m\x01b0\x01a1i" -> integer 40
+        "n\x0103m m\x01b0\x01a1i" -> integer 50
+        "s\x00e1u m\x01b0\x01a1i" -> integer 60
+        "b\x1ea3y m\x01b0\x01a1i" -> integer 70
+        "t\x00e1m m\x01b0\x01a1i" -> integer 80
+        "ch\x00edn m\x01b0\x01a1i" -> integer 90
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleNumerals :: Rule
+ruleNumerals = Rule
+  { name = "numbers 21 31 41 51 61 71 81 91"
+  , pattern =
+    [ oneOf [20, 30 .. 90]
+    , regex "m\x1ed1t"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ v + 1
+      _ -> Nothing
+  }
+
+ruleT :: Rule
+ruleT = Rule
+  { name = "tá"
+  , pattern =
+    [ regex "t\x00e1"
+    ]
+  , prod = \_ -> integer 12 >>= withGrain 1 >>= withMultipliable
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ,"
+  , pattern =
+    [ regex "(\\d{1,3}(,\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> let fmt = Text.replace (Text.singleton ',') Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleInteger
+  , ruleInteger2
+  , ruleInteger3
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleIntersect
+  , ruleIntersectWithAnd
+  , ruleMultiply
+  , ruleNumeralDot
+  , ruleNumeralNghn
+  , ruleNumerals
+  , ruleNumerals2
+  , ruleNumeralsPrefixWithM
+  , ruleNumeralsSuffixesKMG
+  , rulePowersOfTen
+  , ruleT
+  ]
diff --git a/Duckling/Numeral/ZH/Corpus.hs b/Duckling/Numeral/ZH/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/ZH/Corpus.hs
@@ -0,0 +1,109 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.ZH.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Numeral.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = ZH}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (NumeralValue 0)
+             [ "0"
+             , "〇"
+             , "零"
+             , "零个"
+             , "0个"
+             ]
+  , examples (NumeralValue 1)
+             [ "1"
+             , "一"
+             , "一个"
+             , "1个"
+             ]
+  , examples (NumeralValue 2)
+             [ "2"
+             , "二個"
+             , "二个"
+             ]
+  , examples (NumeralValue 10)
+             [ "10"
+             , "十"
+             ]
+  , examples (NumeralValue 11)
+             [ "11"
+             , "十一"
+             ]
+  , examples (NumeralValue 20)
+             [ "20"
+             , "二十"
+             ]
+  , examples (NumeralValue 60)
+             [ "60"
+             , "六十"
+             ]
+  , examples (NumeralValue 33)
+             [ "33"
+             , "三十三"
+             ]
+  , examples (NumeralValue 96)
+             [ "96"
+             , "九十六"
+             ]
+  , examples (NumeralValue 1.1)
+             [ "1.1"
+             , "1.10"
+             , "01.10"
+             ]
+  , examples (NumeralValue 0.77)
+             [ "0.77"
+             , ".77"
+             ]
+  , examples (NumeralValue 100000)
+             [ "100,000"
+             , "100000"
+             , "100K"
+             , "100k"
+             ]
+  , examples (NumeralValue 3000000)
+             [ "3M"
+             , "3000K"
+             , "3000000"
+             , "3,000,000"
+             ]
+  , examples (NumeralValue 1200000)
+             [ "1,200,000"
+             , "1200000"
+             , "1.2M"
+             , "1200K"
+             , ".0012G"
+             ]
+  , examples (NumeralValue (-1200000))
+             [ "- 1,200,000"
+             , "-1200000"
+             , "负1,200,000"
+             , "负 1,200,000"
+             , "負 1,200,000"
+             , "负1200000"
+             , "负 1200000"
+             , "-1.2M"
+             , "-1200K"
+             , "-.0012G"
+             ]
+  ]
diff --git a/Duckling/Numeral/ZH/Rules.hs b/Duckling/Numeral/ZH/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Numeral/ZH/Rules.hs
@@ -0,0 +1,201 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.ZH.Rules
+  ( rules ) where
+
+import qualified Data.HashMap.Strict as HashMap
+import Data.Maybe
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleInteger5 :: Rule
+ruleInteger5 = Rule
+  { name = "integer (0..10)"
+  , pattern =
+    [ regex "(\x3007|\x96f6|\x4e00|\x4e8c|\x4e24|\x5169|\x4e09|\x56db|\x4e94|\x516d|\x4e03|\x516b|\x4e5d|\x5341)(\x4e2a|\x500b)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        HashMap.lookup match integerMap >>= integer
+      _ -> Nothing
+  }
+
+integerMap :: HashMap.HashMap Text.Text Integer
+integerMap = HashMap.fromList
+  [ ( "\x3007", 0 )
+  , ( "\x96f6", 0 )
+  , ( "\x4e00", 1 )
+  , ( "\x5169", 2 )
+  , ( "\x4e24", 2 )
+  , ( "\x4e8c", 2 )
+  , ( "\x4e09", 3 )
+  , ( "\x56db", 4 )
+  , ( "\x4e94", 5 )
+  , ( "\x516d", 6 )
+  , ( "\x4e03", 7 )
+  , ( "\x516b", 8 )
+  , ( "\x4e5d", 9 )
+  , ( "\x5341", 10 )
+  ]
+
+
+ruleNumeralsPrefixWithNegativeOrMinus :: Rule
+ruleNumeralsPrefixWithNegativeOrMinus = Rule
+  { name = "numbers prefix with -, negative or minus"
+  , pattern =
+    [ regex "-|\x8d1f\\s?|\x8ca0\\s?"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral nd:_) -> double (TNumeral.value nd * (-1))
+      _ -> Nothing
+  }
+
+ruleIntegerNumeric :: Rule
+ruleIntegerNumeric = Rule
+  { name = "integer (numeric)"
+  , pattern =
+    [ regex "(\\d{1,18})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- parseInt match
+        integer $ toInteger v
+      _ -> Nothing
+  }
+
+ruleDecimalWithThousandsSeparator :: Rule
+ruleDecimalWithThousandsSeparator = Rule
+  { name = "decimal with thousands separator"
+  , pattern =
+    [ regex "(\\d+(,\\d\\d\\d)+\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        parseDouble (Text.replace (Text.singleton ',') Text.empty match) >>= double
+      _ -> Nothing
+  }
+
+ruleDecimalNumeral :: Rule
+ruleDecimalNumeral = Rule
+  { name = "decimal number"
+  , pattern =
+    [ regex "(\\d*\\.\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> parseDecimal True match
+      _ -> Nothing
+  }
+
+ruleNumeral :: Rule
+ruleNumeral = Rule
+  { name = "<number>个"
+  , pattern =
+    [ dimension Numeral
+    , regex "\x4e2a"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleInteger3 :: Rule
+ruleInteger3 = Rule
+  { name = "integer (20..90)"
+  , pattern =
+    [ numberBetween 2 10
+    , regex "\x5341"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ v * 10
+      _ -> Nothing
+  }
+
+ruleNumeralsSuffixesKMG :: Rule
+ruleNumeralsSuffixesKMG = Rule
+  { name = "numbers suffixes (K, M, G)"
+  , pattern =
+    [ dimension Numeral
+    , regex "([kmg])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "k" -> double $ v * 1e3
+         "m" -> double $ v * 1e6
+         "g" -> double $ v * 1e9
+         _   -> Nothing
+      _ -> Nothing
+  }
+
+ruleInteger4 :: Rule
+ruleInteger4 = Rule
+  { name = "integer 21..99"
+  , pattern =
+    [ oneOf [70, 20, 60, 50, 40, 90, 30, 80]
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v1}):
+       Token Numeral (NumeralData {TNumeral.value = v2}):
+       _) -> double $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleInteger2 :: Rule
+ruleInteger2 = Rule
+  { name = "integer (11..19)"
+  , pattern =
+    [ regex "\x5341"
+    , numberBetween 1 10
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral (NumeralData {TNumeral.value = v}):_) -> double $ 10 + v
+      _ -> Nothing
+  }
+
+ruleIntegerWithThousandsSeparator :: Rule
+ruleIntegerWithThousandsSeparator = Rule
+  { name = "integer with thousands separator ,"
+  , pattern =
+    [ regex "(\\d{1,3}(,\\d\\d\\d){1,5})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       _) -> let fmt = Text.replace (Text.singleton ',') Text.empty match
+        in parseDouble fmt >>= double
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleDecimalNumeral
+  , ruleDecimalWithThousandsSeparator
+  , ruleInteger2
+  , ruleInteger3
+  , ruleInteger4
+  , ruleInteger5
+  , ruleIntegerNumeric
+  , ruleIntegerWithThousandsSeparator
+  , ruleNumeral
+  , ruleNumeralsPrefixWithNegativeOrMinus
+  , ruleNumeralsSuffixesKMG
+  ]
diff --git a/Duckling/Ordinal/AR/Corpus.hs b/Duckling/Ordinal/AR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/AR/Corpus.hs
@@ -0,0 +1,29 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.AR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = AR}, allExamples)
+
+allExamples :: [Example]
+allExamples =
+  examples (OrdinalData 2)
+           [ "الثاني"
+           ]
diff --git a/Duckling/Ordinal/AR/Rules.hs b/Duckling/Ordinal/AR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/AR/Rules.hs
@@ -0,0 +1,123 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.AR.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Ordinal.Helpers
+import Duckling.Types
+
+ruleOrdinalsTh :: Rule
+ruleOrdinalsTh = Rule
+  { name = "ordinals 7th"
+  , pattern =
+    [ regex "(\x0633\x0627\x0628\x0639 | \x0633\x0627\x0628\x0639\x0629 | \x0627\x0644\x0633\x0627\x0628\x0639 | \x0627\x0644\x0633\x0627\x0628\x0639\x0629)"
+    ]
+  , prod = \_ -> Just $ ordinal 7
+  }
+
+ruleOrdinalsSecond :: Rule
+ruleOrdinalsSecond = Rule
+  { name = "ordinals second"
+  , pattern =
+    [ regex "(\x062b\x0627\x0646\x064a|\x062b\x0627\x0646\x064a\x0629|\x0627\x0644\x062b\x0627\x0646\x064a|\x0627\x0644\x062b\x0627\x0646\x064a\x0629)"
+    ]
+  , prod = \_ -> Just $ ordinal 2
+  }
+
+ruleOrdinalsFirst :: Rule
+ruleOrdinalsFirst = Rule
+  { name = "ordinals first"
+  , pattern =
+    [ regex "(\x0623\x0648\x0644|\x0627\x0644\x0623\x0648\x0644|\x0623\x0648\x0644\x0649|\x0627\x0644\x0623\x0648\x0644\x0649)"
+    ]
+  , prod = \_ -> Just $ ordinal 1
+  }
+
+ruleOrdinalsFirst5 :: Rule
+ruleOrdinalsFirst5 = Rule
+  { name = "ordinals first"
+  , pattern =
+    [ regex "(\x0633\x0627\x062f\x0633 | \x0633\x0627\x062f\x0633\x0629 | \x0627\x0644\x0633\x0627\x062f\x0633 | \x0627\x0644\x0633\x0627\x062f\x0633\x0629)"
+    ]
+  , prod = \_ -> Just $ ordinal 6
+  }
+
+ruleOrdinalsTh2 :: Rule
+ruleOrdinalsTh2 = Rule
+  { name = "ordinals 8th"
+  , pattern =
+    [ regex "(\x062b\x0627\x0645\x0646 | \x062b\x0627\x0645\x0646\x0629 | \x0627\x0644\x062b\x0627\x0645\x0646 | \x0627\x0644\x062b\x0627\x0645\x0646\x0629)"
+    ]
+  , prod = \_ -> Just $ ordinal 8
+  }
+
+ruleOrdinalsFirst2 :: Rule
+ruleOrdinalsFirst2 = Rule
+  { name = "ordinals first"
+  , pattern =
+    [ regex "(\x062b\x0627\x0644\x062b|\x062b\x0627\x0644\x062b\x0629|\x0627\x0644\x062b\x0627\x0644\x062b|\x0627\x0644\x062b\x0627\x0644\x062b\x0629)"
+    ]
+  , prod = \_ -> Just $ ordinal 3
+  }
+
+ruleOrdinalsTh4 :: Rule
+ruleOrdinalsTh4 = Rule
+  { name = "ordinals 10th"
+  , pattern =
+    [ regex "(\x0639\x0627\x0634\x0631 | \x0639\x0627\x0634\x0631\x0629 | \x0627\x0644\x0639\x0627\x0634\x0631 | \x0627\x0644\x0639\x0627\x0634\x0631\x0629)"
+    ]
+  , prod = \_ -> Just $ ordinal 10
+  }
+
+ruleOrdinalsTh3 :: Rule
+ruleOrdinalsTh3 = Rule
+  { name = "ordinals 9th"
+  , pattern =
+    [ regex "(\x062a\x0627\x0633\x0639 | \x062a\x0627\x0633\x0639\x0629 | \x0627\x0644\x062a\x0627\x0633\x0639 | \x0627\x0644\x062a\x0627\x0633\x0639\x0629)"
+    ]
+  , prod = \_ -> Just $ ordinal 9
+  }
+
+ruleOrdinalsFirst4 :: Rule
+ruleOrdinalsFirst4 = Rule
+  { name = "ordinals first"
+  , pattern =
+    [ regex "(\x062e\x0627\x0645\x0633 | \x0627\x0644\x062e\x0627\x0645\x0633 | \x062e\x0627\x0645\x0633\x0629 | \x0627\x0644\x062e\x0627\x0645\x0633\x0629)"
+    ]
+  , prod = \_ -> Just $ ordinal 5
+  }
+
+ruleOrdinalsFirst3 :: Rule
+ruleOrdinalsFirst3 = Rule
+  { name = "ordinals first"
+  , pattern =
+    [ regex "(\x0631\x0627\x0628\x0639|\x0631\x0627\x0628\x0639\x0629 | \x0627\x0644\x0631\x0627\x0628\x0639|\x0627\x0644\x0631\x0627\x0628\x0639\x0629)"
+    ]
+  , prod = \_ -> Just $ ordinal 4
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinalsFirst
+  , ruleOrdinalsFirst2
+  , ruleOrdinalsFirst3
+  , ruleOrdinalsFirst4
+  , ruleOrdinalsFirst5
+  , ruleOrdinalsSecond
+  , ruleOrdinalsTh
+  , ruleOrdinalsTh2
+  , ruleOrdinalsTh3
+  , ruleOrdinalsTh4
+  ]
diff --git a/Duckling/Ordinal/DA/Corpus.hs b/Duckling/Ordinal/DA/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/DA/Corpus.hs
@@ -0,0 +1,31 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.DA.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = DA}, allExamples)
+
+allExamples :: [Example]
+allExamples =
+  examples (OrdinalData 4)
+           [ "4."
+           , "fjerde"
+           , "Fjerde"
+           ]
diff --git a/Duckling/Ordinal/DA/Rules.hs b/Duckling/Ordinal/DA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/DA/Rules.hs
@@ -0,0 +1,85 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.DA.Rules
+  ( rules ) where
+
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleOrdinalsFirstst :: Rule
+ruleOrdinalsFirstst = Rule
+  { name = "ordinals (first..31st)"
+  , pattern =
+    [ regex "(f\x00f8rste|anden|tredje|fjerde|femte|sjette|syvende|ottende|niende|tiende|elfte|tolvte|trettende|fjortende|femtende|sekstende|syttende|attende|nittende|tyvende|tenogtyvende|toogtyvende|treogtyvende|fireogtyvende|femogtyvende|seksogtyvende|syvogtyvende|otteogtyvende|niogtyvende|tredivte|enogtredivte)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "f\x00f8rste" -> Just $ ordinal 1
+        "anden" -> Just $ ordinal 2
+        "tredje" -> Just $ ordinal 3
+        "fjerde" -> Just $ ordinal 4
+        "femte" -> Just $ ordinal 5
+        "sjette" -> Just $ ordinal 6
+        "syvende" -> Just $ ordinal 7
+        "ottende" -> Just $ ordinal 8
+        "niende" -> Just $ ordinal 9
+        "tiende" -> Just $ ordinal 10
+        "elfte" -> Just $ ordinal 11
+        "tolvte" -> Just $ ordinal 12
+        "trettende" -> Just $ ordinal 13
+        "fjortende" -> Just $ ordinal 14
+        "femtende" -> Just $ ordinal 15
+        "sekstende" -> Just $ ordinal 16
+        "syttende" -> Just $ ordinal 17
+        "attende" -> Just $ ordinal 18
+        "nittende" -> Just $ ordinal 19
+        "tyvende" -> Just $ ordinal 20
+        "tenogtyvende" -> Just $ ordinal 21
+        "toogtyvende" -> Just $ ordinal 22
+        "treogtyvende" -> Just $ ordinal 23
+        "fireogtyvende" -> Just $ ordinal 24
+        "femogtyvende" -> Just $ ordinal 25
+        "seksogtyvende" -> Just $ ordinal 26
+        "syvogtyvende" -> Just $ ordinal 27
+        "otteogtyvende" -> Just $ ordinal 28
+        "niogtyvende" -> Just $ ordinal 29
+        "tredivte" -> Just $ ordinal 30
+        "enogtredivte" -> Just $ ordinal 31
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern =
+    [ regex "0*(\\d+)(\\.|ste?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- parseInt match
+        Just $ ordinal v
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinalDigits
+  , ruleOrdinalsFirstst
+  ]
diff --git a/Duckling/Ordinal/DE/Corpus.hs b/Duckling/Ordinal/DE/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/DE/Corpus.hs
@@ -0,0 +1,31 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.DE.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = DE}, allExamples)
+
+allExamples :: [Example]
+allExamples =
+  examples (OrdinalData 4)
+           [ "vierter"
+           , "4ter"
+           , "Vierter"
+           ]
diff --git a/Duckling/Ordinal/DE/Rules.hs b/Duckling/Ordinal/DE/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/DE/Rules.hs
@@ -0,0 +1,93 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.DE.Rules
+  ( rules ) where
+
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleOrdinalsFirstth :: Rule
+ruleOrdinalsFirstth = Rule
+  { name = "ordinals (first..19th)"
+  , pattern =
+    [ regex "(erste(r|s)?|zweite(r|s)|dritte(r|s)|vierte(r|s)|fuenfte(r|s)|sechste(r|s)|siebte(r|s)|achte(r|s)|neunte(r|s)|zehnte(r|s)|elfter|zw\x00f6lfter|dreizenter|vierzehnter|f\x00fcnfzehnter|sechzenter|siebzehnter|achtzehnter|neunzehnter)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "erstes" -> Just $ ordinal 1
+        "erster" -> Just $ ordinal 1
+        "erste" -> Just $ ordinal 1
+        "zweiter" -> Just $ ordinal 2
+        "zweite" -> Just $ ordinal 2
+        "zweites" -> Just $ ordinal 2
+        "drittes" -> Just $ ordinal 3
+        "dritte" -> Just $ ordinal 3
+        "dritter" -> Just $ ordinal 3
+        "viertes" -> Just $ ordinal 4
+        "vierte" -> Just $ ordinal 4
+        "vierter" -> Just $ ordinal 4
+        "f\x00fcnftes" -> Just $ ordinal 5
+        "f\x00fcnfter" -> Just $ ordinal 5
+        "f\x00fcnfte" -> Just $ ordinal 5
+        "sechste" -> Just $ ordinal 6
+        "sechstes" -> Just $ ordinal 6
+        "sechster" -> Just $ ordinal 6
+        "siebtes" -> Just $ ordinal 7
+        "siebte" -> Just $ ordinal 7
+        "siebter" -> Just $ ordinal 7
+        "achtes" -> Just $ ordinal 8
+        "achte" -> Just $ ordinal 8
+        "achter" -> Just $ ordinal 8
+        "neuntes" -> Just $ ordinal 9
+        "neunter" -> Just $ ordinal 9
+        "neunte" -> Just $ ordinal 9
+        "zehnte" -> Just $ ordinal 10
+        "zehnter" -> Just $ ordinal 10
+        "zehntes" -> Just $ ordinal 10
+        "elfter" -> Just $ ordinal 11
+        "zw\x00f6lfter" -> Just $ ordinal 12
+        "dreizehnter" -> Just $ ordinal 13
+        "vierzehnter" -> Just $ ordinal 14
+        "f\x00fcnfzehnter" -> Just $ ordinal 15
+        "sechzehnter" -> Just $ ordinal 16
+        "siebzehnter" -> Just $ ordinal 17
+        "achtzehnter" -> Just $ ordinal 18
+        "neunzehnter" -> Just $ ordinal 19
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern =
+    [ regex "0*(\\d+)(\\.| ?(te(n|r|s)?)|(ste(n|r|s)?))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        v <- parseInt match
+        Just $ ordinal v
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinalDigits
+  , ruleOrdinalsFirstth
+  ]
diff --git a/Duckling/Ordinal/EN/Corpus.hs b/Duckling/Ordinal/EN/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/EN/Corpus.hs
@@ -0,0 +1,65 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.EN.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Ordinal.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 1)
+             [ "first"
+             , "1st"
+             ]
+  , examples (OrdinalData 2)
+             [ "second"
+             , "2nd"
+             ]
+  , examples (OrdinalData 3)
+             [ "third"
+             , "3rd"
+             ]
+  , examples (OrdinalData 4)
+             [ "fourth"
+             , "4th"
+             ]
+  , examples (OrdinalData 8)
+             [ "eighth"
+             , "8th"
+             ]
+  , examples (OrdinalData 25)
+             [ "twenty-fifth"
+             , "25th"
+             ]
+  , examples (OrdinalData 31)
+             [ "thirty-first"
+             , "31st"
+             ]
+  , examples (OrdinalData 42)
+             [ "forty-second"
+             , "42nd"
+             ]
+  , examples (OrdinalData 77)
+            [ "seventy-seventh"
+            , "77th"
+            ]
+  , examples (OrdinalData 90)
+            [ "ninetieth"
+            , "90th"
+            ]
+  ]
diff --git a/Duckling/Ordinal/EN/Rules.hs b/Duckling/Ordinal/EN/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/EN/Rules.hs
@@ -0,0 +1,109 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.EN.Rules
+  ( rules ) where
+
+import Control.Monad (join)
+import Data.HashMap.Strict ( HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+
+ordinalsMap :: HashMap Text Int
+ordinalsMap = HashMap.fromList
+  [ ( "first", 1 )
+  , ( "second", 2 )
+  , ( "third", 3 )
+  , ( "fourth", 4 )
+  , ( "fifth", 5 )
+  , ( "sixth", 6 )
+  , ( "seventh", 7 )
+  , ( "eighth", 8 )
+  , ( "ninth", 9 )
+  , ( "tenth", 10 )
+  , ( "eleventh", 11 )
+  , ( "twelfth", 12 )
+  , ( "thirteenth", 13 )
+  , ( "fourteenth", 14 )
+  , ( "fifteenth", 15 )
+  , ( "sixteenth", 16 )
+  , ( "seventeenth", 17 )
+  , ( "eighteenth", 18 )
+  , ( "nineteenth", 19 )
+  , ( "twentieth", 20 )
+  , ( "thirtieth", 30 )
+  , ( "fortieth", 40 )
+  , ( "fiftieth", 50 )
+  , ( "sixtieth", 60 )
+  , ( "seventieth", 70 )
+  , ( "eightieth", 80 )
+  , ( "ninetieth", 90 )
+  ]
+
+cardinalsMap :: HashMap Text Int
+cardinalsMap = HashMap.fromList
+  [ ( "twenty", 20 )
+  , ( "thirty", 30 )
+  , ( "forty", 40 )
+  , ( "fifty", 50 )
+  , ( "sixty", 60 )
+  , ( "seventy", 70 )
+  , ( "eighty", 80 )
+  , ( "ninety", 90 )
+  ]
+
+
+ruleOrdinals :: Rule
+ruleOrdinals = Rule
+  { name = "ordinals (first..twentieth,thirtieth,...)"
+  , pattern = [regex "(first|second|third|fourth|fifth|sixth|seventh|eighth|ninth|tenth|eleventh|twelfth|thirteenth|fourteenth|fifteenth|sixteenth|seventeenth|eighteenth|nineteenth|twentieth|thirtieth|fortieth|fiftieth|sixtieth|seventieth|eightieth|ninetieth)"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        ordinal <$> HashMap.lookup (Text.toLower match) ordinalsMap
+      _ -> Nothing
+    }
+
+ruleCompositeOrdinals :: Rule
+ruleCompositeOrdinals = Rule
+  { name = "ordinals (composite, e.g., eighty-seven)"
+  , pattern = [regex "(twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety)\\-(first|second|third|fourth|fifth|sixth|seventh|eighth|ninth)"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (tens:units:_)):_) -> do
+        tt <- HashMap.lookup (Text.toLower tens) cardinalsMap
+        uu <- HashMap.lookup (Text.toLower units) ordinalsMap
+        Just (ordinal (tt + uu))
+      _ -> Nothing
+  }
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern = [regex "0*(\\d+) ?(st|nd|rd|th)"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> ordinal <$> parseInt match
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinals
+  , ruleCompositeOrdinals
+  , ruleOrdinalDigits
+  ]
diff --git a/Duckling/Ordinal/ES/Rules.hs b/Duckling/Ordinal/ES/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/ES/Rules.hs
@@ -0,0 +1,94 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.ES.Rules
+  ( rules ) where
+
+import qualified Data.HashMap.Strict as HashMap
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+
+ordinalsMap :: HashMap.HashMap Text.Text Int
+ordinalsMap = HashMap.fromList
+  [ ( "primer" , 1 )
+  , ( "primero" , 1 )
+  , ( "primeros" , 1 )
+  , ( "primera" , 1 )
+  , ( "primeras" , 1 )
+  , ( "segundo" , 2 )
+  , ( "segunda" , 2 )
+  , ( "segundas" , 2 )
+  , ( "segundos" , 2 )
+  , ( "terceros" , 3 )
+  , ( "tercera" , 3 )
+  , ( "terceras" , 3 )
+  , ( "tercero" , 3 )
+  , ( "tercer" , 3 )
+  , ( "cuarta" , 4 )
+  , ( "cuartas" , 4 )
+  , ( "cuartos" , 4 )
+  , ( "cuarto" , 4 )
+  , ( "quinto" , 5 )
+  , ( "quinta" , 5 )
+  , ( "quintas" , 5 )
+  , ( "quintos" , 5 )
+  , ( "sextos" , 6 )
+  , ( "sexto" , 6 )
+  , ( "sexta" , 6 )
+  , ( "sextas" , 6 )
+  , ( "s\x00e9ptimas" , 7 )
+  , ( "septimas" , 7 )
+  , ( "s\x00e9ptima" , 7 )
+  , ( "septimos" , 7 )
+  , ( "septima" , 7 )
+  , ( "s\x00e9ptimo" , 7 )
+  , ( "s\x00e9ptimos" , 7 )
+  , ( "septimo" , 7 )
+  , ( "octavas" , 8 )
+  , ( "octavo" , 8 )
+  , ( "octavos" , 8 )
+  , ( "octava" , 8 )
+  , ( "novenos" , 9 )
+  , ( "novena" , 9 )
+  , ( "noveno" , 9 )
+  , ( "novenas" , 9 )
+  , ( "d\x00e9cimos" , 10 )
+  , ( "decimo" , 10 )
+  , ( "decimos" , 10 )
+  , ( "d\x00e9cimo" , 10 )
+  , ( "decimas" , 10 )
+  , ( "d\x00e9cima" , 10 )
+  , ( "decima" , 10 )
+  , ( "d\x00e9cimas" , 10 )
+  ]
+
+ruleOrdinalsPrimero :: Rule
+ruleOrdinalsPrimero = Rule
+  { name = "ordinals (primero..10)"
+  , pattern =
+    [ regex "(primer|tercer(os?|as?)?|(primer|segund|cuart|quint|sext|s[e\x00e9]ptim|octav|noven|d[e\x00e9]cim)(os?|as?))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        ordinal <$> HashMap.lookup (Text.toLower match) ordinalsMap
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinalsPrimero
+  ]
diff --git a/Duckling/Ordinal/ET/Corpus.hs b/Duckling/Ordinal/ET/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/ET/Corpus.hs
@@ -0,0 +1,31 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.ET.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = ET}, allExamples)
+
+allExamples :: [Example]
+allExamples =
+  examples (OrdinalData 4)
+           [ "4."
+           , "neljas"
+           , "Neljas"
+           ]
diff --git a/Duckling/Ordinal/ET/Rules.hs b/Duckling/Ordinal/ET/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/ET/Rules.hs
@@ -0,0 +1,71 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.ET.Rules
+  ( rules ) where
+
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleOrdinalsFirstth :: Rule
+ruleOrdinalsFirstth = Rule
+  { name = "ordinals (first..19th)"
+  , pattern =
+    [ regex "(esimene|teine|kolmas|neljas|viies|kuues|seitsmes|kaheksas|\x00fcheksas|k\x00fcmnes|\x00fcheteistk\x00fcmnes|kaheteistk\x00fcmnes|kolmeteistk\x00fcmnes|neljateistk\x00fcmnes|viieteistk\x00fcmnes|kuueteistk\x00fcmnes|seitsmeteistk\x00fcmnes|kaheksateistk\x00fcmnes|\x00fcheksateistk\x00fcmnes)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "esimene" -> Just $ ordinal 1
+        "teine" -> Just $ ordinal 2
+        "kolmas" -> Just $ ordinal 3
+        "neljas" -> Just $ ordinal 4
+        "viies" -> Just $ ordinal 5
+        "kuues" -> Just $ ordinal 6
+        "seitsmes" -> Just $ ordinal 7
+        "kaheksas" -> Just $ ordinal 8
+        "\x00fcheksas" -> Just $ ordinal 9
+        "k\x00fcmnes" -> Just $ ordinal 10
+        "\x00fcheteistk\x00fcmnes" -> Just $ ordinal 11
+        "kaheteistk\x00fcmnes" -> Just $ ordinal 12
+        "kolmeteistk\x00fcmnes" -> Just $ ordinal 13
+        "neljateistk\x00fcmnes" -> Just $ ordinal 14
+        "viieteistk\x00fcmnes" -> Just $ ordinal 15
+        "kuueteistk\x00fcmnes" -> Just $ ordinal 16
+        "seitsmeteistk\x00fcmnes" -> Just $ ordinal 17
+        "kaheksateistk\x00fcmnes" -> Just $ ordinal 18
+        "\x00fcheksateistk\x00fcmnes" -> Just $ ordinal 19
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern =
+    [ regex "0*(\\d+)\\."
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> ordinal <$> parseInt match
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinalDigits
+  , ruleOrdinalsFirstth
+  ]
diff --git a/Duckling/Ordinal/FR/Corpus.hs b/Duckling/Ordinal/FR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/FR/Corpus.hs
@@ -0,0 +1,49 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.FR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = FR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 1)
+             [ "1er"
+             , "1ere"
+             , "1ère"
+             , "Premier"
+             , "première"
+             ]
+  , examples (OrdinalData 2)
+             [ "deuxieme"
+             , "deuxième"
+             , "2e"
+             , "second"
+             , "2eme"
+             , "2ème"
+             ]
+  , examples (OrdinalData 11)
+             [ "onzieme"
+             , "onzième"
+             , "11eme"
+             , "11ème"
+             , "11e"
+             ]
+  ]
diff --git a/Duckling/Ordinal/FR/Rules.hs b/Duckling/Ordinal/FR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/FR/Rules.hs
@@ -0,0 +1,96 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.FR.Rules
+  ( rules ) where
+
+import Data.HashMap.Strict ( HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleOrdinalsPremierseiziemeMap :: HashMap Text Int
+ruleOrdinalsPremierseiziemeMap = HashMap.fromList
+  [ ( "premi\x00e8re"   , 1 )
+  , ( "premiere"        , 1 )
+  , ( "premier"         , 1 )
+  , ( "deuxi\x00e8me"   , 2 )
+  , ( "deuxieme"        , 2 )
+  , ( "second"          , 2 )
+  , ( "seconde"         , 2 )
+  , ( "troisi\x00e8me"  , 3 )
+  , ( "troisieme"       , 3 )
+  , ( "quatrieme"       , 4 )
+  , ( "quatri\x00e8me"  , 4 )
+  , ( "cinquieme"       , 5 )
+  , ( "cinqui\x00e8me"  , 5 )
+  , ( "sixi\x00e8me"    , 6 )
+  , ( "sixieme"         , 6 )
+  , ( "septieme"        , 7 )
+  , ( "septi\x00e8me"   , 7 )
+  , ( "huiti\x00e8me"   , 8 )
+  , ( "huitieme"        , 8 )
+  , ( "neuvieme"        , 9 )
+  , ( "neuvi\x00e8me"   , 9 )
+  , ( "dixi\x00e8me"    , 10 )
+  , ( "dixieme"         , 10 )
+  , ( "onzi\x00e8me"    , 11 )
+  , ( "onzieme"         , 11 )
+  , ( "douzieme"        , 12 )
+  , ( "douzi\x00e8me"   , 12 )
+  , ( "treizieme"       , 13 )
+  , ( "treizi\x00e8me"  , 13 )
+  , ( "quatorzi\x00e8me", 14 )
+  , ( "quatorzieme"     , 14 )
+  , ( "quinzi\x00e8me"  , 15 )
+  , ( "quinzieme"       , 15 )
+  , ( "seizieme"        , 16 )
+  , ( "seizi\x00e8me"   , 16 )
+  ]
+
+ruleOrdinalsPremierseizieme :: Rule
+ruleOrdinalsPremierseizieme = Rule
+  { name = "ordinals (premier..seizieme)"
+  , pattern =
+    [ regex "(premi(ere?|\x00e8re)|(deux|trois|quatr|cinqu|six|sept|huit|neuv|dix|onz|douz|treiz|quatorz|quinz|seiz)i(e|\x00e8)me|seconde?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        ordinal <$> HashMap.lookup (Text.toLower match) ruleOrdinalsPremierseiziemeMap
+      _ -> Nothing
+  }
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern =
+    [ regex "0*(\\d+) ?(ere?|\x00e8re|\x00e8me|eme|e)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        n <- parseInt match
+        Just $ ordinal n
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinalDigits
+  , ruleOrdinalsPremierseizieme
+  ]
diff --git a/Duckling/Ordinal/GA/Corpus.hs b/Duckling/Ordinal/GA/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/GA/Corpus.hs
@@ -0,0 +1,46 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.GA.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = GA}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 6)
+             [ "séu"
+             ]
+  , examples (OrdinalData 1)
+             [ "chead"
+             , "t-aonu"
+             , "t-aonú"
+             , "Aonu"
+             , "aonú"
+             ]
+  , examples (OrdinalData 30)
+             [ "tríochadú"
+             , "triochadú"
+             , "tríochadu"
+             ]
+  , examples (OrdinalData 50)
+             [ "caogadu"
+             , "caogadú"
+             ]
+  ]
diff --git a/Duckling/Ordinal/GA/Rules.hs b/Duckling/Ordinal/GA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/GA/Rules.hs
@@ -0,0 +1,121 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.GA.Rules
+  ( rules ) where
+
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleOrdinalsChadDaraEtc :: Rule
+ruleOrdinalsChadDaraEtc = Rule
+  { name = "ordinals (chéad, dara, etc.)"
+  , pattern =
+    [ regex "(ch(\x00e9|e)ad|aon(\x00fa|u)|t-aon(\x00fa|u)|dara|tr(\x00ed|i)(\x00fa|u)|ceathr(\x00fa|u)|c(\x00fa|u)igi(\x00fa|u)|s(\x00e9|e)(\x00fa|u)|seacht(\x00fa|u)|ocht(\x00fa|u)|t-ocht(\x00fa|u)|nao(\x00fa|u)|deichi(\x00fa|u)|fichi(\x00fa|u)|tr(\x00ed|i)ochad(\x00fa|u)|daichead(\x00fa|u)|caogad(\x00fa|u)|seascad(\x00fa|u)|seacht(\x00f3|o)d(\x00fa|u)|ocht(\x00f3|o)d(\x00fa|u)|t-ocht(\x00f3|o)d(\x00fa|u)|n(\x00f3|o)chad(\x00fa|u)|c(\x00e9|e)ad(\x00fa|u)|mili(\x00fa|u)|milli(\x00fa|u)n(\x00fa|u))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "t-aonu" -> Just $ ordinal 1
+        "aonu" -> Just $ ordinal 1
+        "aon\x00fa" -> Just $ ordinal 1
+        "ch\x00e9ad" -> Just $ ordinal 1
+        "chead" -> Just $ ordinal 1
+        "t-aon\x00fa" -> Just $ ordinal 1
+        "dara" -> Just $ ordinal 2
+        "tri\x00fa" -> Just $ ordinal 3
+        "tr\x00edu" -> Just $ ordinal 3
+        "tr\x00ed\x00fa" -> Just $ ordinal 3
+        "triu" -> Just $ ordinal 3
+        "ceathr\x00fa" -> Just $ ordinal 4
+        "ceathru" -> Just $ ordinal 4
+        "c\x00faigiu" -> Just $ ordinal 5
+        "c\x00faigi\x00fa" -> Just $ ordinal 5
+        "cuigiu" -> Just $ ordinal 5
+        "cuigi\x00fa" -> Just $ ordinal 5
+        "s\x00e9u" -> Just $ ordinal 6
+        "s\x00e9\x00fa" -> Just $ ordinal 6
+        "seu" -> Just $ ordinal 6
+        "se\x00fa" -> Just $ ordinal 6
+        "seachtu" -> Just $ ordinal 7
+        "seacht\x00fa" -> Just $ ordinal 7
+        "t-ocht\x00fa" -> Just $ ordinal 8
+        "ochtu" -> Just $ ordinal 8
+        "t-ochtu" -> Just $ ordinal 8
+        "ocht\x00fa" -> Just $ ordinal 8
+        "naou" -> Just $ ordinal 9
+        "nao\x00fa" -> Just $ ordinal 9
+        "deichiu" -> Just $ ordinal 10
+        "deichi\x00fa" -> Just $ ordinal 10
+        "fichiu" -> Just $ ordinal 20
+        "fichi\x00fa" -> Just $ ordinal 20
+        "tr\x00edochadu" -> Just $ ordinal 30
+        "triochadu" -> Just $ ordinal 30
+        "tr\x00edochad\x00fa" -> Just $ ordinal 30
+        "triochad\x00fa" -> Just $ ordinal 30
+        "daichead\x00fa" -> Just $ ordinal 40
+        "daicheadu" -> Just $ ordinal 40
+        "caogadu" -> Just $ ordinal 50
+        "caogad\x00fa" -> Just $ ordinal 50
+        "seascadu" -> Just $ ordinal 60
+        "seascad\x00fa" -> Just $ ordinal 60
+        "seachtodu" -> Just $ ordinal 70
+        "seachtod\x00fa" -> Just $ ordinal 70
+        "seacht\x00f3d\x00fa" -> Just $ ordinal 70
+        "seacht\x00f3du" -> Just $ ordinal 70
+        "ocht\x00f3du" -> Just $ ordinal 80
+        "ochtodu" -> Just $ ordinal 80
+        "t-ochtodu" -> Just $ ordinal 80
+        "t-ocht\x00f3d\x00fa" -> Just $ ordinal 80
+        "t-ochtod\x00fa" -> Just $ ordinal 80
+        "ocht\x00f3d\x00fa" -> Just $ ordinal 80
+        "t-ocht\x00f3du" -> Just $ ordinal 80
+        "ochtod\x00fa" -> Just $ ordinal 80
+        "n\x00f3chad\x00fa" -> Just $ ordinal 90
+        "n\x00f3chadu" -> Just $ ordinal 90
+        "nochad\x00fa" -> Just $ ordinal 90
+        "nochadu" -> Just $ ordinal 90
+        "c\x00e9ad\x00fa" -> Just $ ordinal 100
+        "cead\x00fa" -> Just $ ordinal 100
+        "ceadu" -> Just $ ordinal 100
+        "c\x00e9adu" -> Just $ ordinal 100
+        "miliu" -> Just $ ordinal 1000
+        "mili\x00fa" -> Just $ ordinal 1000
+        "milliun\x00fa" -> Just $ ordinal 1000000
+        "milliunu" -> Just $ ordinal 1000000
+        "milli\x00fanu" -> Just $ ordinal 1000000
+        "milli\x00fan\x00fa" -> Just $ ordinal 1000000
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern =
+    [ regex "0*(\\d+) ?(a|d|(\x00fa|u))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> ordinal <$> parseInt match
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinalDigits
+  , ruleOrdinalsChadDaraEtc
+  ]
diff --git a/Duckling/Ordinal/HE/Corpus.hs b/Duckling/Ordinal/HE/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/HE/Corpus.hs
@@ -0,0 +1,29 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.HE.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = HE}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 4)
+             [ "ארבעה"
+             ]
+  ]
diff --git a/Duckling/Ordinal/HE/Rules.hs b/Duckling/Ordinal/HE/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/HE/Rules.hs
@@ -0,0 +1,270 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.HE.Rules
+  ( rules ) where
+
+import Data.Maybe
+import Data.String
+import Data.Text (Text)
+import Prelude
+import qualified Data.Text as Text
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Ordinal.Types (OrdinalData (..))
+import Duckling.Regex.Types
+import Duckling.Types
+import qualified Duckling.Ordinal.Types as TOrdinal
+
+ruleOrdinal4 :: Rule
+ruleOrdinal4 = Rule
+  { name = "ordinal 4"
+  , pattern =
+    [ regex "(\x05d0\x05e8\x05d1\x05e2\x05d4|\x05e8\x05d1\x05d9\x05e2\x05d9)"
+    ]
+  , prod = \_ -> Just $ ordinal 4
+  }
+
+ruleOrdinal9 :: Rule
+ruleOrdinal9 = Rule
+  { name = "ordinal 9"
+  , pattern =
+    [ regex "(\x05ea\x05e9\x05e2\x05d4|\x05ea\x05e9\x05d9\x05e2\x05d9)"
+    ]
+  , prod = \_ -> Just $ ordinal 9
+  }
+
+ruleOrdinal10 :: Rule
+ruleOrdinal10 = Rule
+  { name = "ordinal 10"
+  , pattern =
+    [ regex "(\x05e2\x05e9\x05e8\x05d4|\x05e2\x05e9\x05d9\x05e8\x05d9)"
+    ]
+  , prod = \_ -> Just $ ordinal 10
+  }
+
+ruleOrdinal12 :: Rule
+ruleOrdinal12 = Rule
+  { name = "ordinal 12"
+  , pattern =
+    [ regex "(\x05e9\x05e0\x05d9\x05d9\x05dd \x05e2\x05e9\x05e8|\x05ea\x05e8\x05d9 \x05e2\x05e9\x05e8)"
+    ]
+  , prod = \_ -> Just $ ordinal 12
+  }
+
+ruleOrdinal17 :: Rule
+ruleOrdinal17 = Rule
+  { name = "ordinal 17"
+  , pattern =
+    [ regex "(\x05e9\x05d1\x05e2(\x05d4)? \x05e2\x05e9\x05e8(\x05d4)?)"
+    ]
+  , prod = \_ -> Just $ ordinal 17
+  }
+
+ruleOrdinal18 :: Rule
+ruleOrdinal18 = Rule
+  { name = "ordinal 18"
+  , pattern =
+    [ regex "(\x05e9\x05de\x05d5\x05e0\x05d4 \x05e2\x05e9\x05e8(\x05d4)?)"
+    ]
+  , prod = \_ -> Just $ ordinal 18
+  }
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern =
+    [ regex "0*(\\d+) "
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> ordinal <$> parseInt match
+      _ -> Nothing
+  }
+
+ruleOrdinal15 :: Rule
+ruleOrdinal15 = Rule
+  { name = "ordinal 15"
+  , pattern =
+    [ regex "(\x05d7\x05de\x05d9\x05e9\x05d4 \x05e2\x05e9\x05e8|\x05d7\x05de\x05e9 \x05e2\x05e9\x05e8\x05d4?)"
+    ]
+  , prod = \_ -> Just $ ordinal 15
+  }
+
+ruleOrdinal5 :: Rule
+ruleOrdinal5 = Rule
+  { name = "ordinal 5"
+  , pattern =
+    [ regex "(\x05d7\x05de\x05d9\x05e9\x05d9|\x05d7\x05de\x05d9\x05e9\x05d4)"
+    ]
+  , prod = \_ -> Just $ ordinal 5
+  }
+
+ruleOrdinal16 :: Rule
+ruleOrdinal16 = Rule
+  { name = "ordinal 16"
+  , pattern =
+    [ regex "(\x05e9\x05e9(\x05d4)? \x05e2\x05e9\x05e8(\x05d4)?)"
+    ]
+  , prod = \_ -> Just $ ordinal 16
+  }
+
+ruleOrdinal14 :: Rule
+ruleOrdinal14 = Rule
+  { name = "ordinal 14"
+  , pattern =
+    [ regex "(\x05d0\x05e8\x05d1\x05e2(\x05d4)? \x05e2\x05e9\x05e8(\x05d4)?)"
+    ]
+  , prod = \_ -> Just $ ordinal 14
+  }
+
+ruleOrdinal20 :: Rule
+ruleOrdinal20 = Rule
+  { name = "ordinal 20..90"
+  , pattern =
+    [ regex "(\x05e2\x05e9\x05e8\x05d9\x05dd|\x05e9\x05dc\x05d5\x05e9\x05d9\x05dd|\x05d0\x05e8\x05d1\x05e2\x05d9\x05dd|\x05d7\x05de\x05d9\x05e9\x05d9\x05dd|\x05e9\x05d9\x05e9\x05d9\x05dd|\x05e9\x05d1\x05e2\x05d9\x05dd|\x05e9\x05de\x05d5\x05e0\x05d9\x05dd|\x05ea\x05e9\x05e2\x05d9\x05dd)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case match of
+        "\x05e2\x05e9\x05e8\x05d9\x05dd" -> Just $ ordinal 20
+        "\x05e9\x05dc\x05d5\x05e9\x05d9\x05dd" -> Just $ ordinal 30
+        "\x05d0\x05e8\x05d1\x05e2\x05d9\x05dd" -> Just $ ordinal 40
+        "\x05d7\x05de\x05d9\x05e9\x05d9\x05dd" -> Just $ ordinal 50
+        "\x05e9\x05d9\x05e9\x05d9\x05dd" -> Just $ ordinal 60
+        "\x05e9\x05d1\x05e2\x05d9\x05dd" -> Just $ ordinal 70
+        "\x05e9\x05de\x05d5\x05e0\x05d9\x05dd" -> Just $ ordinal 80
+        "\x05ea\x05e9\x05e2\x05d9\x05dd" -> Just $ ordinal 90
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleOrdinal :: Rule
+ruleOrdinal = Rule
+  { name = "ordinal 1"
+  , pattern =
+    [ regex "(\x05d0\x05d7\x05d3|\x05e8\x05d0\x05e9\x05d5\x05df)"
+    ]
+  , prod = \_ -> Just $ ordinal 1
+  }
+
+ruleOrdinal13 :: Rule
+ruleOrdinal13 = Rule
+  { name = "ordinal 13"
+  , pattern =
+    [ regex "(\x05e9\x05dc\x05d5\x05e9(\x05d4)? \x05e2\x05e9\x05e8(\x05d4)?)"
+    ]
+  , prod = \_ -> Just $ ordinal 13
+  }
+
+ruleOrdinal7 :: Rule
+ruleOrdinal7 = Rule
+  { name = "ordinal 7"
+  , pattern =
+    [ regex "(\x05e9\x05d1\x05e2\x05d4|\x05e9\x05d1\x05d9\x05e2\x05d9)"
+    ]
+  , prod = \_ -> Just $ ordinal 7
+  }
+
+ruleOrdinal8 :: Rule
+ruleOrdinal8 = Rule
+  { name = "ordinal 8"
+  , pattern =
+    [ regex "(\x05e9\x05de\x05d5\x05e0\x05d4|\x05e9\x05de\x05d9\x05e0\x05d9)"
+    ]
+  , prod = \_ -> Just $ ordinal 8
+  }
+
+ruleOrdinal2 :: Rule
+ruleOrdinal2 = Rule
+  { name = "ordinal 2"
+  , pattern =
+    [ regex "(\x05e9\x05ea\x05d9\x05d9\x05dd|\x05e9\x05e0\x05d9\x05d9\x05dd|\x05e9\x05e0\x05d9)"
+    ]
+  , prod = \_ -> Just $ ordinal 2
+  }
+
+ruleOrdinal11 :: Rule
+ruleOrdinal11 = Rule
+  { name = "ordinal 11"
+  , pattern =
+    [ regex "(\x05d0\x05d7\x05d3 \x05e2\x05e9\x05e8(\x05d4)?)"
+    ]
+  , prod = \_ -> Just $ ordinal 11
+  }
+
+ruleOrdinal3 :: Rule
+ruleOrdinal3 = Rule
+  { name = "ordinal 3"
+  , pattern =
+    [ regex "(\x05e9\x05dc\x05d5\x05e9\x05d4|\x05e9\x05dc\x05d9\x05e9\x05d9)"
+    ]
+  , prod = \_ -> Just $ ordinal 3
+  }
+
+ruleOrdinal6 :: Rule
+ruleOrdinal6 = Rule
+  { name = "ordinal 6"
+  , pattern =
+    [ regex "(\x05e9\x05e9\x05d4|\x05e9\x05d9\x05e9\x05d9)"
+    ]
+  , prod = \_ -> Just $ ordinal 6
+  }
+
+ruleOrdinal19 :: Rule
+ruleOrdinal19 = Rule
+  { name = "ordinal 19"
+  , pattern =
+    [ regex "(\x05ea\x05e9\x05e2(\x05d4)? \x05e2\x05e9\x05e8(\x05d4)?)"
+    ]
+  , prod = \_ -> Just $ ordinal 19
+  }
+
+ruleCompositeWithAnd :: Rule
+ruleCompositeWithAnd = Rule
+  { name = "ordinal composition (with and)"
+  , pattern =
+    [ dimension Ordinal
+    , regex "\x05d5"
+    , dimension Ordinal
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v1}):
+       _:
+       Token Ordinal (OrdinalData {TOrdinal.value = v2}):
+       _) -> Just . ordinal $ v1 + v2
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleCompositeWithAnd
+  , ruleOrdinal
+  , ruleOrdinal10
+  , ruleOrdinal11
+  , ruleOrdinal12
+  , ruleOrdinal13
+  , ruleOrdinal14
+  , ruleOrdinal15
+  , ruleOrdinal16
+  , ruleOrdinal17
+  , ruleOrdinal18
+  , ruleOrdinal19
+  , ruleOrdinal2
+  , ruleOrdinal20
+  , ruleOrdinal3
+  , ruleOrdinal4
+  , ruleOrdinal5
+  , ruleOrdinal6
+  , ruleOrdinal7
+  , ruleOrdinal8
+  , ruleOrdinal9
+  , ruleOrdinalDigits
+  ]
diff --git a/Duckling/Ordinal/HR/Corpus.hs b/Duckling/Ordinal/HR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/HR/Corpus.hs
@@ -0,0 +1,50 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.HR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = HR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 3)
+             [ "3."
+             , "trece"
+             , "treći"
+             , "trećeg"
+             ]
+  , examples (OrdinalData 4)
+             [ "4."
+             , "4ti"
+             , "4ta"
+             , "četvrti"
+             , "četvrta"
+             , "četvrto"
+             , "cetvrti"
+             , "cetvrta"
+             , "cetvrto"
+             ]
+  , examples (OrdinalData 6)
+             [ "6."
+             , "6ti"
+             , "šesto"
+             , "šestoga"
+             , "sestog"
+             ]
+  ]
diff --git a/Duckling/Ordinal/HR/Rules.hs b/Duckling/Ordinal/HR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/HR/Rules.hs
@@ -0,0 +1,178 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.HR.Rules
+  ( rules ) where
+
+import Data.HashMap.Strict (HashMap)
+import Data.Maybe
+import Data.String
+import Data.Text (Text)
+import Prelude
+import qualified Data.Text as Text
+import qualified Data.HashMap.Strict as HashMap
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Ordinal.Types (OrdinalData (..))
+import Duckling.Regex.Types
+import Duckling.Types
+import qualified Duckling.Ordinal.Types as TOrdinal
+
+ordinalsMap :: HashMap Text Int
+ordinalsMap = HashMap.fromList
+  [ ("prva", 1)
+  , ("prvi", 1)
+  , ("prvoga", 1)
+  , ("prvo", 1)
+  , ("prvog", 1)
+  , ("drugoga", 2)
+  , ("drugo", 2)
+  , ("druga", 2)
+  , ("drugi", 2)
+  , ("drugog", 2)
+  , ("tre\263i", 3)
+  , ("tre\263ega", 3)
+  , ("trece", 3)
+  , ("tre\263eg", 3)
+  , ("treca", 3)
+  , ("tre\263a", 3)
+  , ("treci", 3)
+  , ("\269etvrti", 4)
+  , ("\269etvrtoga", 4)
+  , ("cetvrtoga", 4)
+  , ("cetvrto", 4)
+  , ("cetvrtog", 4)
+  , ("cetvrti", 4)
+  , ("\269etvrtog", 4)
+  , ("cetvrta", 4)
+  , ("\269etvrto", 4)
+  , ("\269etvrta", 4)
+  , ("peto", 5)
+  , ("peti", 5)
+  , ("petoga", 5)
+  , ("petog", 5)
+  , ("peta", 5)
+  , ("sesti", 6)
+  , ("\353estoga", 6)
+  , ("\353estog", 6)
+  , ("sestoga", 6)
+  , ("sesta", 6)
+  , ("\353esta", 6)
+  , ("\353esto", 6)
+  , ("\353esti", 6)
+  , ("sesto", 6)
+  , ("sestog", 6)
+  , ("sedmi", 7)
+  , ("sedmog", 7)
+  , ("sedma", 7)
+  , ("sedmo", 7)
+  , ("sedmoga", 7)
+  , ("osmo", 8)
+  , ("osmog", 8)
+  , ("osma", 8)
+  , ("osmi", 8)
+  , ("osmoga", 8)
+  , ("devetog", 9)
+  , ("deveta", 9)
+  , ("deveto", 9)
+  , ("devetoga", 9)
+  , ("deveti", 9)
+  , ("desetoga", 10)
+  , ("desetog", 10)
+  , ("deseta", 10)
+  , ("deseto", 10)
+  , ("deseti", 10)
+  , ("jedanaestoga", 11)
+  , ("jedanaesta", 11)
+  , ("jedanaesti", 11)
+  , ("jedanaesto", 11)
+  , ("jedanaestog", 11)
+  , ("dvanaesto", 12)
+  , ("dvanaestog", 12)
+  , ("dvanaesti", 12)
+  , ("dvanaesta", 12)
+  , ("dvanaestoga", 12)
+  , ("trinaesta", 13)
+  , ("trinaestoga", 13)
+  , ("trinaestog", 13)
+  , ("trinaesto", 13)
+  , ("trinaesti", 13)
+  , ("cetrnaestog", 14)
+  , ("cetrnaesto", 14)
+  , ("cetrnaesti", 14)
+  , ("\269etrnaestoga", 14)
+  , ("cetrnaesta", 14)
+  , ("\269etrnaesto", 14)
+  , ("\269etrnaesta", 14)
+  , ("cetrnaestoga", 14)
+  , ("\269etrnaesti", 14)
+  , ("\269etrnaestog", 14)
+  , ("petnaestog", 15)
+  , ("petnaesto", 15)
+  , ("petnaesta", 15)
+  , ("petnaesti", 15)
+  , ("petnaestoga", 15)
+  , ("sesnaesto", 16)
+  , ("sesnaestog", 16)
+  , ("sesnaesti", 16)
+  , ("\353esnaestoga", 16)
+  , ("sesnaesta", 16)
+  , ("\353esnaesto", 16)
+  , ("sesnaestoga", 16)
+  , ("\353esnaesti", 16)
+  , ("\353esnaesta", 16)
+  , ("\353esnaestog", 16)
+  , ("sedamnaesto", 17)
+  , ("sedamnaestoga", 17)
+  , ("sedamnaesti", 17)
+  , ("sedamnaesta", 17)
+  , ("sedamnaestog", 17)
+  , ("osamnaestoga", 18)
+  , ("osamnaestog", 18)
+  , ("osamnaesti", 18)
+  , ("osamnaesta", 18)
+  , ("osamnaesto", 18)
+  , ("devetnaestoga", 19)
+  , ("devetnaestog", 19)
+  , ("devetnaesto", 19)
+  , ("devetnaesti", 19)
+  , ("devetnaesta", 19)
+  ]
+
+ruleOrdinalsFirstth :: Rule
+ruleOrdinalsFirstth = Rule
+  { name = "ordinals (first..19th)"
+  , pattern =
+    [ regex "(prv(i|a|o(ga?)?)|drug(i|a|o(ga?)?)|tre(c|\x0107)(i|a|e(ga?)?)|(\x010d|c)etvrt(i|a|o(ga?)?)|pet(i|a|o(ga?)?)|(\x0161|s)est(i|a|o(ga?)?)|sedm(i|a|o(ga?)?)|osm(i|a|o(ga?)?)|devet(i|a|o(ga?)?)|deset(i|a|o(ga?)?)|jedanaest(i|a|o(ga?)?)|dvanaest(i|a|o(ga?)?)|trinaest(i|a|o(ga?)?)|(c|\x010d)etrnaest(i|a|o(ga?)?)|petnaest(i|a|o(ga?)?)|(s|\x0161)esnaest(i|a|o(ga?)?)|sedamnaest(i|a|o(ga?)?)|osamnaest(i|a|o(ga?)?)|devetnaest(i|a|o(ga?)?))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        ordinal <$> HashMap.lookup (Text.toLower match) ordinalsMap
+      _ -> Nothing
+  }
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern =
+    [ regex "0*(\\d+)(\\.| ?(t(i|a)(n|r|s)?)|(ste(n|r|s)?))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> ordinal <$> parseInt match
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinalDigits
+  , ruleOrdinalsFirstth
+  ]
diff --git a/Duckling/Ordinal/Helpers.hs b/Duckling/Ordinal/Helpers.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/Helpers.hs
@@ -0,0 +1,27 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.Helpers
+  ( ordinal
+  ) where
+
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.Types (OrdinalData (..))
+import qualified Duckling.Ordinal.Types as TOrdinal
+import Duckling.Types
+
+-- -----------------------------------------------------------------
+-- Patterns
+
+-- -----------------------------------------------------------------
+-- Production
+
+ordinal :: Int -> Token
+ordinal x = Token Ordinal OrdinalData {TOrdinal.value = x}
diff --git a/Duckling/Ordinal/ID/Corpus.hs b/Duckling/Ordinal/ID/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/ID/Corpus.hs
@@ -0,0 +1,38 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.ID.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = ID}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 4)
+             [ "ke-4"
+             , "keempat"
+             ]
+  , examples (OrdinalData 10)
+             [ "Kesepuluh"
+             ]
+  , examples (OrdinalData 3)
+             [ "ketiga"
+             , "ke-00003"
+             ]
+  ]
diff --git a/Duckling/Ordinal/ID/Rules.hs b/Duckling/Ordinal/ID/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/ID/Rules.hs
@@ -0,0 +1,62 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.ID.Rules
+  ( rules ) where
+
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleOrdinals :: Rule
+ruleOrdinals = Rule
+  { name = "ordinals"
+  , pattern =
+    [ regex "(pertama|kedua|ketiga|keempat|kelima|keenam|ketujuh|kedelapan|kesembilan|kesepuluh)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "pertama" -> Just $ ordinal 1
+        "kedua" -> Just $ ordinal 2
+        "ketiga" -> Just $ ordinal 3
+        "keempat" -> Just $ ordinal 4
+        "kelima" -> Just $ ordinal 5
+        "keenam" -> Just $ ordinal 6
+        "ketujuh" -> Just $ ordinal 7
+        "kedelapan" -> Just $ ordinal 8
+        "kesembilan" -> Just $ ordinal 9
+        "kesepuluh" -> Just $ ordinal 10
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleOrdinalsDigits :: Rule
+ruleOrdinalsDigits = Rule
+  { name = "ordinals (digits)"
+  , pattern =
+    [ regex "ke-0*(\\d+)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> ordinal <$> parseInt match
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinals
+  , ruleOrdinalsDigits
+  ]
diff --git a/Duckling/Ordinal/IT/Corpus.hs b/Duckling/Ordinal/IT/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/IT/Corpus.hs
@@ -0,0 +1,87 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.IT.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = IT}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 1)
+             [ "primo"
+             , "prima"
+             , "1°"
+             , "1ª"
+             ]
+  , examples (OrdinalData 2)
+             [ "secondo"
+             , "seconda"
+             , "2°"
+             , "2ª"
+             ]
+  , examples (OrdinalData 3)
+             [ "terzo"
+             , "Terza"
+             , "3°"
+             , "3ª"
+             ]
+  , examples (OrdinalData 4)
+             [ "quarto"
+             , "quarta"
+             , "4°"
+             , "4ª"
+             ]
+  , examples (OrdinalData 5)
+             [ "quinto"
+             , "quinta"
+             , "5°"
+             , "5ª"
+             ]
+  , examples (OrdinalData 6)
+             [ "sesto"
+             , "sesta"
+             , "6°"
+             , "6ª"
+             ]
+  , examples (OrdinalData 7)
+             [ "settimo"
+             , "settima"
+             , "7°"
+             , "7ª"
+             ]
+  , examples (OrdinalData 8)
+             [ "ottavo"
+             , "ottava"
+             , "8°"
+             , "8ª"
+             ]
+  , examples (OrdinalData 9)
+             [ "nono"
+             , "nona"
+             , "9°"
+             , "9ª"
+             ]
+  , examples (OrdinalData 10)
+             [ "decimo"
+             , "decima"
+             , "10°"
+             , "10ª"
+             ]
+  ]
diff --git a/Duckling/Ordinal/IT/Rules.hs b/Duckling/Ordinal/IT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/IT/Rules.hs
@@ -0,0 +1,92 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.IT.Rules
+  ( rules ) where
+
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleOrdinalsPrimo :: Rule
+ruleOrdinalsPrimo = Rule
+  { name = "ordinals (primo..10)"
+  , pattern =
+    [ regex "((prim|second|terz|quart|quint|sest|settim|ottav|non|decim)(o|a|i|e))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "primi" -> Just $ ordinal 1
+        "prima" -> Just $ ordinal 1
+        "primo" -> Just $ ordinal 1
+        "prime" -> Just $ ordinal 1
+        "seconda" -> Just $ ordinal 2
+        "secondi" -> Just $ ordinal 2
+        "seconde" -> Just $ ordinal 2
+        "secondo" -> Just $ ordinal 2
+        "terze" -> Just $ ordinal 3
+        "terzi" -> Just $ ordinal 3
+        "terzo" -> Just $ ordinal 3
+        "terza" -> Just $ ordinal 3
+        "quarte" -> Just $ ordinal 4
+        "quarto" -> Just $ ordinal 4
+        "quarta" -> Just $ ordinal 4
+        "quarti" -> Just $ ordinal 4
+        "quinto" -> Just $ ordinal 5
+        "quinta" -> Just $ ordinal 5
+        "quinti" -> Just $ ordinal 5
+        "quinte" -> Just $ ordinal 5
+        "sesti" -> Just $ ordinal 6
+        "seste" -> Just $ ordinal 6
+        "sesta" -> Just $ ordinal 6
+        "sesto" -> Just $ ordinal 6
+        "settimi" -> Just $ ordinal 7
+        "settima" -> Just $ ordinal 7
+        "settimo" -> Just $ ordinal 7
+        "settime" -> Just $ ordinal 7
+        "ottavo" -> Just $ ordinal 8
+        "ottava" -> Just $ ordinal 8
+        "ottavi" -> Just $ ordinal 8
+        "ottave" -> Just $ ordinal 8
+        "none" -> Just $ ordinal 9
+        "noni" -> Just $ ordinal 9
+        "nona" -> Just $ ordinal 9
+        "nono" -> Just $ ordinal 9
+        "decimo" -> Just $ ordinal 10
+        "decima" -> Just $ ordinal 10
+        "decime" -> Just $ ordinal 10
+        "decimi" -> Just $ ordinal 10
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern =
+    [ regex "0*(\\d+) ?(\x00aa|\x00b0|\x00b0)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> ordinal <$> parseInt match
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinalDigits
+  , ruleOrdinalsPrimo
+  ]
diff --git a/Duckling/Ordinal/JA/Corpus.hs b/Duckling/Ordinal/JA/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/JA/Corpus.hs
@@ -0,0 +1,29 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.JA.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = JA}, allExamples)
+
+allExamples :: [Example]
+allExamples =
+  examples (OrdinalData 7)
+           [ "第七"
+           ]
diff --git a/Duckling/Ordinal/JA/Rules.hs b/Duckling/Ordinal/JA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/JA/Rules.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.JA.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Ordinal.Helpers
+import Duckling.Types
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern =
+    [ regex "\x7b2c"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        Just . ordinal $ floor v
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinalDigits
+  ]
diff --git a/Duckling/Ordinal/KO/Corpus.hs b/Duckling/Ordinal/KO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/KO/Corpus.hs
@@ -0,0 +1,38 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.KO.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = KO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 25)
+             [ "스물다섯번째"
+             , "이십오번째"
+             ]
+  , examples (OrdinalData 1)
+             [ "첫번째"
+             , "첫째"
+             ]
+  , examples (OrdinalData 4)
+             [ "네째번"
+             ]
+  ]
diff --git a/Duckling/Ordinal/KO/Rules.hs b/Duckling/Ordinal/KO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/KO/Rules.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.KO.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.Helpers
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Types
+
+ruleOrdinals :: Rule
+ruleOrdinals = Rule
+  { name = "ordinals (첫번째)"
+  , pattern =
+    [ dimension Numeral
+    , regex "\xbc88\xc9f8|\xc9f8(\xbc88)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        Just . ordinal $ floor v
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinals
+  ]
diff --git a/Duckling/Ordinal/NB/Corpus.hs b/Duckling/Ordinal/NB/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/NB/Corpus.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.NB.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = NB}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 4)
+             [ "4."
+             , "fjerde"
+             , "fJerde"
+             ]
+  , examples (OrdinalData 15)
+             [ "15."
+             , "femtende"
+             ]
+  , examples (OrdinalData 5)
+             [ "5."
+             , "femte"
+             ]
+  ]
diff --git a/Duckling/Ordinal/NB/Rules.hs b/Duckling/Ordinal/NB/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/NB/Rules.hs
@@ -0,0 +1,93 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.NB.Rules
+  ( rules ) where
+
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleOrdinalsFirstst :: Rule
+ruleOrdinalsFirstst = Rule
+  { name = "ordinals (first..31st)"
+  , pattern =
+    [ regex "(f\x00f8rste|andre|tredje|fjerde|femtende|femte|sjette|syvende|\x00e5ttende|niende|tiende|ellevte|tolvte|trettende|fjortende|sekstende|syttende|attende|nittende|tyvende|tjuende|enogtyvende|toogtyvende|treogtyvende|fireogtyvende|femogtyvende|seksogtyvende|syvogtyvende|\x00e5tteogtyvende|niogtyvende|enogtjuende|toogtjuende|treogtjuende|fireogtjuende|femogtjuende|seksogtjuende|syvogtjuende|\x00e5tteogtyvend|niogtjuende|tredefte|enogtredefte)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "f\x00f8rste" -> Just $ ordinal 1
+        "andre" -> Just $ ordinal 2
+        "tredje" -> Just $ ordinal 3
+        "fjerde" -> Just $ ordinal 4
+        "femte" -> Just $ ordinal 5
+        "sjette" -> Just $ ordinal 6
+        "syvende" -> Just $ ordinal 7
+        "\x00e5ttende" -> Just $ ordinal 8
+        "niende" -> Just $ ordinal 9
+        "tiende" -> Just $ ordinal 10
+        "ellevte" -> Just $ ordinal 11
+        "tolvte" -> Just $ ordinal 12
+        "trettende" -> Just $ ordinal 13
+        "fjortende" -> Just $ ordinal 14
+        "femtende" -> Just $ ordinal 15
+        "sekstende" -> Just $ ordinal 16
+        "syttende" -> Just $ ordinal 17
+        "attende" -> Just $ ordinal 18
+        "nittende" -> Just $ ordinal 19
+        "tyvende" -> Just $ ordinal 20
+        "tjuende" -> Just $ ordinal 20
+        "enogtjuende" -> Just $ ordinal 21
+        "enogtyvende" -> Just $ ordinal 21
+        "toogtyvende" -> Just $ ordinal 22
+        "toogtjuende" -> Just $ ordinal 22
+        "treogtyvende" -> Just $ ordinal 23
+        "treogtjuende" -> Just $ ordinal 23
+        "fireogtjuende" -> Just $ ordinal 24
+        "fireogtyvende" -> Just $ ordinal 24
+        "femogtyvende" -> Just $ ordinal 25
+        "femogtjuende" -> Just $ ordinal 25
+        "seksogtjuende" -> Just $ ordinal 26
+        "seksogtyvende" -> Just $ ordinal 26
+        "syvogtyvende" -> Just $ ordinal 27
+        "syvogtjuende" -> Just $ ordinal 27
+        "\x00e5tteogtyvende" -> Just $ ordinal 28
+        "\x00e5tteogtjuende" -> Just $ ordinal 28
+        "niogtyvende" -> Just $ ordinal 29
+        "niogtjuende" -> Just $ ordinal 29
+        "tredefte" -> Just $ ordinal 30
+        "enogtredefte" -> Just $ ordinal 31
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern =
+    [ regex "0*(\\d+)(\\.|ste?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> ordinal <$> parseInt match
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinalDigits
+  , ruleOrdinalsFirstst
+  ]
diff --git a/Duckling/Ordinal/NL/Corpus.hs b/Duckling/Ordinal/NL/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/NL/Corpus.hs
@@ -0,0 +1,39 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.NL.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = NL}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 1)
+             [ "1ste"
+             , "eerste"
+             ]
+  , examples (OrdinalData 4)
+             [ "4de"
+             , "vierde"
+             ]
+  , examples (OrdinalData 17)
+             [ "Zeventiende"
+             , "17de"
+             ]
+  ]
diff --git a/Duckling/Ordinal/NL/Rules.hs b/Duckling/Ordinal/NL/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/NL/Rules.hs
@@ -0,0 +1,79 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.NL.Rules
+  ( rules ) where
+
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+
+zeroNineteenMap :: HashMap Text Int
+zeroNineteenMap = HashMap.fromList
+  [ ("eerste", 1)
+  , ("tweede", 2)
+  , ("derde", 3)
+  , ("vierde", 4)
+  , ("vijfde", 5)
+  , ("zesde", 6)
+  , ("zevende", 7)
+  , ("achste", 8)
+  , ("negende", 9)
+  , ("tiende", 10)
+  , ("elfde", 11)
+  , ("twaalfde", 12)
+  , ("dertiende", 13)
+  , ("veertiende", 14)
+  , ("vijftiende", 15)
+  , ("zestiende", 16)
+  , ("zeventiende", 17)
+  , ("achtiende", 18)
+  , ("negentiende", 19)
+  ]
+
+ruleOrdinalsFirstth :: Rule
+ruleOrdinalsFirstth = Rule
+  { name = "ordinals (first..19th)"
+  , pattern =
+    [ regex "(eerste|tweede|derde|vierde|vijfde|zeste|zevende|achtste|negende|tiende|elfde|twaalfde|veertiende|vijftiende|zestiende|zeventiende|achtiende|negentiende)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        ordinal <$> HashMap.lookup (Text.toLower match) zeroNineteenMap
+      _ -> Nothing
+  }
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern =
+    [ regex "0*(\\d+)(\\.| ?(ste|de))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        ordinal <$> parseInt match
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinalDigits
+  , ruleOrdinalsFirstth
+  ]
diff --git a/Duckling/Ordinal/PL/Corpus.hs b/Duckling/Ordinal/PL/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/PL/Corpus.hs
@@ -0,0 +1,30 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.PL.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = PL}, allExamples)
+
+allExamples :: [Example]
+allExamples =
+  examples (OrdinalData 4)
+           [ "4ty"
+           , "czwarty"
+           ]
diff --git a/Duckling/Ordinal/PL/Rules.hs b/Duckling/Ordinal/PL/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/PL/Rules.hs
@@ -0,0 +1,318 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.PL.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Ordinal.Types (OrdinalData (..))
+import qualified Duckling.Ordinal.Types as TOrdinal
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleThOrdinalNoSpace :: Rule
+ruleThOrdinalNoSpace = Rule
+  { name = "24th ordinal no space"
+  , pattern =
+    [ regex "dwudziest(ym|y|ego|emu|(a|\x0105)|ej)czwart(y|ego|emu|ym|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 24
+  }
+
+ruleThOrdinal16 :: Rule
+ruleThOrdinal16 = Rule
+  { name = "31-39th ordinal"
+  , pattern =
+    [ regex "trzydziest(ym|y|ego|emu|(a|\x0105)|ej)( |-)?"
+    , dimension Ordinal
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal (OrdinalData {TOrdinal.value = v}):_) ->
+        Just . ordinal $ 30 + v
+      _ -> Nothing
+  }
+
+ruleThOrdinal3 :: Rule
+ruleThOrdinal3 = Rule
+  { name = "10th ordinal"
+  , pattern =
+    [ regex "dziesi(a|\x0105)t(ym|y|ego|emu|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 10
+  }
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern =
+    [ regex "0*(\\d+)( |-)?(szy|sza|szym|ego|go|szego|gi(ego|ej)?|st(a|y|ej)|t(ej|y|ego)|ci(ego)?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> ordinal <$> parseInt match
+      _ -> Nothing
+  }
+
+ruleThOrdinal8 :: Rule
+ruleThOrdinal8 = Rule
+  { name = "15th ordinal"
+  , pattern =
+    [ regex "pi(e|\x0119)tnast(ym|y|ego|emu|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 15
+  }
+
+ruleThOrdinal13 :: Rule
+ruleThOrdinal13 = Rule
+  { name = "20th ordinal"
+  , pattern =
+    [ regex "dwudziest(ym|y|ego|emu|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 20
+  }
+
+ruleThOrdinal4 :: Rule
+ruleThOrdinal4 = Rule
+  { name = "11th ordinal"
+  , pattern =
+    [ regex "jedenast(ym|y|ego|emu|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 11
+  }
+
+ruleFifthOrdinal :: Rule
+ruleFifthOrdinal = Rule
+  { name = "fifth ordinal"
+  , pattern =
+    [ regex "pi(a|\x0105)t(y|ego|emu|m|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 5
+  }
+
+ruleThOrdinal11 :: Rule
+ruleThOrdinal11 = Rule
+  { name = "18th ordinal"
+  , pattern =
+    [ regex "osiemnast(ym|y|ego|emu|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 18
+  }
+
+ruleSecondOrdinal :: Rule
+ruleSecondOrdinal = Rule
+  { name = "second ordinal"
+  , pattern =
+    [ regex "drugi?(ego|emu|m|(a|\x0105)|ej)?"
+    ]
+  , prod = \_ -> Just $ ordinal 2
+  }
+
+ruleNdOrdinalNoSpace :: Rule
+ruleNdOrdinalNoSpace = Rule
+  { name = "22nd ordinal no space"
+  , pattern =
+    [ regex "dwudziest(ym|y|ego|emu|(a|\x0105)|ej)drugi?(ego|emu|m|(a|\x0105)|ej)?"
+    ]
+  , prod = \_ -> Just $ ordinal 22
+  }
+
+ruleSeventhOrdinal :: Rule
+ruleSeventhOrdinal = Rule
+  { name = "seventh ordinal"
+  , pattern =
+    [ regex "si(o|\x00f3)dm(y|ego|emu|m|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 7
+  }
+
+ruleStOrdinalNoSpace :: Rule
+ruleStOrdinalNoSpace = Rule
+  { name = "21st ordinal no space"
+  , pattern =
+    [ regex "dwudziest(ym|y|ego|emu|(a|\x0105)|ej)pierw?sz(y|ego|emu|m|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 21
+  }
+
+ruleThOrdinal7 :: Rule
+ruleThOrdinal7 = Rule
+  { name = "14th ordinal"
+  , pattern =
+    [ regex "czternast(ym|y|ego|emu|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 14
+  }
+
+ruleThOrdinal2 :: Rule
+ruleThOrdinal2 = Rule
+  { name = "9th ordinal"
+  , pattern =
+    [ regex "dziewi(a|\x0105)t(ym|y|ego|em|emu|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 9
+  }
+
+ruleThOrdinal9 :: Rule
+ruleThOrdinal9 = Rule
+  { name = "16th ordinal"
+  , pattern =
+    [ regex "szesnast(ym|y|ego|emu|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 16
+  }
+
+ruleThOrdinal :: Rule
+ruleThOrdinal = Rule
+  { name = "8th ordinal"
+  , pattern =
+    [ regex "(o|\x00f3|\x00d3)sm(y|ego|emu|m|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 8
+  }
+
+ruleThOrdinal14 :: Rule
+ruleThOrdinal14 = Rule
+  { name = "21-29th ordinal"
+  , pattern =
+    [ regex "dwudziest(ym|y|ego|emu|(a|\x0105)|ej)( |-)?"
+    , dimension Ordinal
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal (OrdinalData {TOrdinal.value = v}):_) ->
+        Just . ordinal $ 20 + v
+      _ -> Nothing
+  }
+
+ruleThOrdinal10 :: Rule
+ruleThOrdinal10 = Rule
+  { name = "17th ordinal"
+  , pattern =
+    [ regex "siedemnast(ym|y|ego|emu|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 17
+  }
+
+ruleRdOrdinalNoSpace :: Rule
+ruleRdOrdinalNoSpace = Rule
+  { name = "23rd ordinal no space"
+  , pattern =
+    [ regex "dwudziest(ym|y|ego|emu|(a|\x0105)|ej)trzeci(ego|ch|emu|m|mi|ej|(a|\x0105))?"
+    ]
+  , prod = \_ -> Just $ ordinal 23
+  }
+
+ruleThOrdinal5 :: Rule
+ruleThOrdinal5 = Rule
+  { name = "12th ordinal"
+  , pattern =
+    [ regex "dwunast(ym|y|ego|emu|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 12
+  }
+
+ruleThOrdinal6 :: Rule
+ruleThOrdinal6 = Rule
+  { name = "13th ordinal"
+  , pattern =
+    [ regex "trzynast(ym|y|ego|emu|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 13
+  }
+
+ruleFirstOrdinal :: Rule
+ruleFirstOrdinal = Rule
+  { name = "first ordinal"
+  , pattern =
+    [ regex "pierw?sz(y|ego|emu|m|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 1
+  }
+
+ruleSixthOrdinal :: Rule
+ruleSixthOrdinal = Rule
+  { name = "sixth ordinal"
+  , pattern =
+    [ regex "sz(o|\x00f3)st(y|ego|emu|m|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 6
+  }
+
+ruleFourthOrdinal :: Rule
+ruleFourthOrdinal = Rule
+  { name = "fourth ordinal"
+  , pattern =
+    [ regex "czwart(y|ego|emu|ym|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 4
+  }
+
+ruleThOrdinal15 :: Rule
+ruleThOrdinal15 = Rule
+  { name = "30th ordinal"
+  , pattern =
+    [ regex "trzydziest(ym|y|ego|emu|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 30
+  }
+
+ruleThOrdinal12 :: Rule
+ruleThOrdinal12 = Rule
+  { name = "19th ordinal"
+  , pattern =
+    [ regex "dziewi(\x0119|e)tnast(ym|y|ego|emu|(a|\x0105)|ej)"
+    ]
+  , prod = \_ -> Just $ ordinal 19
+  }
+
+ruleThirdOrdinal :: Rule
+ruleThirdOrdinal = Rule
+  { name = "third ordinal"
+  , pattern =
+    [ regex "trzeci(ego|ch|emu|m|mi|ej|(a|\x0105))?"
+    ]
+  , prod = \_ -> Just $ ordinal 3
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleFifthOrdinal
+  , ruleFirstOrdinal
+  , ruleFourthOrdinal
+  , ruleNdOrdinalNoSpace
+  , ruleOrdinalDigits
+  , ruleRdOrdinalNoSpace
+  , ruleSecondOrdinal
+  , ruleSeventhOrdinal
+  , ruleSixthOrdinal
+  , ruleStOrdinalNoSpace
+  , ruleThOrdinal
+  , ruleThOrdinal10
+  , ruleThOrdinal11
+  , ruleThOrdinal12
+  , ruleThOrdinal13
+  , ruleThOrdinal14
+  , ruleThOrdinal15
+  , ruleThOrdinal16
+  , ruleThOrdinal2
+  , ruleThOrdinal3
+  , ruleThOrdinal4
+  , ruleThOrdinal5
+  , ruleThOrdinal6
+  , ruleThOrdinal7
+  , ruleThOrdinal8
+  , ruleThOrdinal9
+  , ruleThOrdinalNoSpace
+  , ruleThirdOrdinal
+  ]
diff --git a/Duckling/Ordinal/PT/Corpus.hs b/Duckling/Ordinal/PT/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/PT/Corpus.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.PT.Corpus
+  ( corpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = PT}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 1)
+             [ "primeira"
+             , "primeiros"
+             ]
+  , examples (OrdinalData 2)
+             [ "Segundo"
+             , "segundas"
+             ]
+  , examples (OrdinalData 7)
+             [ "setimo"
+             , "sétimas"
+             ]
+  ]
diff --git a/Duckling/Ordinal/PT/Rules.hs b/Duckling/Ordinal/PT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/PT/Rules.hs
@@ -0,0 +1,87 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.PT.Rules
+  ( rules ) where
+
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleOrdinalsPrimeiro :: Rule
+ruleOrdinalsPrimeiro = Rule
+  { name = "ordinals (primeiro..10)"
+  , pattern =
+    [ regex "((primeir|segund|quart|quint|sext|s(e|\x00e9)tim|oitav|non|d(e|\x00e9)cim)(os?|as?))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "primeira" -> Just $ ordinal 1
+        "primeiros" -> Just $ ordinal 1
+        "primeiras" -> Just $ ordinal 1
+        "primeiro" -> Just $ ordinal 1
+        "segundo" -> Just $ ordinal 2
+        "segunda" -> Just $ ordinal 2
+        "segundas" -> Just $ ordinal 2
+        "segundos" -> Just $ ordinal 2
+        "terceiros" -> Just $ ordinal 3
+        "terceiro" -> Just $ ordinal 3
+        "terceiras" -> Just $ ordinal 3
+        "terceira" -> Just $ ordinal 3
+        "quartos" -> Just $ ordinal 4
+        "quarto" -> Just $ ordinal 4
+        "quarta" -> Just $ ordinal 4
+        "quartas" -> Just $ ordinal 4
+        "quinto" -> Just $ ordinal 5
+        "quinta" -> Just $ ordinal 5
+        "quintas" -> Just $ ordinal 5
+        "quintos" -> Just $ ordinal 5
+        "sextos" -> Just $ ordinal 6
+        "sexto" -> Just $ ordinal 6
+        "sexta" -> Just $ ordinal 6
+        "sextas" -> Just $ ordinal 6
+        "setimas" -> Just $ ordinal 7
+        "s\x00e9tima" -> Just $ ordinal 7
+        "setimo" -> Just $ ordinal 7
+        "setimos" -> Just $ ordinal 7
+        "setima" -> Just $ ordinal 7
+        "s\x00e9timos" -> Just $ ordinal 7
+        "s\x00e9timo" -> Just $ ordinal 7
+        "s\x00e9timas" -> Just $ ordinal 7
+        "oitavas" -> Just $ ordinal 8
+        "oitava" -> Just $ ordinal 8
+        "oitavo" -> Just $ ordinal 8
+        "oitavos" -> Just $ ordinal 8
+        "nonos" -> Just $ ordinal 9
+        "nona" -> Just $ ordinal 9
+        "nono" -> Just $ ordinal 9
+        "nonas" -> Just $ ordinal 9
+        "d\x00e9cimos" -> Just $ ordinal 10
+        "decimo" -> Just $ ordinal 10
+        "decimos" -> Just $ ordinal 10
+        "d\x00e9cimo" -> Just $ ordinal 10
+        "decimas" -> Just $ ordinal 10
+        "d\x00e9cima" -> Just $ ordinal 10
+        "decima" -> Just $ ordinal 10
+        "d\x00e9cimas" -> Just $ ordinal 10
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinalsPrimeiro
+  ]
diff --git a/Duckling/Ordinal/RO/Corpus.hs b/Duckling/Ordinal/RO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/RO/Corpus.hs
@@ -0,0 +1,68 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.RO.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = RO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 1)
+             [ "primul"
+             , "prima"
+             ]
+  , examples (OrdinalData 2)
+             [ "al doilea"
+             , "al doi-lea"
+             , "al doi lea"
+             , "al 2-lea"
+             , "al 2 lea"
+             , "a 2 a"
+             , "a 2-a"
+             , "a doua"
+             ]
+  , examples (OrdinalData 3)
+             [ "al treilea"
+             , "al trei-lea"
+             , "a treia"
+             , "a 3-lea"
+             , "a 3a"
+             ]
+  , examples (OrdinalData 4)
+             [ "a patra"
+             , "a patru-lea"
+             ]
+  , examples (OrdinalData 6)
+             [ "al saselea"
+             , "al șaselea"
+             , "al sase-lea"
+             , "al șase-lea"
+             , "a sasea"
+             , "a șase a"
+             ]
+  , examples (OrdinalData 9)
+             [ "al noualea"
+             , "al noua-lea"
+             , "al noua lea"
+             , "al nouălea"
+             , "al nouă lea"
+             , "a noua"
+             ]
+  ]
diff --git a/Duckling/Ordinal/RO/Rules.hs b/Duckling/Ordinal/RO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/RO/Rules.hs
@@ -0,0 +1,93 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.RO.Rules
+  ( rules ) where
+
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.Maybe
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+
+rulePrimulDouaPatraNoua :: Rule
+rulePrimulDouaPatraNoua = Rule
+ { name = "special ordinals"
+ , pattern =
+   [ regex "(prim(a|ul)|a (patra|[dn]oua))"
+   ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "prima"   -> Just $ ordinal 1
+        "primul"  -> Just $ ordinal 1
+        "a doua"  -> Just $ ordinal 2
+        "a patra" -> Just $ ordinal 4
+        "a noua"  -> Just $ ordinal 9
+        _         -> Nothing
+      _ -> Nothing
+ }
+
+-- We can't have a generic rule of the form "al?" + Numeral + "(le)?a"
+-- It wouldn't match "al optlea"
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinals (digits)"
+  , pattern =
+    [ regex "al?\\s0*(\\d+)[ -]?(le)?a"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        ordinal <$> parseInt match
+      _ -> Nothing
+  }
+
+ordinalMap :: HashMap Text Int
+ordinalMap = HashMap.fromList
+  [ ("doi", 2)
+  , ("trei", 3)
+  , ("patru", 4)
+  , ("cinci", 5)
+  , ("sase", 6)
+  , ("\537ase", 6)
+  , ("sapte", 7)
+  , ("\537apte", 7)
+  , ("opt", 8)
+  , ("noua", 9)
+  , ("nou\x0103", 9)
+  ]
+
+ruleSpelledOutOrdinals :: Rule
+ruleSpelledOutOrdinals = Rule
+  { name = "spelled out ordinals"
+  , pattern =
+    [ regex "al?\\s(doi|trei|patru|cinci|(s|\x0219)a(s|pt)e|opt|nou(a|\x0103))[ -]?(le)?a"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        ordinal <$> HashMap.lookup (Text.toLower match) ordinalMap
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinalDigits
+  , rulePrimulDouaPatraNoua
+  , ruleSpelledOutOrdinals
+  ]
diff --git a/Duckling/Ordinal/RU/Corpus.hs b/Duckling/Ordinal/RU/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/RU/Corpus.hs
@@ -0,0 +1,74 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.RU.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = RU}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 1)
+             [ "первый"
+             , "первая"
+             , "первое"
+             , "1ая"
+             , "1-ая"
+             , "1ый"
+             , "1-ый"
+             , "1ое"
+             , "1-ое"
+             ]
+  , examples (OrdinalData 4)
+             [ "четвертый"
+             , "четвертая"
+             , "четвертое"
+             , "4ый"
+             , "4ая"
+             , "4ое"
+             , "4-ый"
+             , "4-ая"
+             , "4-ое"
+             ]
+  , examples (OrdinalData 15)
+             [ "пятнадцатый"
+             , "15й"
+             , "15-й"
+             ]
+  , examples (OrdinalData 21)
+             [ "21й"
+             , "21-й"
+             , "двадцать первый"
+             ]
+  , examples (OrdinalData 31)
+             [ "31ый"
+             , "31-ый"
+             , "тридцать первый"
+             ]
+  , examples (OrdinalData 48)
+             [ "48ое"
+             , "48-ое"
+             , "сорок восьмое"
+             ]
+  , examples (OrdinalData 99)
+             [ "99ый"
+             , "99-й"
+             , "девяносто девятый"
+             ]
+  ]
diff --git a/Duckling/Ordinal/RU/Rules.hs b/Duckling/Ordinal/RU/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/RU/Rules.hs
@@ -0,0 +1,110 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.RU.Rules
+  ( rules ) where
+
+import Control.Monad (join)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleOrdinalsFirstth :: Rule
+ruleOrdinalsFirstth = Rule
+  { name = "ordinals (first..19th)"
+  , pattern =
+    [ regex "(\x043f\x0435\x0440\x0432|\x0432\x0442\x043e\x0440|\x0442\x0440\x0435\x0442|\x0447\x0435\x0442\x0432\x0435\x0440\x0442|\x043f\x044f\x0442|\x0448\x0435\x0441\x0442|\x0441\x0435\x0434\x044c\x043c|\x0432\x043e\x0441\x044c\x043c|\x0434\x0435\x0432\x044f\x0442|\x0434\x0435\x0441\x044f\x0442|\x043e\x0434\x0438\x043d\x0430\x0434\x0446\x0430\x0442|\x0434\x0432\x0435\x043d\x0430\x0434\x0446\x0430\x0442|\x0442\x0440\x0438\x043d\x0430\x0434\x0446\x0430\x0442|\x0447\x0435\x0442\x044b\x0440\x043d\x0430\x0434\x0446\x0430\x0442|\x043f\x044f\x0442\x043d\x0430\x0434\x0446\x0430\x0442|\x0448\x0435\x0441\x0442\x043d\x0430\x0434\x0446\x0430\x0442|\x0441\x0435\x043c\x043d\x0430\x0434\x0446\x0430\x0442|\x0432\x043e\x0441\x0435\x043c\x043d\x0430\x0434\x0446\x0430\x0442|\x0434\x0435\x0432\x044f\x0442\x043d\x0430\x0434\x0446\x0430\x0442|\x0434\x0432\x0430\x0434\x0446\x0430\x0442)(\x044b\x0439|\x043e\x0439|\x0438\x0439|\x0430\x044f|\x043e\x0435)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case match of
+        "\x043f\x0435\x0440\x0432" -> Just $ ordinal 1
+        "\x0432\x0442\x043e\x0440" -> Just $ ordinal 2
+        "\x0442\x0440\x0435\x0442" -> Just $ ordinal 3
+        "\x0447\x0435\x0442\x0432\x0435\x0440\x0442" -> Just $ ordinal 4
+        "\x043f\x044f\x0442" -> Just $ ordinal 5
+        "\x0448\x0435\x0441\x0442" -> Just $ ordinal 6
+        "\x0441\x0435\x0434\x044c\x043c" -> Just $ ordinal 7
+        "\x0432\x043e\x0441\x044c\x043c" -> Just $ ordinal 8
+        "\x0434\x0435\x0432\x044f\x0442" -> Just $ ordinal 9
+        "\x0434\x0435\x0441\x044f\x0442" -> Just $ ordinal 10
+        "\x043e\x0434\x0438\x043d\x0430\x0434\x0446\x0430\x0442" -> Just $ ordinal 11
+        "\x0434\x0432\x0435\x043d\x0430\x0434\x0446\x0430\x0442" -> Just $ ordinal 12
+        "\x0442\x0440\x0438\x043d\x0430\x0434\x0446\x0430\x0442" -> Just $ ordinal 13
+        "\x0447\x0435\x0442\x044b\x0440\x043d\x0430\x0434\x0446\x0430\x0442" -> Just $ ordinal 14
+        "\x043f\x044f\x0442\x043d\x0430\x0434\x0446\x0430\x0442" -> Just $ ordinal 15
+        "\x0448\x0435\x0441\x0442\x043d\x0430\x0434\x0446\x0430\x0442" -> Just $ ordinal 16
+        "\x0441\x0435\x043c\x043d\x0430\x0434\x0446\x0430\x0442" -> Just $ ordinal 17
+        "\x0432\x043e\x0441\x0435\x043c\x043d\x0430\x0434\x0446\x0430\x0442" -> Just $ ordinal 18
+        "\x0434\x0435\x0432\x044f\x0442\x043d\x0430\x0434\x0446\x0430\x0442" -> Just $ ordinal 19
+        "\x0434\x0432\x0430\x0434\x0446\x0430\x0442" -> Just $ ordinal 20
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleOrdinal :: Rule
+ruleOrdinal = Rule
+  { name = "ordinal 21..99"
+  , pattern =
+    [ regex "(\x0434\x0432\x0430\x0434\x0446\x0430\x0442\x044c|\x0442\x0440\x0438\x0434\x0446\x0430\x0442\x044c|\x0441\x043e\x0440\x043e\x043a|\x043f\x044f\x0442\x044c\x0434\x0435\x0441\x044f\x0442|\x0448\x0435\x0441\x0442\x044c\x0434\x0435\x0441\x044f\x0442|\x0441\x0435\x043c\x044c\x0434\x0435\x0441\x044f\x0442|\x0432\x043e\x0441\x0435\x043c\x044c\x0434\x0435\x0441\x044f\x0442|\x0434\x0435\x0432\x044f\x043d\x043e\x0441\x0442\x043e)"
+    , regex "(\x043f\x0435\x0440\x0432|\x0432\x0442\x043e\x0440|\x0442\x0440\x0435\x0442|\x0447\x0435\x0442\x0432\x0435\x0440\x0442|\x043f\x044f\x0442|\x0448\x0435\x0441\x0442|\x0441\x0435\x0434\x044c\x043c|\x0432\x043e\x0441\x044c\x043c|\x0434\x0435\x0432\x044f\x0442)(\x044b\x0439|\x043e\x0439|\x0438\x0439|\x0430\x044f|\x043e\x0435)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:_)):
+       Token RegexMatch (GroupMatch (m2:_)):
+       _) -> do
+         dozen <- case Text.toLower m1 of
+           "\x0434\x0432\x0430\x0434\x0446\x0430\x0442\x044c" -> Just 20
+           "\x0442\x0440\x0438\x0434\x0446\x0430\x0442\x044c" -> Just 30
+           "\x0441\x043e\x0440\x043e\x043a" -> Just 40
+           "\x043f\x044f\x0442\x044c\x0434\x0435\x0441\x044f\x0442" -> Just 50
+           "\x0448\x0435\x0441\x0442\x044c\x0434\x0435\x0441\x044f\x0442" -> Just 60
+           "\x0441\x0435\x043c\x044c\x0434\x0435\x0441\x044f\x0442" -> Just 70
+           "\x0432\x043e\x0441\x0435\x043c\x044c\x0434\x0435\x0441\x044f\x0442" -> Just 80
+           "\x0434\x0435\x0432\x044f\x043d\x043e\x0441\x0442\x043e" -> Just 90
+           _ -> Nothing
+         unit <- case Text.toLower m2 of
+           "\x043f\x0435\x0440\x0432" -> Just 1
+           "\x0432\x0442\x043e\x0440" -> Just 2
+           "\x0442\x0440\x0435\x0442" -> Just 3
+           "\x0447\x0435\x0442\x0432\x0435\x0440\x0442" -> Just 4
+           "\x043f\x044f\x0442" -> Just 5
+           "\x0448\x0435\x0441\x0442" -> Just 6
+           "\x0441\x0435\x0434\x044c\x043c" -> Just 7
+           "\x0432\x043e\x0441\x044c\x043c" -> Just 8
+           "\x0434\x0435\x0432\x044f\x0442" -> Just 9
+           _ -> Nothing
+         Just . ordinal $ dozen + unit
+      _ -> Nothing
+  }
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern =
+    [ regex "0*(\\d+)-?((\x044b|\x043e|\x0438)?\x0439|\x0430\x044f|\x043e\x0435)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> ordinal <$> parseInt match
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinal
+  , ruleOrdinalDigits
+  , ruleOrdinalsFirstth
+  ]
diff --git a/Duckling/Ordinal/SV/Corpus.hs b/Duckling/Ordinal/SV/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/SV/Corpus.hs
@@ -0,0 +1,43 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.SV.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = SV}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 1)
+             [ "förste"
+             , "första"
+             , "1:a"
+             , "1:e"
+             ]
+  , examples (OrdinalData 10)
+             [ "tionde"
+             , "10."
+             , "10"
+             ]
+  , examples (OrdinalData 26)
+             [ "seksogtjuende"
+             , "Seksogtyvende"
+             , "26e"
+             ]
+  ]
diff --git a/Duckling/Ordinal/SV/Rules.hs b/Duckling/Ordinal/SV/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/SV/Rules.hs
@@ -0,0 +1,94 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.SV.Rules
+  ( rules ) where
+
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleOrdinalsFirstst :: Rule
+ruleOrdinalsFirstst = Rule
+  { name = "ordinals (first..31st)"
+  , pattern =
+    [ regex "(f\x00f6rste|f\x00f6rsta|andra|tredje|fj\x00e4rde|femte|sj\x00e4tte|sjunde|\x00e5ttonde|nionde|tionde|ellevte|tolfte|trettonde|fjortonde|femtonde|sekstende|syttende|attende|nittende|tyvende|tjuende|enogtyvende|toogtyvende|treogtyvende|fireogtyvende|femogtyvende|seksogtyvende|syvogtyvende|\x00e5tteogtyvende|niogtyvende|enogtjuende|toogtjuende|treogtjuende|fireogtjuende|femogtjuende|seksogtjuende|syvogtjuende|\x00e5tteogtyvend|niogtjuende|tredefte|enogtredefte)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "f\x00f6rsta" -> Just $ ordinal 1
+        "f\x00f6rste" -> Just $ ordinal 1
+        "andra" -> Just $ ordinal 2
+        "tredje" -> Just $ ordinal 3
+        "fj\x00e4rde" -> Just $ ordinal 4
+        "femte" -> Just $ ordinal 5
+        "sj\x00e4tte" -> Just $ ordinal 6
+        "sjunde" -> Just $ ordinal 7
+        "\x00e5ttonde" -> Just $ ordinal 8
+        "nionde" -> Just $ ordinal 9
+        "tionde" -> Just $ ordinal 10
+        "ellevte" -> Just $ ordinal 11
+        "tolfte" -> Just $ ordinal 12
+        "trettonde" -> Just $ ordinal 13
+        "fjortonde" -> Just $ ordinal 14
+        "femtonde" -> Just $ ordinal 15
+        "sekstende" -> Just $ ordinal 16
+        "syttende" -> Just $ ordinal 17
+        "attende" -> Just $ ordinal 18
+        "nittende" -> Just $ ordinal 19
+        "tyvende" -> Just $ ordinal 20
+        "tjuende" -> Just $ ordinal 20
+        "enogtjuende" -> Just $ ordinal 21
+        "enogtyvende" -> Just $ ordinal 21
+        "toogtyvende" -> Just $ ordinal 22
+        "toogtjuende" -> Just $ ordinal 22
+        "treogtyvende" -> Just $ ordinal 23
+        "treogtjuende" -> Just $ ordinal 23
+        "fireogtjuende" -> Just $ ordinal 24
+        "fireogtyvende" -> Just $ ordinal 24
+        "femogtyvende" -> Just $ ordinal 25
+        "femogtjuende" -> Just $ ordinal 25
+        "seksogtjuende" -> Just $ ordinal 26
+        "seksogtyvende" -> Just $ ordinal 26
+        "syvogtyvende" -> Just $ ordinal 27
+        "syvogtjuende" -> Just $ ordinal 27
+        "\x00e5tteogtyvende" -> Just $ ordinal 28
+        "\x00e5tteogtjuende" -> Just $ ordinal 28
+        "niogtyvende" -> Just $ ordinal 29
+        "niogtjuende" -> Just $ ordinal 29
+        "tredefte" -> Just $ ordinal 30
+        "enogtredefte" -> Just $ ordinal 31
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern =
+    [ regex "0*(\\d+)(\\.|e|\\:[ae])?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> ordinal <$> parseInt match
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinalDigits
+  , ruleOrdinalsFirstst
+  ]
diff --git a/Duckling/Ordinal/TR/Corpus.hs b/Duckling/Ordinal/TR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/TR/Corpus.hs
@@ -0,0 +1,39 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.TR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = TR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 4)
+             [ "4üncü"
+             , "dördüncü"
+             , "4."
+             , "4'üncü"
+             ]
+  , examples (OrdinalData 8)
+             [ "sekizinci"
+             ]
+  , examples (OrdinalData 23)
+             [ "yirmi üçüncü"
+             ]
+  ]
diff --git a/Duckling/Ordinal/TR/Rules.hs b/Duckling/Ordinal/TR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/TR/Rules.hs
@@ -0,0 +1,95 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.TR.Rules
+  ( rules ) where
+
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+
+ordinals :: [Text]
+ordinals =
+  [ "birinci"
+  , "ikinci"
+  , "\x00fc\x00e7\x00fcnc\x00fc"
+  , "d\x00f6rd\x00fcnc\x00fc"
+  , "be\x015finci"
+  , "alt\x0131nc\x0131"
+  , "yedinci"
+  , "sekizinci"
+  , "dokuzuncu"
+  , "onuncu"
+  , "on birinci"
+  , "on ikinci"
+  , "on \x00fc\x00e7\x00fcnc\x00fc"
+  , "on d\x00f6rd\x00fcnc\x00fc"
+  , "on be\x015finci"
+  , "on alt\x0131nc\x0131"
+  , "on yedinci"
+  , "on sekizinci"
+  , "on dokuzuncu"
+  , "yirminci"
+  , "yirmi birinci"
+  , "yirmi ikinci"
+  , "yirmi \x00fc\x00e7\x00fcnc\x00fc"
+  , "yirmi d\x00f6rd\x00fcnc\x00fc"
+  , "yirmi be\x015finci"
+  , "yirmi alt\x0131nc\x0131"
+  , "yirmi yedinci"
+  , "yirmi sekizinci"
+  , "yirmi dokuzuncu"
+  , "otuzuncu"
+  , "otuz birinci"
+  ]
+
+ordinalsHashMap :: HashMap Text Int
+ordinalsHashMap = HashMap.fromList $ zip ordinals [1..]
+
+ruleOrdinalsFirstst :: Rule
+ruleOrdinalsFirstst = Rule
+  { name = "ordinals (first..31st)"
+  , pattern =
+    [ regex . Text.unpack $
+        Text.concat [ "(", Text.intercalate "|" ordinals, ")" ]
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        ordinal <$> HashMap.lookup (Text.toLower match) ordinalsHashMap
+      _ -> Nothing
+  }
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern =
+    [ regex "0*(\\d+) ?('?)(inci|nci|\x0131nc\x0131|nc\x0131|uncu|ncu|\x00fcnc\x00fc|nc\x00fc|.)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        ordinal <$> parseInt match
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinalDigits
+  , ruleOrdinalsFirstst
+  ]
diff --git a/Duckling/Ordinal/Types.hs b/Duckling/Ordinal/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/Types.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Duckling.Ordinal.Types where
+
+import Control.DeepSeq
+import Data.Aeson
+import Data.Hashable
+import Data.Text (Text)
+import GHC.Generics
+import Prelude
+
+import Duckling.Resolve (Resolve(..))
+
+newtype OrdinalData = OrdinalData { value :: Int }
+  deriving (Eq, Generic, Hashable, Show, Ord, NFData)
+
+instance Resolve OrdinalData where
+  type ResolvedValue OrdinalData = OrdinalData
+  resolve _ x = Just x
+
+instance ToJSON OrdinalData where
+  toJSON (OrdinalData value) = object
+    [ "type" .= ("value" :: Text)
+    , "value" .= value
+    ]
+
+isBetween :: Ord a => a -> a -> a -> Bool
+isBetween x low high = low <= x && x <= high
diff --git a/Duckling/Ordinal/UK/Corpus.hs b/Duckling/Ordinal/UK/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/UK/Corpus.hs
@@ -0,0 +1,74 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.UK.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = UK}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 1)
+             [ "перший"
+             , "перша"
+             , "перше"
+             , "1а"
+             , "1-а"
+             , "1ий"
+             , "1-ий"
+             , "1е"
+             , "1-е"
+             ]
+  , examples (OrdinalData 4)
+             [ "четвертий"
+             , "четверта"
+             , "четверте"
+             , "4ий"
+             , "4а"
+             , "4е"
+             , "4-ий"
+             , "4-а"
+             , "4-е"
+             ]
+  , examples (OrdinalData 15)
+             [ "п‘ятнадцятий"
+             , "15й"
+             , "15-й"
+             ]
+  , examples (OrdinalData 21)
+             [ "21й"
+             , "21-й"
+             , "двадцять перший"
+             ]
+  , examples (OrdinalData 31)
+             [ "31ий"
+             , "31-ий"
+             , "тридцять перший"
+             ]
+  , examples (OrdinalData 48)
+             [ "48е"
+             , "48-е"
+             , "сорок восьме"
+             ]
+  , examples (OrdinalData 99)
+             [ "99ий"
+             , "99-й"
+             , "дев‘яносто дев‘ятий"
+             ]
+  ]
diff --git a/Duckling/Ordinal/UK/Rules.hs b/Duckling/Ordinal/UK/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/UK/Rules.hs
@@ -0,0 +1,110 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.UK.Rules
+  ( rules ) where
+
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Control.Monad (join)
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+
+ordinalsFirstThMap :: HashMap Text Int
+ordinalsFirstThMap = HashMap.fromList
+  [ ( "\x043f\x0435\x0440\x0448"                   , 1 )
+  , ( "\x0434\x0440\x0443\x0433"                   , 2 )
+  , ( "\x0442\x0440\x0435\x0442"                   , 3 )
+  , ( "\x0447\x0435\x0442\x0432\x0435\x0440\x0442" , 4 )
+  , ( "\x043f\x2018\x044f\x0442"                   , 5 )
+  , ( "\x0448\x043e\x0441\x0442"                   , 6 )
+  , ( "\x0441\x044c\x043e\x043c"                   , 7 )
+  , ( "\x0432\x043e\x0441\x044c\x043c"             , 8 )
+  , ( "\x0434\x0435\x0432\x2018\x044f\x0442"       , 9 )
+  , ( "\x0434\x0435\x0441\x044f\x0442"             , 10 )
+  , ( "\x043e\x0434\x0438\x043d\x0430\x0434\x0446\x044f\x0442"             , 11 )
+  , ( "\x0434\x0432\x0430\x043d\x0430\x0434\x0446\x044f\x0442"             , 12 )
+  , ( "\x0442\x0440\x0438\x043d\x0430\x0434\x0446\x044f\x0442"             , 13 )
+  , ( "\x0447\x043e\x0442\x0438\x0440\x043d\x0430\x0434\x0446\x044f\x0442" , 14 )
+  , ( "\x043f\x2018\x044f\x0442\x043d\x0430\x0434\x0446\x044f\x0442"       , 15 )
+  , ( "\x0448\x0456\x0441\x0442\x043d\x0430\x0434\x0446\x044f\x0442"       , 16 )
+  , ( "\x0441\x0456\x043c\x043d\x0430\x0434\x0446\x044f\x0442"             , 17 )
+  , ( "\x0432\x0456\x0441\x0456\x043c\x043d\x0430\x0434\x0446\x044f\x0442" , 18 )
+  , ( "\x0434\x0435\x0432\x2018\x044f\x0442\x043d\x0430\x0434\x0446\x044f\x0442" , 19 )
+  , ( "\x0434\x0432\x0430\x0434\x0446\x044f\x0442"                               , 20 )
+  ]
+
+ruleOrdinalsFirstth :: Rule
+ruleOrdinalsFirstth = Rule
+  { name = "ordinals (first..19th)"
+  , pattern =
+    [ regex "(\x043f\x0435\x0440\x0448|\x0434\x0440\x0443\x0433|\x0442\x0440\x0435\x0442|\x0447\x0435\x0442\x0432\x0435\x0440\x0442|\x043f\x2018\x044f\x0442|\x0448\x043e\x0441\x0442|\x0441\x044c\x043e\x043c|\x0432\x043e\x0441\x044c\x043c|\x0434\x0435\x0432\x2018\x044f\x0442|\x0434\x0435\x0441\x044f\x0442|\x043e\x0434\x0438\x043d\x0430\x0434\x0446\x044f\x0442|\x0434\x0432\x0430\x043d\x0430\x0434\x0446\x044f\x0442|\x0442\x0440\x0438\x043d\x0430\x0434\x0446\x044f\x0442|\x0447\x043e\x0442\x0438\x0440\x043d\x0430\x0434\x0446\x044f\x0442|\x043f\x2018\x044f\x0442\x043d\x0430\x0434\x0446\x044f\x0442|\x0448\x0456\x0441\x0442\x043d\x0430\x0434\x0446\x044f\x0442|\x0441\x0456\x043c\x043d\x0430\x0434\x0446\x044f\x0442|\x0432\x0456\x0441\x0456\x043c\x043d\x0430\x0434\x0446\x044f\x0442|\x0434\x0435\x0432\x2018\x044f\x0442\x043d\x0430\x0434\x0446\x044f\x0442|\x0434\x0432\x0430\x0434\x0446\x044f\x0442)(\x0438\x0439|\x0456\x0439|\x0430|\x044f|\x0435|\x0454)"
+    ]
+  , prod = \tokens -> case tokens of
+    (Token RegexMatch (GroupMatch (match:_)):_) ->
+      ordinal <$> HashMap.lookup (Text.toLower match) ordinalsFirstThMap
+    _ -> Nothing
+  }
+
+ordinalTensMap :: HashMap Text Int
+ordinalTensMap = HashMap.fromList
+  [ ( "\x0434\x0432\x0430\x0434\x0446\x044f\x0442\x044c"             , 20 )
+  , ( "\x0442\x0440\x0438\x0434\x0446\x044f\x0442\x044c"             , 30 )
+  , ( "\x0441\x043e\x0440\x043e\x043a"                               , 40 )
+  , ( "\x043f\x2018\x044f\x0442\x0434\x0435\x0441\x044f\x0442"       , 50 )
+  , ( "\x0448\x0456\x0441\x0442\x0434\x0435\x0441\x044f\x0442"       , 60 )
+  , ( "\x0441\x0456\x043c\x0434\x0435\x0441\x044f\x0442"             , 70 )
+  , ( "\x0432\x0456\x0441\x0456\x043c\x0434\x0435\x0441\x044f\x0442" , 80 )
+  , ( "\x0434\x0435\x0432\x2018\x044f\x043d\x043e\x0441\x0442\x043e" , 90 )
+  ]
+
+ruleOrdinal :: Rule
+ruleOrdinal = Rule
+  { name = "ordinal 21..99"
+  , pattern =
+    [ regex "(\x0434\x0432\x0430\x0434\x0446\x044f\x0442\x044c|\x0442\x0440\x0438\x0434\x0446\x044f\x0442\x044c|\x0441\x043e\x0440\x043e\x043a|\x043f\x2018\x044f\x0442\x0434\x0435\x0441\x044f\x0442|\x0448\x0456\x0441\x0442\x044c\x0434\x0435\x0441\x044f\x0442|\x0441\x0456\x043c\x0434\x0435\x0441\x044f\x0442|\x0432\x0456\x0441\x0456\x043c\x0434\x0435\x0441\x044f\x0442|\x0434\x0435\x0432\x2018\x044f\x043d\x043e\x0441\x0442\x043e)"
+    , regex "(\x043f\x0435\x0440\x0448|\x0434\x0440\x0443\x0433|\x0442\x0440\x0435\x0442|\x0447\x0435\x0442\x0432\x0435\x0440\x0442|\x043f\x2018\x044f\x0442|\x0448\x043e\x0441\x0442|\x0441\x044c\x043e\x043c|\x0432\x043e\x0441\x044c\x043c|\x0434\x0435\x0432\x2018\x044f\x0442)(\x0438\x0439|\x0456\x0439|\x0430|\x044f|\x0435|\x0454)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:_)):
+       Token RegexMatch (GroupMatch (m2:_)):
+       _) -> do
+        v1 <- HashMap.lookup (Text.toLower m1) ordinalTensMap
+        v2 <- HashMap.lookup (Text.toLower m2) ordinalsFirstThMap -- map to 1..9
+        Just . ordinal $ v1 + v2
+      _ -> Nothing
+  }
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern =
+    [ regex "0*(\\d+)-?((\x0438|\x0456)?\x0439|\x0430|\x044f|\x0435|\x0454)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> ordinal <$> parseInt match
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinal
+  , ruleOrdinalDigits
+  , ruleOrdinalsFirstth
+  ]
diff --git a/Duckling/Ordinal/VI/Corpus.hs b/Duckling/Ordinal/VI/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/VI/Corpus.hs
@@ -0,0 +1,31 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.VI.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = VI}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 1)
+             [ "đầu tiên"
+             , "thứ nhất"
+             , "thứ 1"
+             ]
+  ]
diff --git a/Duckling/Ordinal/VI/Rules.hs b/Duckling/Ordinal/VI/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/VI/Rules.hs
@@ -0,0 +1,33 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.VI.Rules
+  ( rules ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.Helpers
+import Duckling.Types
+
+ruleOrdinals :: Rule
+ruleOrdinals = Rule
+  { name = "ordinals"
+  , pattern =
+    [ regex "(\x0111\x1ea7u ti\x00ean|th\x1ee9 nh\x1ea5t|th\x1ee9 1)"
+    ]
+  , prod = \_ -> Just $ ordinal 1
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinals
+  ]
diff --git a/Duckling/Ordinal/ZH/Corpus.hs b/Duckling/Ordinal/ZH/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/ZH/Corpus.hs
@@ -0,0 +1,36 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.ZH.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Ordinal.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = ZH}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (OrdinalData 7)
+             [ "第七"
+             ]
+  , examples (OrdinalData 11)
+             [ "第十一"
+             ]
+  , examples (OrdinalData 91)
+             [ "第九十一"
+             ]
+  ]
diff --git a/Duckling/Ordinal/ZH/Rules.hs b/Duckling/Ordinal/ZH/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ordinal/ZH/Rules.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ordinal.ZH.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData(..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Ordinal.Helpers
+import Duckling.Types
+
+ruleOrdinalDigits :: Rule
+ruleOrdinalDigits = Rule
+  { name = "ordinal (digits)"
+  , pattern =
+    [ regex "\x7b2c"
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral (NumeralData {TNumeral.value = x}):_) ->
+        Just . ordinal $ floor x
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleOrdinalDigits
+  ]
diff --git a/Duckling/PhoneNumber/Corpus.hs b/Duckling/PhoneNumber/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/PhoneNumber/Corpus.hs
@@ -0,0 +1,76 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.PhoneNumber.Corpus
+  ( corpus
+  , negativeCorpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.PhoneNumber.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext, allExamples)
+
+negativeCorpus :: NegativeCorpus
+negativeCorpus = (testContext, examples)
+  where
+    examples =
+      [ "12345"
+      , "1234567890123456777777"
+      , "12345678901234567"
+      ]
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (PhoneNumberValue "6507018887")
+             [ "650-701-8887"
+             ]
+  , examples (PhoneNumberValue "(+1) 6507018887")
+             [ "(+1)650-701-8887"
+             , "(+1)   650 - 701  8887"
+             , "(+1) 650-701-8887"
+             , "+1 6507018887"
+             ]
+  , examples (PhoneNumberValue "(+33) 146647998")
+             [ "+33 1 46647998"
+             ]
+  , examples (PhoneNumberValue "0620702220")
+             [ "06 2070 2220"
+             ]
+  , examples (PhoneNumberValue "6507018887 ext 897")
+             [ "(650)-701-8887 ext 897"
+             ]
+  , examples (PhoneNumberValue "(+1) 2025550121")
+             [ "+1-202-555-0121"
+             , "+1 202.555.0121"
+             ]
+  , examples (PhoneNumberValue "4866827")
+             [ "4.8.6.6.8.2.7"
+             ]
+  , examples (PhoneNumberValue "06354640807")
+             [ "06354640807"
+             ]
+  , examples (PhoneNumberValue "18998078030")
+             [ "18998078030"
+             ]
+  , examples (PhoneNumberValue "61992852776")
+             [ "61 - 9 9285-2776"
+             ]
+  , examples (PhoneNumberValue "19997424919")
+             [ "(19) 997424919"
+             ]
+  , examples (PhoneNumberValue "(+55) 19992842606")
+             [ "+55 19992842606"
+             ]
+  ]
diff --git a/Duckling/PhoneNumber/PT/Corpus.hs b/Duckling/PhoneNumber/PT/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/PhoneNumber/PT/Corpus.hs
@@ -0,0 +1,31 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.PhoneNumber.PT.Corpus
+  ( corpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.PhoneNumber.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = PT}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (PhoneNumberValue "6502834757 ext 897")
+             [ "(650)-283-4757 ramal 897"
+             ]
+  ]
diff --git a/Duckling/PhoneNumber/PT/Rules.hs b/Duckling/PhoneNumber/PT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/PhoneNumber/PT/Rules.hs
@@ -0,0 +1,56 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.PhoneNumber.PT.Rules
+  ( rules ) where
+
+import Data.String
+import Prelude
+import qualified Data.Text as Text
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.PhoneNumber.Types (PhoneNumberData(..))
+import Duckling.Regex.Types
+import Duckling.Types
+import qualified Duckling.PhoneNumber.Types as TPhoneNumber
+
+rulePhoneNumber :: Rule
+rulePhoneNumber = Rule
+  { name = "phone number"
+  , pattern =
+    -- We somewhat arbitrarly use 20 here to limit the length of matches,
+    -- otherwise due to backtracking the regexp will take very long time
+    -- or run out of stack for some inputs.
+    [ regex $
+        "(?:\\(?\\+(\\d{1,2})\\)?[\\s-\\.]*)?" ++ -- area code
+        "((?=[-\\d()\\s\\.]{6,16}(?:\\s*ramal\\.?\\s*(?:\\d{1,20}))?(?:[^\\d]+|$))(?:[\\d(]{1,20}(?:[-)\\s\\.]*\\d{1,20}){0,20}){1,20})" ++ -- nums
+        "(?:\\s*ramal\\.?\\s*(\\d{1,20}))?" -- extension
+    ]
+  , prod = \xs -> case xs of
+      (Token RegexMatch (GroupMatch (code:nums:ext:_)):_) ->
+        let parseNum x = toInteger <$> parseInt x
+            mcode = parseNum code
+            mext = parseNum ext
+            cleanup = Text.filter (not . isWhitespace)
+            isWhitespace x = elem x ['.', ' ', '-', '\t', '(', ')']
+        in Just . Token PhoneNumber $ PhoneNumberData
+          { TPhoneNumber.prefix = mcode
+          , TPhoneNumber.number = cleanup nums
+          , TPhoneNumber.extension = mext
+          }
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ rulePhoneNumber
+  ]
diff --git a/Duckling/PhoneNumber/Rules.hs b/Duckling/PhoneNumber/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/PhoneNumber/Rules.hs
@@ -0,0 +1,54 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.PhoneNumber.Rules
+  ( rules ) where
+
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.PhoneNumber.Types (PhoneNumberData(..))
+import qualified Duckling.PhoneNumber.Types as TPhoneNumber
+import Duckling.Regex.Types
+import Duckling.Types
+
+rulePhoneNumber :: Rule
+rulePhoneNumber = Rule
+  { name = "phone number"
+  , pattern =
+    -- We somewhat arbitrarly use 20 here to limit the length of matches,
+    -- otherwise due to backtracking the regexp will take very long time
+    -- or run out of stack for some inputs.
+    [ regex $
+        "(?:\\(?\\+(\\d{1,2})\\)?[\\s-\\.]*)?" ++ -- area code
+        "((?=[-\\d()\\s\\.]{6,16}(?:\\s*e?xt?\\.?\\s*(?:\\d{1,20}))?(?:[^\\d]+|$))(?:[\\d(]{1,20}(?:[-)\\s\\.]*\\d{1,20}){0,20}){1,20})" ++ -- nums
+        "(?:\\s*e?xt?\\.?\\s*(\\d{1,20}))?" -- extension
+    ]
+  , prod = \xs -> case xs of
+      (Token RegexMatch (GroupMatch (code:nums:ext:_)):_) ->
+        let parseNum x = toInteger <$> parseInt x
+            mcode = parseNum code
+            mext = parseNum ext
+            cleanup = Text.filter (not . isWhitespace)
+            isWhitespace x = elem x ['.', ' ', '-', '\t', '(', ')']
+        in Just . Token PhoneNumber $ PhoneNumberData
+          { TPhoneNumber.prefix = mcode
+          , TPhoneNumber.number = cleanup nums
+          , TPhoneNumber.extension = mext
+          }
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules = [ rulePhoneNumber ]
diff --git a/Duckling/PhoneNumber/Types.hs b/Duckling/PhoneNumber/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/PhoneNumber/Types.hs
@@ -0,0 +1,54 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Duckling.PhoneNumber.Types where
+
+import Control.DeepSeq
+import Data.Aeson
+import Data.Hashable
+import Data.Monoid
+import Data.Text (Text)
+import qualified Data.Text as Text
+import GHC.Generics
+import qualified TextShow as TS
+
+import Prelude
+
+import Duckling.Resolve (Resolve(..))
+
+data PhoneNumberData = PhoneNumberData
+  { prefix :: Maybe Integer
+  , number :: Text
+  , extension :: Maybe Integer
+  }
+  deriving (Eq, Generic, Hashable, Ord, Show, NFData)
+
+instance Resolve PhoneNumberData where
+  type ResolvedValue PhoneNumberData = PhoneNumberValue
+  resolve _ PhoneNumberData {prefix, number, extension} = Just PhoneNumberValue
+    {value = Text.concat [p, number, e]}
+    where
+      p = case prefix of
+        Just p -> "(+" <> TS.showt p <> ") "
+        Nothing -> ""
+      e = case extension of
+        Just e -> " ext " <> TS.showt e
+        Nothing -> ""
+
+data PhoneNumberValue = PhoneNumberValue { value :: Text }
+  deriving (Eq, Ord, Show)
+
+instance ToJSON PhoneNumberValue where
+  toJSON (PhoneNumberValue value) = object [ "value" .= value ]
diff --git a/Duckling/Quantity/EN/Corpus.hs b/Duckling/Quantity/EN/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Quantity/EN/Corpus.hs
@@ -0,0 +1,34 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Quantity.EN.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Quantity.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (QuantityData Pound 2 (Just "meat"))
+             [ "two pounds of meat"
+             ]
+  , examples (QuantityData Pound 1 Nothing)
+             [ "a Pound"
+             ]
+  , examples (QuantityData Cup 3 (Just "sugar"))
+             [ "3 Cups of sugar"
+             ]
+  ]
diff --git a/Duckling/Quantity/EN/Rules.hs b/Duckling/Quantity/EN/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Quantity/EN/Rules.hs
@@ -0,0 +1,77 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Quantity.EN.Rules
+  ( rules ) where
+
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Quantity.Helpers
+import qualified Duckling.Quantity.Types as TQuantity
+import Duckling.Regex.Types
+import Duckling.Dimensions.Types
+import Duckling.Types
+
+ruleNumeralQuantity :: Rule
+ruleNumeralQuantity = Rule
+  { name = "<number> <quantity>"
+  , pattern =
+    [ dimension Numeral
+    , regex "(pound|cup)s?"
+    ]
+  , prod = \tokens -> case tokens of
+    (Token Numeral nd:
+     Token RegexMatch (GroupMatch (match:_)):
+     _) -> case Text.toLower match of
+      "cup"    -> Just . Token Quantity . quantity TQuantity.Cup $ TNumeral.value nd
+      "cups"   -> Just . Token Quantity . quantity TQuantity.Cup $ TNumeral.value nd
+      "pound"  -> Just . Token Quantity . quantity TQuantity.Pound $ TNumeral.value nd
+      "pounds" -> Just . Token Quantity . quantity TQuantity.Pound $ TNumeral.value nd
+      _ -> Nothing
+    _ -> Nothing
+  }
+
+ruleAQuantity :: Rule
+ruleAQuantity = Rule
+  { name = "a quantity"
+  , pattern = [ regex "a (pound|cup)s?"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case Text.toLower match of
+        "cup"    -> Just . Token Quantity $ quantity TQuantity.Cup 1
+        "cups"   -> Just . Token Quantity $ quantity TQuantity.Cup 1
+        "pound"  -> Just . Token Quantity $ quantity TQuantity.Pound 1
+        "pounds" -> Just . Token Quantity $ quantity TQuantity.Pound 1
+        _        -> Nothing
+      _ -> Nothing
+  }
+
+ruleQuantityOfProduct :: Rule
+ruleQuantityOfProduct = Rule
+  { name = "<quantity> of product"
+  , pattern =
+    [ dimension Quantity
+    , regex "of (meat|sugar)"
+    ]
+  , prod = \tokens -> case tokens of
+    (Token Quantity qd:Token RegexMatch (GroupMatch (product:_)):_) ->
+      Just . Token Quantity $ withProduct product qd
+    _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleNumeralQuantity
+  , ruleAQuantity
+  , ruleQuantityOfProduct
+  ]
diff --git a/Duckling/Quantity/FR/Corpus.hs b/Duckling/Quantity/FR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Quantity/FR/Corpus.hs
@@ -0,0 +1,36 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Quantity.FR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Quantity.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = FR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (QuantityData Cup 2 (Just "café"))
+             [ "2 tasses de café"
+             ]
+  , examples (QuantityData Cup 1 Nothing)
+             [ "une Tasse"
+             ]
+  , examples (QuantityData Tablespoon 3 (Just "sucre"))
+             [ "3 Cuillères à soupe de sucre"
+             ]
+  ]
diff --git a/Duckling/Quantity/FR/Rules.hs b/Duckling/Quantity/FR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Quantity/FR/Rules.hs
@@ -0,0 +1,62 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Quantity.FR.Rules
+  ( rules ) where
+
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Quantity.Helpers
+import qualified Duckling.Quantity.Types as TQuantity
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleNumeralUnits :: Rule
+ruleNumeralUnits = Rule
+  { name = "<number> <units>"
+  , pattern =
+    [ dimension Numeral
+    , regex "(tasses?|cuill?(e|\x00e8)res? (a|\x00e0) soupe?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral NumeralData {TNumeral.value = v}:
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "tasse"  -> Just . Token Quantity $ quantity TQuantity.Cup v
+         "tasses" -> Just . Token Quantity $ quantity TQuantity.Cup v
+         _        -> Just . Token Quantity $ quantity TQuantity.Tablespoon v
+      _ -> Nothing
+  }
+
+ruleQuantityOfProduct :: Rule
+ruleQuantityOfProduct = Rule
+  { name = "<quantity> of product"
+  , pattern =
+    [ dimension Quantity
+    , regex "de (caf(e|\x00e9)|sucre)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Quantity qd:
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> Just . Token Quantity $ withProduct match qd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleNumeralUnits
+  , ruleQuantityOfProduct
+  ]
diff --git a/Duckling/Quantity/HR/Corpus.hs b/Duckling/Quantity/HR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Quantity/HR/Corpus.hs
@@ -0,0 +1,33 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Quantity.HR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Quantity.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = HR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (QuantityData Gram 2000 (Just "meso"))
+             [ "dvije kile mesa"
+             , "dva kilograma mesa"
+             ]
+  , examples (QuantityData Gram 1000 Nothing)
+             [ "1 kilograma"
+             ]
+  ]
diff --git a/Duckling/Quantity/HR/Rules.hs b/Duckling/Quantity/HR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Quantity/HR/Rules.hs
@@ -0,0 +1,62 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Quantity.HR.Rules
+  ( rules ) where
+
+import Data.String
+import Prelude
+import qualified Data.Text as Text
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData (..))
+import Duckling.Quantity.Helpers
+import Duckling.Regex.Types
+import Duckling.Types
+import qualified Duckling.Numeral.Types as TNumeral
+import qualified Duckling.Quantity.Types as TQuantity
+
+ruleNumberUnits :: Rule
+ruleNumberUnits = Rule
+  { name = "<number> <units>"
+  , pattern =
+    [ dimension Numeral
+    , regex "k(il(o|e|a))?(g(rama?)?)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral NumeralData {TNumeral.value = v}:_) ->
+        Just . Token Quantity $ quantity TQuantity.Gram (1000 * v)
+      _ -> Nothing
+  }
+
+ruleQuantityProduct :: Rule
+ruleQuantityProduct = Rule
+  { name = "<quantity> product"
+  , pattern =
+    [ dimension Quantity
+    , regex "(mes(o|a)|soli?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Quantity qd:
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "meso" -> Just . Token Quantity $ withProduct "meso" qd
+         "mesa" -> Just . Token Quantity $ withProduct "meso" qd
+         "sol" -> Just . Token Quantity $ withProduct "sol" qd
+         "soli" -> Just . Token Quantity $ withProduct "sol" qd
+         _      -> Nothing
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleNumberUnits
+  , ruleQuantityProduct
+  ]
diff --git a/Duckling/Quantity/Helpers.hs b/Duckling/Quantity/Helpers.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Quantity/Helpers.hs
@@ -0,0 +1,31 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Quantity.Helpers
+  ( quantity
+  , withProduct
+  ) where
+
+import Data.Text (Text)
+import Prelude
+
+import Duckling.Quantity.Types (QuantityData(..))
+import qualified Duckling.Quantity.Types as TQuantity
+
+-- -----------------------------------------------------------------
+-- Patterns
+
+-- -----------------------------------------------------------------
+-- Production
+
+quantity :: TQuantity.Unit -> Double -> QuantityData
+quantity u x = QuantityData
+  {TQuantity.unit = u, TQuantity.value = x, TQuantity.product = Nothing}
+
+withProduct :: Text -> QuantityData -> QuantityData
+withProduct value qd = qd {TQuantity.product = Just value}
diff --git a/Duckling/Quantity/KO/Corpus.hs b/Duckling/Quantity/KO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Quantity/KO/Corpus.hs
@@ -0,0 +1,39 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Quantity.KO.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Quantity.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = KO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (QuantityData (Custom "근") 2 (Just "삼겹살"))
+             [ "삼겹살 두근"
+             ]
+  , examples (QuantityData (Custom "근") 1 Nothing)
+             [ "한근"
+             ]
+  , examples (QuantityData Gram 600 Nothing)
+             [ "육백그람"
+             ]
+  , examples (QuantityData Cup 3 (Just "콜라"))
+             [ "콜라 세컵"
+             ]
+  ]
diff --git a/Duckling/Quantity/KO/Rules.hs b/Duckling/Quantity/KO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Quantity/KO/Rules.hs
@@ -0,0 +1,84 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Quantity.KO.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Quantity.Helpers
+import qualified Duckling.Quantity.Types as TQuantity
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleQuantityOfProduct :: Rule
+ruleQuantityOfProduct = Rule
+  { name = "<quantity> of product"
+  , pattern =
+    [ dimension Quantity
+    , regex "\xc758 (\xc0bc\xacb9\xc0b4|\xcf5c\xb77c)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Quantity qd:
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> Just . Token Quantity $ withProduct match qd
+      _ -> Nothing
+  }
+
+ruleQuantityOfProduct2 :: Rule
+ruleQuantityOfProduct2 = Rule
+  { name = "<quantity> of product"
+  , pattern =
+    [ regex "(\xc0bc\xacb9\xc0b4|\xcf5c\xb77c)"
+    , dimension Quantity
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       Token Quantity qd:
+       _) -> Just . Token Quantity $ withProduct match qd
+      _ -> Nothing
+  }
+
+ruleNumeralUnits :: Rule
+ruleNumeralUnits = Rule
+  { name = "<number> <units>"
+  , pattern =
+    [ dimension Numeral
+    , regex "(\xac1c|\xd310|\xadf8(\xb7a8|\xb78c)|\xadfc|\xd30c\xc6b4(\xb4dc|\xc988)|\xc8111\xc2dc|\xadf8\xb987|\xcef5)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral NumeralData {TNumeral.value = v}:
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case match of
+         "\xac1c"             -> Just . Token Quantity $ quantity TQuantity.Unnamed v
+         "\xd310"             -> Just . Token Quantity $ quantity (TQuantity.Custom "판") v
+         "\xadfc"             -> Just . Token Quantity $ quantity (TQuantity.Custom "근") v
+         "\xadf8\xb7a8"       -> Just . Token Quantity $ quantity TQuantity.Gram v
+         "\xadf8\xb78c"       -> Just . Token Quantity $ quantity TQuantity.Gram v
+         "\xd30c\xc6b4\xb4dc" -> Just . Token Quantity $ quantity TQuantity.Pound v
+         "\xd30c\xc6b4\xc988" -> Just . Token Quantity $ quantity TQuantity.Pound v
+         "\xc8111\xc2dc"      -> Just . Token Quantity $ quantity TQuantity.Dish v
+         "\xadf8\xb987"       -> Just . Token Quantity $ quantity TQuantity.Bowl v
+         "\xcef5"             -> Just . Token Quantity $ quantity TQuantity.Cup v
+         _                    -> Nothing
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleNumeralUnits
+  , ruleQuantityOfProduct
+  , ruleQuantityOfProduct2
+  ]
diff --git a/Duckling/Quantity/PT/Corpus.hs b/Duckling/Quantity/PT/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Quantity/PT/Corpus.hs
@@ -0,0 +1,36 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Quantity.PT.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Quantity.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = PT}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (QuantityData Cup 2 (Just "café"))
+             [ "2 copos de café"
+             ]
+  , examples (QuantityData Cup 1 Nothing)
+             [ "um Copo"
+             ]
+  , examples (QuantityData Pound 100 (Just "acucar"))
+             [ "100 Libras de acucar"
+             ]
+  ]
diff --git a/Duckling/Quantity/PT/Rules.hs b/Duckling/Quantity/PT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Quantity/PT/Rules.hs
@@ -0,0 +1,62 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Quantity.PT.Rules
+  ( rules ) where
+
+import qualified Data.Text as Text
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Quantity.Helpers
+import qualified Duckling.Quantity.Types as TQuantity
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleNumeralUnits :: Rule
+ruleNumeralUnits = Rule
+  { name = "<number> <units>"
+  , pattern =
+    [ dimension Numeral
+    , regex "(libra|copo)s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral NumeralData {TNumeral.value = v}:
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+         "copo"   -> Just . Token Quantity $ quantity TQuantity.Cup v
+         "libra"  -> Just . Token Quantity $ quantity TQuantity.Pound v
+         _        -> Nothing
+      _ -> Nothing
+  }
+
+ruleQuantityOfProduct :: Rule
+ruleQuantityOfProduct = Rule
+  { name = "<quantity> of product"
+  , pattern =
+    [ dimension Quantity
+    , regex "de (caf(e|\x00e9)|a(\x00e7|c)ucar)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Quantity qd:
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> Just . Token Quantity $ withProduct match qd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleNumeralUnits
+  , ruleQuantityOfProduct
+  ]
diff --git a/Duckling/Quantity/RO/Corpus.hs b/Duckling/Quantity/RO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Quantity/RO/Corpus.hs
@@ -0,0 +1,36 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Quantity.RO.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Quantity.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = RO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (QuantityData Pound 2 (Just "carne"))
+             [ "doua livre de carne"
+             ]
+  , examples (QuantityData Pound 1 Nothing)
+             [ "o livră"
+             ]
+  , examples (QuantityData Pound 500 (Just "zahăr"))
+             [ "cinci sute livre de zahăr"
+             ]
+  ]
diff --git a/Duckling/Quantity/RO/Rules.hs b/Duckling/Quantity/RO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Quantity/RO/Rules.hs
@@ -0,0 +1,57 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Quantity.RO.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Quantity.Helpers
+import qualified Duckling.Quantity.Types as TQuantity
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleNumeralUnits :: Rule
+ruleNumeralUnits = Rule
+  { name = "<number> <units>"
+  , pattern =
+    [ dimension Numeral
+    , regex "livr(a|e|\x0103)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral NumeralData {TNumeral.value = v}:_) ->
+        Just . Token Quantity $ quantity TQuantity.Pound v
+      _ -> Nothing
+  }
+
+ruleQuantityOfProduct :: Rule
+ruleQuantityOfProduct = Rule
+  { name = "<quantity> of product"
+  , pattern =
+    [ dimension Quantity
+    , regex "de (carne|can(a|\x0103)|zah(a|\x0103)r)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Quantity qd:
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> Just . Token Quantity $ withProduct match qd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleNumeralUnits
+  , ruleQuantityOfProduct
+  ]
diff --git a/Duckling/Quantity/Types.hs b/Duckling/Quantity/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Quantity/Types.hs
@@ -0,0 +1,61 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Duckling.Quantity.Types where
+
+import Control.DeepSeq
+import Data.Aeson
+import Data.Hashable
+import Data.Text (Text)
+import qualified Data.Text as Text
+import GHC.Generics
+import Prelude
+
+import Duckling.Resolve (Resolve(..))
+
+data Unit
+  = Bowl
+  | Cup
+  | Custom Text
+  | Dish
+  | Gram
+  | Pint
+  | Pound
+  | Quart
+  | Tablespoon
+  | Teaspoon
+  | Unnamed
+  deriving (Eq, Generic, Hashable, Ord, Show, NFData)
+
+instance ToJSON Unit where
+  toJSON (Custom x) = String $ Text.toLower x
+  toJSON x = String . Text.toLower . Text.pack $ show x
+
+data QuantityData = QuantityData
+  { unit :: Unit
+  , value :: Double
+  , product :: Maybe Text
+  } deriving (Eq, Generic, Hashable, Ord, Show, NFData)
+
+instance ToJSON QuantityData where
+  toJSON (QuantityData unit value product) = object $
+    [ "type" .= ("value" :: Text)
+    , "value" .= value
+    , "unit" .= unit
+    ]
+    ++ [ "product" .= p | Just p <- [product] ]
+
+instance Resolve QuantityData where
+  type ResolvedValue QuantityData = QuantityData
+  resolve _ x = Just x
diff --git a/Duckling/Ranking/Classifiers.hs b/Duckling/Ranking/Classifiers.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers.hs
@@ -0,0 +1,68 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ranking.Classifiers
+  ( classifiers
+  ) where
+
+import Duckling.Lang
+import qualified Duckling.Ranking.Classifiers.AR as ARClassifiers
+import qualified Duckling.Ranking.Classifiers.DA as DAClassifiers
+import qualified Duckling.Ranking.Classifiers.DE as DEClassifiers
+import qualified Duckling.Ranking.Classifiers.EN as ENClassifiers
+import qualified Duckling.Ranking.Classifiers.ES as ESClassifiers
+import qualified Duckling.Ranking.Classifiers.ET as ETClassifiers
+import qualified Duckling.Ranking.Classifiers.FR as FRClassifiers
+import qualified Duckling.Ranking.Classifiers.GA as GAClassifiers
+import qualified Duckling.Ranking.Classifiers.HE as HEClassifiers
+import qualified Duckling.Ranking.Classifiers.HR as HRClassifiers
+import qualified Duckling.Ranking.Classifiers.ID as IDClassifiers
+import qualified Duckling.Ranking.Classifiers.IT as ITClassifiers
+import qualified Duckling.Ranking.Classifiers.JA as JAClassifiers
+import qualified Duckling.Ranking.Classifiers.KO as KOClassifiers
+import qualified Duckling.Ranking.Classifiers.MY as MYClassifiers
+import qualified Duckling.Ranking.Classifiers.NB as NBClassifiers
+import qualified Duckling.Ranking.Classifiers.NL as NLClassifiers
+import qualified Duckling.Ranking.Classifiers.PL as PLClassifiers
+import qualified Duckling.Ranking.Classifiers.PT as PTClassifiers
+import qualified Duckling.Ranking.Classifiers.RO as ROClassifiers
+import qualified Duckling.Ranking.Classifiers.RU as RUClassifiers
+import qualified Duckling.Ranking.Classifiers.SV as SVClassifiers
+import qualified Duckling.Ranking.Classifiers.TR as TRClassifiers
+import qualified Duckling.Ranking.Classifiers.UK as UKClassifiers
+import qualified Duckling.Ranking.Classifiers.VI as VIClassifiers
+import qualified Duckling.Ranking.Classifiers.ZH as ZHClassifiers
+import Duckling.Ranking.Types
+
+classifiers :: Lang -> Classifiers
+classifiers AR = ARClassifiers.classifiers
+classifiers DA = DAClassifiers.classifiers
+classifiers DE = DEClassifiers.classifiers
+classifiers EN = ENClassifiers.classifiers
+classifiers ES = ESClassifiers.classifiers
+classifiers ET = ETClassifiers.classifiers
+classifiers FR = FRClassifiers.classifiers
+classifiers GA = GAClassifiers.classifiers
+classifiers HE = HEClassifiers.classifiers
+classifiers HR = HRClassifiers.classifiers
+classifiers ID = IDClassifiers.classifiers
+classifiers IT = ITClassifiers.classifiers
+classifiers JA = JAClassifiers.classifiers
+classifiers KO = KOClassifiers.classifiers
+classifiers MY = MYClassifiers.classifiers
+classifiers NB = NBClassifiers.classifiers
+classifiers NL = NLClassifiers.classifiers
+classifiers PL = PLClassifiers.classifiers
+classifiers PT = PTClassifiers.classifiers
+classifiers RO = ROClassifiers.classifiers
+classifiers RU = RUClassifiers.classifiers
+classifiers SV = SVClassifiers.classifiers
+classifiers TR = TRClassifiers.classifiers
+classifiers UK = UKClassifiers.classifiers
+classifiers VI = VIClassifiers.classifiers
+classifiers ZH = ZHClassifiers.classifiers
diff --git a/Duckling/Ranking/Classifiers/AR.hs b/Duckling/Ranking/Classifiers/AR.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/AR.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.AR (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers = HashMap.fromList []
diff --git a/Duckling/Ranking/Classifiers/DA.hs b/Duckling/Ranking/Classifiers/DA.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/DA.hs
@@ -0,0 +1,1566 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.DA (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers
+  = HashMap.fromList
+      [("<time> timezone",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.2039728043259361),
+                                    ("hh:mm", -1.6094379124341003), ("hour", -1.6094379124341003),
+                                    ("minute", -1.2039728043259361)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (numeric)",
+        Classifier{okData =
+                     ClassData{prior = -0.778087857209029, unseen = -4.962844630259907,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 141},
+                   koData =
+                     ClassData{prior = -0.6148599592306538, unseen = -5.123963979403259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 166}}),
+       ("the day before yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("lunch",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -1.0986122886681098),
+                                    ("tomorrowevening", -2.0149030205422647),
+                                    ("named-daymorning", -2.0149030205422647),
+                                    ("tomorrowlunch", -2.0149030205422647),
+                                    ("yesterdayevening", -2.0149030205422647)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.8472978603872037, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("yearhour", -1.1786549963416462),
+                                    ("year (latent)in|during the <part-of-day>",
+                                     -1.1786549963416462)],
+                               n = 3}}),
+       ("dd/mm",
+        Classifier{okData =
+                     ClassData{prior = -0.2231435513142097,
+                               unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 8},
+                   koData =
+                     ClassData{prior = -1.6094379124341003,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("today",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("at <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.25593337413720063,
+                               unseen = -4.6443908991413725,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> timezone", -3.536116699561526),
+                                    ("time-of-day (latent)", -1.2007417837444896),
+                                    ("relative minutes after|past <integer> (hour-of-day)",
+                                     -3.536116699561526),
+                                    ("hh:mm", -2.069779630768099),
+                                    ("<time-of-day> sharp", -3.536116699561526),
+                                    ("hour", -1.1382214267631554), ("minute", -1.8015156441734197)],
+                               n = 48},
+                   koData =
+                     ClassData{prior = -1.488077055429833, unseen = -3.58351893845611,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.8472978603872037),
+                                    ("hour", -0.8472978603872037)],
+                               n = 14}}),
+       ("absorption of , after named day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("tonight",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("on <date>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.7376696182833684,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("the <day-of-month> (non ordinal)", -2.6149597780361984),
+                                    ("<day-of-month>(ordinal) <named-month>", -1.7676619176489945),
+                                    ("day", -0.7691330875378672),
+                                    ("the <day-of-month> (ordinal)", -3.0204248861443626),
+                                    ("named-day", -1.410986973710262)],
+                               n = 18},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (0..19)",
+        Classifier{okData =
+                     ClassData{prior = -0.2231435513142097,
+                               unseen = -3.7376696182833684,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 40},
+                   koData =
+                     ClassData{prior = -1.6094379124341003,
+                               unseen = -2.4849066497880004,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 10}}),
+       ("between <time-of-day> and <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -0.9808292530117262),
+                                    ("hh:mmhh:mm", -0.9808292530117262)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -0.9808292530117262),
+                                    ("minutehour", -0.9808292530117262)],
+                               n = 2}}),
+       ("between <datetime> and <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.916290731874155, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.0986122886681098),
+                                    ("hh:mmhh:mm", -1.0986122886681098)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -1.2992829841302609),
+                                    ("minuteminute", -1.7047480922384253),
+                                    ("minutehour", -1.2992829841302609),
+                                    ("hh:mmintersect", -1.7047480922384253)],
+                               n = 3}}),
+       ("month (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> more <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)minute (grain)", -1.252762968495368),
+                                    ("integer (0..19)minute (grain)", -1.252762968495368),
+                                    ("minute", -0.8472978603872037)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> o'clock",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hour (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.10536051565782628,
+                               unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -2.3025850929940455,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("<ordinal> quarter",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)quarter (grain)", -1.252762968495368),
+                                    ("quarter", -0.8472978603872037),
+                                    ("ordinals (first..31st)quarter (grain)", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)quarter (grain)", -1.252762968495368),
+                                    ("quarter", -0.8472978603872037),
+                                    ("ordinals (first..31st)quarter (grain)", -1.252762968495368)],
+                               n = 2}}),
+       ("intersect",
+        Classifier{okData =
+                     ClassData{prior = -0.46117571512217015, unseen = -5.8111409929767,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<datetime> - <datetime> (interval)on <date>",
+                                     -4.0163830207523885),
+                                    ("<time-of-day> - <time-of-day> (interval)on <date>",
+                                     -4.0163830207523885),
+                                    ("hourday", -5.1149953094204985),
+                                    ("dayhour", -2.863703510814003),
+                                    ("daymonth", -3.3232358401924436),
+                                    ("monthyear", -3.0355537677406628),
+                                    ("intersecthh:mm", -5.1149953094204985),
+                                    ("the <day-of-month> (ordinal)named-month",
+                                     -3.8622323409251305),
+                                    ("intersect by \"of\", \"from\", \"'s\"year",
+                                     -4.709530201312334),
+                                    ("last <day-of-week> of <time>year", -4.709530201312334),
+                                    ("todayat <time-of-day>", -4.709530201312334),
+                                    ("dayday", -3.0355537677406628),
+                                    ("dd/mmat <time-of-day>", -4.198704577546343),
+                                    ("intersect by \",\"hh:mm", -4.198704577546343),
+                                    ("intersectnamed-month", -5.1149953094204985),
+                                    ("dayyear", -3.243193132518907),
+                                    ("named-daythis <time>", -3.6109179126442243),
+                                    ("tomorrow<time-of-day> sharp", -4.709530201312334),
+                                    ("<day-of-month>(ordinal) <named-month>year",
+                                     -4.709530201312334),
+                                    ("named-day<time> timezone", -4.421848128860553),
+                                    ("named-monthyear", -3.0355537677406628),
+                                    ("absorption of , after named day<named-month> <day-of-month> (non ordinal)",
+                                     -4.0163830207523885),
+                                    ("tomorrowuntil <time-of-day>", -4.709530201312334),
+                                    ("absorption of , after named day<day-of-month> (non ordinal) <named-month>",
+                                     -4.421848128860553),
+                                    ("after <time-of-day>at <time-of-day>", -4.709530201312334),
+                                    ("intersect by \",\"<day-of-month> (non ordinal) <named-month>",
+                                     -4.709530201312334),
+                                    ("named-day<day-of-month> (non ordinal) <named-month>",
+                                     -5.1149953094204985),
+                                    ("named-day<named-month> <day-of-month> (non ordinal)",
+                                     -5.1149953094204985),
+                                    ("named-dayfrom <datetime> - <datetime> (interval)",
+                                     -4.709530201312334),
+                                    ("named-daynext <cycle>", -5.1149953094204985),
+                                    ("named-dayintersect", -5.1149953094204985),
+                                    ("named-dayfrom <time-of-day> - <time-of-day> (interval)",
+                                     -4.709530201312334),
+                                    ("tomorrowafter <time-of-day>", -4.709530201312334),
+                                    ("from <time-of-day> - <time-of-day> (interval)on <date>",
+                                     -4.198704577546343),
+                                    ("dayminute", -2.763620052257021),
+                                    ("from <datetime> - <datetime> (interval)on <date>",
+                                     -4.421848128860553),
+                                    ("<ordinal> <cycle> of <time>year", -4.709530201312334),
+                                    ("minuteday", -2.512305623976115),
+                                    ("absorption of , after named dayintersect",
+                                     -5.1149953094204985),
+                                    ("named-daybetween <time-of-day> and <time-of-day> (interval)",
+                                     -5.1149953094204985),
+                                    ("named-dayon <date>", -5.1149953094204985),
+                                    ("named-dayat <time-of-day>", -4.709530201312334),
+                                    ("yearhh:mm", -5.1149953094204985),
+                                    ("named-daybetween <datetime> and <datetime> (interval)",
+                                     -5.1149953094204985),
+                                    ("absorption of , after named dayintersect by \",\"",
+                                     -4.709530201312334),
+                                    ("dd/mmyear", -4.709530201312334),
+                                    ("at <time-of-day>on <date>", -5.1149953094204985),
+                                    ("between <time-of-day> and <time-of-day> (interval)on <date>",
+                                     -5.1149953094204985),
+                                    ("between <datetime> and <datetime> (interval)on <date>",
+                                     -5.1149953094204985),
+                                    ("dayweek", -4.0163830207523885),
+                                    ("weekyear", -4.198704577546343),
+                                    ("hh:mmtomorrow", -4.421848128860553),
+                                    ("tomorrowat <time-of-day>", -3.8622323409251305),
+                                    ("named-daythe <day-of-month> (ordinal)", -5.1149953094204985),
+                                    ("at <time-of-day>tomorrow", -4.709530201312334),
+                                    ("last <cycle> of <time>year", -4.198704577546343),
+                                    ("named-daythe <day-of-month> (non ordinal)",
+                                     -5.1149953094204985),
+                                    ("<day-of-month> (non ordinal) <named-month>year",
+                                     -4.709530201312334),
+                                    ("yearminute", -5.1149953094204985)],
+                               n = 128},
+                   koData =
+                     ClassData{prior = -0.9957178655054769, unseen = -5.429345628954441,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -4.7318028369214575),
+                                    ("dayhour", -3.3455084758015667),
+                                    ("daymonth", -2.129113151477074),
+                                    ("monthday", -3.8155121050473024),
+                                    ("monthyear", -3.8155121050473024),
+                                    ("intersect by \"of\", \"from\", \"'s\"year",
+                                     -3.4790398684260895),
+                                    ("named-dayhh:mm", -4.7318028369214575),
+                                    ("dd/mmat <time-of-day>", -3.8155121050473024),
+                                    ("hourhour", -3.4790398684260895),
+                                    ("dayyear", -3.3455084758015667),
+                                    ("named-daythis <time>", -2.5917366734251868),
+                                    ("year<hour-of-day> <integer> (as relative minutes)",
+                                     -4.326337728813293),
+                                    ("minutemonth", -4.326337728813293),
+                                    ("named-monthyear", -3.8155121050473024),
+                                    ("in|during the <part-of-day>until <time-of-day>",
+                                     -4.326337728813293),
+                                    ("absorption of , after named daynamed-month",
+                                     -3.6331905482533475),
+                                    ("named-dayfrom <datetime> - <datetime> (interval)",
+                                     -4.326337728813293),
+                                    ("<hour-of-day> <integer> (as relative minutes)named-month",
+                                     -4.326337728813293),
+                                    ("named-dayfrom <time-of-day> - <time-of-day> (interval)",
+                                     -4.7318028369214575),
+                                    ("until <time-of-day>on <date>", -4.7318028369214575),
+                                    ("dayminute", -3.3455084758015667),
+                                    ("minuteday", -3.2277254401451834),
+                                    ("named-daybetween <time-of-day> and <time-of-day> (interval)",
+                                     -4.7318028369214575),
+                                    ("named-dayat <time-of-day>", -3.8155121050473024),
+                                    ("hh:mmon <date>", -3.3455084758015667),
+                                    ("named-daybetween <datetime> and <datetime> (interval)",
+                                     -4.7318028369214575),
+                                    ("named-month<day-of-month> (non ordinal) <named-month>",
+                                     -3.8155121050473024),
+                                    ("<named-month> <day-of-month> (non ordinal)named-month",
+                                     -3.8155121050473024),
+                                    ("this <part-of-day>until <time-of-day>", -4.326337728813293),
+                                    ("<hour-of-day> <integer> (as relative minutes)year",
+                                     -4.326337728813293),
+                                    ("tomorrownoon", -4.7318028369214575),
+                                    ("this <time>until <time-of-day>", -4.326337728813293),
+                                    ("minuteyear", -4.326337728813293),
+                                    ("yearminute", -4.326337728813293)],
+                               n = 75}}),
+       ("<ordinal> <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.7346010553881064),
+                                    ("ordinals (first..31st)week (grain)intersect",
+                                     -1.7346010553881064),
+                                    ("ordinals (first..31st)week (grain)named-month",
+                                     -1.7346010553881064),
+                                    ("weekmonth", -1.2237754316221157),
+                                    ("ordinals (first..31st)day (grain)named-month",
+                                     -1.7346010553881064)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("season",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.772588722239781,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 14},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("from <datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -1.0296194171811581,
+                               unseen = -3.0910424533583156,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.4350845252893227),
+                                    ("time-of-day (latent)time-of-day (latent)",
+                                     -2.3513752571634776),
+                                    ("hh:mmhh:mm", -1.4350845252893227),
+                                    ("hourhour", -2.3513752571634776)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.4418327522790392,
+                               unseen = -3.4011973816621555,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("yearhour", -2.6741486494265287),
+                                    ("hh:mmtime-of-day (latent)", -1.7578579175523736),
+                                    ("minuteminute", -1.9810014688665833),
+                                    ("yearyear", -2.6741486494265287),
+                                    ("year (latent)year (latent)", -2.6741486494265287),
+                                    ("minutehour", -1.7578579175523736),
+                                    ("hh:mmintersect", -1.9810014688665833),
+                                    ("year (latent)time-of-day (latent)", -2.6741486494265287)],
+                               n = 9}}),
+       ("next <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6094379124341003),
+                                    ("month (grain)", -2.3025850929940455),
+                                    ("year (grain)", -2.3025850929940455),
+                                    ("week (grain)", -1.6094379124341003),
+                                    ("quarter", -2.3025850929940455), ("year", -2.3025850929940455),
+                                    ("month", -2.3025850929940455),
+                                    ("quarter (grain)", -2.3025850929940455)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("number.number hours",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("from <time-of-day> - <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.6061358035703156,
+                               unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.0986122886681098),
+                                    ("time-of-day (latent)time-of-day (latent)",
+                                     -2.1972245773362196),
+                                    ("hh:mmhh:mm", -1.0986122886681098),
+                                    ("hourhour", -2.1972245773362196)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.7884573603642702, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -0.9808292530117262),
+                                    ("minutehour", -0.9808292530117262)],
+                               n = 5}}),
+       ("yyyy-mm-dd",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (latent)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -3.258096538021482,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.4462871026284195),
+                                    ("integer (0..19)", -1.0216512475319814)],
+                               n = 23}}),
+       ("dd/mm/yyyy",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<ordinal> quarter <year>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..31st)quarter (grain)year",
+                                     -1.252762968495368),
+                                    ("quarteryear", -0.8472978603872037),
+                                    ("ordinal (digits)quarter (grain)year", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("after lunch",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh:mm:ss",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the <cycle> after <time>",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("year (grain)christmas eve", -0.6931471805599453),
+                                    ("yearday", -0.6931471805599453)],
+                               n = 2}}),
+       ("quarter to|till|before <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("a pair",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("nth <time> of <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228, unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -0.7472144018302211),
+                                    ("ordinals (first..31st)named-dayintersect",
+                                     -0.9985288301111273),
+                                    ("ordinals (first..31st)named-daynamed-month",
+                                     -1.845826690498331)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -0.8472978603872037, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -0.7621400520468967),
+                                    ("ordinals (first..31st)named-daynamed-month",
+                                     -0.7621400520468967)],
+                               n = 6}}),
+       ("the <day-of-month> (non ordinal)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinals (first..31st)",
+        Classifier{okData =
+                     ClassData{prior = -5.406722127027582e-2,
+                               unseen = -2.995732273553991,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 18},
+                   koData =
+                     ClassData{prior = -2.9444389791664407,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("named-month",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.189654742026425,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 64},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("week (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.332204510175204,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 26},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("now",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4}}),
+       ("this <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.8472978603872037), ("evening", -1.252762968495368),
+                                    ("morning", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -3.332204510175204,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.7308875085427924),
+                                    ("morning", -0.7308875085427924)],
+                               n = 12}}),
+       ("christmas eve",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month>(ordinal) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -9.53101798043249e-2,
+                               unseen = -3.1780538303479458,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..31st)named-month", -1.749199854809259),
+                                    ("ordinal (digits)named-month", -1.0560526742493137),
+                                    ("month", -0.7375989431307791)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -2.3978952727983707, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..31st)named-month", -0.916290731874155),
+                                    ("month", -0.916290731874155)],
+                               n = 1}}),
+       ("numbers prefix with -, negative or minus",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -3.4011973816621555,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 28}}),
+       ("in|during the <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("afternoon", -1.3862943611198906),
+                                    ("hour", -0.9808292530117262),
+                                    ("evening", -1.3862943611198906)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.7672551527136672),
+                                    ("morning", -0.7672551527136672)],
+                               n = 12}}),
+       ("new year's eve",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("tomorrow",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.772588722239781,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 14},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<cycle> after <time>",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("year (grain)christmas eve", -0.6931471805599453),
+                                    ("yearday", -0.6931471805599453)],
+                               n = 2}}),
+       ("<time> after next",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -1.791759469228055),
+                                    ("day", -1.0986122886681098),
+                                    ("named-day", -1.0986122886681098),
+                                    ("month", -1.791759469228055)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("half an hour",
+        Classifier{okData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -0.916290731874155, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("the <day-of-month> (ordinal)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..31st)", -1.2039728043259361),
+                                    ("ordinal (digits)", -0.35667494393873245)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("afternoon",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<duration> from now",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("second", -1.4469189829363254), ("year", -2.1400661634962708),
+                                    ("<integer> <unit-of-duration>", -1.041453874828161),
+                                    ("a <unit-of-duration>", -2.1400661634962708),
+                                    ("minute", -1.7346010553881064)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.11778303565638351,
+                               unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.5686159179138452),
+                                    ("year (grain)", -2.0794415416798357),
+                                    ("week (grain)", -1.5686159179138452),
+                                    ("day", -2.4849066497880004), ("quarter", -2.4849066497880004),
+                                    ("year", -2.0794415416798357),
+                                    ("quarter (grain)", -2.4849066497880004),
+                                    ("day (grain)", -2.4849066497880004)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -2.1972245773362196,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.6094379124341003),
+                                    ("day (grain)", -1.6094379124341003)],
+                               n = 1}}),
+       ("minute (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.833213344056216,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 15},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("about <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.8472978603872037,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.916290731874155),
+                                    ("hour", -0.916290731874155)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -0.5596157879354228,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.3862943611198906),
+                                    ("hour", -1.3862943611198906),
+                                    ("<hour-of-day> half (as relative minutes)",
+                                     -1.3862943611198906),
+                                    ("minute", -1.3862943611198906)],
+                               n = 4}}),
+       ("time-of-day (latent)",
+        Classifier{okData =
+                     ClassData{prior = -0.9711673420999893,
+                               unseen = -3.7376696182833684,
+                               likelihoods =
+                                 HashMap.fromList [("integer (numeric)", -2.469261259037152e-2)],
+                               n = 39},
+                   koData =
+                     ClassData{prior = -0.475845904869964, unseen = -4.204692619390966,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.29783444391579894),
+                                    ("integer (0..19)", -1.3564413979702095)],
+                               n = 64}}),
+       ("year",
+        Classifier{okData =
+                     ClassData{prior = -0.1431008436406733, unseen = -3.332204510175204,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 26},
+                   koData =
+                     ClassData{prior = -2.0149030205422647, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 4}}),
+       ("last <day-of-week> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.0986122886681098),
+                                    ("daymonth", -0.7621400520468967),
+                                    ("named-dayintersect", -1.6094379124341003)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.5408064559779265, unseen = -4.74493212836325,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.538973871058276),
+                                    ("integer (0..19)year (grain)", -3.349904087274605),
+                                    ("integer (numeric)day (grain)", -2.9444389791664407),
+                                    ("integer (0..19)hour (grain)", -4.04305126783455),
+                                    ("second", -2.9444389791664407),
+                                    ("integer (numeric)second (grain)", -4.04305126783455),
+                                    ("a pairhour (grain)", -4.04305126783455),
+                                    ("integer (numeric)year (grain)", -3.6375861597263857),
+                                    ("day", -2.338303175596125), ("year", -2.9444389791664407),
+                                    ("integer (numeric)week (grain)", -3.349904087274605),
+                                    ("integer (0..19)month (grain)", -4.04305126783455),
+                                    ("integer (0..19)second (grain)", -3.126760535960395),
+                                    ("hour", -2.9444389791664407), ("month", -3.6375861597263857),
+                                    ("integer (numeric)minute (grain)", -2.790288299339182),
+                                    ("integer (0..19)minute (grain)", -2.9444389791664407),
+                                    ("integer (numeric)month (grain)", -4.04305126783455),
+                                    ("minute", -2.2512917986064953),
+                                    ("integer (numeric)hour (grain)", -3.349904087274605),
+                                    ("integer (0..19)day (grain)", -2.9444389791664407),
+                                    ("integer (0..19)week (grain)", -2.9444389791664407)],
+                               n = 46},
+                   koData =
+                     ClassData{prior = -0.8729402910005413, unseen = -4.48863636973214,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.6855773452501515),
+                                    ("integer (0..19)year (grain)", -3.378724525810097),
+                                    ("integer (numeric)day (grain)", -3.0910424533583156),
+                                    ("integer (0..19)hour (grain)", -3.784189633918261),
+                                    ("second", -2.867898902044106),
+                                    ("integer (numeric)second (grain)", -3.378724525810097),
+                                    ("integer (numeric)year (grain)", -3.0910424533583156),
+                                    ("day", -2.6855773452501515), ("year", -2.6855773452501515),
+                                    ("integer (numeric)week (grain)", -3.378724525810097),
+                                    ("integer (0..19)month (grain)", -3.0910424533583156),
+                                    ("integer (0..19)second (grain)", -3.378724525810097),
+                                    ("hour", -2.6855773452501515), ("month", -2.6855773452501515),
+                                    ("integer (numeric)minute (grain)", -3.378724525810097),
+                                    ("integer (0..19)minute (grain)", -3.378724525810097),
+                                    ("integer (numeric)month (grain)", -3.378724525810097),
+                                    ("minute", -2.867898902044106),
+                                    ("integer (numeric)hour (grain)", -2.867898902044106),
+                                    ("integer (0..19)day (grain)", -3.378724525810097),
+                                    ("integer (0..19)week (grain)", -3.0910424533583156)],
+                               n = 33}}),
+       ("<duration> after <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("a <unit-of-duration>christmas eve", -1.5040773967762742),
+                                    ("yearday", -0.8109302162163288),
+                                    ("<integer> <unit-of-duration>christmas eve",
+                                     -1.0986122886681098)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("relative minutes after|past <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.6931471805599453),
+                                    ("integer (numeric)time-of-day (latent)", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("a <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.0794415416798357),
+                                    ("year (grain)", -2.4849066497880004),
+                                    ("second", -2.0794415416798357),
+                                    ("week (grain)", -2.0794415416798357),
+                                    ("day", -2.4849066497880004),
+                                    ("minute (grain)", -2.4849066497880004),
+                                    ("year", -2.4849066497880004),
+                                    ("second (grain)", -2.0794415416798357),
+                                    ("minute", -2.4849066497880004),
+                                    ("day (grain)", -2.4849066497880004)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect by \",\"",
+        Classifier{okData =
+                     ClassData{prior = -0.1590646946296874, unseen = -4.442651256490317,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>named-day", -3.7376696182833684),
+                                    ("intersect by \",\"year", -3.7376696182833684),
+                                    ("hh:mmintersect by \",\"", -3.7376696182833684),
+                                    ("dayday", -2.032921526044943),
+                                    ("hh:mmnamed-day", -3.7376696182833684),
+                                    ("named-dayintersect by \",\"", -3.332204510175204),
+                                    ("dayyear", -2.8213788864092133),
+                                    ("<named-month> <day-of-month> (non ordinal)intersect",
+                                     -3.7376696182833684),
+                                    ("intersect by \",\"<day-of-month> (non ordinal) <named-month>",
+                                     -3.332204510175204),
+                                    ("<named-month> <day-of-month> (non ordinal)named-day",
+                                     -3.7376696182833684),
+                                    ("named-day<day-of-month> (non ordinal) <named-month>",
+                                     -3.044522437723423),
+                                    ("named-day<named-month> <day-of-month> (non ordinal)",
+                                     -2.639057329615259),
+                                    ("hh:mmintersect", -3.7376696182833684),
+                                    ("intersect by \",\"intersect", -3.7376696182833684),
+                                    ("named-dayintersect", -3.7376696182833684),
+                                    ("at <time-of-day>intersect", -3.7376696182833684),
+                                    ("dayminute", -2.639057329615259),
+                                    ("intersectyear", -3.7376696182833684),
+                                    ("minuteday", -2.032921526044943),
+                                    ("hh:mmabsorption of , after named day", -3.7376696182833684),
+                                    ("at <time-of-day>intersect by \",\"", -3.7376696182833684),
+                                    ("at <time-of-day>absorption of , after named day",
+                                     -3.7376696182833684),
+                                    ("intersectintersect", -3.7376696182833684),
+                                    ("<named-month> <day-of-month> (non ordinal)year",
+                                     -3.332204510175204)],
+                               n = 29},
+                   koData =
+                     ClassData{prior = -1.916922612182061, unseen = -3.6109179126442243,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.791759469228055),
+                                    ("daymonth", -1.791759469228055)],
+                               n = 5}}),
+       ("hh:mm",
+        Classifier{okData =
+                     ClassData{prior = -1.8018505502678365e-2,
+                               unseen = -4.04305126783455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 55},
+                   koData =
+                     ClassData{prior = -4.02535169073515, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("quarter after|past <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("named-day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.276666119016055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 70},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("second (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> sharp",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.2992829841302609),
+                                    ("time-of-day (latent)", -1.2992829841302609),
+                                    ("hour", -0.7884573603642702)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect by \"of\", \"from\", \"'s\"",
+        Classifier{okData =
+                     ClassData{prior = -0.8209805520698302,
+                               unseen = -3.4011973816621555,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.7578579175523736),
+                                    ("daymonth", -1.4213856809311607),
+                                    ("named-daylast <cycle>", -2.6741486494265287),
+                                    ("named-daynext <cycle>", -2.6741486494265287),
+                                    ("named-dayintersect", -2.268683541318364),
+                                    ("dayweek", -1.575536360758419),
+                                    ("named-daythis <cycle>", -1.9810014688665833)],
+                               n = 11},
+                   koData =
+                     ClassData{prior = -0.579818495252942, unseen = -3.58351893845611,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.3581234841531944),
+                                    ("daymonth", -0.8472978603872037),
+                                    ("named-dayintersect", -1.6094379124341003)],
+                               n = 14}}),
+       ("<duration> ago",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.5553480614894135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.580450375560848), ("day", -1.916922612182061),
+                                    ("year", -2.4277482359480516),
+                                    ("<integer> <unit-of-duration>", -0.8873031950009028),
+                                    ("a <unit-of-duration>", -2.833213344056216),
+                                    ("month", -2.4277482359480516)],
+                               n = 14},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <time>",
+        Classifier{okData =
+                     ClassData{prior = -2.751535313041949, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.540445040947149), ("named-day", -1.540445040947149),
+                                    ("hour", -1.9459101490553135),
+                                    ("week-end", -1.9459101490553135)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -6.59579677917974e-2,
+                               unseen = -4.574710978503383,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("year (latent)", -1.6199092123013958),
+                                    ("day", -2.367123614131617),
+                                    ("time-of-day (latent)", -1.6199092123013958),
+                                    ("year", -1.6199092123013958),
+                                    ("named-day", -2.9549102790337356),
+                                    ("intersect by \"of\", \"from\", \"'s\"", -2.9549102790337356),
+                                    ("hour", -1.6199092123013958)],
+                               n = 44}}),
+       ("the day after tomorrow",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("noon",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("until <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.25131442828090605,
+                               unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -0.8649974374866046),
+                                    ("hour", -0.8649974374866046)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -1.5040773967762742,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -1.5040773967762742),
+                                    ("hh:mm", -1.5040773967762742),
+                                    ("minute", -1.0986122886681098)],
+                               n = 2}}),
+       ("<integer> and an half hours",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.6931471805599453),
+                                    ("integer (0..19)", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("after <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("<integer> <unit-of-duration>", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("evening",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("decimal number",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("next <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.587786664902119, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -1.9459101490553135),
+                                    ("day", -1.0296194171811581),
+                                    ("named-day", -1.0296194171811581),
+                                    ("month", -1.9459101490553135)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.8109302162163288,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -1.791759469228055),
+                                    ("day", -1.0986122886681098),
+                                    ("named-day", -1.0986122886681098),
+                                    ("month", -1.791759469228055)],
+                               n = 4}}),
+       ("last <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6739764335716716),
+                                    ("month (grain)", -2.0794415416798357),
+                                    ("year (grain)", -2.0794415416798357),
+                                    ("week (grain)", -1.6739764335716716),
+                                    ("year", -2.0794415416798357), ("month", -2.0794415416798357)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.6931471805599453, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6739764335716716),
+                                    ("week (grain)", -1.6739764335716716),
+                                    ("day", -1.6739764335716716),
+                                    ("day (grain)", -1.6739764335716716)],
+                               n = 4}}),
+       ("christmas",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("new year's day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("next n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.912023005428146,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.793208009442517),
+                                    ("integer (0..19)year (grain)", -3.1986731175506815),
+                                    ("integer (numeric)day (grain)", -3.1986731175506815),
+                                    ("integer (0..19)hour (grain)", -3.1986731175506815),
+                                    ("second", -2.793208009442517),
+                                    ("integer (numeric)second (grain)", -3.1986731175506815),
+                                    ("integer (numeric)year (grain)", -3.1986731175506815),
+                                    ("day", -2.793208009442517), ("year", -2.793208009442517),
+                                    ("integer (numeric)week (grain)", -3.1986731175506815),
+                                    ("integer (0..19)month (grain)", -3.1986731175506815),
+                                    ("integer (0..19)second (grain)", -3.1986731175506815),
+                                    ("hour", -2.793208009442517), ("month", -2.793208009442517),
+                                    ("integer (numeric)minute (grain)", -3.1986731175506815),
+                                    ("integer (0..19)minute (grain)", -3.1986731175506815),
+                                    ("integer (numeric)month (grain)", -3.1986731175506815),
+                                    ("minute", -2.793208009442517),
+                                    ("integer (numeric)hour (grain)", -3.1986731175506815),
+                                    ("integer (0..19)day (grain)", -3.1986731175506815),
+                                    ("integer (0..19)week (grain)", -3.1986731175506815)],
+                               n = 14},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -3.0910424533583156,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("in <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.418840607796598,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -3.0204248861443626),
+                                    ("<integer> more <unit-of-duration>", -3.3081069585961433),
+                                    ("number.number hours", -3.713572066704308),
+                                    ("second", -2.797281334830153), ("day", -2.6149597780361984),
+                                    ("half an hour", -3.713572066704308),
+                                    ("<integer> <unit-of-duration>", -1.2286654169163076),
+                                    ("a <unit-of-duration>", -2.797281334830153),
+                                    ("<integer> and an half hours", -3.3081069585961433),
+                                    ("hour", -2.6149597780361984), ("minute", -1.4622802680978126),
+                                    ("about <duration>", -3.3081069585961433)],
+                               n = 35},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.6649763035932491, unseen = -3.912023005428146,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.252762968495368),
+                                    ("hh:mmhh:mm", -1.252762968495368),
+                                    ("dayday", -2.1000608288825715),
+                                    ("<day-of-month> (non ordinal) <named-month><day-of-month> (non ordinal) <named-month>",
+                                     -2.1000608288825715)],
+                               n = 18},
+                   koData =
+                     ClassData{prior = -0.7221347174331976, unseen = -3.871201010907891,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthday", -2.0583881324820035),
+                                    ("minuteminute", -1.9042374526547454),
+                                    ("hh:mmhh:mm", -3.1570004211501135),
+                                    ("dayyear", -2.751535313041949),
+                                    ("year<hour-of-day> <integer> (as relative minutes)",
+                                     -2.751535313041949),
+                                    ("hh:mmintersect", -2.0583881324820035),
+                                    ("dd/mmyear", -2.751535313041949),
+                                    ("named-month<day-of-month> (non ordinal) <named-month>",
+                                     -2.0583881324820035),
+                                    ("<hour-of-day> <integer> (as relative minutes)year",
+                                     -2.751535313041949),
+                                    ("minuteyear", -2.751535313041949),
+                                    ("yearminute", -2.751535313041949)],
+                               n = 17}}),
+       ("<time-of-day> - <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.7672551527136672,
+                               unseen = -3.4339872044851463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -0.7621400520468967),
+                                    ("hh:mmhh:mm", -0.7621400520468967)],
+                               n = 13},
+                   koData =
+                     ClassData{prior = -0.6241543090729939,
+                               unseen = -3.5553480614894135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -0.8183103235139514),
+                                    ("minuteminute", -2.833213344056216),
+                                    ("hh:mmhh:mm", -2.833213344056216),
+                                    ("minutehour", -0.8183103235139514)],
+                               n = 15}}),
+       ("last n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.04305126783455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.639057329615259),
+                                    ("integer (0..19)year (grain)", -3.332204510175204),
+                                    ("integer (numeric)day (grain)", -2.9267394020670396),
+                                    ("second", -2.9267394020670396),
+                                    ("integer (numeric)second (grain)", -3.332204510175204),
+                                    ("integer (numeric)year (grain)", -2.9267394020670396),
+                                    ("day", -2.639057329615259), ("year", -2.639057329615259),
+                                    ("integer (numeric)week (grain)", -3.332204510175204),
+                                    ("integer (0..19)month (grain)", -2.9267394020670396),
+                                    ("integer (0..19)second (grain)", -3.332204510175204),
+                                    ("hour", -2.9267394020670396), ("month", -2.639057329615259),
+                                    ("integer (numeric)minute (grain)", -3.332204510175204),
+                                    ("integer (0..19)minute (grain)", -3.332204510175204),
+                                    ("integer (numeric)month (grain)", -3.332204510175204),
+                                    ("minute", -2.9267394020670396),
+                                    ("integer (numeric)hour (grain)", -2.9267394020670396),
+                                    ("integer (0..19)day (grain)", -3.332204510175204),
+                                    ("integer (0..19)week (grain)", -2.9267394020670396)],
+                               n = 18},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -3.044522437723423,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-month> <day-of-month> (non ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -0.4418327522790392, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-monthinteger (numeric)", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -1.0296194171811581,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-monthinteger (numeric)", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 5}}),
+       ("<day-of-month> (non ordinal) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -3.6635616461296463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 18},
+                   koData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 3}}),
+       ("this|next <day-of-week>",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.8472978603872037,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 3}}),
+       ("<hour-of-day> half (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("about <time-of-day>", -1.7346010553881064),
+                                    ("time-of-day (latent)", -1.041453874828161),
+                                    ("hour", -0.7537718023763802)],
+                               n = 7}}),
+       ("ordinal (digits)",
+        Classifier{okData =
+                     ClassData{prior = -8.701137698962981e-2,
+                               unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -2.4849066497880004,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("quarter (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.4816045409242156),
+                                    ("week (grain)named-month", -1.9924301646902063),
+                                    ("day (grain)intersect", -1.9924301646902063),
+                                    ("weekmonth", -1.4816045409242156),
+                                    ("day (grain)named-month", -1.9924301646902063),
+                                    ("week (grain)intersect", -1.9924301646902063)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month>(ordinal) <named-month> year",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..31st)named-month", -1.0986122886681098),
+                                    ("ordinal (digits)named-month", -1.5040773967762742),
+                                    ("month", -0.8109302162163288)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("morning",
+        Classifier{okData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12}}),
+       ("week-end",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("after <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.7537718023763802, unseen = -3.332204510175204,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.6863989535702288),
+                                    ("intersect", -2.1972245773362196),
+                                    ("tomorrow", -2.1972245773362196), ("day", -2.1972245773362196),
+                                    ("hour", -1.349926716949016)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -0.6359887667199967,
+                               unseen = -3.4011973816621555,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("this <part-of-day>", -2.268683541318364),
+                                    ("christmas eve", -2.268683541318364),
+                                    ("in|during the <part-of-day>", -2.268683541318364),
+                                    ("day", -2.268683541318364), ("hh:mm", -2.6741486494265287),
+                                    ("hour", -1.4213856809311607), ("minute", -2.6741486494265287),
+                                    ("this <time>", -2.268683541318364)],
+                               n = 9}}),
+       ("day (grain)",
+        Classifier{okData =
+                     ClassData{prior = -4.8790164169432056e-2,
+                               unseen = -3.0910424533583156,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 20},
+                   koData =
+                     ClassData{prior = -3.044522437723423, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("<month> dd-dd (interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("about <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("half an hour", -0.6931471805599453),
+                                    ("minute", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> <integer> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)integer (numeric)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 10}}),
+       ("this <time>",
+        Classifier{okData =
+                     ClassData{prior = -1.3217558399823195, unseen = -3.828641396489095,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.1972245773362196), ("intersect", -2.70805020110221),
+                                    ("season", -2.1972245773362196),
+                                    ("next <cycle>", -3.1135153092103742),
+                                    ("named-month", -2.70805020110221),
+                                    ("day", -2.1972245773362196),
+                                    ("this <cycle>", -2.4203681286504293),
+                                    ("hour", -2.1972245773362196), ("evening", -3.1135153092103742),
+                                    ("month", -2.1972245773362196),
+                                    ("morning", -3.1135153092103742),
+                                    ("week-end", -2.70805020110221)],
+                               n = 16},
+                   koData =
+                     ClassData{prior = -0.3101549283038396, unseen = -4.624972813284271,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -2.0501711593797225),
+                                    ("named-month", -1.670681537674819),
+                                    ("time-of-day (latent)", -3.5165082281731497),
+                                    ("hour", -1.9070703157390494), ("month", -1.1811333123561132),
+                                    ("morning", -2.0501711593797225)],
+                               n = 44}}),
+       ("within <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -0.6931471805599453),
+                                    ("<integer> <unit-of-duration>", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}})]
diff --git a/Duckling/Ranking/Classifiers/DE.hs b/Duckling/Ranking/Classifiers/DE.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/DE.hs
@@ -0,0 +1,1824 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.DE (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers
+  = HashMap.fromList
+      [("<time> timezone",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day>  o'clock", -1.3862943611198906),
+                                    ("hh:mm", -1.3862943611198906), ("hour", -1.3862943611198906),
+                                    ("minute", -1.3862943611198906)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (numeric)",
+        Classifier{okData =
+                     ClassData{prior = -0.668083211896729, unseen = -4.634728988229636,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 101},
+                   koData =
+                     ClassData{prior = -0.7188555372701523, unseen = -4.584967478670572,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 96}}),
+       ("exactly <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.0986122886681098),
+                                    ("<time-of-day>  o'clock", -1.5040773967762742),
+                                    ("hour", -0.8109302162163288)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Father's Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> <half> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1}}),
+       ("lunch",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (20..90)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.11778303565638351,
+                               unseen = -5.720311776607412,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day>  o'clockmorning", -4.618415412738112),
+                                    ("until <time-of-day>afternoon", -5.0238805208462765),
+                                    ("dayhour", -3.319132428607851),
+                                    ("todayevening", -4.618415412738112),
+                                    ("half <integer> (german style hour-of-day)afternoon",
+                                     -5.0238805208462765),
+                                    ("<time-of-day>  o'clockafter lunch", -4.330733340286331),
+                                    ("intersect by ','evening", -3.5198031240700023),
+                                    ("<time-of-day>  o'clockon <date>", -3.925268232178167),
+                                    ("intersectevening", -1.9558275857126592),
+                                    ("time-of-day (latent)on <date>", -4.330733340286331),
+                                    ("about <time-of-day>on <date>", -4.618415412738112),
+                                    ("<hour-of-day> <integer> (as relative minutes)in|during the <part-of-day>",
+                                     -4.618415412738112),
+                                    ("hourhour", -1.1952391243571814),
+                                    ("<time-of-day>  o'clockin|during the <part-of-day>",
+                                     -3.925268232178167),
+                                    ("<time-of-day>  o'clockafternoon", -4.330733340286331),
+                                    ("half <integer> (german style hour-of-day)after lunch",
+                                     -5.0238805208462765),
+                                    ("until <time-of-day>morning", -4.618415412738112),
+                                    ("minutehour", -2.384823191231018),
+                                    ("at <time-of-day>in|during the <part-of-day>",
+                                     -4.107589788972121),
+                                    ("<hour-of-day> <integer> (as relative minutes)after lunch",
+                                     -5.0238805208462765),
+                                    ("<time-of-day> am|pmmorning", -4.618415412738112),
+                                    ("tomorrowevening", -5.0238805208462765),
+                                    ("time-of-day (latent)morning", -5.0238805208462765),
+                                    ("named-daymorning", -4.618415412738112),
+                                    ("hh:mmmorning", -5.0238805208462765),
+                                    ("half <integer> (german style hour-of-day)in|during the <part-of-day>",
+                                     -4.618415412738112),
+                                    ("until <time-of-day>on <date>", -5.0238805208462765),
+                                    ("<day-of-month>(ordinal) <named-month>morning",
+                                     -5.0238805208462765),
+                                    ("<time-of-day>  o'clockevening", -5.0238805208462765),
+                                    ("hh:mmon <date>", -4.107589788972121),
+                                    ("<hour-of-day> <integer> (as relative minutes)afternoon",
+                                     -5.0238805208462765),
+                                    ("tomorrowlunch", -4.618415412738112),
+                                    ("until <time-of-day>after lunch", -5.0238805208462765),
+                                    ("at <time-of-day>on <date>", -4.107589788972121),
+                                    ("yesterdayevening", -5.0238805208462765),
+                                    ("intersectmorning", -5.0238805208462765),
+                                    ("hh:mmin|during the <part-of-day>", -4.107589788972121),
+                                    ("time-of-day (latent)in|during the <part-of-day>",
+                                     -4.330733340286331),
+                                    ("<hour-of-day> <integer> (as relative minutes)on <date>",
+                                     -4.618415412738112),
+                                    ("about <time-of-day>in|during the <part-of-day>",
+                                     -4.618415412738112),
+                                    ("half <integer> (german style hour-of-day)on <date>",
+                                     -4.618415412738112),
+                                    ("at <time-of-day>morning", -5.0238805208462765),
+                                    ("until <time-of-day>in|during the <part-of-day>",
+                                     -5.0238805208462765)],
+                               n = 128},
+                   koData =
+                     ClassData{prior = -2.1972245773362196, unseen = -4.394449154672439,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("yearhour", -3.283414346005772),
+                                    ("year (latent)on <date>", -3.6888794541139363),
+                                    ("<time-of-day>  o'clockafter lunch", -3.6888794541139363),
+                                    ("intersectevening", -2.995732273553991),
+                                    ("monthhour", -3.6888794541139363),
+                                    ("time-of-day (latent)on <date>", -2.772588722239781),
+                                    ("hourhour", -1.742969305058623),
+                                    ("<time-of-day>  o'clockafternoon", -3.6888794541139363),
+                                    ("year (latent)in|during the <part-of-day>",
+                                     -3.6888794541139363),
+                                    ("named-monthmorning", -3.6888794541139363),
+                                    ("time-of-day (latent)in|during the <part-of-day>",
+                                     -2.772588722239781)],
+                               n = 16}}),
+       ("today",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("mm/dd",
+        Classifier{okData =
+                     ClassData{prior = -0.2231435513142097, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -1.6094379124341003,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("at <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.20972053098206903,
+                               unseen = -4.276666119016055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day>  o'clock", -2.065455299705096),
+                                    ("half <integer> (german style hour-of-day)",
+                                     -3.56953269648137),
+                                    ("about <time-of-day>", -3.164067588373206),
+                                    ("time-of-day (latent)", -1.6236225474260568),
+                                    ("relative minutes after|past <integer> (hour-of-day)",
+                                     -3.56953269648137),
+                                    ("hh:mm", -2.8763855159214247),
+                                    ("quarter after|past <integer> (hour-of-day)",
+                                     -3.56953269648137),
+                                    ("hour", -1.08462604669337), ("minute", -2.1832383353614793),
+                                    ("<hour-of-day> <integer> (as relative minutes)",
+                                     -3.56953269648137)],
+                               n = 30},
+                   koData =
+                     ClassData{prior = -1.6650077635889111, unseen = -3.258096538021482,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.6094379124341003),
+                                    ("<time-of-day> am|pm", -1.8325814637483102),
+                                    ("hour", -1.4271163556401458), ("minute", -2.120263536200091)],
+                               n = 7}}),
+       ("absorption of , after named day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1780538303479458,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("on <date>", -2.03688192726104), ("day", -0.7375989431307791),
+                                    ("named-day", -0.9382696385929302)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("on <date>",
+        Classifier{okData =
+                     ClassData{prior = -0.20409535634351528,
+                               unseen = -4.820281565605037,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("mm/dd", -4.119037174812472),
+                                    ("absorption of , after named day", -3.713572066704308),
+                                    ("intersect", -1.867745376205977),
+                                    ("after lunch", -2.866274206317104),
+                                    ("day", -1.4799798451972137), ("afternoon", -2.866274206317104),
+                                    ("named-day", -2.866274206317104),
+                                    ("intersect by ','", -2.866274206317104),
+                                    ("intersect by 'of', 'from', 's", -3.4258899942525267),
+                                    ("<day-of-month> (ordinal)", -3.713572066704308),
+                                    ("hour", -1.9218125974762528), ("evening", -4.119037174812472),
+                                    ("<datetime> - <datetime> (interval)", -4.119037174812472),
+                                    ("minute", -2.509599262378372),
+                                    ("morning", -4.119037174812472)],
+                               n = 53},
+                   koData =
+                     ClassData{prior = -1.6894806201076367,
+                               unseen = -3.7376696182833684,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -1.6341305250244718),
+                                    ("after lunch", -3.0204248861443626),
+                                    ("<day-of-month>(ordinal) <named-month>", -3.0204248861443626),
+                                    ("tomorrow", -3.0204248861443626), ("day", -2.1041341542702074),
+                                    ("intersect by 'of', 'from', 's", -3.0204248861443626),
+                                    ("<day-of-month> (ordinal)", -3.0204248861443626),
+                                    ("hour", -3.0204248861443626), ("minute", -1.6341305250244718)],
+                               n = 12}}),
+       ("integer (0..19)",
+        Classifier{okData =
+                     ClassData{prior = -0.10536051565782628,
+                               unseen = -3.6375861597263857,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 36},
+                   koData =
+                     ClassData{prior = -2.3025850929940455, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4}}),
+       ("between <time-of-day> and <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.0986122886681098),
+                                    ("hh:mmhh:mm", -1.0986122886681098)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -1.0986122886681098),
+                                    ("minutehour", -1.0986122886681098)],
+                               n = 1}}),
+       ("between <datetime> and <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.0986122886681098),
+                                    ("hh:mmhh:mm", -1.0986122886681098)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -1.0986122886681098),
+                                    ("minutehour", -1.0986122886681098)],
+                               n = 1}}),
+       ("month (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day>  o'clock",
+        Classifier{okData =
+                     ClassData{prior = -4.348511193973878e-2,
+                               unseen = -4.61512051684126,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("exactly <time-of-day>", -3.912023005428146),
+                                    ("at <time-of-day>", -2.4079456086518722),
+                                    ("half <integer> (german style hour-of-day)",
+                                     -3.912023005428146),
+                                    ("about <time-of-day>", -3.2188758248682006),
+                                    ("time-of-day (latent)", -1.3470736479666092),
+                                    ("quarter after|past <integer> (hour-of-day)",
+                                     -3.912023005428146),
+                                    ("until <time-of-day>", -3.2188758248682006),
+                                    ("hour", -0.8209805520698302), ("minute", -3.506557897319982),
+                                    ("after <time-of-day>", -3.2188758248682006)],
+                               n = 45},
+                   koData =
+                     ClassData{prior = -3.1570004211501135, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.9459101490553135),
+                                    ("hour", -1.540445040947149),
+                                    ("after <time-of-day>", -1.9459101490553135)],
+                               n = 2}}),
+       ("hour (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("<ordinal> quarter",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..19th)quarter (grain)", -0.916290731874155),
+                                    ("quarter", -0.916290731874155)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)quarter (grain)", -0.916290731874155),
+                                    ("quarter", -0.916290731874155)],
+                               n = 1}}),
+       ("intersect",
+        Classifier{okData =
+                     ClassData{prior = -0.24751603072644626, unseen = -6.8001700683022,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect by ','named-month", -4.49647076906475),
+                                    ("hourday", -4.8531457130034825),
+                                    ("dayhour", -2.481567748522486),
+                                    ("daymonth", -3.3333199592590694),
+                                    ("<day-of-month>(ordinal) <named-month> year<time-of-day>  o'clock",
+                                     -6.105908681498851),
+                                    ("monthyear", -4.49647076906475),
+                                    ("yearhour", -6.105908681498851),
+                                    ("intersect<time-of-day>  o'clock", -3.9658425180025803),
+                                    ("christmasyear", -6.105908681498851),
+                                    ("after lunchat <time-of-day>", -6.105908681498851),
+                                    ("named-daylast <cycle>", -6.105908681498851),
+                                    ("intersect by 'of', 'from', 'syear", -5.700443573390687),
+                                    ("<time-of-day> am|pmintersect", -5.0072963928307415),
+                                    ("intersect<time> <part-of-day>", -4.026467139819015),
+                                    ("<time-of-day>  o'clockafter lunch", -5.412761500938905),
+                                    ("<time> <part-of-day><time-of-day>  o'clock",
+                                     -6.105908681498851),
+                                    ("today<time-of-day>  o'clock", -6.105908681498851),
+                                    ("<time-of-day>  o'clockon <date>", -5.700443573390687),
+                                    ("<time-of-day> am|pmintersect by ','", -5.412761500938905),
+                                    ("intersect by ','year", -5.700443573390687),
+                                    ("on <date><time-of-day>  o'clock", -6.105908681498851),
+                                    ("exactly <time-of-day>tomorrow", -5.700443573390687),
+                                    ("monthhour", -5.700443573390687),
+                                    ("on <date>between <datetime> and <datetime> (interval)",
+                                     -6.105908681498851),
+                                    ("last <day-of-week> of <time>year", -6.105908681498851),
+                                    ("hourmonth", -5.700443573390687),
+                                    ("todayat <time-of-day>", -5.412761500938905),
+                                    ("on <date>between <time-of-day> and <time-of-day> (interval)",
+                                     -6.105908681498851),
+                                    ("on <date>at <time-of-day>", -5.700443573390687),
+                                    ("named-dayhh:mm", -6.105908681498851),
+                                    ("dayday", -3.1881379494145716),
+                                    ("<time> <part-of-day>at <time-of-day>", -5.700443573390687),
+                                    ("<time-of-day> am|pmabsorption of , after named day",
+                                     -6.105908681498851),
+                                    ("about <time-of-day>on <date>", -6.105908681498851),
+                                    ("<hour-of-day> <integer> (as relative minutes)in|during the <part-of-day>",
+                                     -5.700443573390687),
+                                    ("<day-of-month> (ordinal)intersect", -5.1896179496246955),
+                                    ("hourhour", -3.5801800371905954),
+                                    ("<part-of-day> of <time>named-month", -5.700443573390687),
+                                    ("hh:mmintersect by ','", -4.8531457130034825),
+                                    ("intersectnamed-month", -3.3333199592590694),
+                                    ("dayyear", -3.9086841041626315),
+                                    ("<time-of-day>  o'clockin|during the <part-of-day>",
+                                     -5.0072963928307415),
+                                    ("tomorrow<time-of-day>  o'clock", -6.105908681498851),
+                                    ("<time-of-day>  o'clocktomorrow", -5.412761500938905),
+                                    ("<day-of-month>(ordinal) <named-month>year",
+                                     -6.105908681498851),
+                                    ("half <integer> (german style hour-of-day)after lunch",
+                                     -6.105908681498851),
+                                    ("absorption of , after named day<day-of-month>(ordinal) <named-month>",
+                                     -5.0072963928307415),
+                                    ("hourminute", -5.412761500938905),
+                                    ("on <date><day-of-month>(ordinal) <named-month>",
+                                     -5.700443573390687),
+                                    ("minutemonth", -3.431760032072322),
+                                    ("minutehour", -3.8546168828923557),
+                                    ("named-day<time> timezone", -6.105908681498851),
+                                    ("at <time-of-day>in|during the <part-of-day>",
+                                     -5.1896179496246955),
+                                    ("named-monthyear", -4.49647076906475),
+                                    ("absorption of , after named day<day-of-month>(ordinal) <named-month> year",
+                                     -6.105908681498851),
+                                    ("absorption of , after named day<named-month> <day-of-month> (non ordinal)",
+                                     -5.0072963928307415),
+                                    ("named-day<time-of-day> - <time-of-day> (interval)",
+                                     -6.105908681498851),
+                                    ("<day-of-month>(ordinal) <named-month> year<time> <part-of-day>",
+                                     -6.105908681498851),
+                                    ("<hour-of-day> <integer> (as relative minutes)after lunch",
+                                     -6.105908681498851),
+                                    ("named-day<datetime> - <datetime> (interval)",
+                                     -6.105908681498851),
+                                    ("on <date>named-month", -5.1896179496246955),
+                                    ("intersect<day-of-month>(ordinal) <named-month>",
+                                     -4.71961432037896),
+                                    ("this <part-of-day><time-of-day>  o'clock",
+                                     -6.105908681498851),
+                                    ("<day-of-month>(ordinal) <named-month>intersect",
+                                     -6.105908681498851),
+                                    ("hh:mmintersect", -3.6635616461296463),
+                                    ("named-dayfrom <datetime> - <datetime> (interval)",
+                                     -5.700443573390687),
+                                    ("named-daynext <cycle>", -6.105908681498851),
+                                    ("named-dayfrom <time-of-day> - <time-of-day> (interval)",
+                                     -5.412761500938905),
+                                    ("<day-of-month> (ordinal)named-day", -5.700443573390687),
+                                    ("intersect by ','intersect", -5.0072963928307415),
+                                    ("half <integer> (german style hour-of-day)in|during the <part-of-day>",
+                                     -5.700443573390687),
+                                    ("at <time-of-day>intersect", -4.314149212270796),
+                                    ("on <date>from <time-of-day> - <time-of-day> (interval)",
+                                     -5.700443573390687),
+                                    ("<time> <part-of-day>from <time-of-day> - <time-of-day> (interval)",
+                                     -6.105908681498851),
+                                    ("absorption of , after named day<day-of-month> (ordinal)",
+                                     -5.0072963928307415),
+                                    ("dayminute", -3.9086841041626315),
+                                    ("on <date>from <datetime> - <datetime> (interval)",
+                                     -6.105908681498851),
+                                    ("<time> <part-of-day>from <datetime> - <datetime> (interval)",
+                                     -6.105908681498851),
+                                    ("intersectyear", -4.8531457130034825),
+                                    ("on <date>intersect", -5.700443573390687),
+                                    ("on <date><day-of-month> (ordinal)", -5.700443573390687),
+                                    ("<ordinal> <cycle> of <time>year", -6.105908681498851),
+                                    ("minuteday", -2.1262270275968898),
+                                    ("absorption of , after named dayintersect",
+                                     -3.9658425180025803),
+                                    ("named-daybetween <time-of-day> and <time-of-day> (interval)",
+                                     -6.105908681498851),
+                                    ("<datetime> - <datetime> (interval)named-month",
+                                     -5.700443573390687),
+                                    ("year<time-of-day>  o'clock", -6.105908681498851),
+                                    ("at <time-of-day>intersect by ','", -5.412761500938905),
+                                    ("named-dayat <time-of-day>", -5.700443573390687),
+                                    ("hh:mmabsorption of , after named day", -5.700443573390687),
+                                    ("<time-of-day> am|pmnamed-day", -6.105908681498851),
+                                    ("intersect by ','<time> <part-of-day>", -5.1896179496246955),
+                                    ("hh:mmon <date>", -3.6635616461296463),
+                                    ("named-daybetween <datetime> and <datetime> (interval)",
+                                     -6.105908681498851),
+                                    ("at <time-of-day>absorption of , after named day",
+                                     -6.105908681498851),
+                                    ("until <time-of-day>after lunch", -6.105908681498851),
+                                    ("mm/ddyear", -6.105908681498851),
+                                    ("intersect by ','<time-of-day>  o'clock", -5.1896179496246955),
+                                    ("intersect<day-of-month> (ordinal)", -4.71961432037896),
+                                    ("named-day<time-of-day>  o'clock", -6.105908681498851),
+                                    ("<day-of-month> (ordinal)intersect by 'of', 'from', 's",
+                                     -5.412761500938905),
+                                    ("at <time-of-day>on <date>", -4.23410650459726),
+                                    ("intersectintersect", -4.026467139819015),
+                                    ("dayweek", -5.0072963928307415),
+                                    ("weekyear", -5.700443573390687),
+                                    ("hh:mmin|during the <part-of-day>", -5.1896179496246955),
+                                    ("named-monthintersect", -6.105908681498851),
+                                    ("<day-of-month> (ordinal)named-month", -4.401160589260425),
+                                    ("tomorrowat <time-of-day>", -5.700443573390687),
+                                    ("<hour-of-day> <integer> (as relative minutes)on <date>",
+                                     -6.105908681498851),
+                                    ("named-daythis <cycle>", -5.412761500938905),
+                                    ("at <time-of-day>tomorrow", -6.105908681498851),
+                                    ("about <time-of-day>in|during the <part-of-day>",
+                                     -5.700443573390687),
+                                    ("half <integer> (german style hour-of-day)on <date>",
+                                     -6.105908681498851),
+                                    ("this <part-of-day>at <time-of-day>", -5.700443573390687),
+                                    ("after lunch<hour-of-day> <integer> (as relative minutes)",
+                                     -5.700443573390687),
+                                    ("last <cycle> of <time>year", -5.700443573390687),
+                                    ("<named-month> <day-of-month> (non ordinal)year",
+                                     -6.105908681498851),
+                                    ("<day-of-month> (non ordinal) <named-month>year",
+                                     -6.105908681498851)],
+                               n = 381},
+                   koData =
+                     ClassData{prior = -1.517486571391241, unseen = -5.857933154483459,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect by ','named-month", -4.468777561082536),
+                                    ("named-day<part-of-day> of <time>", -5.161924741642482),
+                                    ("dayhour", -4.468777561082536),
+                                    ("daymonth", -3.2901225647408903),
+                                    ("monthday", -4.756459633534318),
+                                    ("monthyear", -5.161924741642482),
+                                    ("yearhour", -5.161924741642482),
+                                    ("after lunchat <time-of-day>", -4.756459633534318),
+                                    ("intersect by 'of', 'from', 'syear", -4.468777561082536),
+                                    ("<time-of-day> am|pmintersect", -3.457176649404057),
+                                    ("intersect<time> <part-of-day>", -5.161924741642482),
+                                    ("<time-of-day>  o'clockafter lunch", -5.161924741642482),
+                                    ("<day-of-month> (ordinal)year", -5.161924741642482),
+                                    ("after lunch<time-of-day>  o'clock", -5.161924741642482),
+                                    ("at <time-of-day>named-day", -5.161924741642482),
+                                    ("<time-of-day> am|pmintersect by ','", -3.909161773147114),
+                                    ("<time-of-day>  o'clock<time> <part-of-day>",
+                                     -4.2456340097683265),
+                                    ("monthhour", -5.161924741642482),
+                                    ("on <date>between <datetime> and <datetime> (interval)",
+                                     -5.161924741642482),
+                                    ("todayat <time-of-day>", -5.161924741642482),
+                                    ("on <date>between <time-of-day> and <time-of-day> (interval)",
+                                     -5.161924741642482),
+                                    ("named-dayhh:mm", -5.161924741642482),
+                                    ("dayday", -3.909161773147114),
+                                    ("<time-of-day> am|pmabsorption of , after named day",
+                                     -4.756459633534318),
+                                    ("hourhour", -3.657847344866208),
+                                    ("intersectnamed-month", -3.5524868292083815),
+                                    ("dayyear", -4.0633124529743725),
+                                    ("minutemonth", -3.2160145925871686),
+                                    ("named-monthyear", -5.161924741642482),
+                                    ("named-day<time-of-day> - <time-of-day> (interval)",
+                                     -5.161924741642482),
+                                    ("on <date>named-month", -5.161924741642482),
+                                    ("absorption of , after named daynamed-month",
+                                     -4.0633124529743725),
+                                    ("intersect<day-of-month>(ordinal) <named-month>",
+                                     -4.468777561082536),
+                                    ("named-dayafter <time-of-day>", -4.756459633534318),
+                                    ("named-dayfrom <datetime> - <datetime> (interval)",
+                                     -4.756459633534318),
+                                    ("<hour-of-day> <integer> (as relative minutes)named-month",
+                                     -5.161924741642482),
+                                    ("named-dayfrom <time-of-day> - <time-of-day> (interval)",
+                                     -4.468777561082536),
+                                    ("<day-of-month> (ordinal)named-day", -4.468777561082536),
+                                    ("at <time-of-day>intersect", -4.0633124529743725),
+                                    ("on <date>from <time-of-day> - <time-of-day> (interval)",
+                                     -4.756459633534318),
+                                    ("dayminute", -3.0218585781462113),
+                                    ("on <date>from <datetime> - <datetime> (interval)",
+                                     -5.161924741642482),
+                                    ("minuteday", -2.1661924680884908),
+                                    ("named-daybetween <time-of-day> and <time-of-day> (interval)",
+                                     -5.161924741642482),
+                                    ("at <time-of-day>intersect by ','", -4.468777561082536),
+                                    ("<time-of-day> am|pmnamed-day", -4.756459633534318),
+                                    ("named-daybetween <datetime> and <datetime> (interval)",
+                                     -5.161924741642482),
+                                    ("at <time-of-day>absorption of , after named day",
+                                     -5.161924741642482),
+                                    ("named-month<day-of-month> (non ordinal) <named-month>",
+                                     -4.756459633534318),
+                                    ("year<time> <part-of-day>", -5.161924741642482),
+                                    ("intersect<day-of-month> (ordinal)", -4.468777561082536),
+                                    ("<day-of-month> (ordinal)intersect by 'of', 'from', 's",
+                                     -4.756459633534318),
+                                    ("<named-month> <day-of-month> (non ordinal)named-month",
+                                     -4.756459633534318),
+                                    ("intersectintersect", -4.468777561082536),
+                                    ("<day-of-month> (ordinal)named-month", -4.468777561082536),
+                                    ("until <time-of-day>named-month", -5.161924741642482),
+                                    ("after <time-of-day>year", -5.161924741642482),
+                                    ("on <date>after <time-of-day>", -5.161924741642482),
+                                    ("tomorrownoon", -5.161924741642482)],
+                               n = 107}}),
+       ("<ordinal> <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.7047480922384253),
+                                    ("ordinals (first..19th)week (grain)named-month",
+                                     -1.7047480922384253),
+                                    ("ordinals (first..19th)day (grain)named-month",
+                                     -1.7047480922384253),
+                                    ("ordinals (first..19th)week (grain)intersect",
+                                     -1.7047480922384253),
+                                    ("weekmonth", -1.2992829841302609)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("season",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("from <datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.3862943611198906),
+                                    ("time-of-day (latent)time-of-day (latent)",
+                                     -1.791759469228055),
+                                    ("hh:mmhh:mm", -1.3862943611198906),
+                                    ("hourhour", -1.791759469228055)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -0.916290731874155, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -1.2039728043259361),
+                                    ("minutehour", -1.2039728043259361)],
+                               n = 2}}),
+       ("after tomorrow",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("next <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6094379124341003),
+                                    ("month (grain)", -2.3025850929940455),
+                                    ("year (grain)", -2.3025850929940455),
+                                    ("week (grain)", -1.6094379124341003),
+                                    ("quarter", -2.3025850929940455), ("year", -2.3025850929940455),
+                                    ("month", -2.3025850929940455),
+                                    ("quarter (grain)", -2.3025850929940455)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6094379124341003),
+                                    ("week (grain)", -1.6094379124341003)],
+                               n = 1}}),
+       ("number.number hours",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("from <time-of-day> - <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.252762968495368),
+                                    ("time-of-day (latent)time-of-day (latent)",
+                                     -1.9459101490553135),
+                                    ("hh:mmhh:mm", -1.252762968495368),
+                                    ("hourhour", -1.9459101490553135)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.8472978603872037,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -1.0986122886681098),
+                                    ("minutehour", -1.0986122886681098)],
+                               n = 3}}),
+       ("year (latent)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 1}}),
+       ("mm/dd/yyyy",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("couple",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<ordinal> quarter <year>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("quarteryear", -0.6931471805599453),
+                                    ("ordinal (digits)quarter (grain)year", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("after lunch",
+        Classifier{okData =
+                     ClassData{prior = -8.701137698962981e-2,
+                               unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -2.4849066497880004,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("quarter to|till|before <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Allerheiligen",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("nth <time> of <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -0.7884573603642702),
+                                    ("ordinals (first..19th)named-dayintersect",
+                                     -1.0116009116784799),
+                                    ("ordinals (first..19th)named-daynamed-month",
+                                     -1.7047480922384253)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.8472978603872037,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -0.8109302162163288),
+                                    ("ordinals (first..19th)named-daynamed-month",
+                                     -0.8109302162163288)],
+                               n = 3}}),
+       ("named-month",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.8501476017100584,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 45},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("week (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.332204510175204,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 26},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<part-of-day> of <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hourday", -0.9444616088408514),
+                                    ("morning<day-of-month>(ordinal) <named-month>",
+                                     -1.791759469228055),
+                                    ("morningintersect", -1.791759469228055),
+                                    ("morning<day-of-month> (ordinal)", -1.791759469228055)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hourhour", -1.3862943611198906),
+                                    ("morningtime-of-day (latent)", -1.3862943611198906)],
+                               n = 1}}),
+       ("valentine's day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("now",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("half <integer> (german style hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day>  o'clock", -1.7047480922384253),
+                                    ("time-of-day (latent)", -1.0116009116784799),
+                                    ("hour", -0.7884573603642702)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.6931471805599453),
+                                    ("evening", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month>(ordinal) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.262364264467491, unseen = -3.1780538303479458,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)named-month", -0.832909122935104),
+                                    ("month", -0.7375989431307791),
+                                    ("ordinals (first..19th)named-month", -2.4423470353692043)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -1.466337068793427, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)named-month", -0.8109302162163288),
+                                    ("month", -0.8109302162163288)],
+                               n = 3}}),
+       ("numbers prefix with -, negative or minus",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 7}}),
+       ("in|during the <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -6.0624621816434854e-2,
+                               unseen = -3.6375861597263857,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("after lunch", -1.6650077635889111),
+                                    ("afternoon", -1.5314763709643884),
+                                    ("hour", -0.7777045685880083), ("evening", -2.512305623976115),
+                                    ("morning", -2.917770732084279)],
+                               n = 16},
+                   koData =
+                     ClassData{prior = -2.833213344056216, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("after lunch", -1.252762968495368),
+                                    ("hour", -1.252762968495368)],
+                               n = 1}}),
+       ("new year's eve",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("tomorrow",
+        Classifier{okData =
+                     ClassData{prior = -0.13353139262452263,
+                               unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -2.0794415416798357,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("Mother's Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> after next",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("after next <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -1.3862943611198906),
+                                    ("day", -1.3862943611198906),
+                                    ("named-day", -1.3862943611198906),
+                                    ("month", -1.3862943611198906)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("half an hour",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("afternoon",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 10},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.2039728043259361),
+                                    ("year (grain)", -2.3025850929940455),
+                                    ("week (grain)", -1.2039728043259361),
+                                    ("quarter", -2.3025850929940455), ("year", -2.3025850929940455),
+                                    ("quarter (grain)", -2.3025850929940455)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("minute (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 10},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("about <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day>  o'clock", -1.3862943611198906),
+                                    ("time-of-day (latent)", -1.3862943611198906),
+                                    ("hour", -0.8266785731844679)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.3862943611198906),
+                                    ("<time-of-day> am|pm", -1.3862943611198906),
+                                    ("hour", -0.9808292530117262)],
+                               n = 2}}),
+       ("time-of-day (latent)",
+        Classifier{okData =
+                     ClassData{prior = -0.8082165103447325,
+                               unseen = -3.8066624897703196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.1466034741918754),
+                                    ("integer (0..19)", -2.174751721484161)],
+                               n = 41},
+                   koData =
+                     ClassData{prior = -0.5899629443247145, unseen = -4.007333185232471,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.587786664902119),
+                                    ("integer (0..19)", -0.9444616088408514),
+                                    ("couple", -2.890371757896165)],
+                               n = 51}}),
+       ("year",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.833213344056216,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 15},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <day-of-week> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.0986122886681098),
+                                    ("daymonth", -0.8109302162163288),
+                                    ("named-dayintersect", -1.5040773967762742)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.8850381883700507, unseen = -4.330733340286331,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.2380465718564744),
+                                    ("integer (0..19)year (grain)", -3.2188758248682006),
+                                    ("integer (numeric)day (grain)", -2.70805020110221),
+                                    ("couplehour (grain)", -3.624340932976365),
+                                    ("integer (0..19)hour (grain)", -3.2188758248682006),
+                                    ("second", -3.624340932976365),
+                                    ("integer (numeric)year (grain)", -3.624340932976365),
+                                    ("day", -2.70805020110221), ("year", -2.9311937524164198),
+                                    ("integer (numeric)week (grain)", -2.9311937524164198),
+                                    ("integer (0..19)month (grain)", -3.624340932976365),
+                                    ("integer (0..19)second (grain)", -3.624340932976365),
+                                    ("hour", -2.70805020110221), ("month", -3.624340932976365),
+                                    ("integer (numeric)minute (grain)", -2.5257286443082556),
+                                    ("integer (0..19)minute (grain)", -3.624340932976365),
+                                    ("minute", -2.371577964480997),
+                                    ("integer (numeric)hour (grain)", -3.624340932976365),
+                                    ("integer (0..19)week (grain)", -2.70805020110221)],
+                               n = 26},
+                   koData =
+                     ClassData{prior = -0.5322168137473082, unseen = -4.584967478670572,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.62880082944807),
+                                    ("integer (0..19)year (grain)", -2.9652730660692823),
+                                    ("integer (numeric)day (grain)", -3.4760986898352733),
+                                    ("integer (0..19)hour (grain)", -3.8815637979434374),
+                                    ("second", -2.9652730660692823),
+                                    ("integer (numeric)second (grain)", -3.4760986898352733),
+                                    ("integer (numeric)year (grain)", -3.4760986898352733),
+                                    ("day", -2.3774864011671633), ("year", -2.62880082944807),
+                                    ("integer (numeric)week (grain)", -3.188416617383492),
+                                    ("integer (0..19)month (grain)", -2.9652730660692823),
+                                    ("integer (0..19)second (grain)", -3.4760986898352733),
+                                    ("hour", -3.188416617383492), ("month", -2.62880082944807),
+                                    ("integer (numeric)minute (grain)", -3.4760986898352733),
+                                    ("integer (0..19)minute (grain)", -3.4760986898352733),
+                                    ("integer (numeric)month (grain)", -3.4760986898352733),
+                                    ("minute", -2.9652730660692823),
+                                    ("coupleday (grain)", -3.4760986898352733),
+                                    ("integer (numeric)hour (grain)", -3.4760986898352733),
+                                    ("integer (0..19)day (grain)", -2.9652730660692823),
+                                    ("integer (0..19)week (grain)", -3.188416617383492)],
+                               n = 37}}),
+       ("ordinals (first..19th)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> am|pm",
+        Classifier{okData =
+                     ClassData{prior = -1.845826690498331, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -2.0794415416798357),
+                                    ("hh:mm", -2.0794415416798357),
+                                    ("until <time-of-day>", -2.0794415416798357),
+                                    ("hour", -1.6739764335716716), ("minute", -2.0794415416798357)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -0.17185025692665928,
+                               unseen = -3.7612001156935624,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -2.3513752571634776),
+                                    ("<time-of-day>  o'clock", -2.3513752571634776),
+                                    ("half <integer> (german style hour-of-day)",
+                                     -3.044522437723423),
+                                    ("about <time-of-day>", -3.044522437723423),
+                                    ("time-of-day (latent)", -2.128231705849268),
+                                    ("hh:mm", -2.3513752571634776), ("hour", -1.4350845252893227),
+                                    ("minute", -1.6582280766035324),
+                                    ("<hour-of-day> <integer> (as relative minutes)",
+                                     -3.044522437723423)],
+                               n = 16}}),
+       ("<duration> after <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("a <unit-of-duration>christmas", -1.252762968495368),
+                                    ("<integer> <unit-of-duration>christmas", -1.252762968495368),
+                                    ("yearday", -0.8472978603872037)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("relative minutes after|past <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.8472978603872037),
+                                    ("integer (numeric)time-of-day (latent)", -1.252762968495368),
+                                    ("integer (20..90)time-of-day (latent)", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("a <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.9924301646902063),
+                                    ("hour (grain)", -2.3978952727983707),
+                                    ("year (grain)", -2.3978952727983707),
+                                    ("second", -2.3978952727983707),
+                                    ("week (grain)", -1.9924301646902063),
+                                    ("minute (grain)", -2.3978952727983707),
+                                    ("year", -2.3978952727983707),
+                                    ("second (grain)", -2.3978952727983707),
+                                    ("hour", -2.3978952727983707), ("minute", -2.3978952727983707)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh:mm",
+        Classifier{okData =
+                     ClassData{prior = -3.17486983145803e-2,
+                               unseen = -3.4965075614664802,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 31},
+                   koData =
+                     ClassData{prior = -3.4657359027997265,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("quarter after|past <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day>  o'clock", -1.5040773967762742),
+                                    ("time-of-day (latent)", -1.0986122886681098),
+                                    ("hour", -0.8109302162163288)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("named-day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.02535169073515,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 54},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<duration> before <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> <unit-of-duration>time-of-day (latent)",
+                                     -0.6931471805599453),
+                                    ("minutehour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect by ','",
+        Classifier{okData =
+                     ClassData{prior = -0.27087495413539975,
+                               unseen = -4.6913478822291435,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -2.4849066497880004),
+                                    ("named-day<day-of-month> (ordinal)", -3.295836866004329),
+                                    ("dayday", -1.463255402256019),
+                                    ("dayyear", -3.9889840465642745),
+                                    ("on <date><day-of-month>(ordinal) <named-month>",
+                                     -3.58351893845611),
+                                    ("named-day<named-month> <day-of-month> (non ordinal)",
+                                     -2.890371757896165),
+                                    ("intersect<day-of-month>(ordinal) <named-month>",
+                                     -3.0726933146901194),
+                                    ("named-day<day-of-month>(ordinal) <named-month> year",
+                                     -3.9889840465642745),
+                                    ("named-dayintersect", -1.9740810260220096),
+                                    ("on <date>intersect", -3.58351893845611),
+                                    ("on <date><day-of-month> (ordinal)", -3.58351893845611),
+                                    ("minuteday", -2.117181869662683),
+                                    ("intersect<day-of-month> (ordinal)", -3.0726933146901194),
+                                    ("intersectintersect", -3.0726933146901194),
+                                    ("named-day<day-of-month>(ordinal) <named-month>",
+                                     -3.295836866004329),
+                                    ("<named-month> <day-of-month> (non ordinal)year",
+                                     -3.9889840465642745)],
+                               n = 45},
+                   koData =
+                     ClassData{prior = -1.4384801142904609,
+                               unseen = -3.8501476017100584,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -2.03688192726104),
+                                    ("daymonth", -2.03688192726104),
+                                    ("intersect<day-of-month>(ordinal) <named-month>",
+                                     -2.4423470353692043),
+                                    ("minuteday", -1.5260563034950494),
+                                    ("intersect<day-of-month> (ordinal)", -2.4423470353692043),
+                                    ("intersectintersect", -2.4423470353692043)],
+                               n = 14}}),
+       ("second (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect by 'of', 'from', 's",
+        Classifier{okData =
+                     ClassData{prior = -0.534082485930258, unseen = -3.912023005428146,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -2.793208009442517),
+                                    ("daymonth", -1.9459101490553135),
+                                    ("named-daylast <cycle>", -3.1986731175506815),
+                                    ("on <date>hh:mm", -3.1986731175506815),
+                                    ("named-dayhh:mm", -2.793208009442517),
+                                    ("intersectnamed-month", -2.793208009442517),
+                                    ("named-day<time-of-day> - <time-of-day> (interval)",
+                                     -2.793208009442517),
+                                    ("named-day<datetime> - <datetime> (interval)",
+                                     -2.793208009442517),
+                                    ("on <date><time-of-day> - <time-of-day> (interval)",
+                                     -3.1986731175506815),
+                                    ("on <date><datetime> - <datetime> (interval)",
+                                     -3.1986731175506815),
+                                    ("named-daynext <cycle>", -3.1986731175506815),
+                                    ("named-dayintersect", -3.1986731175506815),
+                                    ("dayminute", -1.589235205116581),
+                                    ("intersectintersect", -3.1986731175506815),
+                                    ("dayweek", -2.793208009442517)],
+                               n = 17},
+                   koData =
+                     ClassData{prior = -0.8823891801984737,
+                               unseen = -3.6888794541139363,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -2.0541237336955462),
+                                    ("daymonth", -1.3609765531356008),
+                                    ("intersectnamed-month", -2.5649493574615367),
+                                    ("named-day<time-of-day> - <time-of-day> (interval)",
+                                     -2.5649493574615367),
+                                    ("on <date><time-of-day> - <time-of-day> (interval)",
+                                     -2.9704144655697013),
+                                    ("named-dayintersect", -2.277267285009756),
+                                    ("dayminute", -2.277267285009756)],
+                               n = 12}}),
+       ("<duration> ago",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.4816045409242156), ("day", -1.9924301646902063),
+                                    ("year", -2.3978952727983707),
+                                    ("<integer> <unit-of-duration>", -1.0116009116784799),
+                                    ("a <unit-of-duration>", -2.3978952727983707),
+                                    ("month", -2.3978952727983707)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <time>",
+        Classifier{okData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.9808292530117262),
+                                    ("named-day", -0.9808292530117262)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.7672551527136672),
+                                    ("hour", -0.7672551527136672)],
+                               n = 12}}),
+       ("EOM|End of month",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month> (ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -0.6390799592896695,
+                               unseen = -3.0910424533583156,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..19th)", -1.6582280766035324),
+                                    ("ordinal (digits)", -0.2113090936672069)],
+                               n = 19},
+                   koData =
+                     ClassData{prior = -0.750305594399894, unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..19th)", -0.9985288301111273),
+                                    ("ordinal (digits)", -0.4595323293784402)],
+                               n = 17}}),
+       ("noon",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("until <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.3022808718729337, unseen = -3.912023005428146,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> <part-of-day>", -1.9459101490553135),
+                                    ("<time-of-day>  o'clock", -2.505525936990736),
+                                    ("intersect", -3.1986731175506815),
+                                    ("time-of-day (latent)", -2.2823823856765264),
+                                    ("<time-of-day> am|pm", -3.1986731175506815),
+                                    ("EOM|End of month", -3.1986731175506815),
+                                    ("hour", -1.0586069540544105), ("month", -3.1986731175506815),
+                                    ("midnight|EOD|end of day", -3.1986731175506815)],
+                               n = 17},
+                   koData =
+                     ClassData{prior = -1.3437347467010947, unseen = -3.332204510175204,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -2.6026896854443837),
+                                    ("<day-of-month>(ordinal) <named-month>", -2.6026896854443837),
+                                    ("day", -1.6863989535702288), ("hh:mm", -2.1972245773362196),
+                                    ("<day-of-month> (ordinal)", -2.6026896854443837),
+                                    ("<day-of-month> (non ordinal) <named-month>",
+                                     -2.6026896854443837),
+                                    ("minute", -2.1972245773362196)],
+                               n = 6}}),
+       ("<integer> and an half hours",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("integer (0..19)", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Nikolaus",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("evening",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("midnight|EOD|end of day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("next <time>",
+        Classifier{okData =
+                     ClassData{prior = -1.466337068793427, unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -2.1972245773362196),
+                                    ("day", -1.0986122886681098),
+                                    ("named-day", -1.0986122886681098),
+                                    ("month", -2.1972245773362196)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.262364264467491, unseen = -3.8501476017100584,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.7841189587656721),
+                                    ("hour", -0.7841189587656721)],
+                               n = 20}}),
+       ("last <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.3364722366212129,
+                               unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.5040773967762742),
+                                    ("month (grain)", -2.1972245773362196),
+                                    ("year (grain)", -2.1972245773362196),
+                                    ("week (grain)", -1.5040773967762742),
+                                    ("year", -2.1972245773362196), ("month", -2.1972245773362196)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -1.252762968495368, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.791759469228055),
+                                    ("week (grain)", -1.791759469228055),
+                                    ("day", -1.791759469228055),
+                                    ("day (grain)", -1.791759469228055)],
+                               n = 2}}),
+       ("christmas",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Tag der Deutschen Einheit",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("new year's day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("next n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.143134726391533,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.740840023925201),
+                                    ("integer (0..19)year (grain)", -3.028522096376982),
+                                    ("integer (numeric)day (grain)", -3.4339872044851463),
+                                    ("integer (0..19)hour (grain)", -3.4339872044851463),
+                                    ("second", -3.028522096376982),
+                                    ("integer (numeric)second (grain)", -3.4339872044851463),
+                                    ("integer (numeric)year (grain)", -3.4339872044851463),
+                                    ("day", -2.3353749158170367), ("year", -2.740840023925201),
+                                    ("integer (numeric)week (grain)", -3.4339872044851463),
+                                    ("integer (0..19)month (grain)", -3.028522096376982),
+                                    ("integer (0..19)second (grain)", -3.4339872044851463),
+                                    ("hour", -3.028522096376982), ("month", -2.740840023925201),
+                                    ("integer (numeric)minute (grain)", -3.4339872044851463),
+                                    ("integer (0..19)minute (grain)", -3.4339872044851463),
+                                    ("integer (numeric)month (grain)", -3.4339872044851463),
+                                    ("minute", -3.028522096376982),
+                                    ("coupleday (grain)", -3.028522096376982),
+                                    ("integer (numeric)hour (grain)", -3.4339872044851463),
+                                    ("integer (0..19)day (grain)", -3.028522096376982),
+                                    ("integer (0..19)week (grain)", -3.028522096376982)],
+                               n = 20},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -3.1354942159291497,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("halloween day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("by the end of <time>",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.0986122886681098),
+                                    ("hh:mm", -1.791759469228055), ("hour", -1.0986122886681098),
+                                    ("minute", -1.791759469228055)],
+                               n = 4}}),
+       ("in <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.07753744390572,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.6741486494265287),
+                                    ("number.number hours", -3.367295829986474),
+                                    ("second", -2.9618307218783095), ("day", -2.9618307218783095),
+                                    ("half an hour", -3.367295829986474),
+                                    ("year", -3.367295829986474),
+                                    ("<integer> <unit-of-duration>", -1.2878542883066382),
+                                    ("a <unit-of-duration>", -2.451005098112319),
+                                    ("<integer> and an half hours", -3.367295829986474),
+                                    ("hour", -2.268683541318364), ("minute", -1.6625477377480489),
+                                    ("about <duration>", -3.367295829986474)],
+                               n = 23},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.3364722366212129,
+                               unseen = -3.7612001156935624,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersecthh:mm", -3.044522437723423),
+                                    ("on <date>hh:mm", -3.044522437723423),
+                                    ("minuteminute", -1.252762968495368),
+                                    ("<day-of-month> (ordinal)<day-of-month> (ordinal)",
+                                     -2.639057329615259),
+                                    ("hh:mmhh:mm", -1.791759469228055),
+                                    ("dayday", -2.128231705849268),
+                                    ("<named-month> <day-of-month> (non ordinal)<named-month> <day-of-month> (non ordinal)",
+                                     -2.639057329615259),
+                                    ("intersect by 'of', 'from', 'shh:mm", -2.3513752571634776)],
+                               n = 15},
+                   koData =
+                     ClassData{prior = -1.252762968495368, unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<day-of-month> (ordinal)<day-of-month>(ordinal) <named-month>",
+                                     -2.0794415416798357),
+                                    ("daymonth", -2.0794415416798357),
+                                    ("dayday", -1.5686159179138452),
+                                    ("<day-of-month> (ordinal)intersect", -2.0794415416798357),
+                                    ("<named-month> <day-of-month> (non ordinal)named-month",
+                                     -2.0794415416798357)],
+                               n = 6}}),
+       ("<time-of-day> - <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -0.8266785731844679),
+                                    ("hh:mmhh:mm", -0.8266785731844679)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.6931471805599453, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -0.8266785731844679),
+                                    ("minutehour", -0.8266785731844679)],
+                               n = 6}}),
+       ("last n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.9318256327243257,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.5257286443082556),
+                                    ("integer (0..19)year (grain)", -2.8134107167600364),
+                                    ("integer (numeric)day (grain)", -3.2188758248682006),
+                                    ("second", -2.8134107167600364),
+                                    ("integer (numeric)second (grain)", -3.2188758248682006),
+                                    ("integer (numeric)year (grain)", -3.2188758248682006),
+                                    ("day", -2.5257286443082556), ("year", -2.5257286443082556),
+                                    ("integer (numeric)week (grain)", -2.8134107167600364),
+                                    ("integer (0..19)month (grain)", -2.8134107167600364),
+                                    ("integer (0..19)second (grain)", -3.2188758248682006),
+                                    ("month", -2.5257286443082556),
+                                    ("integer (numeric)minute (grain)", -3.2188758248682006),
+                                    ("integer (0..19)minute (grain)", -3.2188758248682006),
+                                    ("integer (numeric)month (grain)", -3.2188758248682006),
+                                    ("minute", -2.8134107167600364),
+                                    ("integer (0..19)day (grain)", -2.8134107167600364),
+                                    ("integer (0..19)week (grain)", -3.2188758248682006)],
+                               n = 16},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.9444389791664407,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("nth <time> after <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayday", -0.916290731874155),
+                                    ("ordinals (first..19th)named-dayintersect",
+                                     -0.916290731874155)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayday", -0.916290731874155),
+                                    ("ordinals (first..19th)named-daychristmas",
+                                     -0.916290731874155)],
+                               n = 1}}),
+       ("<named-month> <day-of-month> (non ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -6.453852113757118e-2,
+                               unseen = -3.4965075614664802,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-monthinteger (numeric)", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 15},
+                   koData =
+                     ClassData{prior = -2.772588722239781, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-monthinteger (numeric)", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 1}}),
+       ("<day-of-month> (non ordinal) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 4}}),
+       ("this|next <day-of-week>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinal (digits)",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.995732273553991,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 18},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9}}),
+       ("quarter (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.540445040947149),
+                                    ("week (grain)named-month", -1.9459101490553135),
+                                    ("day (grain)intersect", -1.9459101490553135),
+                                    ("weekmonth", -1.540445040947149),
+                                    ("day (grain)named-month", -1.9459101490553135),
+                                    ("week (grain)intersect", -1.9459101490553135)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month>(ordinal) <named-month> year",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("morning",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("week-end",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("after <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day>  o'clock", -1.6094379124341003),
+                                    ("time-of-day (latent)", -1.6094379124341003),
+                                    ("hour", -1.0498221244986778)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.6931471805599453, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day>  o'clock", -2.3025850929940455),
+                                    ("intersect", -2.3025850929940455),
+                                    ("day", -1.6094379124341003),
+                                    ("time-of-day (latent)", -2.3025850929940455),
+                                    ("hh:mm", -2.3025850929940455), ("hour", -1.8971199848858813),
+                                    ("christmas", -1.8971199848858813),
+                                    ("minute", -2.3025850929940455)],
+                               n = 6}}),
+       ("day (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.772588722239781,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 14},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<month> dd-dd (interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("about <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("half an hour", -0.6931471805599453),
+                                    ("minute", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> <integer> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = -0.3364722366212129, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.916290731874155),
+                                    ("at <time-of-day>integer (numeric)", -2.0149030205422647),
+                                    ("<time-of-day>  o'clockinteger (numeric)",
+                                     -1.0986122886681098)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -1.252762968495368, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)integer (0..19)", -1.5040773967762742),
+                                    ("time-of-day (latent)integer (numeric)", -1.5040773967762742),
+                                    ("hour", -1.0986122886681098)],
+                               n = 2}}),
+       ("this <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("season", -1.466337068793427), ("day", -1.1786549963416462),
+                                    ("named-day", -1.8718021769015913),
+                                    ("hour", -1.8718021769015913),
+                                    ("week-end", -1.8718021769015913)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("within <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -0.6931471805599453),
+                                    ("<integer> <unit-of-duration>", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("before yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}})]
diff --git a/Duckling/Ranking/Classifiers/EN.hs b/Duckling/Ranking/Classifiers/EN.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/EN.hs
@@ -0,0 +1,2133 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.EN (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers
+  = HashMap.fromList
+      [("<integer> to|till|before <hour-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -1.791759469228055, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)noon|midnight|EOD|end of day",
+                                     -0.916290731874155),
+                                    ("hour", -0.916290731874155)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.1823215567939546, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.7731898882334817),
+                                    ("integer (numeric)time-of-day (latent)", -0.7731898882334817)],
+                               n = 5}}),
+       ("<time> timezone",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.7047480922384253),
+                                    ("<time-of-day> am|pm", -1.7047480922384253),
+                                    ("hh:mm", -1.7047480922384253), ("hour", -1.2992829841302609),
+                                    ("minute", -1.7047480922384253)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Thursday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.995732273553991,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 18},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (numeric)",
+        Classifier{okData =
+                     ClassData{prior = -0.840653317668535, unseen = -5.030437921392435,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 151},
+                   koData =
+                     ClassData{prior = -0.5646283297589669, unseen = -5.303304908059076,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 199}}),
+       ("<duration> hence|ago",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.784189633918261,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.563975538357343), ("day", -1.8152899666382492),
+                                    ("year", -2.662587827025453),
+                                    ("<integer> <unit-of-duration>", -1.0531499145913523),
+                                    ("a <unit-of-duration>", -2.662587827025453),
+                                    ("month", -2.662587827025453),
+                                    ("fortnight", -2.662587827025453)],
+                               n = 18},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("noon|midnight|EOD|end of day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("quarter to|till|before <hour-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("noon|midnight|EOD|end of day", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Father's Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<cycle> after|before <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day (grain)tomorrow", -1.6094379124341003),
+                                    ("dayday", -1.0986122886681098),
+                                    ("day (grain)yesterday", -1.6094379124341003)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.8472978603872037, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -1.466337068793427),
+                                    ("year (grain)Christmas", -1.8718021769015913),
+                                    ("day (grain)intersect", -1.466337068793427),
+                                    ("yearday", -1.8718021769015913)],
+                               n = 3}}),
+       ("Martin Luther King's Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (20..90)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer after|past <hour-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.8109302162163288),
+                                    ("integer (numeric)time-of-day (latent)", -1.0986122886681098),
+                                    ("integer (20..90)time-of-day (latent)", -1.5040773967762742)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month> (ordinal or number) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.916290731874155, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)February", -1.6094379124341003),
+                                    ("integer (numeric)April", -1.6094379124341003),
+                                    ("month", -1.2039728043259361)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)August", -1.791759469228055),
+                                    ("ordinal (digits)April", -1.791759469228055),
+                                    ("month", -1.0986122886681098),
+                                    ("integer (numeric)July", -1.791759469228055)],
+                               n = 3}}),
+       ("<time> <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.10536051565782628,
+                               unseen = -4.382026634673881,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<day-of-month> (ordinal)in|during the <part-of-day>",
+                                     -3.676300671907076),
+                                    ("dayhour", -1.5968591302272404),
+                                    ("Mondayearly morning", -3.270835563798912),
+                                    ("time-of-day (latent)tonight", -3.270835563798912),
+                                    ("hourhour", -2.2900063107871858),
+                                    ("<time-of-day> o'clockin|during the <part-of-day>",
+                                     -3.676300671907076),
+                                    ("todaypart of days", -3.676300671907076),
+                                    ("minutehour", -2.5776883832389665),
+                                    ("at <time-of-day>in|during the <part-of-day>",
+                                     -3.270835563798912),
+                                    ("time-of-day (latent)this <part-of-day>", -3.676300671907076),
+                                    ("Mondayin|during the <part-of-day>", -3.676300671907076),
+                                    ("intersectpart of days", -3.676300671907076),
+                                    ("intersectin|during the <part-of-day>", -3.676300671907076),
+                                    ("<day-of-month> (ordinal or number) of <named-month>in|during the <part-of-day>",
+                                     -3.676300671907076),
+                                    ("the <day-of-month> (ordinal)in|during the <part-of-day>",
+                                     -3.676300671907076),
+                                    ("tomorrowpart of days", -2.5776883832389665),
+                                    ("hh:mmin|during the <part-of-day>", -2.9831534913471307),
+                                    ("time-of-day (latent)in|during the <part-of-day>",
+                                     -3.676300671907076),
+                                    ("hhmm (latent)in|during the <part-of-day>",
+                                     -3.676300671907076),
+                                    ("yesterdaypart of days", -3.676300671907076),
+                                    ("Mondaypart of days", -3.676300671907076)],
+                               n = 27},
+                   koData =
+                     ClassData{prior = -2.3025850929940455,
+                               unseen = -3.4657359027997265,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("yearhour", -2.740840023925201),
+                                    ("monthhour", -2.740840023925201),
+                                    ("hourhour", -2.740840023925201),
+                                    ("past year (latent)in|during the <part-of-day>",
+                                     -2.740840023925201),
+                                    ("Februaryin|during the <part-of-day>", -2.740840023925201),
+                                    ("time-of-day (latent)in|during the <part-of-day>",
+                                     -2.740840023925201)],
+                               n = 3}}),
+       ("today",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("mm/dd",
+        Classifier{okData =
+                     ClassData{prior = -1.6739764335716716,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -0.2076393647782445, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 13}}),
+       ("at <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.10919929196499197,
+                               unseen = -4.7535901911063645,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("noon|midnight|EOD|end of day", -3.6463198396951406),
+                                    ("integer after|past <hour-of-day>", -3.6463198396951406),
+                                    ("half after|past <hour-of-day>", -4.051784947803305),
+                                    ("time-of-day (latent)", -1.8004931491968097),
+                                    ("hhmm (latent)", -3.3586377672433594),
+                                    ("<time-of-day> am|pm", -1.749199854809259),
+                                    ("hh:mm", -3.1354942159291497),
+                                    ("about|exactly <time-of-day>", -3.6463198396951406),
+                                    ("hour", -1.16141318990714),
+                                    ("<time-of-day> sharp|exactly", -4.051784947803305),
+                                    ("minute", -1.8545603704670852)],
+                               n = 52},
+                   koData =
+                     ClassData{prior = -2.268683541318364, unseen = -3.1780538303479458,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.1895840668738362),
+                                    ("hour", -1.1895840668738362)],
+                               n = 6}}),
+       ("absorption of , after named day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.332204510175204,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("on <date>", -2.1972245773362196),
+                                    ("Wednesday", -2.6026896854443837),
+                                    ("Saturday", -2.6026896854443837),
+                                    ("Monday", -2.1972245773362196),
+                                    ("Friday", -1.9095425048844386), ("day", -0.8979415932059586),
+                                    ("Sunday", -2.6026896854443837)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("September",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("tonight",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the ides of <named-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("March", -0.6931471805599453), ("month", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("on <date>",
+        Classifier{okData =
+                     ClassData{prior = -0.1670540846631662, unseen = -4.007333185232471,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("Thursday", -1.791759469228055), ("mm/dd", -3.295836866004329),
+                                    ("absorption of , after named day", -2.890371757896165),
+                                    ("intersect", -2.890371757896165),
+                                    ("Saturday", -2.6026896854443837),
+                                    ("Friday", -3.295836866004329), ("day", -0.8979415932059586),
+                                    ("the <day-of-month> (ordinal)", -2.890371757896165),
+                                    ("intersect by \",\", \"of\", \"from\", \"'s\"",
+                                     -2.6026896854443837),
+                                    ("hour", -3.295836866004329)],
+                               n = 22},
+                   koData =
+                     ClassData{prior = -1.8718021769015913,
+                               unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -1.791759469228055), ("day", -1.2809338454620642),
+                                    ("intersect by \",\", \"of\", \"from\", \"'s\"",
+                                     -1.791759469228055)],
+                               n = 4}}),
+       ("integer (0..19)",
+        Classifier{okData =
+                     ClassData{prior = -2.7398974188114388e-2,
+                               unseen = -3.6375861597263857,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 36},
+                   koData =
+                     ClassData{prior = -3.6109179126442243,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("between <time-of-day> and <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -0.916290731874155),
+                                    ("hh:mmhh:mm", -0.916290731874155)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -0.916290731874155),
+                                    ("minutehour", -0.916290731874155)],
+                               n = 3}}),
+       ("Halloween",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("October",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("month (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> more <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)minute (grain)", -0.6931471805599453),
+                                    ("minute", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> o'clock",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("in|within|after <duration>",
+        Classifier{okData =
+                     ClassData{prior = -5.129329438755058e-2,
+                               unseen = -4.532599493153256,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -3.1354942159291497),
+                                    ("<integer> more <unit-of-duration>", -3.828641396489095),
+                                    ("three-quarters of an hour", -2.91235066461494),
+                                    ("<integer> + '\"", -3.1354942159291497),
+                                    ("number.number hours", -3.828641396489095),
+                                    ("second", -3.4231762883809305), ("day", -3.1354942159291497),
+                                    ("half an hour", -2.91235066461494),
+                                    ("<integer> <unit-of-duration>", -1.749199854809259),
+                                    ("a <unit-of-duration>", -2.91235066461494),
+                                    ("quarter of an hour", -2.91235066461494),
+                                    ("hour", -2.4423470353692043),
+                                    ("about|exactly <duration>", -3.828641396489095),
+                                    ("<integer> and an half hour", -3.828641396489095),
+                                    ("minute", -1.3437347467010947)],
+                               n = 38},
+                   koData =
+                     ClassData{prior = -2.995732273553991, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("quarter", -1.8971199848858813),
+                                    ("<integer> <unit-of-duration>", -2.3025850929940455),
+                                    ("a <unit-of-duration>", -2.3025850929940455)],
+                               n = 2}}),
+       ("three-quarters of an hour",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Wednesday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("half after|past <hour-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> + '\"",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("half <integer> (UK style hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("July",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hour (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.8209805520698302,
+                               unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -0.579818495252942, unseen = -2.772588722239781,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 14}}),
+       ("<ordinal> quarter",
+        Classifier{okData =
+                     ClassData{prior = -0.4700036292457356, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)quarter (grain)", -1.1786549963416462),
+                                    ("ordinals (first..twentieth,thirtieth,...)quarter (grain)",
+                                     -1.466337068793427),
+                                    ("quarter", -0.7731898882334817)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.9808292530117262,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)quarter (grain)", -0.8109302162163288),
+                                    ("quarter", -0.8109302162163288)],
+                               n = 3}}),
+       ("one twenty two",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods =
+                                 HashMap.fromList [("integer (numeric)integer (numeric)", 0.0)],
+                               n = 1}}),
+       ("intersect",
+        Classifier{okData =
+                     ClassData{prior = -0.44313061099463913,
+                               unseen = -6.1092475827643655,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<datetime> - <datetime> (interval)on <date>",
+                                     -4.497584975308154),
+                                    ("<time-of-day> - <time-of-day> (interval)on <date>",
+                                     -4.497584975308154),
+                                    ("hourday", -3.709127614943884),
+                                    ("dayhour", -3.162583908575814),
+                                    ("daymonth", -5.413875707182309),
+                                    ("monthday", -5.0084105990741445),
+                                    ("monthyear", -3.8044377947482086),
+                                    ("Tuesdaythe <day-of-month> (ordinal)", -5.413875707182309),
+                                    ("Christmasyear", -5.413875707182309),
+                                    ("this|next <day-of-week>hh(:mm) - <time-of-day> am|pm",
+                                     -5.413875707182309),
+                                    ("<time-of-day> am|pmintersect by \",\", \"of\", \"from\", \"'s\"",
+                                     -4.720728526622364),
+                                    ("<time-of-day> am|pmintersect", -4.161112738686941),
+                                    ("intersect by \",\", \"of\", \"from\", \"'s\"year",
+                                     -5.413875707182309),
+                                    ("Marchyear", -5.413875707182309),
+                                    ("<named-month>|<named-day> <day-of-month> (ordinal)year",
+                                     -5.0084105990741445),
+                                    ("intersect<time-of-day> am|pm", -5.413875707182309),
+                                    ("Thursdayhh(:mm) - <time-of-day> am|pm", -5.413875707182309),
+                                    ("monthhour", -5.0084105990741445),
+                                    ("last <day-of-week> of <time>year", -5.413875707182309),
+                                    ("todayat <time-of-day>", -5.413875707182309),
+                                    ("Thursday<time> timezone", -5.0084105990741445),
+                                    ("this <time>hh(:mm) - <time-of-day> am|pm",
+                                     -5.413875707182309),
+                                    ("dayday", -3.398972686640044),
+                                    ("Thanksgiving Dayyear", -5.0084105990741445),
+                                    ("<time> <part-of-day>at <time-of-day>", -5.413875707182309),
+                                    ("Tuesdayin <named-month>", -5.413875707182309),
+                                    ("mm/ddat <time-of-day>", -5.413875707182309),
+                                    ("tonightat <time-of-day>", -5.413875707182309),
+                                    ("<time-of-day> am|pmabsorption of , after named day",
+                                     -4.720728526622364),
+                                    ("today<time-of-day> am|pm", -5.413875707182309),
+                                    ("Februarythe <day-of-month> (ordinal)", -5.0084105990741445),
+                                    ("at <time-of-day><time> <part-of-day>", -5.413875707182309),
+                                    ("mm/dd<time-of-day> am|pm", -5.413875707182309),
+                                    ("hourhour", -4.161112738686941),
+                                    ("<time-of-day> am|pmon <date>", -3.398972686640044),
+                                    ("Wednesdaythis|last|next <cycle>", -5.413875707182309),
+                                    ("intersect<named-month> <day-of-month> (non ordinal)",
+                                     -3.909798310406035),
+                                    ("dayyear", -3.2166511298460896),
+                                    ("<time-of-day> o'clockin|during the <part-of-day>",
+                                     -5.413875707182309),
+                                    ("<time-of-day> am|pmtomorrow", -4.720728526622364),
+                                    ("minutehour", -4.497584975308154),
+                                    ("Mother's Dayyear", -5.413875707182309),
+                                    ("at <time-of-day>in|during the <part-of-day>",
+                                     -5.0084105990741445),
+                                    ("absorption of , after named day<named-month> <day-of-month> (non ordinal)",
+                                     -3.709127614943884),
+                                    ("tomorrow<time-of-day> sharp|exactly", -5.413875707182309),
+                                    ("Thursdayfrom <datetime> - <datetime> (interval)",
+                                     -4.497584975308154),
+                                    ("on <date><named-month> <day-of-month> (non ordinal)",
+                                     -5.0084105990741445),
+                                    ("Thursdayfrom <time-of-day> - <time-of-day> (interval)",
+                                     -4.497584975308154),
+                                    ("Mondayin|during the <part-of-day>", -5.413875707182309),
+                                    ("tomorrowfrom <time-of-day> - <time-of-day> (interval)",
+                                     -5.0084105990741445),
+                                    ("intersectin|during the <part-of-day>", -5.413875707182309),
+                                    ("<day-of-month> (ordinal or number) of <named-month>in|during the <part-of-day>",
+                                     -5.413875707182309),
+                                    ("from <time-of-day> - <time-of-day> (interval)on <date>",
+                                     -4.720728526622364),
+                                    ("intersect by \",\", \"of\", \"from\", \"'s\"<time-of-day> am|pm",
+                                     -4.497584975308154),
+                                    ("at <time-of-day>intersect", -5.0084105990741445),
+                                    ("<time-of-day> - <time-of-day> (interval)tomorrow",
+                                     -5.413875707182309),
+                                    ("at <time-of-day>intersect by \",\", \"of\", \"from\", \"'s\"",
+                                     -5.413875707182309),
+                                    ("dayminute", -3.1112906141882632),
+                                    ("from <datetime> - <datetime> (interval)on <date>",
+                                     -5.0084105990741445),
+                                    ("<datetime> - <datetime> (interval)tomorrow",
+                                     -5.413875707182309),
+                                    ("absorption of , after named dayintersect by \",\", \"of\", \"from\", \"'s\"",
+                                     -5.0084105990741445),
+                                    ("<ordinal> <cycle> of <time>year", -5.413875707182309),
+                                    ("minuteday", -2.0126783255201537),
+                                    ("absorption of , after named dayintersect",
+                                     -5.413875707182309),
+                                    ("Octoberyear", -4.161112738686941),
+                                    ("the <day-of-month> (ordinal)in|during the <part-of-day>",
+                                     -5.413875707182309),
+                                    ("at <time-of-day>absorption of , after named day",
+                                     -5.413875707182309),
+                                    ("<day-of-month> (ordinal or number) <named-month>year",
+                                     -5.413875707182309),
+                                    ("year<time-of-day> am|pm", -5.413875707182309),
+                                    ("Septemberyear", -5.0084105990741445),
+                                    ("at <time-of-day>on <date>", -4.315263418514199),
+                                    ("between <time-of-day> and <time-of-day> (interval)on <date>",
+                                     -4.720728526622364),
+                                    ("Halloweenyear", -5.413875707182309),
+                                    ("dayweek", -5.413875707182309),
+                                    ("weekyear", -5.0084105990741445),
+                                    ("hh:mmin|during the <part-of-day>", -4.720728526622364),
+                                    ("Father's Dayyear", -5.413875707182309),
+                                    ("<cycle> after|before <time><time-of-day> am|pm",
+                                     -5.0084105990741445),
+                                    ("February<time> <part-of-day>", -5.413875707182309),
+                                    ("Martin Luther King's Dayyear", -5.0084105990741445),
+                                    ("tomorrowat <time-of-day>", -4.720728526622364),
+                                    ("between <time> and <time>on <date>", -4.720728526622364),
+                                    ("at <time-of-day>tomorrow", -5.0084105990741445),
+                                    ("tomorrow<time-of-day> am|pm", -5.413875707182309),
+                                    ("in|during the <part-of-day>at <time-of-day>",
+                                     -5.413875707182309),
+                                    ("Labor Dayyear", -5.413875707182309),
+                                    ("Februaryintersect", -5.413875707182309),
+                                    ("last <cycle> of <time>year", -4.720728526622364),
+                                    ("<named-month> <day-of-month> (non ordinal)year",
+                                     -5.413875707182309),
+                                    ("yearminute", -5.413875707182309)],
+                               n = 165},
+                   koData =
+                     ClassData{prior = -1.0272875078461794, unseen = -5.717027701406222,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("in <named-month>year", -5.020585624949423),
+                                    ("<time-of-day> - <time-of-day> (interval)on <date>",
+                                     -5.020585624949423),
+                                    ("hourday", -5.020585624949423),
+                                    ("<named-month> <day-of-month> (non ordinal)July",
+                                     -5.020585624949423),
+                                    ("dayhour", -3.228826155721369),
+                                    ("daymonth", -3.005682604407159),
+                                    ("monthday", -4.61512051684126),
+                                    ("monthyear", -4.61512051684126),
+                                    ("intersecthh:mm", -5.020585624949423),
+                                    ("until <time-of-day><time-of-day> am|pm", -5.020585624949423),
+                                    ("<time-of-day> am|pmintersect by \",\", \"of\", \"from\", \"'s\"",
+                                     -4.327438444389479),
+                                    ("<time-of-day> am|pmintersect", -3.767822656454056),
+                                    ("intersect by \",\", \"of\", \"from\", \"'s\"year",
+                                     -4.327438444389479),
+                                    ("Tuesdayafter <time-of-day>", -5.020585624949423),
+                                    ("July<day-of-month> (ordinal or number) <named-month>",
+                                     -5.020585624949423),
+                                    ("absorption of , after named dayJuly", -4.61512051684126),
+                                    ("monthhour", -4.61512051684126),
+                                    ("todayat <time-of-day>", -5.020585624949423),
+                                    ("dayday", -5.020585624949423),
+                                    ("mm/ddat <time-of-day>", -4.61512051684126),
+                                    ("<time-of-day> am|pmon <date>", -3.767822656454056),
+                                    ("dayyear", -4.104294893075269),
+                                    ("Thursdayat <time-of-day>", -5.020585624949423),
+                                    ("August<time-of-day> am|pm", -5.020585624949423),
+                                    ("monthminute", -5.020585624949423),
+                                    ("<time-of-day> am|pmtomorrow", -5.020585624949423),
+                                    ("Thursdayhh:mm", -5.020585624949423),
+                                    ("August<day-of-month> (ordinal or number) <named-month>",
+                                     -5.020585624949423),
+                                    ("minutemonth", -3.5165082281731497),
+                                    ("Thursdayfrom <datetime> - <datetime> (interval)",
+                                     -4.61512051684126),
+                                    ("Thursdayfrom <time-of-day> - <time-of-day> (interval)",
+                                     -4.61512051684126),
+                                    ("Aprilyear", -5.020585624949423),
+                                    ("mm/dd<time-of-day> - <time-of-day> (interval)",
+                                     -4.61512051684126),
+                                    ("tomorrowfrom <time-of-day> - <time-of-day> (interval)",
+                                     -5.020585624949423),
+                                    ("yesterday<time-of-day> am|pm", -5.020585624949423),
+                                    ("intersect by \",\", \"of\", \"from\", \"'s\"hh:mm",
+                                     -4.104294893075269),
+                                    ("<named-month> <day-of-month> (non ordinal)August",
+                                     -5.020585624949423),
+                                    ("until <time-of-day>on <date>", -4.327438444389479),
+                                    ("at <time-of-day>intersect", -4.61512051684126),
+                                    ("at <time-of-day>intersect by \",\", \"of\", \"from\", \"'s\"",
+                                     -5.020585624949423),
+                                    ("dayminute", -3.0746754758941104),
+                                    ("intersectSeptember", -3.5165082281731497),
+                                    ("absorption of , after named dayintersect by \",\", \"of\", \"from\", \"'s\"",
+                                     -5.020585624949423),
+                                    ("minuteday", -2.217225244042889),
+                                    ("absorption of , after named dayintersect",
+                                     -5.020585624949423),
+                                    ("Februaryin|during the <part-of-day>", -5.020585624949423),
+                                    ("yearhh:mm", -5.020585624949423),
+                                    ("hh:mmon <date>", -3.5165082281731497),
+                                    ("absorption of , after named daySeptember",
+                                     -4.104294893075269),
+                                    ("on <date>September", -4.61512051684126),
+                                    ("at <time-of-day>on <date>", -4.61512051684126),
+                                    ("absorption of , after named dayFebruary", -4.104294893075269),
+                                    ("July<integer> to|till|before <hour-of-day>",
+                                     -5.020585624949423),
+                                    ("tomorrowat <time-of-day>", -5.020585624949423),
+                                    ("tomorrow<time-of-day> am|pm", -5.020585624949423),
+                                    ("after <time-of-day><time-of-day> am|pm", -5.020585624949423),
+                                    ("after <time-of-day>year", -5.020585624949423),
+                                    ("yearminute", -5.020585624949423)],
+                               n = 92}}),
+       ("after lunch/work/school",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("early morning",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("in <number> (implicit minutes)",
+        Classifier{okData =
+                     ClassData{prior = -1.3217558399823195,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.2231435513142097),
+                                    ("integer (0..19)", -1.6094379124341003)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -0.3101549283038396,
+                               unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.3448404862917295),
+                                    ("integer (0..19)", -1.2321436812926323)],
+                               n = 22}}),
+       ("<ordinal> <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -2.0149030205422647),
+                                    ("quarteryear", -2.0149030205422647),
+                                    ("ordinals (first..twentieth,thirtieth,...)day (grain)October",
+                                     -2.0149030205422647),
+                                    ("ordinals (first..twentieth,thirtieth,...)week (grain)intersect",
+                                     -2.0149030205422647),
+                                    ("weekmonth", -1.6094379124341003),
+                                    ("ordinal (digits)quarter (grain)year", -2.0149030205422647),
+                                    ("ordinals (first..twentieth,thirtieth,...)week (grain)October",
+                                     -2.0149030205422647)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.0910424533583156,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 20},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("from <datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.8472978603872037,
+                               unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.4816045409242156),
+                                    ("hh:mmhh:mm", -1.4816045409242156),
+                                    ("<time-of-day> am|pmtime-of-day (latent)",
+                                     -2.3978952727983707),
+                                    ("hourhour", -1.9924301646902063),
+                                    ("<time-of-day> am|pm<time-of-day> am|pm",
+                                     -2.3978952727983707)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.5596157879354228, unseen = -3.295836866004329,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -1.6486586255873816),
+                                    ("minuteminute", -2.159484249353372),
+                                    ("time-of-day (latent)time-of-day (latent)",
+                                     -2.5649493574615367),
+                                    ("hourhour", -2.159484249353372),
+                                    ("minutehour", -1.6486586255873816),
+                                    ("hh:mmintersect", -2.159484249353372),
+                                    ("time-of-day (latent)<time-of-day> am|pm",
+                                     -2.5649493574615367)],
+                               n = 8}}),
+       ("Saturday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week (grain)<named-month>|<named-day> <day-of-month> (ordinal)",
+                                     -1.7346010553881064),
+                                    ("weekmonth", -1.7346010553881064),
+                                    ("week (grain)October", -1.7346010553881064),
+                                    ("week (grain)<named-month> <day-of-month> (non ordinal)",
+                                     -1.7346010553881064),
+                                    ("weekday", -1.2237754316221157)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("number.number hours",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("from <time-of-day> - <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.6359887667199967,
+                               unseen = -3.4339872044851463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.6094379124341003),
+                                    ("hh:mmhh:mm", -1.6094379124341003),
+                                    ("<time-of-day> am|pmtime-of-day (latent)", -2.70805020110221),
+                                    ("hourhour", -2.3025850929940455),
+                                    ("hourminute", -2.3025850929940455),
+                                    ("time-of-day (latent)<time-of-day> sharp|exactly",
+                                     -2.70805020110221),
+                                    ("time-of-day (latent)hh:mm", -2.70805020110221),
+                                    ("<time-of-day> am|pm<time-of-day> am|pm", -2.70805020110221)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -0.7537718023763802, unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -1.540445040947149),
+                                    ("time-of-day (latent)time-of-day (latent)",
+                                     -2.2335922215070942),
+                                    ("hourhour", -1.9459101490553135),
+                                    ("minutehour", -1.540445040947149),
+                                    ("time-of-day (latent)<time-of-day> am|pm",
+                                     -2.639057329615259)],
+                               n = 8}}),
+       ("integer 21..99",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods =
+                                 HashMap.fromList [("integer (numeric)integer (numeric)", 0.0)],
+                               n = 2}}),
+       ("last|next n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.430816798843313,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.627081138568543),
+                                    ("integer (0..19)year (grain)", -3.3202283191284883),
+                                    ("integer (numeric)day (grain)", -3.0325462466767075),
+                                    ("integer (0..19)second (grain) ", -3.3202283191284883),
+                                    ("integer (0..19)hour (grain)", -3.3202283191284883),
+                                    ("second", -2.8094026953624978),
+                                    ("integer (numeric)second (grain) ", -3.3202283191284883),
+                                    ("integer (numeric)year (grain)", -3.3202283191284883),
+                                    ("day", -2.472930458741285), ("year", -2.8094026953624978),
+                                    ("integer (numeric)week (grain)", -3.0325462466767075),
+                                    ("integer (0..19)month (grain)", -3.3202283191284883),
+                                    ("hour", -2.8094026953624978), ("month", -2.8094026953624978),
+                                    ("integer (numeric)minute (grain)", -3.3202283191284883),
+                                    ("integer (0..19)minute (grain)", -3.3202283191284883),
+                                    ("integer (numeric)month (grain)", -3.3202283191284883),
+                                    ("minute", -2.8094026953624978),
+                                    ("integer (numeric)hour (grain)", -3.3202283191284883),
+                                    ("integer (0..19)day (grain)", -3.0325462466767075),
+                                    ("integer (0..19)week (grain)", -3.3202283191284883)],
+                               n = 31},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -3.0910424533583156,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("yyyy-mm-dd",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("mm/dd/yyyy",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Memorial Day",
+        Classifier{okData =
+                     ClassData{prior = -0.2231435513142097, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -1.6094379124341003,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("Monday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<ordinal> quarter <year>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("quarteryear", -0.6931471805599453),
+                                    ("ordinal (digits)quarter (grain)year", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh:mm:ss",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> <integer>",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("about|exactly <time-of-day>integer (numeric)",
+                                     -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1}}),
+       ("nth <time> of <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.4700036292457356, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -0.9808292530117262),
+                                    ("ordinals (first..twentieth,thirtieth,...)Tuesdayintersect",
+                                     -2.0794415416798357),
+                                    ("ordinals (first..twentieth,thirtieth,...)Wednesdayintersect",
+                                     -1.6739764335716716),
+                                    ("ordinals (first..twentieth,thirtieth,...)TuesdayOctober",
+                                     -1.6739764335716716)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.9808292530117262,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.0986122886681098),
+                                    ("ordinals (first..twentieth,thirtieth,...)TuesdaySeptember",
+                                     -1.791759469228055),
+                                    ("ordinals (first..twentieth,thirtieth,...)WednesdayOctober",
+                                     -1.3862943611198906)],
+                               n = 3}}),
+       ("Valentine's Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("April",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("end of month",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("week (grain)",
+        Classifier{okData =
+                     ClassData{prior = -2.7398974188114388e-2,
+                               unseen = -3.6375861597263857,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 36},
+                   koData =
+                     ClassData{prior = -3.6109179126442243,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("<part-of-day> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("part of daysintersect by \",\", \"of\", \"from\", \"'s\"",
+                                     -1.6094379124341003),
+                                    ("hourday", -0.916290731874155),
+                                    ("part of daysthe <day-of-month> (ordinal)",
+                                     -1.6094379124341003),
+                                    ("part of daysthe <day-of-month> (number)",
+                                     -1.6094379124341003)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("past year (latent)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 1}}),
+       ("now",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month> (ordinal or number) of <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.13353139262452263,
+                               unseen = -3.0910424533583156,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)July", -2.3513752571634776),
+                                    ("ordinals (first..twentieth,thirtieth,...)March",
+                                     -2.3513752571634776),
+                                    ("ordinal (digits)February", -2.3513752571634776),
+                                    ("integer (numeric)February", -1.9459101490553135),
+                                    ("month", -0.9650808960435872),
+                                    ("ordinal (digits)March", -2.3513752571634776),
+                                    ("integer (numeric)July", -2.3513752571634776)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -2.0794415416798357,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)February", -1.5040773967762742),
+                                    ("month", -1.5040773967762742)],
+                               n = 1}}),
+       ("this <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.6931471805599453),
+                                    ("part of days", -0.6931471805599453)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Friday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("in|during the <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -8.701137698962981e-2,
+                               unseen = -3.258096538021482,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("early morning", -2.5257286443082556),
+                                    ("hour", -0.7339691750802004),
+                                    ("part of days", -0.8209805520698302)],
+                               n = 11},
+                   koData =
+                     ClassData{prior = -2.4849066497880004, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.916290731874155),
+                                    ("part of days", -0.916290731874155)],
+                               n = 1}}),
+       ("tomorrow",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.833213344056216,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 15},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh(:mm) - <time-of-day> am|pm",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this|last|next <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.11122563511022437,
+                               unseen = -4.3694478524670215,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.5234954826333758),
+                                    ("month (grain)", -2.9704144655697013),
+                                    ("year (grain)", -1.8718021769015913),
+                                    ("week (grain)", -1.5234954826333758),
+                                    ("quarter", -2.7472709142554916), ("year", -1.8718021769015913),
+                                    ("month", -2.9704144655697013),
+                                    ("quarter (grain)", -2.7472709142554916)],
+                               n = 34},
+                   koData =
+                     ClassData{prior = -2.2512917986064953,
+                               unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.791759469228055),
+                                    ("week (grain)", -1.791759469228055),
+                                    ("day", -1.791759469228055),
+                                    ("day (grain)", -1.791759469228055)],
+                               n = 4}}),
+       ("Mother's Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("New Year's Eve",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the <ordinal> <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("quarteryear", -0.6931471805599453),
+                                    ("ordinal (digits)quarter (grain)year", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("by <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("noon|midnight|EOD|end of day", -2.1972245773362196),
+                                    ("end of month", -2.1972245773362196),
+                                    ("time-of-day (latent)", -2.1972245773362196),
+                                    ("<time-of-day> am|pm", -2.1972245773362196),
+                                    ("hh:mm", -2.1972245773362196), ("hour", -1.791759469228055),
+                                    ("month", -2.1972245773362196), ("minute", -1.791759469228055)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("half an hour",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the <day-of-month> (ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -0.2006706954621511,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..twentieth,thirtieth,...)",
+                                     -1.7047480922384253),
+                                    ("ordinal (digits)", -0.2006706954621511)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -1.7047480922384253,
+                               unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList [("ordinal (digits)", -0.2876820724517809)],
+                               n = 2}}),
+       ("the <day-of-month> (number)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Sunday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("February",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 13},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("minute (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the <ordinal> quarter",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)quarter (grain)", -0.6931471805599453),
+                                    ("quarter", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)quarter (grain)", -0.6931471805599453),
+                                    ("quarter", -0.6931471805599453)],
+                               n = 1}}),
+       ("time-of-day (latent)",
+        Classifier{okData =
+                     ClassData{prior = -0.6330432564902398, unseen = -4.143134726391533,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.10178269430994236),
+                                    ("integer (0..19)", -2.3353749158170367)],
+                               n = 60},
+                   koData =
+                     ClassData{prior = -0.7570959051602187, unseen = -4.02535169073515,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.15718558352241238),
+                                    ("integer (0..19)", -1.927891643552635)],
+                               n = 53}}),
+       ("year",
+        Classifier{okData =
+                     ClassData{prior = -0.2231435513142097,
+                               unseen = -3.4011973816621555,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 28},
+                   koData =
+                     ClassData{prior = -1.6094379124341003,
+                               unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 7}}),
+       ("last <day-of-week> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -0.916290731874155),
+                                    ("SundayMarch", -1.6094379124341003),
+                                    ("MondayMarch", -1.6094379124341003),
+                                    ("Sundayintersect", -1.6094379124341003)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.8209805520698302, unseen = -4.499809670330265,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.2914117923959205),
+                                    ("integer (0..19)year (grain)", -3.1023420086122493),
+                                    ("integer (numeric)day (grain)", -2.5427262206768266),
+                                    ("integer (0..19)second (grain) ", -3.7954891891721947),
+                                    ("integer (0..19)hour (grain)", -2.6968769005040847),
+                                    ("second", -3.7954891891721947),
+                                    ("integer (numeric)year (grain)", -3.7954891891721947),
+                                    ("day", -2.5427262206768266), ("year", -2.8791984572980396),
+                                    ("integer (numeric)week (grain)", -2.8791984572980396),
+                                    ("integer (0..19)month (grain)", -3.39002408106403),
+                                    ("hour", -2.409194828052304), ("month", -3.39002408106403),
+                                    ("integer (numeric)minute (grain)", -2.8791984572980396),
+                                    ("integer (0..19)minute (grain)", -3.7954891891721947),
+                                    ("minute", -2.6968769005040847),
+                                    ("integer (numeric)hour (grain)", -3.39002408106403),
+                                    ("integer (0..19)week (grain)", -2.8791984572980396)],
+                               n = 33},
+                   koData =
+                     ClassData{prior = -0.579818495252942, unseen = -4.68213122712422,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.8810693652338513),
+                                    ("integer (0..19)year (grain)", -3.5742165457937967),
+                                    ("integer (numeric)day (grain)", -3.2865344733420154),
+                                    ("integer (0..19)second (grain) ", -3.5742165457937967),
+                                    ("integer (0..19)hour (grain)", -3.5742165457937967),
+                                    ("second", -3.0633909220278057),
+                                    ("integer (numeric)second (grain) ", -3.5742165457937967),
+                                    ("integer (numeric)year (grain)", -3.5742165457937967),
+                                    ("day", -2.7269186854065928), ("quarter", -3.979681653901961),
+                                    ("year", -3.0633909220278057),
+                                    ("integer (numeric)week (grain)", -3.2865344733420154),
+                                    ("integer (0..19)month (grain)", -3.5742165457937967),
+                                    ("hour", -1.9647786333596962), ("month", -3.0633909220278057),
+                                    ("integer (numeric)minute (grain)", -3.5742165457937967),
+                                    ("integer (0..19)minute (grain)", -3.5742165457937967),
+                                    ("integer (numeric)month (grain)", -3.5742165457937967),
+                                    ("minute", -3.0633909220278057),
+                                    ("integer (numeric)hour (grain)", -2.1078794770003695),
+                                    ("integer (0..19)day (grain)", -3.2865344733420154),
+                                    ("integer (0..19)week (grain)", -3.5742165457937967),
+                                    ("integer (0..19)quarter (grain)", -3.979681653901961)],
+                               n = 42}}),
+       ("hhmm (latent)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> am|pm",
+        Classifier{okData =
+                     ClassData{prior = -0.29354719190417905,
+                               unseen = -5.231108616854587,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer after|past <hour-of-day>", -3.8394523125933104),
+                                    ("at <time-of-day>", -2.2300144001592104),
+                                    ("<time-of-day> o'clock", -4.127134385045092),
+                                    ("half after|past <hour-of-day>", -4.127134385045092),
+                                    ("time-of-day (latent)", -1.7600107709134747),
+                                    ("hhmm (latent)", -4.532599493153256),
+                                    ("hh:mm", -2.181224235989778),
+                                    ("quarter after|past <hour-of-day>", -4.532599493153256),
+                                    ("about|exactly <time-of-day>", -4.532599493153256),
+                                    ("until <time-of-day>", -3.8394523125933104),
+                                    ("hour", -1.2939210409888755),
+                                    ("<time-of-day> sharp|exactly", -4.532599493153256),
+                                    ("minute", -1.6422277352570913),
+                                    ("after <time-of-day>", -4.532599493153256)],
+                               n = 85},
+                   koData =
+                     ClassData{prior = -1.3689026184080213, unseen = -4.31748811353631,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> to|till|before <hour-of-day>", -3.20545280453606),
+                                    ("<hour-of-day> <integer>", -3.6109179126442243),
+                                    ("time-of-day (latent)", -1.0459685551826876),
+                                    ("hour", -1.0082282271998406), ("minute", -2.917770732084279),
+                                    ("after <time-of-day>", -3.6109179126442243)],
+                               n = 29}}),
+       ("Thanksgiving Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("a <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.6286086594223742,
+                               unseen = -3.4339872044851463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.0149030205422647),
+                                    ("year (grain)", -2.70805020110221),
+                                    ("second", -2.70805020110221),
+                                    ("week (grain)", -2.0149030205422647),
+                                    ("day", -2.3025850929940455),
+                                    ("minute (grain)", -2.70805020110221),
+                                    ("year", -2.70805020110221),
+                                    ("second (grain) ", -2.70805020110221),
+                                    ("minute", -2.70805020110221),
+                                    ("day (grain)", -2.3025850929940455)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -0.7621400520468967, unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour (grain)", -1.7227665977411035),
+                                    ("quarter", -1.9459101490553135), ("hour", -1.7227665977411035),
+                                    ("quarter (grain)", -1.9459101490553135)],
+                               n = 7}}),
+       ("hh:mm",
+        Classifier{okData =
+                     ClassData{prior = -5.406722127027582e-2,
+                               unseen = -4.02535169073515,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 54},
+                   koData =
+                     ClassData{prior = -2.9444389791664407,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3}}),
+       ("quarter of an hour",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("second (grain) ",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinals (first..twentieth,thirtieth,...)",
+        Classifier{okData =
+                     ClassData{prior = -6.899287148695143e-2,
+                               unseen = -2.772588722239781,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 14},
+                   koData =
+                     ClassData{prior = -2.70805020110221, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("quarter after|past <hour-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the <cycle> after|before <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day (grain)tomorrow", -1.252762968495368),
+                                    ("dayday", -0.8472978603872037),
+                                    ("day (grain)yesterday", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("about|exactly <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.11778303565638351,
+                               unseen = -3.332204510175204,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.6026896854443837),
+                                    ("hh(:mm) - <time-of-day> am|pm", -2.6026896854443837),
+                                    ("this|last|next <cycle>", -2.6026896854443837),
+                                    ("day", -2.1972245773362196),
+                                    ("time-of-day (latent)", -2.6026896854443837),
+                                    ("hhmm (latent)", -2.1972245773362196),
+                                    ("<time-of-day> am|pm", -2.6026896854443837),
+                                    ("hour", -1.9095425048844386),
+                                    ("next <time>", -2.6026896854443837),
+                                    ("this|next <day-of-week>", -2.6026896854443837),
+                                    ("minute", -2.1972245773362196)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -2.1972245773362196, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.8718021769015913),
+                                    ("hour", -1.8718021769015913)],
+                               n = 1}}),
+       ("intersect by \",\", \"of\", \"from\", \"'s\"",
+        Classifier{okData =
+                     ClassData{prior = -0.41773520069997866,
+                               unseen = -5.1298987149230735,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("Wednesday<named-month> <day-of-month> (non ordinal)",
+                                     -4.430816798843313),
+                                    ("dayhour", -3.1780538303479458),
+                                    ("daymonth", -3.1780538303479458),
+                                    ("<named-month> <day-of-month> (non ordinal)Friday",
+                                     -4.430816798843313),
+                                    ("Friday<named-month> <day-of-month> (non ordinal)",
+                                     -3.7376696182833684),
+                                    ("Wednesdayintersect", -4.430816798843313),
+                                    ("Labor Daythis|last|next <cycle>", -4.430816798843313),
+                                    ("intersect by \",\", \"of\", \"from\", \"'s\"year",
+                                     -4.430816798843313),
+                                    ("<part-of-day> of <time>February", -4.430816798843313),
+                                    ("Saturday<time-of-day> am|pm", -4.430816798843313),
+                                    ("Martin Luther King's Daythis|last|next <cycle>",
+                                     -4.430816798843313),
+                                    ("on <date><time-of-day> am|pm", -4.430816798843313),
+                                    ("hourmonth", -4.430816798843313),
+                                    ("intersect by \",\", \"of\", \"from\", \"'s\"intersect",
+                                     -4.430816798843313),
+                                    ("dayday", -2.559014621941722),
+                                    ("the <day-of-month> (ordinal)February", -4.02535169073515),
+                                    ("WednesdayOctober", -4.430816798843313),
+                                    ("Wednesdaythis|last|next <cycle>", -4.02535169073515),
+                                    ("intersect<named-month> <day-of-month> (non ordinal)",
+                                     -3.5145260669691587),
+                                    ("dayyear", -2.9267394020670396),
+                                    ("Saturday<named-month> <day-of-month> (non ordinal)",
+                                     -4.430816798843313),
+                                    ("<named-month> <day-of-month> (non ordinal)intersect",
+                                     -4.430816798843313),
+                                    ("Thursdayhh:mm", -4.02535169073515),
+                                    ("Thanksgiving Daythis|last|next <cycle>", -4.430816798843313),
+                                    ("Memorial Daythis|last|next <cycle>", -4.430816798843313),
+                                    ("on <date><named-month> <day-of-month> (non ordinal)",
+                                     -4.02535169073515),
+                                    ("TuesdayOctober", -4.430816798843313),
+                                    ("the <day-of-month> (ordinal)March", -4.430816798843313),
+                                    ("Mondaythis|last|next <cycle>", -4.430816798843313),
+                                    ("Fridayintersect by \",\", \"of\", \"from\", \"'s\"",
+                                     -4.02535169073515),
+                                    ("Fridayintersect", -4.430816798843313),
+                                    ("Thursday<datetime> - <datetime> (interval)",
+                                     -3.7376696182833684),
+                                    ("Thursday<time-of-day> - <time-of-day> (interval)",
+                                     -3.5145260669691587),
+                                    ("Tuesdaythis|last|next <cycle>", -4.430816798843313),
+                                    ("Sunday<named-month> <day-of-month> (non ordinal)",
+                                     -4.430816798843313),
+                                    ("dayminute", -2.639057329615259),
+                                    ("intersectyear", -4.430816798843313),
+                                    ("minuteday", -3.5145260669691587),
+                                    ("this|last|next <cycle>Sunday", -4.430816798843313),
+                                    ("Sundaythis|last|next <cycle>", -4.430816798843313),
+                                    ("intersectintersect", -4.430816798843313),
+                                    ("weekday", -4.430816798843313),
+                                    ("dayweek", -3.332204510175204),
+                                    ("Thursday<time-of-day> am|pm", -4.430816798843313),
+                                    ("Monday<named-month> <day-of-month> (non ordinal)",
+                                     -4.02535169073515),
+                                    ("<named-month> <day-of-month> (non ordinal)year",
+                                     -4.02535169073515)],
+                               n = 54},
+                   koData =
+                     ClassData{prior = -1.074514737089049, unseen = -4.762173934797756,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.8632184332102),
+                                    ("TuesdaySeptember", -4.060443010546419),
+                                    ("Wednesdayintersect", -4.060443010546419),
+                                    ("intersect by \",\", \"of\", \"from\", \"'s\"intersect",
+                                     -4.060443010546419),
+                                    ("SundayFebruary", -4.060443010546419),
+                                    ("WednesdayOctober", -4.060443010546419),
+                                    ("FridayJuly", -3.654977902438255),
+                                    ("<named-month> <day-of-month> (non ordinal)intersect",
+                                     -4.060443010546419),
+                                    ("FridaySeptember", -4.060443010546419),
+                                    ("WednesdayFebruary", -4.060443010546419),
+                                    ("minutemonth", -3.144152278672264),
+                                    ("SundayMarch", -4.060443010546419),
+                                    ("Fridayintersect by \",\", \"of\", \"from\", \"'s\"",
+                                     -4.060443010546419),
+                                    ("MondayFebruary", -3.654977902438255),
+                                    ("Fridayintersect", -4.060443010546419),
+                                    ("Thursday<time-of-day> - <time-of-day> (interval)",
+                                     -3.654977902438255),
+                                    ("dayminute", -2.6741486494265287),
+                                    ("SaturdaySeptember", -4.060443010546419),
+                                    ("intersectSeptember", -3.144152278672264),
+                                    ("MondayMarch", -4.060443010546419),
+                                    ("on <date>September", -3.654977902438255),
+                                    ("intersectintersect", -4.060443010546419),
+                                    ("Tuesdayintersect", -4.060443010546419),
+                                    ("Sundayintersect", -4.060443010546419)],
+                               n = 28}}),
+       ("last <time>",
+        Classifier{okData =
+                     ClassData{prior = -1.2729656758128873, unseen = -3.295836866004329,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("Father's Day", -2.5649493574615367),
+                                    ("Martin Luther King's Day", -2.5649493574615367),
+                                    ("Memorial Day", -2.5649493574615367),
+                                    ("Mother's Day", -2.5649493574615367),
+                                    ("day", -1.3121863889661687), ("Sunday", -2.5649493574615367),
+                                    ("hour", -2.5649493574615367), ("Tuesday", -2.5649493574615367),
+                                    ("week-end", -2.5649493574615367)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -0.3285040669720361, unseen = -3.891820298110627,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("Monday", -3.1780538303479458), ("day", -2.2617630984737906),
+                                    ("Sunday", -3.1780538303479458),
+                                    ("time-of-day (latent)", -1.1631508098056809),
+                                    ("intersect by \",\", \"of\", \"from\", \"'s\"",
+                                     -2.772588722239781),
+                                    ("hour", -1.1631508098056809)],
+                               n = 18}}),
+       ("March",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.772588722239781,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 14},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-month>|<named-day> <day-of-month> (ordinal)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.0910424533583156,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("Octoberordinal (digits)", -1.9459101490553135),
+                                    ("Thursdayordinal (digits)", -2.3513752571634776),
+                                    ("day", -1.9459101490553135),
+                                    ("Marchordinals (first..twentieth,thirtieth,...)",
+                                     -1.9459101490553135),
+                                    ("Tuesdayordinal (digits)", -2.3513752571634776),
+                                    ("month", -1.252762968495368),
+                                    ("Marchordinal (digits)", -2.3513752571634776)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Labor Day weekend",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month> (ordinal)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("ordinal (digits)", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Christmas",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("until <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.9808292530117262, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -2.3025850929940455),
+                                    ("<time-of-day> am|pm", -1.6094379124341003),
+                                    ("hh:mm", -1.8971199848858813), ("hour", -1.8971199848858813),
+                                    ("minute", -1.3862943611198906)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.4700036292457356, unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -1.7227665977411035),
+                                    ("yesterday", -2.2335922215070942),
+                                    ("day", -2.2335922215070942), ("hh:mm", -1.7227665977411035),
+                                    ("hour", -2.639057329615259), ("minute", -1.252762968495368)],
+                               n = 10}}),
+       ("<duration> after|before|from <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.0910424533583156,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("a <unit-of-duration>now", -2.3513752571634776),
+                                    ("a <unit-of-duration>Christmas", -2.3513752571634776),
+                                    ("<integer> <unit-of-duration>today", -2.3513752571634776),
+                                    ("secondsecond", -2.3513752571634776),
+                                    ("daysecond", -2.3513752571634776),
+                                    ("<integer> <unit-of-duration>Christmas", -2.3513752571634776),
+                                    ("yearday", -1.6582280766035324),
+                                    ("minutesecond", -2.3513752571634776),
+                                    ("<integer> <unit-of-duration>now", -1.9459101490553135)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Independence Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("decimal number",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("next <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.5263605246161616,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("Martin Luther King's Day", -2.803360380906535),
+                                    ("Halloween", -2.803360380906535),
+                                    ("Wednesday", -2.803360380906535),
+                                    ("Memorial Day", -2.803360380906535),
+                                    ("Monday", -2.803360380906535),
+                                    ("Mother's Day", -2.803360380906535),
+                                    ("day", -1.0986122886681098),
+                                    ("Thanksgiving Day", -2.803360380906535),
+                                    ("March", -2.803360380906535), ("month", -2.803360380906535),
+                                    ("Tuesday", -2.1102132003465894)],
+                               n = 11},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.4849066497880004,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> sharp|exactly",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -2.1400661634962708),
+                                    ("time-of-day (latent)", -2.1400661634962708),
+                                    ("hhmm (latent)", -2.1400661634962708),
+                                    ("<time-of-day> am|pm", -2.1400661634962708),
+                                    ("hh:mm", -2.1400661634962708), ("hour", -1.7346010553881064),
+                                    ("minute", -1.4469189829363254)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Memorial Day Weekend",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("negative numbers",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.995732273553991,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 18}}),
+       ("about|exactly <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("half an hour", -0.6931471805599453),
+                                    ("minute", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> before last|after next",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("Wednesday", -1.8718021769015913),
+                                    ("Friday", -1.466337068793427), ("day", -1.1786549963416462),
+                                    ("March", -1.8718021769015913), ("month", -1.8718021769015913)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("by the end of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("this|last|next <cycle>", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hhmm (military) am|pm",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.48550781578170077,
+                               unseen = -3.891820298110627,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.4733057381095205),
+                                    ("hh:mmhh:mm", -1.6739764335716716),
+                                    ("dayday", -2.772588722239781),
+                                    ("hourhour", -2.2617630984737906),
+                                    ("<named-month> <day-of-month> (non ordinal)<named-month> <day-of-month> (non ordinal)",
+                                     -2.772588722239781),
+                                    ("intersect by \",\", \"of\", \"from\", \"'s\"hh:mm",
+                                     -2.772588722239781),
+                                    ("intersect by \",\", \"of\", \"from\", \"'s\"<time-of-day> am|pm",
+                                     -3.1780538303479458),
+                                    ("<time-of-day> am|pm<time-of-day> am|pm",
+                                     -2.4849066497880004)],
+                               n = 16},
+                   koData =
+                     ClassData{prior = -0.9555114450274363,
+                               unseen = -3.6109179126442243,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<named-month> <day-of-month> (non ordinal)July",
+                                     -2.890371757896165),
+                                    ("daymonth", -2.4849066497880004),
+                                    ("<time-of-day> am|pmintersect", -2.890371757896165),
+                                    ("hh:mm<time-of-day> am|pm", -2.4849066497880004),
+                                    ("minuteminute", -1.9740810260220096),
+                                    ("hourhour", -2.4849066497880004),
+                                    ("minutehour", -2.4849066497880004),
+                                    ("hh:mmintersect", -1.9740810260220096),
+                                    ("<named-month> <day-of-month> (non ordinal)August",
+                                     -2.890371757896165),
+                                    ("about|exactly <time-of-day><time-of-day> am|pm",
+                                     -2.890371757896165)],
+                               n = 10}}),
+       ("Tuesday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("New Year's Day",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("fortnight",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> and an half hour",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("in <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("October", -1.252762968495368), ("March", -1.252762968495368),
+                                    ("month", -0.8472978603872037)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -1.0986122886681098, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("October", -0.916290731874155), ("month", -0.916290731874155)],
+                               n = 1}}),
+       ("<time-of-day> - <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.8873031950009028,
+                               unseen = -3.7376696182833684,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.5163474893680884),
+                                    ("hh:mmhh:mm", -1.5163474893680884),
+                                    ("<time-of-day> am|pmtime-of-day (latent)", -2.327277705584417),
+                                    ("hourhour", -1.7676619176489945),
+                                    ("<time-of-day> am|pm<time-of-day> am|pm", -2.327277705584417)],
+                               n = 14},
+                   koData =
+                     ClassData{prior = -0.5306282510621704,
+                               unseen = -3.9889840465642745,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("about|exactly <time-of-day>time-of-day (latent)",
+                                     -3.2771447329921766),
+                                    ("hh:mmtime-of-day (latent)", -1.5723966407537513),
+                                    ("hh:mm<time-of-day> am|pm", -2.871679624884012),
+                                    ("<time-of-day> am|pmtime-of-day (latent)",
+                                     -3.2771447329921766),
+                                    ("at <time-of-day><time-of-day> am|pm", -3.2771447329921766),
+                                    ("hourhour", -2.0243817644968085),
+                                    ("minutehour", -1.262241712449912),
+                                    ("about|exactly <time-of-day><time-of-day> am|pm",
+                                     -3.2771447329921766),
+                                    ("at <time-of-day>time-of-day (latent)", -2.871679624884012),
+                                    ("<integer> to|till|before <hour-of-day>time-of-day (latent)",
+                                     -2.871679624884012)],
+                               n = 20}}),
+       ("nth <time> after <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayday", -0.916290731874155),
+                                    ("ordinals (first..twentieth,thirtieth,...)Tuesdayintersect",
+                                     -0.916290731874155)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayday", -0.916290731874155),
+                                    ("ordinals (first..twentieth,thirtieth,...)TuesdayChristmas",
+                                     -0.916290731874155)],
+                               n = 1}}),
+       ("<named-month> <day-of-month> (non ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -0.25131442828090605,
+                               unseen = -3.9318256327243257,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("Augustinteger (numeric)", -2.5257286443082556),
+                                    ("Marchinteger (numeric)", -2.8134107167600364),
+                                    ("Aprilinteger (numeric)", -3.2188758248682006),
+                                    ("month", -0.8209805520698302),
+                                    ("Februaryinteger (numeric)", -1.9661128563728327),
+                                    ("Septemberinteger (numeric)", -2.8134107167600364),
+                                    ("Octoberinteger (numeric)", -2.8134107167600364),
+                                    ("Julyinteger (numeric)", -2.120263536200091)],
+                               n = 21},
+                   koData =
+                     ClassData{prior = -1.5040773967762742, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("Marchinteger (numeric)", -2.3025850929940455),
+                                    ("Aprilinteger (numeric)", -2.3025850929940455),
+                                    ("month", -1.0498221244986778),
+                                    ("Julyinteger (numeric)", -1.3862943611198906)],
+                               n = 6}}),
+       ("this|next <day-of-week>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("Thursday", -2.2512917986064953),
+                                    ("Wednesday", -2.2512917986064953),
+                                    ("Monday", -1.845826690498331), ("day", -0.8649974374866046),
+                                    ("Tuesday", -1.55814461804655)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinal (digits)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1780538303479458,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 22},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("quarter (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12},
+                   koData =
+                     ClassData{prior = -1.3862943611198906, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4}}),
+       ("last <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day (grain)October", -1.791759469228055),
+                                    ("daymonth", -1.2809338454620642),
+                                    ("day (grain)intersect", -1.791759469228055),
+                                    ("weekmonth", -1.791759469228055),
+                                    ("week (grain)intersect", -2.1972245773362196),
+                                    ("week (grain)September", -2.1972245773362196)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("seasons",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month>(ordinal) <named-month> year",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)April", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Labor Day",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("week-end",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("after <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -1.6094379124341003,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.7047480922384253),
+                                    ("<time-of-day> am|pm", -1.7047480922384253),
+                                    ("hour", -1.2992829841302609)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.2231435513142097,
+                               unseen = -3.1780538303479458,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -2.03688192726104),
+                                    ("tomorrow", -2.03688192726104), ("day", -1.3437347467010947),
+                                    ("time-of-day (latent)", -2.4423470353692043),
+                                    ("<time-of-day> am|pm", -2.4423470353692043),
+                                    ("Christmas", -2.03688192726104), ("hour", -1.749199854809259)],
+                               n = 8}}),
+       ("day (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1354942159291497,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 21},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("between <time> and <time>",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.0116009116784799),
+                                    ("hh:mmhh:mm", -1.0116009116784799)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -1.4469189829363254),
+                                    ("minuteminute", -1.4469189829363254),
+                                    ("minutehour", -1.4469189829363254),
+                                    ("hh:mmintersect", -1.4469189829363254)],
+                               n = 6}}),
+       ("<month> dd-dd (interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("July", -0.6931471805599453), ("month", -0.6931471805599453)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("part of days",
+        Classifier{okData =
+                     ClassData{prior = -4.445176257083381e-2,
+                               unseen = -3.1780538303479458,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 22},
+                   koData =
+                     ClassData{prior = -3.1354942159291497,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("this <time>",
+        Classifier{okData =
+                     ClassData{prior = -8.701137698962981e-2,
+                               unseen = -3.4965075614664802,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("Thursday", -2.772588722239781),
+                                    ("Martin Luther King's Day", -2.772588722239781),
+                                    ("intersect", -2.772588722239781),
+                                    ("Monday", -2.772588722239781), ("day", -1.3862943611198906),
+                                    ("Thanksgiving Day", -2.772588722239781),
+                                    ("hour", -1.8562979903656263), ("seasons", -2.0794415416798357),
+                                    ("week-end", -2.772588722239781),
+                                    ("part of days", -2.367123614131617)],
+                               n = 11},
+                   koData =
+                     ClassData{prior = -2.4849066497880004,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -1.791759469228055),
+                                    ("part of days", -1.791759469228055)],
+                               n = 1}}),
+       ("August",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}})]
diff --git a/Duckling/Ranking/Classifiers/ES.hs b/Duckling/Ranking/Classifiers/ES.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/ES.hs
@@ -0,0 +1,1160 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.ES (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers
+  = HashMap.fromList
+      [("midnight",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> timezone",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("a las <time-of-day>", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (numeric)",
+        Classifier{okData =
+                     ClassData{prior = -0.5839478885949533, unseen = -4.0943445622221,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 58},
+                   koData =
+                     ClassData{prior = -0.8157495026522777, unseen = -3.871201010907891,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 46}}),
+       ("the day before yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh(:|.|h)mm (time-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-month|named-day> past",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("el <time>", -1.2992829841302609),
+                                    ("day", -0.7884573603642702),
+                                    ("named-day", -1.2992829841302609)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dd[/-]mm",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect by `de`",
+        Classifier{okData =
+                     ClassData{prior = -0.16362942378180204,
+                               unseen = -4.812184355372417,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("el <day-of-month> (non ordinal)intersect by `de`",
+                                     -3.7054087560651467),
+                                    ("day of month (1st)named-month", -3.1945831322991562),
+                                    ("daymonth", -1.7594986070098335),
+                                    ("monthyear", -3.7054087560651467),
+                                    ("named-dayel proximo <cycle> ", -4.110873864173311),
+                                    ("named-dayla <cycle> pasado", -4.110873864173311),
+                                    ("el <time>named-month", -3.7054087560651467),
+                                    ("dayyear", -2.164963715117998),
+                                    ("dd-dd <month>(interval)year", -4.110873864173311),
+                                    ("el <time>el <cycle> (proximo|que viene)", -4.110873864173311),
+                                    ("two time tokens separated by \",\"named-month",
+                                     -4.110873864173311),
+                                    ("named-monthyear", -3.7054087560651467),
+                                    ("el <time>year", -3.7054087560651467),
+                                    ("el <time>la <cycle> pasado", -4.110873864173311),
+                                    ("el <day-of-month> de <named-month>year", -3.7054087560651467),
+                                    ("el <time>este|en un <cycle>", -3.7054087560651467),
+                                    ("named-dayel <cycle> (proximo|que viene)", -4.110873864173311),
+                                    ("el <day-of-month> (non ordinal)named-month",
+                                     -2.406125771934886),
+                                    ("<day-of-month> de <named-month>year", -3.7054087560651467),
+                                    ("two time tokens separated by \",\"year", -3.417726683613366),
+                                    ("two time tokens separated by \",\"intersect by `de`",
+                                     -4.110873864173311),
+                                    ("dayweek", -2.406125771934886),
+                                    ("intersect by `de`year", -3.417726683613366),
+                                    ("named-dayeste|en un <cycle>", -3.417726683613366)],
+                               n = 45},
+                   koData =
+                     ClassData{prior = -1.890850371872286, unseen = -3.891820298110627,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthyear", -3.1780538303479458),
+                                    ("hourmonth", -2.772588722239781),
+                                    ("monthmonth", -3.1780538303479458),
+                                    ("a las <time-of-day>named-month", -2.772588722239781),
+                                    ("dayyear", -3.1780538303479458),
+                                    ("minutemonth", -2.772588722239781),
+                                    ("named-monthyear", -3.1780538303479458),
+                                    ("de <datetime> - <datetime> (interval)named-month",
+                                     -3.1780538303479458),
+                                    ("<hour-of-day> <integer> (as relative minutes)named-month",
+                                     -3.1780538303479458),
+                                    ("<day-of-month> de <named-month>year", -3.1780538303479458),
+                                    ("intersect by `de`year", -3.1780538303479458),
+                                    ("<hour-of-day> <integer> (as relative minutes)intersect by `de`",
+                                     -3.1780538303479458),
+                                    ("minuteyear", -3.1780538303479458)],
+                               n = 8}}),
+       ("n pasados <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("number (0..15)a\241o (grain)", -1.3862943611198906),
+                                    ("year", -1.3862943611198906),
+                                    ("number (0..15)mes (grain)", -1.3862943611198906),
+                                    ("month", -1.3862943611198906)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> and half",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("a las <time-of-day>", -1.3217558399823195),
+                                    ("time-of-day (latent)", -1.3217558399823195),
+                                    ("hour", -0.7621400520468967)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("pasados n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.4849066497880004),
+                                    ("integer (numeric)dia (grain)", -2.4849066497880004),
+                                    ("integer (numeric)mes (grain)", -2.4849066497880004),
+                                    ("second", -2.4849066497880004),
+                                    ("integer (numeric)minutos (grain)", -2.4849066497880004),
+                                    ("day", -2.4849066497880004),
+                                    ("integer (numeric)segundo (grain)", -2.4849066497880004),
+                                    ("year", -2.4849066497880004), ("month", -2.4849066497880004),
+                                    ("minute", -2.4849066497880004),
+                                    ("integer (numeric)a\241o (grain)", -2.4849066497880004),
+                                    ("number (0..15)semana (grain)", -2.4849066497880004)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("semana (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.833213344056216,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 15},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("two time tokens separated by \",\"",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.295836866004329,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-dayel <time>", -2.159484249353372),
+                                    ("dayday", -0.8602012652231115),
+                                    ("named-dayel <day-of-month> (non ordinal)",
+                                     -2.5649493574615367),
+                                    ("named-dayintersect by `de`", -1.466337068793427),
+                                    ("named-day<day-of-month> de <named-month>",
+                                     -2.5649493574615367),
+                                    ("named-dayel <day-of-month> de <named-month>",
+                                     -2.5649493574615367)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<dim time> de la manana",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("a las <time-of-day>", -1.3217558399823195),
+                                    ("time-of-day (latent)", -1.3217558399823195),
+                                    ("hour", -0.7621400520468967)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -1.9459101490553135, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.916290731874155),
+                                    ("hour", -0.916290731874155)],
+                               n = 1}}),
+       ("del mediod\237a",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month> de <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -9.53101798043249e-2,
+                               unseen = -3.784189633918261,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("number (0..15)named-month", -2.151762203259462),
+                                    ("integer (numeric)named-month", -0.9279867716373464),
+                                    ("month", -0.7166776779701395)],
+                               n = 20},
+                   koData =
+                     ClassData{prior = -2.3978952727983707,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.8472978603872037),
+                                    ("month", -0.8472978603872037)],
+                               n = 2}}),
+       ("<time-of-day> <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.3528213746227423, unseen = -4.31748811353631,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<day-of-month> de <named-month>in the <part-of-day>",
+                                     -3.6109179126442243),
+                                    ("dayhour", -2.2246235515243336),
+                                    ("tomorrowin the <part-of-day>", -3.6109179126442243),
+                                    ("a las <time-of-day>del mediod\237a", -3.6109179126442243),
+                                    ("el <day-of-month> de <named-month>in the <part-of-day>",
+                                     -3.6109179126442243),
+                                    ("hourhour", -2.2246235515243336),
+                                    ("a las <time-of-day>in the <part-of-day>",
+                                     -2.3581549441488563),
+                                    ("intersectin the <part-of-day>", -3.6109179126442243),
+                                    ("minutehour", -1.739115735742633),
+                                    ("intersect by `de`in the <part-of-day>", -3.6109179126442243),
+                                    ("<hour-of-day> and halfin the <part-of-day>",
+                                     -3.20545280453606),
+                                    ("named-dayin the <part-of-day>", -3.6109179126442243),
+                                    ("<hour-of-day> and <relative minutes>del mediod\237a",
+                                     -3.20545280453606),
+                                    ("el <time>in the <part-of-day>", -3.6109179126442243),
+                                    ("<hour-of-day> and quarterin the <part-of-day>",
+                                     -2.6946271807700692),
+                                    ("yesterdayin the <part-of-day>", -3.6109179126442243),
+                                    ("time-of-day (latent)in the <part-of-day>",
+                                     -2.917770732084279)],
+                               n = 26},
+                   koData =
+                     ClassData{prior = -1.213022639845854, unseen = -3.8066624897703196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("yearhour", -1.4816045409242156),
+                                    ("year (latent)del mediod\237a", -3.0910424533583156),
+                                    ("monthhour", -3.0910424533583156),
+                                    ("hourhour", -3.0910424533583156),
+                                    ("named-monthin the <part-of-day>", -3.0910424533583156),
+                                    ("year (latent)in the <part-of-day>", -1.5869650565820417),
+                                    ("time-of-day (latent)in the <part-of-day>",
+                                     -3.0910424533583156)],
+                               n = 11}}),
+       ("de <datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthday", -1.791759469228055),
+                                    ("monthyear", -1.791759469228055),
+                                    ("monthhour", -1.791759469228055),
+                                    ("named-monthyear (latent)", -1.791759469228055),
+                                    ("named-monthtime-of-day (latent)", -1.791759469228055),
+                                    ("named-month<day-of-month> de <named-month>",
+                                     -1.791759469228055)],
+                               n = 3}}),
+       ("<time-of-day> horas",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("a las <time-of-day>", -1.252762968495368),
+                                    ("time-of-day (latent)", -1.252762968495368),
+                                    ("hour", -0.8472978603872037)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.7884573603642702),
+                                    ("hour", -0.7884573603642702)],
+                               n = 4}}),
+       ("intersect",
+        Classifier{okData =
+                     ClassData{prior = -0.2578291093020998,
+                               unseen = -4.6443908991413725,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<day-of-month> de <named-month>in the <part-of-day>",
+                                     -3.9415818076696905),
+                                    ("dayhour", -1.9956716586143772),
+                                    ("tomorrowin the <part-of-day>", -3.9415818076696905),
+                                    ("el <day-of-month> de <named-month>in the <part-of-day>",
+                                     -3.9415818076696905),
+                                    ("hourhour", -3.0252910757955354),
+                                    ("now<hour-of-day> minus quarter (as relative minutes)",
+                                     -3.9415818076696905),
+                                    ("dayyear", -3.0252910757955354),
+                                    ("a las <time-of-day>in the <part-of-day>",
+                                     -2.6888188391743224),
+                                    ("intersectin the <part-of-day>", -3.9415818076696905),
+                                    ("<named-month> <day-of-month>del <year>", -3.9415818076696905),
+                                    ("minutehour", -2.33214389523559),
+                                    ("intersect by `de`in the <part-of-day>", -3.9415818076696905),
+                                    ("now<hour-of-day> and <relative minutes>",
+                                     -3.9415818076696905),
+                                    ("<hour-of-day> and halfin the <part-of-day>",
+                                     -3.536116699561526),
+                                    ("named-dayin the <part-of-day>", -3.9415818076696905),
+                                    ("tomorrowa las <time-of-day>", -3.536116699561526),
+                                    ("named-daya las <time-of-day>", -3.9415818076696905),
+                                    ("named-dayintersect", -3.9415818076696905),
+                                    ("dayminute", -3.0252910757955354),
+                                    ("<named-month> <day-of-month>el <time>", -3.9415818076696905),
+                                    ("named-day<dim time> de la manana", -3.9415818076696905),
+                                    ("el <time>in the <part-of-day>", -3.9415818076696905),
+                                    ("named-day<time-of-day> <part-of-day>", -3.9415818076696905),
+                                    ("dd[/-]mmyear", -3.536116699561526),
+                                    ("nowa las <time-of-day>", -3.536116699561526),
+                                    ("<hour-of-day> and quarterin the <part-of-day>",
+                                     -3.0252910757955354),
+                                    ("yesterdayin the <part-of-day>", -3.9415818076696905)],
+                               n = 34},
+                   koData =
+                     ClassData{prior = -1.4816045409242156, unseen = -4.02535169073515,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hourday", -3.3141860046725258),
+                                    ("dayhour", -2.6210388241125804),
+                                    ("<day-of-month> de <named-month>a las <time-of-day>",
+                                     -3.3141860046725258),
+                                    ("<time-of-day> am|pm<day-of-month> de <named-month>",
+                                     -3.3141860046725258),
+                                    ("monthhour", -2.908720896564361),
+                                    ("now<hour-of-day> and <relative minutes>",
+                                     -3.3141860046725258),
+                                    ("dayminute", -2.908720896564361),
+                                    ("named-monthin the <part-of-day>", -3.3141860046725258),
+                                    ("named-montha las <time-of-day>", -3.3141860046725258),
+                                    ("nowa las <time-of-day>", -2.6210388241125804),
+                                    ("<hour-of-day> <integer> (as relative minutes)year",
+                                     -2.908720896564361),
+                                    ("minuteyear", -2.908720896564361)],
+                               n = 10}}),
+       ("a las <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -8.338160893905101e-2,
+                               unseen = -4.634728988229636,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<hour-of-day> and half", -3.2386784521643803),
+                                    ("<time-of-day> horas", -3.9318256327243257),
+                                    ("<hour-of-day> and quarter", -3.0155349008501706),
+                                    ("time-of-day (latent)", -1.2237754316221157),
+                                    ("<hour-of-day> and <relative minutes>", -2.833213344056216),
+                                    ("<time-of-day> am|pm", -3.9318256327243257),
+                                    ("<hour-of-day> minus <integer> (as relative minutes)",
+                                     -3.9318256327243257),
+                                    ("<hour-of-day> minus quarter (as relative minutes)",
+                                     -3.5263605246161616),
+                                    ("hour", -1.1592369104845446), ("minute", -1.8523840910444898)],
+                               n = 46},
+                   koData =
+                     ClassData{prior = -2.5257286443082556,
+                               unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.791759469228055),
+                                    ("<hour-of-day> and <relative minutes>", -1.791759469228055),
+                                    ("hour", -1.791759469228055), ("minute", -1.791759469228055)],
+                               n = 4}}),
+       ("season",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("minutos (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <day-of-week>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (latent)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -1.1786549963416462),
+                                    ("number (0..15)", -0.6190392084062235),
+                                    ("number (20..90)", -1.8718021769015913)],
+                               n = 10}}),
+       ("el <cycle> (proximo|que viene)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.540445040947149),
+                                    ("semana (grain)", -1.540445040947149),
+                                    ("mes (grain)", -1.9459101490553135),
+                                    ("year", -1.9459101490553135),
+                                    ("a\241o (grain)", -1.9459101490553135),
+                                    ("month", -1.9459101490553135)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> and quarter",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.0910424533583156,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("a las <time-of-day>", -1.4350845252893227),
+                                    ("time-of-day (latent)", -1.252762968495368),
+                                    ("hour", -0.7419373447293773)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dentro de <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> <unit-of-duration>", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hace <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.8718021769015913), ("year", -1.8718021769015913),
+                                    ("<integer> <unit-of-duration>", -0.9555114450274363),
+                                    ("hour", -1.8718021769015913), ("month", -1.8718021769015913)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("el <day-of-month> de <named-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1780538303479458,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("number (0..15)named-month", -1.749199854809259),
+                                    ("integer (numeric)named-month", -1.0560526742493137),
+                                    ("month", -0.7375989431307791)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("named-month",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.295836866004329,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 25},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("el <time>",
+        Classifier{okData =
+                     ClassData{prior = -5.406722127027582e-2,
+                               unseen = -4.465908118654584,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<named-month|named-day> past", -3.355735007585398),
+                                    ("dd[/-]mm", -3.068052935133617),
+                                    ("intersect by `de`", -2.257122718917288),
+                                    ("<day-of-month> de <named-month>", -2.056452023455137),
+                                    ("<time-of-day> <part-of-day>", -3.7612001156935624),
+                                    ("intersect", -3.7612001156935624),
+                                    ("day", -0.9279867716373464), ("year", -3.7612001156935624),
+                                    ("named-day", -2.374905754573672),
+                                    ("day of month (1st)", -3.355735007585398),
+                                    ("<named-month|named-day> next", -3.7612001156935624),
+                                    ("hour", -3.355735007585398)],
+                               n = 36},
+                   koData =
+                     ClassData{prior = -2.9444389791664407, unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("noon", -2.2512917986064953), ("hour", -2.2512917986064953),
+                                    ("minute", -2.2512917986064953),
+                                    ("<hour-of-day> <integer> (as relative minutes)",
+                                     -2.2512917986064953)],
+                               n = 2}}),
+       ("now",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinals (primero..10)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 10}}),
+       ("this <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.6931471805599453),
+                                    ("evening", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("number (0..15)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.9318256327243257,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 49},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("este|en un <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.0296194171811581),
+                                    ("semana (grain)", -1.0296194171811581),
+                                    ("year", -1.9459101490553135),
+                                    ("a\241o (grain)", -1.9459101490553135)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("numbers prefix with -, negative or minus",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.2006706954621511),
+                                    ("number (0..15)", -1.7047480922384253)],
+                               n = 9}}),
+       ("dd-dd <month>(interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("tomorrow",
+        Classifier{okData =
+                     ClassData{prior = -1.0116009116784799, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -0.45198512374305727,
+                               unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7}}),
+       ("mes (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 10},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("number (20..90)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-week> <day-of-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-dayinteger (numeric)", -0.6931471805599453),
+                                    ("day", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("afternoon",
+        Classifier{okData =
+                     ClassData{prior = -0.3364722366212129,
+                               unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -1.252762968495368, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("time-of-day (latent)",
+        Classifier{okData =
+                     ClassData{prior = -0.3272129112084162,
+                               unseen = -3.5263605246161616,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -1.1939224684724346),
+                                    ("number (0..15)", -0.3610133455373305)],
+                               n = 31},
+                   koData =
+                     ClassData{prior = -1.276293465905562, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.3364722366212129),
+                                    ("number (0..15)", -1.252762968495368)],
+                               n = 12}}),
+       ("<hour-of-day> and <relative minutes>",
+        Classifier{okData =
+                     ClassData{prior = -0.3364722366212129, unseen = -3.332204510175204,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)number (21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99)",
+                                     -2.1972245773362196),
+                                    ("time-of-day (latent)number (0..15)", -2.6026896854443837),
+                                    ("a las <time-of-day>number (20..90)", -2.1972245773362196),
+                                    ("a las <time-of-day>number (21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99)",
+                                     -2.1972245773362196),
+                                    ("hour", -0.8979415932059586),
+                                    ("a las <time-of-day>number (0..15)", -2.6026896854443837),
+                                    ("time-of-day (latent)number (20..90)", -2.1972245773362196)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -1.252762968495368, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("a las <time-of-day>number (20..90)", -1.6094379124341003),
+                                    ("hour", -1.0986122886681098),
+                                    ("time-of-day (latent)number (20..90)", -1.6094379124341003)],
+                               n = 4}}),
+       ("year",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 3}}),
+       ("en <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.6635616461296463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.9444389791664407), ("second", -2.9444389791664407),
+                                    ("day", -2.538973871058276), ("year", -2.538973871058276),
+                                    ("<integer> <unit-of-duration>", -0.8649974374866046),
+                                    ("hour", -2.2512917986064953), ("month", -2.9444389791664407),
+                                    ("minute", -1.845826690498331)],
+                               n = 15},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("n <cycle> (proximo|que viene)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.791759469228055),
+                                    ("integer (numeric)semana (grain)", -1.791759469228055),
+                                    ("year", -1.791759469228055),
+                                    ("number (0..15)mes (grain)", -1.791759469228055),
+                                    ("month", -1.791759469228055),
+                                    ("integer (numeric)a\241o (grain)", -1.791759469228055)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.587786664902119, unseen = -4.143134726391533,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -3.028522096376982),
+                                    ("integer (numeric)hora (grain)", -3.4339872044851463),
+                                    ("integer (numeric)dia (grain)", -3.4339872044851463),
+                                    ("number (0..15)segundo (grain)", -3.4339872044851463),
+                                    ("second", -3.4339872044851463),
+                                    ("number (0..15)a\241o (grain)", -3.028522096376982),
+                                    ("integer (numeric)minutos (grain)", -2.740840023925201),
+                                    ("day", -3.028522096376982), ("year", -2.740840023925201),
+                                    ("number (0..15)mes (grain)", -3.028522096376982),
+                                    ("number (0..15)hora (grain)", -2.740840023925201),
+                                    ("hour", -2.3353749158170367), ("month", -3.028522096376982),
+                                    ("number (0..15)dia (grain)", -3.4339872044851463),
+                                    ("number (0..15)minutos (grain)", -3.028522096376982),
+                                    ("number (16..19 21..29)hora (grain)", -3.4339872044851463),
+                                    ("minute", -2.3353749158170367),
+                                    ("integer (numeric)a\241o (grain)", -3.4339872044851463),
+                                    ("number (0..15)semana (grain)", -3.028522096376982)],
+                               n = 20},
+                   koData =
+                     ClassData{prior = -0.8109302162163288, unseen = -4.007333185232471,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.890371757896165),
+                                    ("integer (numeric)hora (grain)", -2.890371757896165),
+                                    ("integer (numeric)dia (grain)", -2.890371757896165),
+                                    ("integer (numeric)mes (grain)", -3.295836866004329),
+                                    ("second", -2.890371757896165),
+                                    ("number (0..15)a\241o (grain)", -3.295836866004329),
+                                    ("integer (numeric)semana (grain)", -3.295836866004329),
+                                    ("integer (numeric)minutos (grain)", -2.890371757896165),
+                                    ("day", -2.890371757896165),
+                                    ("integer (numeric)segundo (grain)", -2.890371757896165),
+                                    ("year", -2.6026896854443837),
+                                    ("number (0..15)mes (grain)", -2.890371757896165),
+                                    ("hour", -2.890371757896165), ("month", -2.6026896854443837),
+                                    ("minute", -2.890371757896165),
+                                    ("integer (numeric)a\241o (grain)", -2.890371757896165),
+                                    ("number (0..15)semana (grain)", -3.295836866004329)],
+                               n = 16}}),
+       ("proximas n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)hora (grain)", -2.4849066497880004),
+                                    ("integer (numeric)dia (grain)", -2.4849066497880004),
+                                    ("second", -2.4849066497880004),
+                                    ("number (0..15)a\241o (grain)", -2.4849066497880004),
+                                    ("integer (numeric)minutos (grain)", -2.4849066497880004),
+                                    ("day", -2.4849066497880004),
+                                    ("integer (numeric)segundo (grain)", -2.4849066497880004),
+                                    ("year", -2.4849066497880004),
+                                    ("number (0..15)mes (grain)", -2.4849066497880004),
+                                    ("hour", -2.4849066497880004), ("month", -2.4849066497880004),
+                                    ("minute", -2.4849066497880004)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> am|pm",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("a las <time-of-day>", -1.252762968495368),
+                                    ("time-of-day (latent)", -1.252762968495368),
+                                    ("hour", -0.8472978603872037)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -1.0986122886681098, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.916290731874155),
+                                    ("hour", -0.916290731874155)],
+                               n = 1}}),
+       ("n proximas <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.791759469228055),
+                                    ("integer (numeric)mes (grain)", -1.791759469228055),
+                                    ("integer (numeric)semana (grain)", -1.791759469228055),
+                                    ("year", -1.791759469228055), ("month", -1.791759469228055),
+                                    ("integer (numeric)a\241o (grain)", -1.791759469228055)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ano nuevo",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("la <cycle> pasado",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.2039728043259361),
+                                    ("semana (grain)", -1.2039728043259361),
+                                    ("year", -1.6094379124341003),
+                                    ("a\241o (grain)", -1.6094379124341003)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("la pasado <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("mes (grain)", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("named-day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.4657359027997265,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 30},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("number (21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods =
+                                 HashMap.fromList [("number (20..90)number (0..15)", 0.0)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("day of month (1st)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("a\241o (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> minus <integer> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)number (0..15)", -1.252762968495368),
+                                    ("hour", -0.8472978603872037),
+                                    ("a las <time-of-day>number (0..15)", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-month|named-day> next",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("el <time>", -1.5040773967762742),
+                                    ("day", -0.8109302162163288),
+                                    ("named-day", -1.0986122886681098)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("number (16..19 21..29)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<dim time> de la tarde",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.713572066704308,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<hour-of-day> and half", -2.5902671654458267),
+                                    ("a las <time-of-day>", -1.6094379124341003),
+                                    ("<hour-of-day> and quarter", -2.0794415416798357),
+                                    ("time-of-day (latent)", -2.0794415416798357),
+                                    ("hour", -1.491654876777717), ("minute", -1.3862943611198906)],
+                               n = 17},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("el proximo <cycle> ",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.540445040947149),
+                                    ("semana (grain)", -1.540445040947149),
+                                    ("mes (grain)", -1.9459101490553135),
+                                    ("year", -1.9459101490553135),
+                                    ("a\241o (grain)", -1.9459101490553135),
+                                    ("month", -1.9459101490553135)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> minus quarter (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("a las <time-of-day>", -1.2992829841302609),
+                                    ("time-of-day (latent)", -1.2992829841302609),
+                                    ("hour", -0.7884573603642702)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the day after tomorrow",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("del <year>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dd[/-.]mm[/-.]yyyy",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("noon",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("evening",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -1.252762968495368, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.9459101490553135),
+                                    ("dayday", -1.9459101490553135),
+                                    ("hh(:|.|h)mm (time-of-day)hh(:|.|h)mm (time-of-day)",
+                                     -1.9459101490553135),
+                                    ("<day-of-month> de <named-month><day-of-month> de <named-month>",
+                                     -1.9459101490553135)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.3364722366212129, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthday", -2.3025850929940455),
+                                    ("dayyear", -1.8971199848858813),
+                                    ("named-month<day-of-month> de <named-month>",
+                                     -2.3025850929940455),
+                                    ("dd[/-]mmyear", -1.8971199848858813),
+                                    ("<hour-of-day> <integer> (as relative minutes)year",
+                                     -1.8971199848858813),
+                                    ("minuteyear", -1.8971199848858813)],
+                               n = 5}}),
+       ("el <day-of-month> (non ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -6.453852113757118e-2,
+                               unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.2682639865946794),
+                                    ("number (0..15)", -1.4469189829363254)],
+                               n = 15},
+                   koData =
+                     ClassData{prior = -2.772588722239781, unseen = -1.3862943611198906,
+                               likelihoods =
+                                 HashMap.fromList [("integer (numeric)", -0.40546510810816444)],
+                               n = 1}}),
+       ("segundo (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-month> <day-of-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-monthinteger (numeric)", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dia (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("in the <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.3101549283038396, unseen = -3.295836866004329,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("afternoon", -1.466337068793427),
+                                    ("hour", -0.7731898882334817), ("evening", -2.159484249353372),
+                                    ("morning", -1.6486586255873816)],
+                               n = 11},
+                   koData =
+                     ClassData{prior = -1.3217558399823195,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("afternoon", -1.3862943611198906),
+                                    ("hour", -0.8754687373538999),
+                                    ("morning", -1.3862943611198906)],
+                               n = 4}}),
+       ("morning",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("week-end",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Nochevieja",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hora (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("right now",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Navidad",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> <integer> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)integer (numeric)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 5}}),
+       ("ce <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("season", -1.6094379124341003), ("day", -1.3217558399823195),
+                                    ("named-day", -2.0149030205422647),
+                                    ("hour", -1.6094379124341003),
+                                    ("week-end", -1.6094379124341003)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}})]
diff --git a/Duckling/Ranking/Classifiers/ET.hs b/Duckling/Ranking/Classifiers/ET.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/ET.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.ET (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers = HashMap.fromList []
diff --git a/Duckling/Ranking/Classifiers/FR.hs b/Duckling/Ranking/Classifiers/FR.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/FR.hs
@@ -0,0 +1,2395 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.FR (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers
+  = HashMap.fromList
+      [("n derniers <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.5263605246161616,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.803360380906535),
+                                    ("number (0..16)seconde (grain)", -2.803360380906535),
+                                    ("number (0..16)jour (grain)", -2.803360380906535),
+                                    ("second", -2.3978952727983707),
+                                    ("integer (numeric)ann\233e (grain)", -2.803360380906535),
+                                    ("integer (numeric)seconde (grain)", -2.803360380906535),
+                                    ("number (0..16)minute (grain)", -2.803360380906535),
+                                    ("integer (numeric)jour (grain)", -2.803360380906535),
+                                    ("day", -2.3978952727983707), ("year", -2.803360380906535),
+                                    ("month", -2.803360380906535),
+                                    ("integer (numeric)minute (grain)", -2.803360380906535),
+                                    ("integer (numeric)mois (grain)", -2.803360380906535),
+                                    ("minute", -2.3978952727983707),
+                                    ("integer (numeric)semaine (grain)", -2.803360380906535)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.772588722239781,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-month> prochain",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 2}}),
+       ("du|dans le <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.2657031657330056, unseen = -4.07753744390572,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("soir", -2.114532861491106),
+                                    ("fin de journ\233e", -3.367295829986474),
+                                    ("fin de matin\233e", -3.367295829986474),
+                                    ("d\233but de soir\233e", -3.367295829986474),
+                                    ("d\233but de matin\233e", -3.367295829986474),
+                                    ("apr\232s-midi", -2.9618307218783095),
+                                    ("d\233but de journ\233e", -3.367295829986474),
+                                    ("matin", -2.114532861491106), ("hour", -0.8823891801984737),
+                                    ("fin de soir\233e", -3.367295829986474),
+                                    ("fin d'apr\232s-midi", -2.9618307218783095),
+                                    ("d\233but d'apr\232s-midi", -3.367295829986474)],
+                               n = 23},
+                   koData =
+                     ClassData{prior = -1.455287232606842, unseen = -3.295836866004329,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("soir", -2.5649493574615367), ("matin", -1.3121863889661687),
+                                    ("hour", -1.1786549963416462)],
+                               n = 7}}),
+       ("dans <duration>",
+        Classifier{okData =
+                     ClassData{prior = -8.338160893905101e-2,
+                               unseen = -4.04305126783455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.639057329615259),
+                                    ("<integer> + '\"", -2.9267394020670396),
+                                    ("second", -2.639057329615259), ("day", -2.9267394020670396),
+                                    ("year", -2.639057329615259),
+                                    ("<integer> <unit-of-duration>", -1.1921383466789333),
+                                    ("une <unit-of-duration>", -2.2335922215070942),
+                                    ("hour", -2.4159137783010487), ("month", -3.332204510175204),
+                                    ("minute", -1.9459101490553135)],
+                               n = 23},
+                   koData =
+                     ClassData{prior = -2.5257286443082556, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.9459101490553135), ("day", -1.9459101490553135),
+                                    ("une <unit-of-duration>", -1.540445040947149)],
+                               n = 2}}),
+       ("<time> timezone",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("\224|vers <time-of-day>", -1.5040773967762742),
+                                    ("hour", -0.8109302162163288),
+                                    ("<time-of-day> heures", -1.0986122886681098)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("n <cycle> apr\232s",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("number (0..16)jour (grain)", -0.6931471805599453),
+                                    ("day", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (numeric)",
+        Classifier{okData =
+                     ClassData{prior = -0.500101660403015, unseen = -5.424950017481403,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 225},
+                   koData =
+                     ClassData{prior = -0.9325954408990987, unseen = -4.997212273764115,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 146}}),
+       ("<named-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("apr\232s le travail",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("n prochains <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.5553480614894135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.833213344056216),
+                                    ("number (0..16)seconde (grain)", -2.833213344056216),
+                                    ("second", -2.4277482359480516),
+                                    ("integer (numeric)ann\233e (grain)", -2.833213344056216),
+                                    ("integer (numeric)seconde (grain)", -2.833213344056216),
+                                    ("number (0..16)minute (grain)", -2.833213344056216),
+                                    ("integer (numeric)jour (grain)", -2.833213344056216),
+                                    ("day", -2.833213344056216), ("year", -2.833213344056216),
+                                    ("hour", -2.833213344056216), ("month", -2.833213344056216),
+                                    ("integer (numeric)minute (grain)", -2.833213344056216),
+                                    ("integer (numeric)mois (grain)", -2.833213344056216),
+                                    ("minute", -2.4277482359480516),
+                                    ("integer (numeric)semaine (grain)", -2.833213344056216),
+                                    ("integer (numeric)heure (grain)", -2.833213344056216)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.833213344056216,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-week> <day-of-month> \224 <time-of-day>)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.58351893845611,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -0.7827593392496325),
+                                    ("named-dayinteger (numeric)\224|vers <time-of-day>",
+                                     -1.6094379124341003),
+                                    ("named-daynumber (0..16)<time-of-day> heures",
+                                     -2.456735772821304),
+                                    ("named-dayinteger (numeric)<time-of-day> heures",
+                                     -2.169053700369523),
+                                    ("named-daynumber (0..16)\224|vers <time-of-day>",
+                                     -1.9459101490553135)],
+                               n = 15},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("apr\232s le d\233jeuner",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-day> en quinze",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("entre <datetime> et <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.25131442828090605,
+                               unseen = -4.127134385045092,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("le <time>le <day-of-month> (non ordinal)",
+                                     -3.417726683613366),
+                                    ("le <time>le <time>", -3.012261575505202),
+                                    ("dayday", -1.7129785913749407),
+                                    ("hourhour", -3.417726683613366),
+                                    ("le <time>intersect", -3.417726683613366),
+                                    ("miditime-of-day (latent)", -3.417726683613366),
+                                    ("intersectle <day-of-month> (non ordinal)",
+                                     -3.417726683613366),
+                                    ("minutehour", -1.7129785913749407),
+                                    ("hh(:|h)mm (time-of-day)time-of-day (latent)",
+                                     -3.012261575505202),
+                                    ("<hour-of-day> <integer> (as relative minutes)<time-of-day> heures",
+                                     -2.7245795030534206),
+                                    ("le <day-of-month> (non ordinal)le <day-of-month> (non ordinal)",
+                                     -3.012261575505202),
+                                    ("hh(:|h)mm (time-of-day)<time-of-day> heures",
+                                     -2.7245795030534206),
+                                    ("intersectintersect", -3.417726683613366),
+                                    ("intersectle <time>", -3.012261575505202),
+                                    ("<hour-of-day> <integer> (as relative minutes)time-of-day (latent)",
+                                     -3.012261575505202)],
+                               n = 21},
+                   koData =
+                     ClassData{prior = -1.5040773967762742,
+                               unseen = -3.4657359027997265,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<hour-of-day> <integer> (as relative minutes)intersect",
+                                     -2.740840023925201),
+                                    ("dayday", -2.3353749158170367),
+                                    ("le <day-of-month> (non ordinal)intersect",
+                                     -2.740840023925201),
+                                    ("minutehour", -1.824549292051046),
+                                    ("hh(:|h)mm (time-of-day)time-of-day (latent)",
+                                     -2.740840023925201),
+                                    ("le <day-of-month> (non ordinal)le <time>",
+                                     -2.740840023925201),
+                                    ("hh(:|h)mm (time-of-day)intersect", -2.740840023925201),
+                                    ("<hour-of-day> <integer> (as relative minutes)time-of-day (latent)",
+                                     -2.740840023925201)],
+                               n = 6}}),
+       ("soir",
+        Classifier{okData =
+                     ClassData{prior = -8.701137698962981e-2,
+                               unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -2.4849066497880004,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("ann\233e (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("d\233but de semaine",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> moins <integer> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("midinumber (0..16)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-month|named-day> suivant|d'apr\232s",
+        Classifier{okData =
+                     ClassData{prior = -0.10178269430994236,
+                               unseen = -4.174387269895637,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -2.5494451709255714),
+                                    ("en semaine", -1.9616585060234524),
+                                    ("intersect by 'de' or ','", -2.5494451709255714),
+                                    ("day", -0.7915872533731978),
+                                    ("named-day", -2.5494451709255714),
+                                    ("le <time>", -1.9616585060234524)],
+                               n = 28},
+                   koData =
+                     ClassData{prior = -2.3353749158170367, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("en semaine", -1.9459101490553135),
+                                    ("day", -1.540445040947149), ("hour", -1.9459101490553135),
+                                    ("<time-of-day> heures", -1.9459101490553135),
+                                    ("le <time>", -1.9459101490553135)],
+                               n = 3}}),
+       ("seconde (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> et|pass\233 de <number>",
+        Classifier{okData =
+                     ClassData{prior = -0.13353139262452263,
+                               unseen = -3.0910424533583156,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("\224|vers <time-of-day>number (20..60)", -2.3513752571634776),
+                                    ("<time-of-day> heuresinteger (numeric)", -2.3513752571634776),
+                                    ("\224|vers <time-of-day>number (0..16)", -2.3513752571634776),
+                                    ("hour", -0.9650808960435872),
+                                    ("<time-of-day> heuresnumber (20..60)", -1.9459101490553135),
+                                    ("<time-of-day> heuresnumber (0..16)", -1.9459101490553135)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -2.0794415416798357,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("midiinteger (numeric)", -1.5040773967762742),
+                                    ("hour", -1.5040773967762742)],
+                               n = 1}}),
+       ("<hour-of-day> moins quart",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("midi", -0.6931471805599453), ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("de <time-of-day> - <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.587786664902119, unseen = -3.8066624897703196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh(:|h)mm (time-of-day)<hour-of-day> <integer> (as relative minutes)",
+                                     -2.6855773452501515),
+                                    ("minuteminute", -1.5869650565820417),
+                                    ("time-of-day (latent)time-of-day (latent)",
+                                     -3.0910424533583156),
+                                    ("<time-of-day> heures<time-of-day> heures",
+                                     -3.0910424533583156),
+                                    ("hh(:|h)mm (time-of-day)hh(:|h)mm (time-of-day)",
+                                     -2.6855773452501515),
+                                    ("hourhour", -2.3978952727983707),
+                                    ("minutehour", -2.174751721484161),
+                                    ("<hour-of-day> <integer> (as relative minutes)<time-of-day> heures",
+                                     -2.3978952727983707),
+                                    ("<hour-of-day> <integer> (as relative minutes)hh(:|h)mm (time-of-day)",
+                                     -2.6855773452501515),
+                                    ("hh(:|h)mm (time-of-day)<time-of-day> heures",
+                                     -3.0910424533583156),
+                                    ("<hour-of-day> <integer> (as relative minutes)<hour-of-day> <integer> (as relative minutes)",
+                                     -2.6855773452501515),
+                                    ("<time-of-day> heurestime-of-day (latent)",
+                                     -3.0910424533583156)],
+                               n = 15},
+                   koData =
+                     ClassData{prior = -0.8109302162163288,
+                               unseen = -3.6635616461296463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minutehour", -1.072636802264849),
+                                    ("hh(:|h)mm (time-of-day)time-of-day (latent)",
+                                     -2.2512917986064953),
+                                    ("<hour-of-day> <integer> (as relative minutes)<time-of-day> heures",
+                                     -2.538973871058276),
+                                    ("hh(:|h)mm (time-of-day)<time-of-day> heures",
+                                     -2.538973871058276),
+                                    ("<hour-of-day> <integer> (as relative minutes)time-of-day (latent)",
+                                     -1.845826690498331)],
+                               n = 12}}),
+       ("<datetime>-dd <month>(interval)",
+        Classifier{okData =
+                     ClassData{prior = -1.466337068793427, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.3217558399823195),
+                                    ("<day-of-month> <named-month>named-month",
+                                     -1.6094379124341003),
+                                    ("day of month (premier)named-month", -2.0149030205422647)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -0.262364264467491, unseen = -3.4011973816621555,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hourmonth", -1.7578579175523736),
+                                    ("named-monthnamed-month", -2.268683541318364),
+                                    ("monthmonth", -2.268683541318364),
+                                    ("year (latent)named-month", -1.7578579175523736),
+                                    ("yearmonth", -1.7578579175523736),
+                                    ("time-of-day (latent)named-month", -1.7578579175523736)],
+                               n = 10}}),
+       ("semaine (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.6635616461296463,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 37},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> + '\"",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("demain",
+        Classifier{okData =
+                     ClassData{prior = -0.45198512374305727,
+                               unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -1.0116009116784799, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4}}),
+       ("mois (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect by 'mais/par exemple/plut\244t'",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.8066624897703196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -2.3978952727983707),
+                                    ("named-dayentre <time-of-day> et <time-of-day> (interval)",
+                                     -1.5869650565820417),
+                                    ("le <day-of-month> (non ordinal)<time-of-day> heures",
+                                     -3.0910424533583156),
+                                    ("le <day-of-month> (non ordinal)\224|vers <time-of-day>",
+                                     -2.6855773452501515),
+                                    ("dayminute", -0.9509762898620451),
+                                    ("named-dayentre <datetime> et <datetime> (interval)",
+                                     -1.5869650565820417)],
+                               n = 19},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("de <datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -1.410986973710262, unseen = -4.143134726391533,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day of month (premier)<day-of-week> <day-of-month>",
+                                     -3.4339872044851463),
+                                    ("hh(:|h)mm (time-of-day)<hour-of-day> <integer> (as relative minutes)",
+                                     -3.4339872044851463),
+                                    ("minuteminute", -2.517696472610991),
+                                    ("hh(:|h)mm (time-of-day)hh(:|h)mm (time-of-day)",
+                                     -3.4339872044851463),
+                                    ("dayday", -2.517696472610991),
+                                    ("<day-of-month> <named-month>day of month (premier)",
+                                     -3.4339872044851463),
+                                    ("minutehour", -3.028522096376982),
+                                    ("<hour-of-day> <integer> (as relative minutes)<time-of-day> heures",
+                                     -3.4339872044851463),
+                                    ("<hour-of-day> <integer> (as relative minutes)hh(:|h)mm (time-of-day)",
+                                     -3.4339872044851463),
+                                    ("hh(:|h)mm (time-of-day)<time-of-day> heures",
+                                     -3.4339872044851463),
+                                    ("<hour-of-day> <integer> (as relative minutes)<hour-of-day> <integer> (as relative minutes)",
+                                     -3.4339872044851463),
+                                    ("<day-of-month> <named-month>intersect", -3.4339872044851463),
+                                    ("intersect<day-of-week> <day-of-month>", -3.4339872044851463)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -0.27958486221916157,
+                               unseen = -4.653960350157523,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<hour-of-day> <integer> (as relative minutes)intersect",
+                                     -3.951243718581427),
+                                    ("<day-of-month> <named-month>time-of-day (latent)",
+                                     -3.951243718581427),
+                                    ("hourday", -3.0349529867072724),
+                                    ("dayhour", -3.951243718581427),
+                                    ("yearhour", -3.951243718581427),
+                                    ("intersectnamed-day", -3.951243718581427),
+                                    ("time-of-day (latent)year (latent)", -3.951243718581427),
+                                    ("houryear", -3.951243718581427),
+                                    ("day of month (premier)named-day", -3.951243718581427),
+                                    ("time-of-day (latent)intersect", -3.545778610473263),
+                                    ("year (latent)intersect", -3.545778610473263),
+                                    ("yearyear", -3.951243718581427),
+                                    ("time-of-day (latent)time-of-day (latent)",
+                                     -3.951243718581427),
+                                    ("dayday", -2.2464956263430023),
+                                    ("year (latent)year (latent)", -3.951243718581427),
+                                    ("<day-of-week> <day-of-month><day-of-week> <day-of-month>",
+                                     -3.951243718581427),
+                                    ("hourhour", -3.951243718581427),
+                                    ("time-of-day (latent)<day-of-week> <day-of-month>",
+                                     -3.951243718581427),
+                                    ("minutehour", -2.4471663218051534),
+                                    ("hh(:|h)mm (time-of-day)time-of-day (latent)",
+                                     -3.545778610473263),
+                                    ("year (latent)<day-of-week> <day-of-month>",
+                                     -3.951243718581427),
+                                    ("year (latent)time-of-day (latent)", -3.951243718581427),
+                                    ("<hour-of-day> <integer> (as relative minutes)<time-of-day> heures",
+                                     -3.951243718581427),
+                                    ("year (latent)named-day", -3.951243718581427),
+                                    ("time-of-day (latent)named-day", -3.951243718581427),
+                                    ("day of month (premier)intersect", -3.545778610473263),
+                                    ("hh(:|h)mm (time-of-day)<time-of-day> heures",
+                                     -3.951243718581427),
+                                    ("intersectintersect", -3.545778610473263),
+                                    ("hh(:|h)mm (time-of-day)intersect", -3.951243718581427),
+                                    ("yearday", -3.0349529867072724),
+                                    ("<hour-of-day> <integer> (as relative minutes)time-of-day (latent)",
+                                     -3.545778610473263),
+                                    ("<day-of-week> <day-of-month>named-day", -3.951243718581427),
+                                    ("<day-of-week> <day-of-month>intersect", -3.545778610473263)],
+                               n = 31}}),
+       ("<ordinal> <cycle> de <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.7346010553881064),
+                                    ("ordinals (premier..seizieme)semaine (grain)named-month",
+                                     -1.7346010553881064),
+                                    ("ordinal (digits)jour (grain)named-month",
+                                     -1.7346010553881064),
+                                    ("weekmonth", -1.2237754316221157),
+                                    ("ordinals (premier..seizieme)semaine (grain)intersect",
+                                     -1.7346010553881064)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("premi\232re quinzaine de <named-month>(interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("n <cycle> suivants",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -1.6094379124341003), ("month", -1.6094379124341003),
+                                    ("integer (numeric)mois (grain)", -1.6094379124341003),
+                                    ("integer (numeric)heure (grain)", -1.6094379124341003)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("number (0..16)jour (grain)", -1.2039728043259361),
+                                    ("day", -1.2039728043259361)],
+                               n = 2}}),
+       ("intersect",
+        Classifier{okData =
+                     ClassData{prior = -0.2911008792005665,
+                               unseen = -6.9440872082295275,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("apr\232s <time-of-day>named-month", -5.844510134151319),
+                                    ("<day-of-month> <named-month><dim time> <part-of-day>",
+                                     -5.844510134151319),
+                                    ("intersect<dim time> du matin", -5.844510134151319),
+                                    ("<hour-of-day> <integer> (as relative minutes)intersect",
+                                     -5.844510134151319),
+                                    ("hh(:|h)mm (time-of-day)named-day", -5.844510134151319),
+                                    ("demain<time-of-day> heures", -5.844510134151319),
+                                    ("hourday", -4.640537329825383),
+                                    ("<day-of-month> <named-month>year", -5.556828061699537),
+                                    ("dayhour", -1.6960983506589422),
+                                    ("daymonth", -3.0719214119115374),
+                                    ("monthyear", -4.745897845483209),
+                                    ("dernier <cycle> de <time> (latent)year", -5.333684510385328),
+                                    ("named-month<dim time> du matin", -5.844510134151319),
+                                    ("<day-of-month> <named-month>\224|vers <time-of-day>",
+                                     -4.109909078763212),
+                                    ("dd mmyear", -4.997212273764115),
+                                    ("le <day-of-month> (non ordinal)<dim time> du matin",
+                                     -5.844510134151319),
+                                    ("hh(:|h)mm (time-of-day)<day-of-week> <day-of-month>",
+                                     -6.249975242259483),
+                                    ("named-month\224|vers <time-of-day>", -4.997212273764115),
+                                    ("named-dayday of month (premier)", -5.556828061699537),
+                                    ("<time-of-day> heuresdu|dans le <part-of-day>",
+                                     -4.745897845483209),
+                                    ("<hour-of-day> et quart<dim time> <part-of-day>",
+                                     -5.556828061699537),
+                                    ("<day-of-week> <day-of-month>named-month", -5.556828061699537),
+                                    ("le <time>du|dans le <part-of-day>", -5.333684510385328),
+                                    ("\224|vers <time-of-day>ce <part-of-day>", -5.556828061699537),
+                                    ("le <day-of-month> (non ordinal)<dim time> du soir",
+                                     -4.997212273764115),
+                                    ("entre <datetime> et <datetime> (interval)named-day",
+                                     -5.844510134151319),
+                                    ("<hour-of-day> et demice <part-of-day>", -5.556828061699537),
+                                    ("le <day-of-month> (non ordinal)<time-of-day> heures",
+                                     -4.640537329825383),
+                                    ("<hour-of-day> <integer> (as relative minutes)<day-of-week> <day-of-month>",
+                                     -6.249975242259483),
+                                    ("le <day-of-month> (non ordinal)named-month",
+                                     -4.2350722217172185),
+                                    ("monthhour", -4.1705337005796475),
+                                    ("le <time><time-of-day> heures", -5.151362953591374),
+                                    ("hourmonth", -5.333684510385328),
+                                    ("<time-of-day> heuresle <time>", -5.333684510385328),
+                                    ("dayday", -3.898599985096005),
+                                    ("aujourd'hui<hour-of-day> <integer> (as relative minutes)",
+                                     -5.556828061699537),
+                                    ("apr\232s <time-of-day>named-day", -5.844510134151319),
+                                    ("named-dayle <cycle> prochain|suivant|d'apr\232s",
+                                     -4.863680881139593),
+                                    ("named-dayapr\232s <time-of-day>", -5.556828061699537),
+                                    ("hourhour", -3.2295503561151206),
+                                    ("<day-of-week> <day-of-month>\224|vers <time-of-day>",
+                                     -4.5452271500210575),
+                                    ("le <time>intersect", -5.556828061699537),
+                                    ("\224|vers <time-of-day><dim time> <part-of-day>",
+                                     -6.249975242259483),
+                                    ("<time-of-day> heuresintersect", -5.844510134151319),
+                                    ("intersectnamed-month", -4.997212273764115),
+                                    ("intersect<time-of-day> heures", -5.556828061699537),
+                                    ("<hour-of-day> et quartce <part-of-day>", -5.556828061699537),
+                                    ("dayyear", -3.8520799694611125),
+                                    ("named-dayce|dans le <cycle>", -5.556828061699537),
+                                    ("apr\232s-demain\224|vers <time-of-day>", -5.333684510385328),
+                                    ("le <ordinal> <cycle> de <time>year", -6.249975242259483),
+                                    ("demain\224|vers <time-of-day>", -5.333684510385328),
+                                    ("le <day-of-month> (non ordinal)intersect",
+                                     -3.6472855568150995),
+                                    ("hourminute", -6.249975242259483),
+                                    ("dd-dd <month>(interval)year", -6.249975242259483),
+                                    ("intersect<day-of-month> <named-month>", -5.844510134151319),
+                                    ("minutemonth", -5.844510134151319),
+                                    ("\224|vers <time-of-day>demain", -6.249975242259483),
+                                    ("minutehour", -4.378173065357892),
+                                    ("named-daydu|dans le <part-of-day>", -5.556828061699537),
+                                    ("named-monthyear", -4.745897845483209),
+                                    ("entre <datetime> et <datetime> (interval)named-month",
+                                     -6.249975242259483),
+                                    ("intersectdu|dans le <part-of-day>", -3.898599985096005),
+                                    ("le <day-of-month> \224 <datetime>du|dans le <part-of-day>",
+                                     -5.844510134151319),
+                                    ("named-month<dim time> <part-of-day>", -5.844510134151319),
+                                    ("le <day-of-month> (non ordinal)apr\232s <time-of-day>",
+                                     -6.249975242259483),
+                                    ("named-day<day-of-month> <named-month>", -5.556828061699537),
+                                    ("named-dayle <time>", -4.109909078763212),
+                                    ("le <day-of-month> (non ordinal)<dim time> <part-of-day>",
+                                     -4.5452271500210575),
+                                    ("dd/-mm<time-of-day> heures", -5.844510134151319),
+                                    ("de <datetime> - <datetime> (interval)named-month",
+                                     -5.844510134151319),
+                                    ("apr\232s <time-of-day>le <time>", -5.844510134151319),
+                                    ("named-day<time-of-day> heures", -5.556828061699537),
+                                    ("<hour-of-day> et quartdemain", -5.556828061699537),
+                                    ("<time-of-day> heuresce <time>", -6.249975242259483),
+                                    ("<day-of-month> <named-month>du|dans le <part-of-day>",
+                                     -5.844510134151319),
+                                    ("named-dayintersect", -6.249975242259483),
+                                    ("le <time><dim time> du matin", -5.844510134151319),
+                                    ("apr\232s <time-of-day>intersect", -6.249975242259483),
+                                    ("le <day-of-month> (non ordinal)\224|vers <time-of-day>",
+                                     -4.304065093204169),
+                                    ("hierdu|dans le <part-of-day>", -6.249975242259483),
+                                    ("intersect<dim time> <part-of-day>", -5.844510134151319),
+                                    ("dayminute", -4.745897845483209),
+                                    ("<ordinal> <cycle> de <time>year", -5.844510134151319),
+                                    ("le <time>\224|vers <time-of-day>", -4.5452271500210575),
+                                    ("<day-of-month> <named-month><dim time> du matin",
+                                     -5.844510134151319),
+                                    ("intersectyear", -5.556828061699537),
+                                    ("<day-of-week> <day-of-month><time-of-day> heures",
+                                     -5.151362953591374),
+                                    ("minuteday", -3.279560776689782),
+                                    ("<datetime> - <datetime> (interval)named-month",
+                                     -4.997212273764115),
+                                    ("<day-of-month> <named-month><time-of-day> heures",
+                                     -4.745897845483209),
+                                    ("aujourd'hui\224|vers <time-of-day>", -5.333684510385328),
+                                    ("day of month (premier)intersect", -6.249975242259483),
+                                    ("entre <time-of-day> et <time-of-day> (interval)named-day",
+                                     -5.844510134151319),
+                                    ("named-day<named-month|named-day> suivant|d'apr\232s",
+                                     -5.333684510385328),
+                                    ("<dim time> <part-of-day>apr\232s <time-of-day>",
+                                     -6.249975242259483),
+                                    ("named-month<time-of-day> heures", -5.556828061699537),
+                                    ("<time-of-day> heuresce <part-of-day>", -5.556828061699537),
+                                    ("le <time>named-month", -5.151362953591374),
+                                    ("apr\232s le <day-of-month>named-month", -6.249975242259483),
+                                    ("\224|vers <time-of-day>du|dans le <part-of-day>",
+                                     -5.151362953591374),
+                                    ("intersectintersect", -5.844510134151319),
+                                    ("de <time-of-day> - <time-of-day> (interval)named-day",
+                                     -5.333684510385328),
+                                    ("de <datetime> - <datetime> (interval)named-day",
+                                     -5.844510134151319),
+                                    ("named-dayde <time-of-day> - <time-of-day> (interval)",
+                                     -5.844510134151319),
+                                    ("dayweek", -4.052750664923264),
+                                    ("weekyear", -4.863680881139593),
+                                    ("<hour-of-day> <integer> (as relative minutes)named-day",
+                                     -5.844510134151319),
+                                    ("hh(:|h)mm (time-of-day)intersect", -5.844510134151319),
+                                    ("named-monthintersect", -5.844510134151319),
+                                    ("dd/-mm\224|vers <time-of-day>", -5.333684510385328),
+                                    ("day of month (premier)named-month", -4.5452271500210575),
+                                    ("named-day\224|vers <time-of-day>", -4.863680881139593),
+                                    ("<day-of-month> <named-month>intersect", -5.844510134151319),
+                                    ("intersect\224|vers <time-of-day>", -4.997212273764115),
+                                    ("le <time>year", -4.745897845483209),
+                                    ("le <time><dim time> <part-of-day>", -5.844510134151319),
+                                    ("<time-of-day> - <time-of-day> (interval)named-day",
+                                     -4.997212273764115),
+                                    ("apr\232s-demain<time-of-day> heures", -5.844510134151319),
+                                    ("<datetime> - <datetime> (interval)named-day",
+                                     -5.333684510385328)],
+                               n = 438},
+                   koData =
+                     ClassData{prior = -1.3761075158128977, unseen = -6.124683390894205,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<hour-of-day> <integer> (as relative minutes)intersect",
+                                     -5.429345628954441),
+                                    ("hh(:|h)mm (time-of-day)named-day", -5.429345628954441),
+                                    ("year<time-of-day> - <time-of-day> (interval)",
+                                     -4.736198448394496),
+                                    ("demain<time-of-day> heures", -4.736198448394496),
+                                    ("hourday", -3.2321210516182215),
+                                    ("<day-of-month> <named-month>year", -5.429345628954441),
+                                    ("demainavant <time-of-day>", -5.429345628954441),
+                                    ("dayhour", -2.4336133554004498),
+                                    ("le lendemain du <time>named-month", -5.429345628954441),
+                                    ("daymonth", -3.8199077165203406),
+                                    ("monthday", -5.429345628954441),
+                                    ("monthyear", -4.736198448394496),
+                                    ("yearhour", -4.736198448394496),
+                                    ("houryear", -5.0238805208462765),
+                                    ("aujourd'hui<time-of-day> heures", -5.429345628954441),
+                                    ("named-month\224|vers <time-of-day>", -3.724597536716016),
+                                    ("<time-of-day> heuresdu|dans le <part-of-day>",
+                                     -4.736198448394496),
+                                    ("<day-of-week> <day-of-month>named-month", -4.04305126783455),
+                                    ("le <time>du|dans le <part-of-day>", -5.0238805208462765),
+                                    ("le <day-of-month> (non ordinal)<hour-of-day> <integer> (as relative minutes)",
+                                     -4.736198448394496),
+                                    ("named-month<day-of-month> <named-month>", -5.429345628954441),
+                                    ("<time-of-day> heuresle <day-of-month> (non ordinal)",
+                                     -5.0238805208462765),
+                                    ("le <day-of-month> (non ordinal)<time-of-day> heures",
+                                     -5.429345628954441),
+                                    ("aujourd'huidu|dans le <part-of-day>", -5.0238805208462765),
+                                    ("le <day-of-month> (non ordinal)named-month",
+                                     -5.429345628954441),
+                                    ("monthhour", -3.2321210516182215),
+                                    ("hourmonth", -4.176582660459073),
+                                    ("dayday", -3.6375861597263857),
+                                    ("named-dayapr\232s <time-of-day>", -5.0238805208462765),
+                                    ("hourhour", -3.8199077165203406),
+                                    ("named-day<hour-of-day> <integer> (as relative minutes)",
+                                     -5.0238805208462765),
+                                    ("dayyear", -4.176582660459073),
+                                    ("le <cycle> de <time>named-month", -5.0238805208462765),
+                                    ("year<hour-of-day> <integer> (as relative minutes)",
+                                     -4.736198448394496),
+                                    ("demain\224|vers <time-of-day>", -4.513054897080286),
+                                    ("<dim time> <part-of-day><hour-of-day> <integer> (as relative minutes)",
+                                     -5.0238805208462765),
+                                    ("hourminute", -5.0238805208462765),
+                                    ("minutemonth", -4.736198448394496),
+                                    ("named-monthyear", -4.736198448394496),
+                                    ("intersect by 'de' or ','year", -5.0238805208462765),
+                                    ("named-day<time-of-day> - <time-of-day> (interval)",
+                                     -3.724597536716016),
+                                    ("named-day<datetime> - <datetime> (interval)",
+                                     -5.0238805208462765),
+                                    ("named-day<day-of-month> <named-month>", -4.04305126783455),
+                                    ("weekmonth", -5.0238805208462765),
+                                    ("named-dayle <time>", -5.0238805208462765),
+                                    ("avant <time-of-day>named-day", -5.429345628954441),
+                                    ("de <datetime> - <datetime> (interval)named-month",
+                                     -5.429345628954441),
+                                    ("le <day-of-month> (non ordinal)year", -5.0238805208462765),
+                                    ("named-day<time-of-day> heures", -5.429345628954441),
+                                    ("<time-of-day> heuresnamed-day", -3.925268232178167),
+                                    ("apr\232s <time-of-day><time-of-day> heures",
+                                     -5.0238805208462765),
+                                    ("<time-of-day> heuresdd/-mm", -5.429345628954441),
+                                    ("<time-of-day> - <time-of-day> (interval)du|dans le <part-of-day>",
+                                     -5.0238805208462765),
+                                    ("<hour-of-day> <integer> (as relative minutes)named-month",
+                                     -5.0238805208462765),
+                                    ("le <day-of-month> (non ordinal)\224|vers <time-of-day>",
+                                     -4.736198448394496),
+                                    ("dayminute", -4.330733340286331),
+                                    ("named-monthdu|dans le <part-of-day>", -5.0238805208462765),
+                                    ("intersectyear", -5.0238805208462765),
+                                    ("minuteday", -4.736198448394496),
+                                    ("aujourd'hui\224|vers <time-of-day>", -4.736198448394496),
+                                    ("<datetime>-dd <month>(interval)year", -5.0238805208462765),
+                                    ("<time-of-day> - <time-of-day> (interval)named-month",
+                                     -4.513054897080286),
+                                    ("le <day-of-month> (non ordinal)avant <time-of-day>",
+                                     -5.429345628954441),
+                                    ("<dim time> <part-of-day>apr\232s <time-of-day>",
+                                     -5.429345628954441),
+                                    ("named-month<time-of-day> heures", -4.330733340286331),
+                                    ("demainapr\232s <time-of-day>", -5.429345628954441),
+                                    ("<hour-of-day> <integer> (as relative minutes)named-day",
+                                     -5.0238805208462765),
+                                    ("<hour-of-day> <integer> (as relative minutes)year",
+                                     -4.513054897080286),
+                                    ("<time-of-day> - <time-of-day> (interval)intersect",
+                                     -5.429345628954441),
+                                    ("apr\232s <time-of-day>\224|vers <time-of-day>",
+                                     -4.513054897080286),
+                                    ("\224|vers <time-of-day>named-day", -4.736198448394496),
+                                    ("le <time>year", -5.0238805208462765),
+                                    ("apr\232s <time-of-day>le <day-of-month> (non ordinal)",
+                                     -5.429345628954441),
+                                    ("de <datetime> - <datetime> (interval)<day-of-month> <named-month>",
+                                     -5.429345628954441),
+                                    ("minuteyear", -4.04305126783455),
+                                    ("yearminute", -4.736198448394496),
+                                    ("<dim time> <part-of-day><time-of-day> heures",
+                                     -5.429345628954441)],
+                               n = 148}}),
+       ("season",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ce <day-of-week>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("le <cycle> de <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.8472978603872037, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthmonth", -1.9459101490553135),
+                                    ("mois (grain)named-month", -1.9459101490553135),
+                                    ("weekday", -1.540445040947149),
+                                    ("semaine (grain)<day-of-month> <named-month>",
+                                     -1.540445040947149)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -0.5596157879354228, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("semaine (grain)year (latent)", -1.6739764335716716),
+                                    ("weekhour", -1.6739764335716716),
+                                    ("semaine (grain)time-of-day (latent)", -1.6739764335716716),
+                                    ("weekyear", -1.6739764335716716)],
+                               n = 4}}),
+       ("<hour-of-day> et trois quarts",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.6931471805599453),
+                                    ("<time-of-day> heures", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("le <day-of-month> \224 <datetime>",
+        Classifier{okData =
+                     ClassData{prior = -0.325422400434628, unseen = -3.58351893845611,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)<time-of-day> heures", -1.9459101490553135),
+                                    ("integer (numeric)<dim time> du soir", -2.8622008809294686),
+                                    ("integer (numeric)intersect", -2.456735772821304),
+                                    ("integer (numeric)<dim time> du matin", -2.8622008809294686),
+                                    ("hour", -0.916290731874155),
+                                    ("integer (numeric)time-of-day (latent)", -2.169053700369523),
+                                    ("integer (numeric)<dim time> <part-of-day>",
+                                     -2.456735772821304)],
+                               n = 13},
+                   koData =
+                     ClassData{prior = -1.2809338454620642, unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)year (latent)", -1.3350010667323402),
+                                    ("year", -1.3350010667323402), ("hour", -2.2512917986064953),
+                                    ("integer (numeric)time-of-day (latent)", -2.2512917986064953)],
+                               n = 5}}),
+       ("yyyy-mm-dd",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("le <cycle> dernier",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.540445040947149),
+                                    ("ann\233e (grain)", -1.9459101490553135),
+                                    ("semaine (grain)", -1.540445040947149),
+                                    ("mois (grain)", -1.9459101490553135),
+                                    ("year", -1.9459101490553135), ("month", -1.9459101490553135)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (latent)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 19}}),
+       ("soir de no\235l",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("fin de journ\233e",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("du <datetime>-<day-of-week> dd <month>(interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.5108256237659907, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersectnamed-daynamed-month", -1.9459101490553135),
+                                    ("day of month (premier)named-daynamed-month",
+                                     -1.9459101490553135),
+                                    ("<day-of-week> <day-of-month>named-daynamed-month",
+                                     -1.9459101490553135),
+                                    ("daydaymonth", -1.252762968495368)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -0.916290731874155, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("year (latent)named-daynamed-month", -1.791759469228055),
+                                    ("hourdaymonth", -1.791759469228055),
+                                    ("time-of-day (latent)named-daynamed-month",
+                                     -1.791759469228055),
+                                    ("yeardaymonth", -1.791759469228055)],
+                               n = 2}}),
+       ("milieu de semaine",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("noel",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("fin de matin\233e",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("le <day-of-month> (non ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -0.4228568508200336, unseen = -3.713572066704308,
+                               likelihoods =
+                                 HashMap.fromList [("integer (numeric)", -2.5317807984289897e-2)],
+                               n = 38},
+                   koData =
+                     ClassData{prior = -1.0647107369924282,
+                               unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.2578291093020998),
+                                    ("numbers 22..29 32..39 .. 52..59", -1.4816045409242156)],
+                               n = 20}}),
+       ("du dd-<day-of-week> dd <month>(interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -0.6931471805599453),
+                                    ("daymonth", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("en semaine",
+        Classifier{okData =
+                     ClassData{prior = -1.1856236656577395,
+                               unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -0.3646431135879093, unseen = -3.295836866004329,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 25}}),
+       ("numbers 22..29 32..39 .. 52..59",
+        Classifier{okData =
+                     ClassData{prior = -2.3025850929940455,
+                               unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("number (20..60)number (0..16)", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.10536051565782628,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)integer (numeric)", -0.2876820724517809),
+                                    ("integer (numeric)number (0..16)", -1.791759469228055)],
+                               n = 9}}),
+       ("<day-of-week> prochain",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect by 'de' or ','",
+        Classifier{okData =
+                     ClassData{prior = -0.2719337154836418, unseen = -4.394449154672439,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -3.283414346005772),
+                                    ("hourmonth", -3.6888794541139363),
+                                    ("dayday", -1.742969305058623),
+                                    ("named-dayle <cycle> prochain|suivant|d'apr\232s",
+                                     -2.3025850929940455),
+                                    ("named-day<time-of-day> - <time-of-day> (interval)",
+                                     -3.283414346005772),
+                                    ("named-dayle <time>", -1.491654876777717),
+                                    ("named-dayle <cycle> dernier", -3.6888794541139363),
+                                    ("named-day<named-month|named-day> suivant|d'apr\232s",
+                                     -2.772588722239781),
+                                    ("week-endnamed-month", -3.6888794541139363),
+                                    ("dayweek", -1.5488132906176655)],
+                               n = 32},
+                   koData =
+                     ClassData{prior = -1.4350845252893227,
+                               unseen = -3.6109179126442243,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hourday", -2.890371757896165),
+                                    ("dayhour", -2.890371757896165),
+                                    ("daymonth", -1.9740810260220096),
+                                    ("du|dans le <part-of-day>noel", -2.890371757896165),
+                                    ("en semainenamed-month", -2.4849066497880004),
+                                    ("hourmonth", -2.4849066497880004),
+                                    ("dayday", -2.4849066497880004),
+                                    ("en semaineintersect", -2.4849066497880004),
+                                    ("named-dayle <time>", -2.4849066497880004),
+                                    ("named-day<time-of-day> heures", -2.890371757896165),
+                                    ("week-endnamed-month", -2.4849066497880004)],
+                               n = 10}}),
+       ("named-month",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.499809670330265,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 88},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ce <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("soir", -1.6094379124341003),
+                                    ("apr\232s-midi", -1.0986122886681098),
+                                    ("hour", -0.7621400520468967)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh(:|h)mm (time-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.258096538021482,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 24},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("number (20..60)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hier",
+        Classifier{okData =
+                     ClassData{prior = -0.2231435513142097, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -1.6094379124341003,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("numbers prefix with -, negative or minus",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.2876820724517809),
+                                    ("numbers 22..29 32..39 .. 52..59", -2.0794415416798357),
+                                    ("number (0..16)", -2.0794415416798357)],
+                               n = 13}}),
+       ("dd-dd <month>(interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<datetime>-<day-of-week> dd <month>(interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersectnamed-daynamed-month", -2.0794415416798357),
+                                    ("day of month (premier)named-daynamed-month",
+                                     -1.791759469228055),
+                                    ("<day-of-week> <day-of-month>named-daynamed-month",
+                                     -1.791759469228055),
+                                    ("daydaymonth", -0.9808292530117262)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("year (latent)named-daynamed-month", -1.5686159179138452),
+                                    ("hourdaymonth", -1.5686159179138452),
+                                    ("time-of-day (latent)named-daynamed-month",
+                                     -1.5686159179138452),
+                                    ("yeardaymonth", -1.5686159179138452)],
+                               n = 8}}),
+       ("<dim time> du soir",
+        Classifier{okData =
+                     ClassData{prior = -0.2231435513142097, unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("\224|vers <time-of-day>", -1.55814461804655),
+                                    ("hour", -0.7472144018302211),
+                                    ("<time-of-day> heures", -1.1526795099383855)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -1.6094379124341003,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.8472978603872037),
+                                    ("<time-of-day> heures", -0.8472978603872037)],
+                               n = 2}}),
+       ("aujourd'hui",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("midi",
+        Classifier{okData =
+                     ClassData{prior = -0.7375989431307791,
+                               unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -0.6505875661411494, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12}}),
+       ("toussaint",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("d\233but de soir\233e",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-week> <day-of-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.24512245803298496,
+                               unseen = -3.6888794541139363,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynumber (0..16)", -2.5649493574615367),
+                                    ("named-dayinteger (numeric)", -0.8303483020734304),
+                                    ("day", -0.719122666963206)],
+                               n = 18},
+                   koData =
+                     ClassData{prior = -1.5260563034950494, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-dayinteger (numeric)", -0.7731898882334817),
+                                    ("day", -0.7731898882334817)],
+                               n = 5}}),
+       ("d'ici <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.252762968495368), ("day", -1.252762968495368),
+                                    ("<integer> <unit-of-duration>", -0.8472978603872037)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("\224|vers <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.21936282847430377,
+                               unseen = -5.420534999272286,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> timezone", -4.722953221644475),
+                                    ("<hour-of-day> et|pass\233 de <number>", -4.31748811353631),
+                                    ("hh(:|h)mm (time-of-day)", -4.722953221644475),
+                                    ("midi", -4.31748811353631),
+                                    ("time-of-day (latent)", -1.587459005715325),
+                                    ("<hour-of-day> et|pass\233 de <number> minutes",
+                                     -4.31748811353631),
+                                    ("minuit", -4.722953221644475),
+                                    ("<hour-of-day> et quart", -4.31748811353631),
+                                    ("hour", -0.8622235106038793),
+                                    ("<hour-of-day> et demi", -4.722953221644475),
+                                    ("minute", -2.8511510447428834),
+                                    ("<time-of-day> heures", -1.587459005715325),
+                                    ("<hour-of-day> <integer> (as relative minutes)",
+                                     -3.8066624897703196)],
+                               n = 106},
+                   koData =
+                     ClassData{prior = -1.6247053845648889, unseen = -4.189654742026425,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh(:|h)mm (time-of-day)", -3.0757749812275272),
+                                    ("time-of-day (latent)", -1.5353299402803784),
+                                    ("hour", -1.0388930539664873), ("minute", -2.5649493574615367),
+                                    ("<time-of-day> heures", -1.8718021769015913),
+                                    ("<hour-of-day> <integer> (as relative minutes)",
+                                     -3.0757749812275272)],
+                               n = 26}}),
+       ("d\233but <named-month>(interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("d\233but de matin\233e",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("apr\232s-midi",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("le <ordinal> <cycle> de <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.7047480922384253),
+                                    ("ordinals (premier..seizieme)semaine (grain)named-month",
+                                     -1.7047480922384253),
+                                    ("ordinal (digits)jour (grain)named-month",
+                                     -1.7047480922384253),
+                                    ("weekmonth", -1.2992829841302609),
+                                    ("ordinals (premier..seizieme)semaine (grain)intersect",
+                                     -1.7047480922384253)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinals (premier..seizieme)",
+        Classifier{okData =
+                     ClassData{prior = -0.916290731874155, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6}}),
+       ("minute (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3}}),
+       ("deuxi\232me quinzaine de <named-month>(interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<dim time> <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.13858616328614667,
+                               unseen = -5.181783550292085,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -1.9980959022258835),
+                                    ("<time-of-day> heuresdu|dans le <part-of-day>",
+                                     -2.9789251552376097),
+                                    ("le <time>du|dans le <part-of-day>", -3.5667118201397288),
+                                    ("\224|vers <time-of-day>ce <part-of-day>",
+                                     -3.7898553714539385),
+                                    ("named-daymatin", -4.07753744390572),
+                                    ("demainsoir", -4.483002552013883),
+                                    ("<hour-of-day> et demice <part-of-day>", -3.7898553714539385),
+                                    ("hourhour", -1.4872702784598928),
+                                    ("aujourd'huiau d\233jeuner", -4.483002552013883),
+                                    ("<hour-of-day> et quartce <part-of-day>", -3.7898553714539385),
+                                    ("demainapr\232s-midi", -4.483002552013883),
+                                    ("minutehour", -2.6112003751122925),
+                                    ("named-daydu|dans le <part-of-day>", -3.7898553714539385),
+                                    ("intersectdu|dans le <part-of-day>", -2.1316272948504063),
+                                    ("le <day-of-month> \224 <datetime>du|dans le <part-of-day>",
+                                     -4.07753744390572),
+                                    ("named-dayfin d'apr\232s-midi", -4.483002552013883),
+                                    ("<day-of-month> <named-month>du|dans le <part-of-day>",
+                                     -4.07753744390572),
+                                    ("hierdu|dans le <part-of-day>", -4.483002552013883),
+                                    ("named-daysoir", -4.483002552013883),
+                                    ("aujourd'huid\233but de matin\233e", -3.5667118201397288),
+                                    ("<time-of-day> heuresce <part-of-day>", -3.7898553714539385),
+                                    ("\224|vers <time-of-day>du|dans le <part-of-day>",
+                                     -3.3843902633457743),
+                                    ("named-dayapr\232s-midi", -4.483002552013883),
+                                    ("intersectapr\232s-midi", -3.5667118201397288),
+                                    ("hiersoir", -4.483002552013883)],
+                               n = 74},
+                   koData =
+                     ClassData{prior = -2.044755983691946, unseen = -3.951243718581427,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -2.833213344056216),
+                                    ("<time-of-day> heuresdu|dans le <part-of-day>",
+                                     -2.5455312716044354),
+                                    ("le <time>du|dans le <part-of-day>", -2.833213344056216),
+                                    ("aujourd'huidu|dans le <part-of-day>", -2.833213344056216),
+                                    ("monthhour", -2.833213344056216),
+                                    ("hourhour", -1.8523840910444898),
+                                    ("<time-of-day> - <time-of-day> (interval)du|dans le <part-of-day>",
+                                     -2.833213344056216),
+                                    ("named-monthdu|dans le <part-of-day>", -2.833213344056216)],
+                               n = 11}}),
+       ("jour de l'an",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("time-of-day (latent)",
+        Classifier{okData =
+                     ClassData{prior = -0.5690945318899665, unseen = -4.553876891600541,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.16126814759612226),
+                                    ("number (0..16)", -2.0583881324820035)],
+                               n = 90},
+                   koData =
+                     ClassData{prior = -0.8347976976229721, unseen = -4.304065093204169,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.24740817331384096),
+                                    ("numbers 22..29 32..39 .. 52..59", -2.681021528714291),
+                                    ("number (20..60)", -3.597312260588446),
+                                    ("number (0..16)", -2.093234863812172)],
+                               n = 69}}),
+       ("apr\232s-demain",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year",
+        Classifier{okData =
+                     ClassData{prior = -0.46262352194811296,
+                               unseen = -2.9444389791664407,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 17},
+                   koData =
+                     ClassData{prior = -0.9932517730102834,
+                               unseen = -2.4849066497880004,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 10}}),
+       ("<hour-of-day> et|pass\233 de <number> minutes",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("\224|vers <time-of-day>number (20..60)", -2.3025850929940455),
+                                    ("<time-of-day> heuresinteger (numeric)", -2.3025850929940455),
+                                    ("\224|vers <time-of-day>number (0..16)", -2.3025850929940455),
+                                    ("hour", -0.916290731874155),
+                                    ("<time-of-day> heuresnumber (20..60)", -1.8971199848858813),
+                                    ("<time-of-day> heuresnumber (0..16)", -1.8971199848858813)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.916290731874155, unseen = -4.204692619390966,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.580216829592325),
+                                    ("number (0..16)semaine (grain)", -3.0910424533583156),
+                                    ("number (0..16)ann\233e (grain)", -3.0910424533583156),
+                                    ("number (0..16)seconde (grain)", -3.4965075614664802),
+                                    ("number (0..16)jour (grain)", -3.4965075614664802),
+                                    ("second", -3.4965075614664802),
+                                    ("integer (numeric)ann\233e (grain)", -3.4965075614664802),
+                                    ("number (0..16)minute (grain)", -3.0910424533583156),
+                                    ("integer (numeric)jour (grain)", -3.0910424533583156),
+                                    ("day", -2.803360380906535), ("year", -2.803360380906535),
+                                    ("number (0..16)mois (grain)", -3.0910424533583156),
+                                    ("hour", -2.580216829592325),
+                                    ("number (0..16)heure (grain)", -3.0910424533583156),
+                                    ("month", -3.0910424533583156),
+                                    ("integer (numeric)minute (grain)", -2.803360380906535),
+                                    ("minute", -2.3978952727983707),
+                                    ("integer (numeric)semaine (grain)", -3.0910424533583156),
+                                    ("numbers 22..29 32..39 .. 52..59heure (grain)",
+                                     -3.4965075614664802),
+                                    ("integer (numeric)heure (grain)", -3.4965075614664802)],
+                               n = 22},
+                   koData =
+                     ClassData{prior = -0.5108256237659907, unseen = -4.48863636973214,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -3.784189633918261),
+                                    ("number (20..60)minute (grain)", -3.784189633918261),
+                                    ("number (0..16)jour (grain)", -3.0910424533583156),
+                                    ("integer (numeric)ann\233e (grain)", -3.784189633918261),
+                                    ("number (0..16)minute (grain)", -3.784189633918261),
+                                    ("day", -3.0910424533583156), ("year", -3.784189633918261),
+                                    ("hour", -1.2584609896100056),
+                                    ("number (0..16)heure (grain)", -1.9123874570166697),
+                                    ("month", -3.784189633918261),
+                                    ("integer (numeric)minute (grain)", -3.784189633918261),
+                                    ("integer (numeric)mois (grain)", -3.784189633918261),
+                                    ("minute", -3.0910424533583156),
+                                    ("integer (numeric)semaine (grain)", -3.784189633918261),
+                                    ("integer (numeric)heure (grain)", -1.9123874570166697)],
+                               n = 33}}),
+       ("avant-hier",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("d\233but de journ\233e",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("entre <time-of-day> et <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.1670540846631662,
+                               unseen = -3.4011973816621555,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hourhour", -2.6741486494265287),
+                                    ("miditime-of-day (latent)", -2.6741486494265287),
+                                    ("minutehour", -0.9694005571881036),
+                                    ("hh(:|h)mm (time-of-day)time-of-day (latent)",
+                                     -2.268683541318364),
+                                    ("<hour-of-day> <integer> (as relative minutes)<time-of-day> heures",
+                                     -1.9810014688665833),
+                                    ("hh(:|h)mm (time-of-day)<time-of-day> heures",
+                                     -1.9810014688665833),
+                                    ("<hour-of-day> <integer> (as relative minutes)time-of-day (latent)",
+                                     -2.268683541318364)],
+                               n = 11},
+                   koData =
+                     ClassData{prior = -1.8718021769015913,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minutehour", -1.2992829841302609),
+                                    ("hh(:|h)mm (time-of-day)time-of-day (latent)",
+                                     -1.7047480922384253),
+                                    ("<hour-of-day> <integer> (as relative minutes)time-of-day (latent)",
+                                     -1.7047480922384253)],
+                               n = 2}}),
+       ("matin",
+        Classifier{okData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -0.916290731874155, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6}}),
+       ("en <named-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("maintenant",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-week> 1er-<day-of-week> dd <month>(interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-daynamed-month", -0.6931471805599453),
+                                    ("daydaymonth", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("named-day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.543294782270004,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 92},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("au d\233jeuner",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("le lendemain du <time>",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.3862943611198906),
+                                    ("<day-of-month> <named-month>", -1.3862943611198906)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("year (latent)", -1.6094379124341003),
+                                    ("time-of-day (latent)", -1.6094379124341003),
+                                    ("year", -1.6094379124341003), ("hour", -1.6094379124341003)],
+                               n = 2}}),
+       ("le <cycle> prochain|suivant|d'apr\232s",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.970291913552122,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.0608719606852628),
+                                    ("ann\233e (grain)", -3.258096538021482),
+                                    ("semaine (grain)", -1.0608719606852628),
+                                    ("mois (grain)", -2.8526314299133175),
+                                    ("day", -2.8526314299133175), ("year", -3.258096538021482),
+                                    ("jour (grain)", -2.8526314299133175),
+                                    ("month", -2.8526314299133175)],
+                               n = 22},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("apr\232s <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.9267620317414506,
+                               unseen = -3.9318256327243257,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -1.8325814637483102),
+                                    ("hh(:|h)mm (time-of-day)", -2.8134107167600364),
+                                    ("midi", -3.2188758248682006), ("day", -2.5257286443082556),
+                                    ("time-of-day (latent)", -2.8134107167600364),
+                                    ("hour", -1.6094379124341003), ("minute", -1.8325814637483102),
+                                    ("<time-of-day> heures", -2.8134107167600364),
+                                    ("le <time>", -2.8134107167600364),
+                                    ("<hour-of-day> <integer> (as relative minutes)",
+                                     -2.5257286443082556)],
+                               n = 19},
+                   koData =
+                     ClassData{prior = -0.503905180921417, unseen = -4.2626798770413155,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("demain", -2.8622008809294686),
+                                    ("intersect", -2.169053700369523),
+                                    ("le <day-of-month> (non ordinal)", -3.5553480614894135),
+                                    ("midi", -1.6835458845878222), ("day", -2.639057329615259),
+                                    ("time-of-day (latent)", -2.8622008809294686),
+                                    ("hour", -0.9903987040278769),
+                                    ("<time-of-day> heures", -2.8622008809294686)],
+                               n = 29}}),
+       ("dd/-mm",
+        Classifier{okData =
+                     ClassData{prior = -0.7375989431307791,
+                               unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -0.6505875661411494, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12}}),
+       ("minuit",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("entre dd et dd <month>(interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("une <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = -2.0281482472922856,
+                               unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.4849066497880004),
+                                    ("ann\233e (grain)", -2.4849066497880004),
+                                    ("seconde (grain)", -2.4849066497880004),
+                                    ("semaine (grain)", -2.4849066497880004),
+                                    ("second", -2.4849066497880004),
+                                    ("minute (grain)", -2.4849066497880004),
+                                    ("year", -2.4849066497880004),
+                                    ("heure (grain)", -2.4849066497880004),
+                                    ("hour", -2.4849066497880004), ("minute", -2.4849066497880004)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.14107859825990549,
+                               unseen = -4.394449154672439,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.246532418744732),
+                                    ("semaine (grain)", -1.246532418744732),
+                                    ("mois (grain)", -2.772588722239781),
+                                    ("day", -2.3025850929940455),
+                                    ("jour (grain)", -2.3025850929940455),
+                                    ("month", -2.772588722239781)],
+                               n = 33}}),
+       ("<hour-of-day> et quart",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("midi", -2.1972245773362196),
+                                    ("\224|vers <time-of-day>", -1.791759469228055),
+                                    ("hour", -0.8109302162163288),
+                                    ("<time-of-day> heures", -1.2809338454620642)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("number (0..16)",
+        Classifier{okData =
+                     ClassData{prior = -0.2076393647782445, unseen = -3.713572066704308,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 39},
+                   koData =
+                     ClassData{prior = -1.6739764335716716,
+                               unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9}}),
+       ("heure (grain)",
+        Classifier{okData =
+                     ClassData{prior = -1.540445040947149, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -0.2411620568168881,
+                               unseen = -3.1780538303479458,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 22}}),
+       ("<named-month|named-day> dernier|pass\233",
+        Classifier{okData =
+                     ClassData{prior = -2.3978952727983707,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.791759469228055), ("named-day", -1.791759469228055)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -9.53101798043249e-2,
+                               unseen = -3.4339872044851463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("year (latent)", -2.70805020110221),
+                                    ("en semaine", -2.3025850929940455),
+                                    ("day", -1.791759469228055),
+                                    ("\224|vers <time-of-day>", -2.70805020110221),
+                                    ("time-of-day (latent)", -2.70805020110221),
+                                    ("year", -2.70805020110221), ("hour", -1.6094379124341003),
+                                    ("<time-of-day> heures", -2.0149030205422647),
+                                    ("le <time>", -2.3025850929940455)],
+                               n = 10}}),
+       ("dd/-mm/-yyyy",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 13},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("fin de semaine",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dd mm yyyy",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("day of month (premier)",
+        Classifier{okData =
+                     ClassData{prior = -6.899287148695143e-2,
+                               unseen = -2.772588722239781,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 14},
+                   koData =
+                     ClassData{prior = -2.70805020110221, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("jour (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.1466034741918754, unseen = -3.044522437723423,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 19},
+                   koData =
+                     ClassData{prior = -1.9924301646902063,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3}}),
+       ("dd mm",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6}}),
+       ("dernier <cycle> de <time> (latent)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.4816045409242156),
+                                    ("semaine (grain)intersect", -1.9924301646902063),
+                                    ("weekmonth", -1.4816045409242156),
+                                    ("semaine (grain)named-month", -1.9924301646902063),
+                                    ("jour (grain)intersect", -1.9924301646902063),
+                                    ("jour (grain)named-month", -1.9924301646902063)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("avant le d\233jeuner",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("1er mai",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("fin de soir\233e",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -1.0185695809945732, unseen = -4.454347296253507,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("demain<time-of-day> heures", -3.7495040759303713),
+                                    ("day of month (premier)<day-of-week> <day-of-month>",
+                                     -3.056356895370426),
+                                    ("dayhour", -3.3440389678222067),
+                                    ("hh(:|h)mm (time-of-day)<hour-of-day> <integer> (as relative minutes)",
+                                     -3.3440389678222067),
+                                    ("le <day-of-month> (non ordinal)<time-of-day> heures",
+                                     -3.7495040759303713),
+                                    ("minuteminute", -2.044755983691946),
+                                    ("<day-of-month> <named-month><day-of-month> <named-month>",
+                                     -3.3440389678222067),
+                                    ("hh(:|h)mm (time-of-day)hh(:|h)mm (time-of-day)",
+                                     -3.056356895370426),
+                                    ("dayday", -1.8777018990287797),
+                                    ("<day-of-week> <day-of-month><day-of-week> <day-of-month>",
+                                     -3.7495040759303713),
+                                    ("<day-of-month> <named-month>day of month (premier)",
+                                     -3.3440389678222067),
+                                    ("minutehour", -3.3440389678222067),
+                                    ("<hour-of-day> <integer> (as relative minutes)<time-of-day> heures",
+                                     -3.7495040759303713),
+                                    ("<hour-of-day> <integer> (as relative minutes)hh(:|h)mm (time-of-day)",
+                                     -3.056356895370426),
+                                    ("hh(:|h)mm (time-of-day)<time-of-day> heures",
+                                     -3.7495040759303713),
+                                    ("<hour-of-day> <integer> (as relative minutes)<hour-of-day> <integer> (as relative minutes)",
+                                     -3.3440389678222067),
+                                    ("<day-of-month> <named-month>intersect", -3.3440389678222067),
+                                    ("intersect<day-of-week> <day-of-month>", -3.3440389678222067)],
+                               n = 26},
+                   koData =
+                     ClassData{prior = -0.4480247225269604, unseen = -4.836281906951478,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<hour-of-day> <integer> (as relative minutes)intersect",
+                                     -3.7297014486341915),
+                                    ("monthday", -2.8824035882469876),
+                                    ("intersectnamed-day", -3.7297014486341915),
+                                    ("day of month (premier)named-day", -3.4420193761824103),
+                                    ("named-month<day-of-month> <named-month>",
+                                     -3.7297014486341915),
+                                    ("minuteminute", -3.7297014486341915),
+                                    ("dayday", -1.4961092271270973),
+                                    ("<day-of-week> <day-of-month><day-of-week> <day-of-month>",
+                                     -3.7297014486341915),
+                                    ("day of month (premier)<day-of-month> <named-month>",
+                                     -4.135166556742356),
+                                    ("year<hour-of-day> <integer> (as relative minutes)",
+                                     -3.4420193761824103),
+                                    ("minutehour", -2.631089159966082),
+                                    ("<hour-of-day> <integer> (as relative minutes)<time-of-day> heures",
+                                     -3.4420193761824103),
+                                    ("day of month (premier)intersect", -2.8824035882469876),
+                                    ("hh(:|h)mm (time-of-day)<time-of-day> heures",
+                                     -3.4420193761824103),
+                                    ("intersectintersect", -3.2188758248682006),
+                                    ("hh(:|h)mm (time-of-day)intersect", -3.7297014486341915),
+                                    ("named-monthintersect", -3.7297014486341915),
+                                    ("<day-of-week> <day-of-month>named-day", -3.4420193761824103),
+                                    ("named-monthday of month (premier)", -3.7297014486341915),
+                                    ("<day-of-week> <day-of-month>intersect", -2.8824035882469876),
+                                    ("yearminute", -3.4420193761824103)],
+                               n = 46}}),
+       ("<day-of-month> <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.30010459245033816,
+                               unseen = -4.418840607796598,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 40},
+                   koData =
+                     ClassData{prior = -1.349926716949016, unseen = -3.4339872044851463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 14}}),
+       ("<cycle> prochain|suivant|d'apr\232s",
+        Classifier{okData =
+                     ClassData{prior = -8.338160893905101e-2,
+                               unseen = -4.007333185232471,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.0986122886681098),
+                                    ("ann\233e (grain)", -3.295836866004329),
+                                    ("semaine (grain)", -1.0986122886681098),
+                                    ("mois (grain)", -2.890371757896165),
+                                    ("day", -2.6026896854443837), ("year", -3.295836866004329),
+                                    ("jour (grain)", -2.6026896854443837),
+                                    ("month", -2.890371757896165)],
+                               n = 23},
+                   koData =
+                     ClassData{prior = -2.5257286443082556,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("mois (grain)", -1.791759469228055),
+                                    ("day", -1.791759469228055),
+                                    ("jour (grain)", -1.791759469228055),
+                                    ("month", -1.791759469228055)],
+                               n = 2}}),
+       ("fin <named-month>(interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> - <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -1.2321436812926323, unseen = -4.0943445622221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh(:|h)mm (time-of-day)<hour-of-day> <integer> (as relative minutes)",
+                                     -2.691243082785829),
+                                    ("minuteminute", -1.3694872428035094),
+                                    ("time-of-day (latent)time-of-day (latent)",
+                                     -3.3843902633457743),
+                                    ("<time-of-day> heures<time-of-day> heures",
+                                     -3.3843902633457743),
+                                    ("hh(:|h)mm (time-of-day)hh(:|h)mm (time-of-day)",
+                                     -2.468099531471619),
+                                    ("hourhour", -2.691243082785829),
+                                    ("minutehour", -2.468099531471619),
+                                    ("<hour-of-day> <integer> (as relative minutes)<time-of-day> heures",
+                                     -2.691243082785829),
+                                    ("<hour-of-day> <integer> (as relative minutes)hh(:|h)mm (time-of-day)",
+                                     -2.468099531471619),
+                                    ("hh(:|h)mm (time-of-day)<time-of-day> heures",
+                                     -3.3843902633457743),
+                                    ("<hour-of-day> <integer> (as relative minutes)<hour-of-day> <integer> (as relative minutes)",
+                                     -2.691243082785829),
+                                    ("<time-of-day> heurestime-of-day (latent)",
+                                     -3.3843902633457743)],
+                               n = 21},
+                   koData =
+                     ClassData{prior = -0.3448404862917295, unseen = -4.787491742782046,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)time-of-day (latent)",
+                                     -1.7833912195575383),
+                                    ("hourhour", -1.3451362886263831),
+                                    ("time-of-day (latent)<hour-of-day> <integer> (as relative minutes)",
+                                     -4.085976312551584),
+                                    ("hourminute", -4.085976312551584),
+                                    ("minutehour", -1.7346010553881064),
+                                    ("hh(:|h)mm (time-of-day)time-of-day (latent)",
+                                     -2.987364023883474),
+                                    ("<hour-of-day> <integer> (as relative minutes)<time-of-day> heures",
+                                     -3.169685580677429),
+                                    ("hh(:|h)mm (time-of-day)<time-of-day> heures",
+                                     -3.169685580677429),
+                                    ("<hour-of-day> <integer> (as relative minutes)time-of-day (latent)",
+                                     -2.6996819514316934),
+                                    ("time-of-day (latent)<time-of-day> heures",
+                                     -2.2942168433235293)],
+                               n = 51}}),
+       ("<named-day> en huit",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> et demi",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("midi", -1.791759469228055),
+                                    ("\224|vers <time-of-day>", -1.791759469228055),
+                                    ("hour", -0.8754687373538999),
+                                    ("<time-of-day> heures", -1.3862943611198906)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dernier week-end de <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("du dd au dd(interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinal (digits)",
+        Classifier{okData =
+                     ClassData{prior = -1.8718021769015913,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -0.1670540846631662,
+                               unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11}}),
+       ("avant <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -1.3862943611198906, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.7346010553881064),
+                                    ("hour", -1.2237754316221157),
+                                    ("<time-of-day> heures", -1.7346010553881064)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -3.5263605246161616,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -2.803360380906535),
+                                    ("hh(:|h)mm (time-of-day)", -2.803360380906535),
+                                    ("hier", -2.803360380906535), ("day", -2.803360380906535),
+                                    ("time-of-day (latent)", -1.8870696490323797),
+                                    ("hour", -1.1939224684724346), ("minute", -2.3978952727983707),
+                                    ("<time-of-day> heures", -1.8870696490323797),
+                                    ("<hour-of-day> <integer> (as relative minutes)",
+                                     -2.803360380906535)],
+                               n = 12}}),
+       ("fin d'apr\232s-midi",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<part-of-day> du <dim time>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("matin<day-of-month> <named-month>", -1.0986122886681098),
+                                    ("hourday", -1.0986122886681098)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("matinyear (latent)", -1.0986122886681098),
+                                    ("houryear", -1.0986122886681098)],
+                               n = 1}}),
+       ("<time-of-day> heures",
+        Classifier{okData =
+                     ClassData{prior = -0.16251892949777494,
+                               unseen = -5.634789603169249,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("\224|vers <time-of-day>", -1.8025703853322705),
+                                    ("time-of-day (latent)", -1.1538749673431592),
+                                    ("apr\232s <time-of-day>", -4.532599493153256),
+                                    ("hour", -0.7112308559932407),
+                                    ("avant <time-of-day>", -4.532599493153256)],
+                               n = 136},
+                   koData =
+                     ClassData{prior = -1.8971199848858813, unseen = -4.02535169073515,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("\224|vers <time-of-day>", -1.7047480922384253),
+                                    ("time-of-day (latent)", -1.927891643552635),
+                                    ("apr\232s <time-of-day>", -2.6210388241125804),
+                                    ("hour", -0.8292793548845253),
+                                    ("avant <time-of-day>", -2.3978952727983707),
+                                    ("minute", -3.3141860046725258),
+                                    ("<hour-of-day> <integer> (as relative minutes)",
+                                     -3.3141860046725258)],
+                               n = 24}}),
+       ("week-end",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("le <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809, unseen = -5.739792912179234,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.4784757594577096),
+                                    ("<named-month|named-day> suivant|d'apr\232s",
+                                     -3.5393477201429726),
+                                    ("<ordinal> <cycle> de <time>", -4.350277936359301),
+                                    ("premi\232re quinzaine de <named-month>(interval)",
+                                     -5.043425116919247),
+                                    ("intersect", -2.0476928433652555),
+                                    ("soir de no\235l", -5.043425116919247),
+                                    ("en semaine", -3.3386770246808215),
+                                    ("toussaint", -4.637960008811082), ("day", -1.3421231428067533),
+                                    ("deuxi\232me quinzaine de <named-month>(interval)",
+                                     -5.043425116919247),
+                                    ("<dim time> <part-of-day>", -4.127134385045092),
+                                    ("dd/-mm", -3.7906621484238787),
+                                    ("dd/-mm/-yyyy", -4.350277936359301),
+                                    ("dd mm yyyy", -3.7906621484238787),
+                                    ("day of month (premier)", -3.9448128282511368),
+                                    ("dd mm", -3.7906621484238787), ("hour", -2.2102117728630306),
+                                    ("month", -4.350277936359301),
+                                    ("dernier <cycle> de <time> (latent)", -4.127134385045092),
+                                    ("<day-of-month> <named-month>", -3.028522096376982),
+                                    ("<cycle> prochain|suivant|d'apr\232s", -2.6920498597557687),
+                                    ("dernier week-end de <time>", -5.043425116919247),
+                                    ("<ordinal> week-end de <time>", -4.637960008811082),
+                                    ("<cycle> dernier", -3.9448128282511368)],
+                               n = 141},
+                   koData =
+                     ClassData{prior = -1.3862943611198906, unseen = -4.812184355372417,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<named-month|named-day> suivant|d'apr\232s",
+                                     -4.110873864173311),
+                                    ("intersect", -3.1945831322991562),
+                                    ("en semaine", -2.23907168727172), ("day", -1.625967214385311),
+                                    ("<dim time> <part-of-day>", -3.7054087560651467),
+                                    ("dd/-mm", -3.417726683613366),
+                                    ("<named-month|named-day> dernier|pass\233",
+                                     -3.7054087560651467),
+                                    ("day of month (premier)", -4.110873864173311),
+                                    ("dd mm", -3.417726683613366), ("hour", -2.0959708436310467),
+                                    ("<day-of-month> <named-month>", -4.110873864173311),
+                                    ("<time-of-day> - <time-of-day> (interval)",
+                                     -2.406125771934886),
+                                    ("minute", -2.406125771934886),
+                                    ("<hour-of-day> <integer> (as relative minutes)",
+                                     -2.606796467397037)],
+                               n = 47}}),
+       ("apr\232s le <day-of-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 1}}),
+       ("n <cycle> passes|precedents",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6094379124341003),
+                                    ("integer (numeric)ann\233e (grain)", -1.6094379124341003),
+                                    ("year", -1.6094379124341003),
+                                    ("integer (numeric)semaine (grain)", -1.6094379124341003)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -1.3862943611198906),
+                                    ("number (0..16)heure (grain)", -1.3862943611198906)],
+                               n = 1}}),
+       ("il y a <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.8718021769015913), ("year", -1.8718021769015913),
+                                    ("<integer> <unit-of-duration>", -0.9555114450274363),
+                                    ("hour", -1.8718021769015913), ("month", -1.8718021769015913)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<ordinal> week-end de <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (premier..seizieme)named-month",
+                                     -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<dim time> du matin",
+        Classifier{okData =
+                     ClassData{prior = -0.13353139262452263,
+                               unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("\224|vers <time-of-day>", -1.7346010553881064),
+                                    ("hour", -0.7537718023763802),
+                                    ("<time-of-day> heures", -1.041453874828161)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -2.0794415416798357, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.916290731874155),
+                                    ("<time-of-day> heures", -0.916290731874155)],
+                               n = 1}}),
+       ("d\233but d'apr\232s-midi",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> <integer> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = -0.8174448972375224, unseen = -4.430816798843313,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuitnumber (0..16)", -3.3202283191284883),
+                                    ("midinumber (20..60)", -3.7256934272366524),
+                                    ("\224|vers <time-of-day>number (20..60)", -3.7256934272366524),
+                                    ("<time-of-day> heuresinteger (numeric)", -1.2833463918674481),
+                                    ("\224|vers <time-of-day>number (0..16)", -3.3202283191284883),
+                                    ("midinumber (0..16)", -3.7256934272366524),
+                                    ("hour", -0.8634925463071842),
+                                    ("<time-of-day> heuresnumber (20..60)", -3.3202283191284883),
+                                    ("\224|vers <time-of-day>integer (numeric)",
+                                     -3.7256934272366524),
+                                    ("<time-of-day> heuresnumber (0..16)", -3.3202283191284883)],
+                               n = 34},
+                   koData =
+                     ClassData{prior = -0.5826053061601215, unseen = -4.624972813284271,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)numbers 22..29 32..39 .. 52..59",
+                                     -3.9219733362813143),
+                                    ("time-of-day (latent)number (0..16)", -3.5165082281731497),
+                                    ("<time-of-day> heuresinteger (numeric)", -2.6692103677859462),
+                                    ("avant <time-of-day>integer (numeric)", -3.9219733362813143),
+                                    ("time-of-day (latent)integer (numeric)", -1.2829160066660554),
+                                    ("apr\232s <time-of-day>integer (numeric)", -3.228826155721369),
+                                    ("hour", -0.8309308829229983),
+                                    ("\224|vers <time-of-day>integer (numeric)",
+                                     -3.5165082281731497),
+                                    ("<time-of-day> heuresnumber (0..16)", -3.9219733362813143)],
+                               n = 43}}),
+       ("<cycle> dernier",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.9924301646902063),
+                                    ("ann\233e (grain)", -2.3978952727983707),
+                                    ("semaine (grain)", -1.9924301646902063),
+                                    ("mois (grain)", -2.3978952727983707),
+                                    ("day", -1.9924301646902063), ("year", -2.3978952727983707),
+                                    ("jour (grain)", -1.9924301646902063),
+                                    ("month", -2.3978952727983707)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("heure (grain)", -1.791759469228055),
+                                    ("hour", -1.791759469228055)],
+                               n = 1}}),
+       ("ce|dans le <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.2992829841302609),
+                                    ("ann\233e (grain)", -2.3978952727983707),
+                                    ("semaine (grain)", -1.2992829841302609),
+                                    ("day", -1.9924301646902063), ("year", -2.3978952727983707),
+                                    ("jour (grain)", -1.9924301646902063)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ce <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("soir", -1.466337068793427), ("day", -1.8718021769015913),
+                                    ("named-day", -1.8718021769015913),
+                                    ("hour", -1.1786549963416462),
+                                    ("week-end", -1.8718021769015913)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}})]
diff --git a/Duckling/Ranking/Classifiers/GA.hs b/Duckling/Ranking/Classifiers/GA.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/GA.hs
@@ -0,0 +1,155 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.GA (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers
+  = HashMap.fromList
+      [("integer (numeric)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3}}),
+       ("ar\250 am\225rach",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dd/mm",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("inniu",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dd/mm/yyyy",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 1}}),
+       ("<time> seo chugainn",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.8266785731844679),
+                                    ("d\233 named-day", -2.0794415416798357),
+                                    ("named-day", -1.3862943611198906),
+                                    ("an named-day", -1.6739764335716716)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("d\233 named-day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("anois",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("named-day",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("an named-day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("am\225rach",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("inn\233",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("<time> seo",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.7985076962177716),
+                                    ("d\233 named-day", -2.3025850929940455),
+                                    ("named-day", -1.3862943611198906),
+                                    ("an named-day", -1.6094379124341003)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ar\250 inn\233",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}})]
diff --git a/Duckling/Ranking/Classifiers/HE.hs b/Duckling/Ranking/Classifiers/HE.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/HE.hs
@@ -0,0 +1,992 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.HE (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers
+  = HashMap.fromList
+      [("integer (numeric)",
+        Classifier{okData =
+                     ClassData{prior = -1.110882381259924, unseen = -3.367295829986474,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 27},
+                   koData =
+                     ClassData{prior = -0.3993860620317821, unseen = -4.04305126783455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 55}}),
+       ("\1489 <date>",
+        Classifier{okData =
+                     ClassData{prior = -0.12783337150988489,
+                               unseen = -4.1588830833596715,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -3.044522437723423),
+                                    ("<time> <part-of-day>", -3.4499875458315876),
+                                    ("mm/dd", -3.4499875458315876),
+                                    ("intersect", -3.044522437723423),
+                                    ("named-month", -2.5336968139574325),
+                                    ("day", -2.1972245773362196),
+                                    ("this <cycle>", -3.4499875458315876),
+                                    ("time-of-day (latent)", -3.044522437723423),
+                                    ("<time-of-day> am|pm", -3.4499875458315876),
+                                    ("named-day", -3.044522437723423),
+                                    ("current <day-of-week>", -3.4499875458315876),
+                                    ("last <time>", -3.4499875458315876),
+                                    ("hour", -1.7452394535931621), ("month", -2.5336968139574325),
+                                    ("last <cycle>", -3.4499875458315876),
+                                    ("<named-month> <day-of-month> (non ordinal)",
+                                     -3.4499875458315876),
+                                    ("week-end", -3.044522437723423),
+                                    ("this <time>", -3.044522437723423)],
+                               n = 22},
+                   koData =
+                     ClassData{prior = -2.120263536200091, unseen = -3.258096538021482,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -2.5257286443082556), ("day", -2.120263536200091),
+                                    ("<day-of-month> (ordinal)", -2.5257286443082556),
+                                    ("month", -2.5257286443082556),
+                                    ("this <time>", -2.5257286443082556)],
+                               n = 3}}),
+       ("lunch",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (20..90)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.10536051565782628,
+                               unseen = -3.4011973816621555,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -1.4213856809311607),
+                                    ("\1489 <date>morning", -2.6741486494265287),
+                                    ("yesterdayevening|night", -2.6741486494265287),
+                                    ("hourhour", -1.9810014688665833),
+                                    ("time-of-day (latent)morning", -2.6741486494265287),
+                                    ("named-daymorning", -2.6741486494265287),
+                                    ("todayevening|night", -2.6741486494265287),
+                                    ("tomorrowlunch", -2.268683541318364),
+                                    ("at <time-of-day>morning", -2.6741486494265287),
+                                    ("tomorrowevening|night", -2.6741486494265287)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -2.3025850929940455, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -1.8718021769015913),
+                                    ("<day-of-month> (ordinal)morning", -1.8718021769015913)],
+                               n = 1}}),
+       ("today",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("mm/dd",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4}}),
+       ("at <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.0986122886681098),
+                                    ("<time-of-day> am|pm", -1.5040773967762742),
+                                    ("hour", -0.8109302162163288)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -1.3862943611198906, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.916290731874155),
+                                    ("hour", -0.916290731874155)],
+                               n = 1}}),
+       ("<day-of-month> (non ordinal) of <named-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.8649974374866046),
+                                    ("month", -0.7472144018302211),
+                                    ("integer 3named-month", -2.2512917986064953)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("absorption of , after named day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the ides of <named-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinal 4",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3}}),
+       ("\1489 <named-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("month (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hour (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinal 9",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("integer 21..99 (with and)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods =
+                                 HashMap.fromList [("integer (20..90)integer 4", 0.0)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -4.852030263919617,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hourday", -2.646962509122372),
+                                    ("daymonth", -3.2347491740244907),
+                                    ("monthyear", -3.7455747977904816),
+                                    ("the <day-of-month> (ordinal)in <named-month>",
+                                     -3.7455747977904816),
+                                    ("intersect<time-of-day> am|pm", -4.151039905898646),
+                                    ("<time> <part-of-day>\1489 <named-day>", -3.457892725338701),
+                                    ("intersect by \",\"<time-of-day> am|pm", -3.2347491740244907),
+                                    ("last <day-of-week> of <time>year", -4.151039905898646),
+                                    ("dayday", -2.7647455447787554),
+                                    ("the <day-of-month> (ordinal)\1489 <date>",
+                                     -3.7455747977904816),
+                                    ("dayyear", -2.359280436670591),
+                                    ("<day-of-month>(ordinal) <named-month>year",
+                                     -3.7455747977904816),
+                                    ("\1489 <date>\1489 <named-day>", -4.151039905898646),
+                                    ("named-monthyear", -3.7455747977904816),
+                                    ("absorption of , after named day<named-month> <day-of-month> (non ordinal)",
+                                     -4.151039905898646),
+                                    ("absorption of , after named day<day-of-month> (ordinal) of <named-month>",
+                                     -4.151039905898646),
+                                    ("<day-of-month> (ordinal) of <named-month>year",
+                                     -3.457892725338701),
+                                    ("named-daynext <cycle>", -4.151039905898646),
+                                    ("dayminute", -2.7647455447787554),
+                                    ("named-day<day-of-month> (ordinal) of <named-month>",
+                                     -4.151039905898646),
+                                    ("named-day\1489 <date>", -4.151039905898646),
+                                    ("absorption of , after named dayintersect",
+                                     -4.151039905898646),
+                                    ("named-day<day-of-month> (non ordinal) of <named-month>",
+                                     -4.151039905898646),
+                                    ("absorption of , after named dayintersect by \",\"",
+                                     -3.7455747977904816),
+                                    ("year<time-of-day> am|pm", -4.151039905898646),
+                                    ("\1489 <date>\1489 <date>", -4.151039905898646),
+                                    ("<day-of-month> (non ordinal) of <named-month>year",
+                                     -3.457892725338701),
+                                    ("absorption of , after named day<day-of-month> (non ordinal) of <named-month>",
+                                     -4.151039905898646),
+                                    ("dayweek", -3.7455747977904816),
+                                    ("<time> <part-of-day>\1489 <date>", -3.457892725338701),
+                                    ("named-daythe <day-of-month> (ordinal)", -4.151039905898646),
+                                    ("<day-of-month> (non ordinal) <named-month>year",
+                                     -3.7455747977904816),
+                                    ("yearminute", -4.151039905898646)],
+                               n = 42},
+                   koData =
+                     ClassData{prior = -1.0986122886681098, unseen = -4.454347296253507,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("in <named-month>year", -3.7495040759303713),
+                                    ("dayhour", -3.3440389678222067),
+                                    ("year<hour-of-day> <integer>", -3.3440389678222067),
+                                    ("daymonth", -3.056356895370426),
+                                    ("monthyear", -2.6508917872622613),
+                                    ("intersecthh:mm", -3.7495040759303713),
+                                    ("dayday", -3.7495040759303713),
+                                    ("intersect by \",\"hh:mm", -2.833213344056216),
+                                    ("named-monthyear", -3.056356895370426),
+                                    ("absorption of , after named daynamed-month",
+                                     -3.7495040759303713),
+                                    ("named-dayin <named-month>", -3.7495040759303713),
+                                    ("dayminute", -2.3632097148104805),
+                                    ("named-day\1489 <date>", -3.7495040759303713),
+                                    ("absorption of , after named dayintersect",
+                                     -3.7495040759303713),
+                                    ("yearhh:mm", -3.7495040759303713),
+                                    ("absorption of , after named dayintersect by \",\"",
+                                     -3.7495040759303713),
+                                    ("\1489 <date>year", -3.7495040759303713),
+                                    ("named-daythe <day-of-month> (ordinal)", -3.7495040759303713),
+                                    ("tomorrownoon", -3.3440389678222067),
+                                    ("yearminute", -3.056356895370426)],
+                               n = 21}}),
+       ("year (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("next <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.540445040947149),
+                                    ("month (grain)", -1.9459101490553135),
+                                    ("year (grain)", -1.9459101490553135),
+                                    ("week (grain)", -1.540445040947149),
+                                    ("year", -1.9459101490553135), ("month", -1.9459101490553135)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("yyyy-mm-dd",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("mm/dd/yyyy",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("evening|night",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinal 3",
+        Classifier{okData =
+                     ClassData{prior = -0.916290731874155, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3}}),
+       ("yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh:mm:ss",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month> (ordinal) of <named-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.0910424533583156,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal 3named-month", -1.9459101490553135),
+                                    ("ordinal (digits)named-month", -0.9650808960435872),
+                                    ("month", -0.7419373447293773)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> and <integer>",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)integer 4", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1}}),
+       ("quarter to|till|before <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> <integer>",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)integer 10", -1.7047480922384253),
+                                    ("time-of-day (latent)integer (numeric)", -1.0116009116784799),
+                                    ("hour", -0.7884573603642702)],
+                               n = 4}}),
+       ("ordinal 19",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("named-month",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 19},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("week (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("now",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer 11..19",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("integer 9integer 10", 0.0)],
+                               n = 1}}),
+       ("integer 7",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month>(ordinal) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal 3\1489 <date>", -1.7346010553881064),
+                                    ("ordinal (digits)in <named-month>", -2.1400661634962708),
+                                    ("month", -0.8873031950009028),
+                                    ("ordinal 3in <named-month>", -1.7346010553881064),
+                                    ("ordinal (digits)\1489 <date>", -2.1400661634962708)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("numbers prefix with -, negative or minus",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 6}}),
+       ("tomorrow",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinal 2",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6}}),
+       ("next <day-of-week>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("half an hour",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the <day-of-month> (ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal 3", -1.252762968495368),
+                                    ("ordinal 19", -1.252762968495368),
+                                    ("ordinal (digits)", -1.252762968495368)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -1.3862943611198906, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("ordinal 9", -0.916290731874155)],
+                               n = 1}}),
+       ("this <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -0.6931471805599453),
+                                    ("week (grain)", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("minute (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinal 7",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("time-of-day (latent)",
+        Classifier{okData =
+                     ClassData{prior = -1.2809338454620642,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.4700036292457356),
+                                    ("integer 9", -1.3862943611198906)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.325422400434628, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.2876820724517809),
+                                    ("integer (20..90)", -2.0794415416798357),
+                                    ("integer 9", -2.0794415416798357)],
+                               n = 13}}),
+       ("year",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -1.0986122886681098, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 4}}),
+       ("integer 9",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("last <day-of-week> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.0986122886681098),
+                                    ("daymonth", -0.8109302162163288),
+                                    ("named-dayintersect", -1.5040773967762742)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer 21..99 (with and)hour (grain)", -2.3025850929940455),
+                                    ("day", -1.8971199848858813),
+                                    ("integer 7day (grain)", -1.8971199848858813),
+                                    ("hour", -1.8971199848858813),
+                                    ("integer (numeric)minute (grain)", -1.8971199848858813),
+                                    ("minute", -1.8971199848858813),
+                                    ("integer (numeric)hour (grain)", -2.3025850929940455)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -1.6094379124341003),
+                                    ("integer 4hour (grain)", -1.6094379124341003)],
+                               n = 1}}),
+       ("<time-of-day> am|pm",
+        Classifier{okData =
+                     ClassData{prior = -0.3629054936893685,
+                               unseen = -3.6635616461296463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("\1489 <date>", -2.9444389791664407),
+                                    ("at <time-of-day>", -2.9444389791664407),
+                                    ("time-of-day (latent)", -2.2512917986064953),
+                                    ("hh:mm", -1.1526795099383855), ("hour", -1.845826690498331),
+                                    ("minute", -1.1526795099383855)],
+                               n = 16},
+                   koData =
+                     ClassData{prior = -1.1895840668738362, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.916290731874155),
+                                    ("hour", -0.916290731874155)],
+                               n = 7}}),
+       ("integer 10",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("intersect by \",\"",
+        Classifier{okData =
+                     ClassData{prior = -0.3794896217049037,
+                               unseen = -3.7612001156935624,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect by \",\"year", -3.044522437723423),
+                                    ("dayday", -2.128231705849268),
+                                    ("named-dayintersect by \",\"", -2.639057329615259),
+                                    ("dayyear", -2.128231705849268),
+                                    ("<named-month> <day-of-month> (non ordinal)intersect",
+                                     -3.044522437723423),
+                                    ("named-day<named-month> <day-of-month> (non ordinal)",
+                                     -3.044522437723423),
+                                    ("intersect by \",\"intersect", -3.044522437723423),
+                                    ("named-dayintersect", -3.044522437723423),
+                                    ("dayminute", -1.9459101490553135),
+                                    ("named-day<day-of-month> (ordinal) of <named-month>",
+                                     -3.044522437723423),
+                                    ("intersectyear", -3.044522437723423),
+                                    ("named-day<day-of-month> (non ordinal) of <named-month>",
+                                     -3.044522437723423),
+                                    ("intersectintersect", -3.044522437723423),
+                                    ("<named-month> <day-of-month> (non ordinal)year",
+                                     -2.639057329615259)],
+                               n = 13},
+                   koData =
+                     ClassData{prior = -1.1526795099383855, unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -2.639057329615259),
+                                    ("daymonth", -2.639057329615259),
+                                    ("named-dayintersect by \",\"", -2.639057329615259),
+                                    ("<named-month> <day-of-month> (non ordinal)intersect",
+                                     -2.639057329615259),
+                                    ("intersect by \",\"intersect", -2.639057329615259),
+                                    ("named-dayintersect", -2.639057329615259),
+                                    ("dayminute", -1.540445040947149),
+                                    ("intersectintersect", -2.639057329615259)],
+                               n = 6}}),
+       ("hh:mm",
+        Classifier{okData =
+                     ClassData{prior = -7.410797215372185e-2,
+                               unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 13},
+                   koData =
+                     ClassData{prior = -2.639057329615259, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("named-day",
+        Classifier{okData =
+                     ClassData{prior = -3.7740327982847086e-2,
+                               unseen = -3.332204510175204,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 26},
+                   koData =
+                     ClassData{prior = -3.295836866004329, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("ordinal 1",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7}}),
+       ("current <day-of-week>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("\1489 <date>", -1.9459101490553135),
+                                    ("\1489 <named-day>", -1.9459101490553135),
+                                    ("day", -0.8472978603872037),
+                                    ("named-day", -1.252762968495368)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer 4",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("quarter of an hour",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-day> <day-of-month> (ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-dayordinal (digits)", -0.916290731874155),
+                                    ("day", -0.916290731874155)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("absorption of , after named dayordinal (digits)",
+                                     -0.916290731874155),
+                                    ("day", -0.916290731874155)],
+                               n = 1}}),
+       ("<duration> ago",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("<integer> <unit-of-duration>", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinal 6",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4}}),
+       ("integer 3",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.9808292530117262,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("\1489 <date>", -1.791759469228055),
+                                    ("day", -1.791759469228055), ("named-day", -1.791759469228055),
+                                    ("hour", -1.3862943611198906),
+                                    ("week-end", -1.791759469228055)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -0.4700036292457356, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.9808292530117262),
+                                    ("named-day", -1.6739764335716716),
+                                    ("<day-of-month> (ordinal)", -1.3862943611198906)],
+                               n = 5}}),
+       ("<day-of-month> (ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal 4", -1.7047480922384253),
+                                    ("ordinal 3", -1.7047480922384253),
+                                    ("ordinal 2", -1.0116009116784799),
+                                    ("ordinal 1", -1.2992829841302609)],
+                               n = 7}}),
+       ("noon",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("next <time>",
+        Classifier{okData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -1.0986122886681098),
+                                    ("month", -1.0986122886681098)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.916290731874155), ("named-day", -0.916290731874155)],
+                               n = 3}}),
+       ("last <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.0986122886681098),
+                                    ("month (grain)", -1.791759469228055),
+                                    ("week (grain)", -1.0986122886681098),
+                                    ("month", -1.791759469228055)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinal 20..90",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("hhmm (military) am|pm",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("in <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -2.3025850929940455),
+                                    ("half an hour", -2.3025850929940455),
+                                    ("<integer> <unit-of-duration>", -1.2039728043259361),
+                                    ("quarter of an hour", -2.3025850929940455),
+                                    ("hour", -1.8971199848858813), ("minute", -1.3862943611198906)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("year<hour-of-day> <integer>", -0.6931471805599453),
+                                    ("yearminute", -0.6931471805599453)],
+                               n = 2}}),
+       ("in <named-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinal 5",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("<named-month> <day-of-month> (non ordinal)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("in <named-month>integer (numeric)", -2.1972245773362196),
+                                    ("\1489 <date>integer (numeric)", -2.1972245773362196),
+                                    ("named-monthinteger (numeric)", -1.0986122886681098),
+                                    ("month", -0.8109302162163288)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month> (non ordinal) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)in <named-month>", -1.8718021769015913),
+                                    ("integer 3\1489 <date>", -1.8718021769015913),
+                                    ("integer (numeric)\1489 <date>", -1.8718021769015913),
+                                    ("month", -0.9555114450274363),
+                                    ("integer 3in <named-month>", -1.8718021769015913)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinal (digits)",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7}}),
+       ("<day-of-month>(ordinal) <named-month> year",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal 3\1489 <date>", -1.252762968495368),
+                                    ("month", -0.8472978603872037),
+                                    ("ordinal 3in <named-month>", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("morning",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("week-end",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this evening",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("day (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.45198512374305727,
+                               unseen = -3.0910424533583156,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("\1489 <date>", -1.9459101490553135),
+                                    ("\1489 <named-day>", -2.3513752571634776),
+                                    ("day", -1.252762968495368), ("named-day", -1.6582280766035324),
+                                    ("hour", -1.9459101490553135),
+                                    ("week-end", -2.3513752571634776)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -1.0116009116784799, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("\1489 <date>", -2.0149030205422647),
+                                    ("day", -1.0986122886681098),
+                                    ("<day-of-month> (ordinal)", -1.3217558399823195)],
+                               n = 4}})]
diff --git a/Duckling/Ranking/Classifiers/HR.hs b/Duckling/Ranking/Classifiers/HR.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/HR.hs
@@ -0,0 +1,1850 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.HR (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers
+  = HashMap.fromList
+      [("<time> timezone",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> <part-of-day>", -1.7047480922384253),
+                                    ("intersect", -1.7047480922384253),
+                                    ("hh:mm", -1.7047480922384253), ("hour", -1.7047480922384253),
+                                    ("minute", -1.2992829841302609)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (numeric)",
+        Classifier{okData =
+                     ClassData{prior = -0.9697794168251413, unseen = -4.532599493153256,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 91},
+                   koData =
+                     ClassData{prior = -0.4766926173965321, unseen = -5.017279836814924,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 149}}),
+       ("Father's Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("lunch",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (20..90)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("few",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.3184537311185346, unseen = -5.19295685089021,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day> o'clockmorning", -4.4942386252808095),
+                                    ("until <time-of-day>afternoon", -4.4942386252808095),
+                                    ("dayhour", -2.7894905330423847),
+                                    ("time-of-day (latent)late night", -3.801091444720864),
+                                    ("time-of-day (latent)evening|night", -4.088773517172645),
+                                    ("<time-of-day> o'clockevening|night", -4.4942386252808095),
+                                    ("<time-of-day> o'clocktonight", -4.4942386252808095),
+                                    ("<hour-of-day> halfafternoon", -4.4942386252808095),
+                                    ("time-of-day (latent)tonight", -4.088773517172645),
+                                    ("yesterdayevening|night", -4.4942386252808095),
+                                    ("hourhour", -1.4497161875573867),
+                                    ("at <time-of-day>afternoon", -3.3956263366127),
+                                    ("minutehour", -2.5483284762254965),
+                                    ("<time-of-day> o'clockafternoon", -4.4942386252808095),
+                                    ("intersectafternoon", -4.4942386252808095),
+                                    ("time-of-day (latent)morning", -4.4942386252808095),
+                                    ("named-daymorning", -4.4942386252808095),
+                                    ("todayevening|night", -4.4942386252808095),
+                                    ("named-dayearly morning", -3.801091444720864),
+                                    ("at <time-of-day>late night", -3.801091444720864),
+                                    ("hh:mmearly morning", -4.4942386252808095),
+                                    ("<day-of-month>(ordinal) <named-month>morning",
+                                     -4.4942386252808095),
+                                    ("half <integer> (HR style hour-of-day)afternoon",
+                                     -4.088773517172645),
+                                    ("numeral after|past (hour-of-day)afternoon",
+                                     -4.4942386252808095),
+                                    ("quarter after|past (hour-of-day)afternoon",
+                                     -4.4942386252808095),
+                                    ("<time-of-day> o'clocklate night", -4.088773517172645),
+                                    ("tomorrowlunch", -4.4942386252808095),
+                                    ("todaytonight", -4.4942386252808095),
+                                    ("time-of-day (latent)afternoon", -3.2414756567854415),
+                                    ("intersectmorning", -3.2414756567854415),
+                                    ("about <time-of-day>afternoon", -3.801091444720864),
+                                    ("hh:mmafternoon", -3.2414756567854415),
+                                    ("between <datetime> and <datetime> (interval)morning",
+                                     -4.4942386252808095),
+                                    ("between <time-of-day> and <time-of-day> (interval)morning",
+                                     -4.4942386252808095),
+                                    ("at <time-of-day>morning", -4.4942386252808095),
+                                    ("tomorrowevening|night", -4.4942386252808095)],
+                               n = 64},
+                   koData =
+                     ClassData{prior = -1.2992829841302609, unseen = -4.605170185988091,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day> o'clockmorning", -3.9019726695746444),
+                                    ("dayhour", -3.4965075614664802),
+                                    ("yearhour", -3.4965075614664802),
+                                    ("named-daylate night", -3.9019726695746444),
+                                    ("monthhour", -3.4965075614664802),
+                                    ("by <time>afternoon", -3.9019726695746444),
+                                    ("after <time-of-day>morning", -3.9019726695746444),
+                                    ("hourhour", -2.1972245773362196),
+                                    ("until <time-of-day>morning", -3.9019726695746444),
+                                    ("since <time-of-day>morning", -3.9019726695746444),
+                                    ("minutehour", -2.649209701079277),
+                                    ("time-of-day (latent)morning", -3.9019726695746444),
+                                    ("year (latent)afternoon", -3.4965075614664802),
+                                    ("by <time>morning", -3.9019726695746444),
+                                    ("numeral to|till|before <integer> (hour-of-day)morning",
+                                     -3.9019726695746444),
+                                    ("named-monthmorning", -3.9019726695746444),
+                                    ("secondhour", -3.4965075614664802),
+                                    ("time-of-day (latent)afternoon", -2.515678308454754),
+                                    ("intersectmorning", -3.4965075614664802),
+                                    ("<duration> after <time>afternoon", -3.9019726695746444),
+                                    ("named-monthlate night", -3.9019726695746444),
+                                    ("<day-of-month>(ordinal) <named-month>late night",
+                                     -3.9019726695746444)],
+                               n = 24}}),
+       ("today",
+        Classifier{okData =
+                     ClassData{prior = -1.1526795099383855,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -0.3794896217049037, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 13}}),
+       ("at <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.1670540846631662, unseen = -3.912023005428146,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day> o'clock", -2.2823823856765264),
+                                    ("time-of-day (latent)", -1.1192315758708455),
+                                    ("hh:mm", -2.505525936990736), ("hour", -0.8960880245566356),
+                                    ("minute", -2.505525936990736)],
+                               n = 22},
+                   koData =
+                     ClassData{prior = -1.8718021769015913, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.9555114450274363),
+                                    ("hour", -0.9555114450274363)],
+                               n = 4}}),
+       ("absorption of , after named day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.4011973816621555,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.7282385003712154),
+                                    ("named-day", -0.8823891801984737),
+                                    ("u <named-day>", -2.268683541318364)],
+                               n = 13},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("tonight",
+        Classifier{okData =
+                     ClassData{prior = -0.3364722366212129,
+                               unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -1.252762968495368, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("integer (0..19)",
+        Classifier{okData =
+                     ClassData{prior = -0.15822400521489416,
+                               unseen = -3.6109179126442243,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 35},
+                   koData =
+                     ClassData{prior = -1.9218125974762528,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6}}),
+       ("between <time-of-day> and <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.6061358035703156,
+                               unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.0986122886681098),
+                                    ("time-of-day (latent)time-of-day (latent)",
+                                     -2.1972245773362196),
+                                    ("hh:mmhh:mm", -1.0986122886681098),
+                                    ("hourhour", -2.1972245773362196)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.7884573603642702, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -0.9808292530117262),
+                                    ("minutehour", -0.9808292530117262)],
+                               n = 5}}),
+       ("between <datetime> and <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.916290731874155, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.2039728043259361),
+                                    ("time-of-day (latent)time-of-day (latent)",
+                                     -2.3025850929940455),
+                                    ("hh:mmhh:mm", -1.2039728043259361),
+                                    ("hourhour", -2.3025850929940455)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.5108256237659907, unseen = -3.295836866004329,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -1.466337068793427),
+                                    ("minuteminute", -1.8718021769015913),
+                                    ("hourhour", -2.5649493574615367),
+                                    ("minutehour", -1.466337068793427),
+                                    ("hh:mmintersect", -1.8718021769015913),
+                                    ("time-of-day (latent)<time> <part-of-day>",
+                                     -2.5649493574615367)],
+                               n = 9}}),
+       ("month (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 10},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> o'clock",
+        Classifier{okData =
+                     ClassData{prior = -0.7308875085427924,
+                               unseen = -3.5263605246161616,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.8870696490323797),
+                                    ("time-of-day (latent)", -1.2992829841302609),
+                                    ("until <time-of-day>", -2.803360380906535),
+                                    ("hour", -0.8574502318512216)],
+                               n = 13},
+                   koData =
+                     ClassData{prior = -0.6567795363890705, unseen = -3.58351893845611,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<hour-of-day> quarter", -2.8622008809294686),
+                                    ("time-of-day (latent)", -0.9903987040278769),
+                                    ("hour", -0.916290731874155), ("minute", -2.8622008809294686),
+                                    ("after <time-of-day>", -2.8622008809294686)],
+                               n = 14}}),
+       ("three-quarters of an hour",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hour (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.8690378470236094, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 13},
+                   koData =
+                     ClassData{prior = -0.5436154465889815, unseen = -2.995732273553991,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 18}}),
+       ("<ordinal> quarter",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)quarter (grain)", -1.2992829841302609),
+                                    ("ordinals (first..19th)quarter (grain)", -1.2992829841302609),
+                                    ("quarter", -0.7884573603642702)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)quarter (grain)", -1.252762968495368),
+                                    ("ordinals (first..19th)quarter (grain)", -1.252762968495368),
+                                    ("quarter", -0.8472978603872037)],
+                               n = 2}}),
+       ("intersect",
+        Classifier{okData =
+                     ClassData{prior = -0.5171347929592555, unseen = -6.049733455231958,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("u <named-day><day-of-month>(ordinal) <named-month>",
+                                     -5.354224998486333),
+                                    ("hourday", -4.948759890378168),
+                                    ("dd.mm<time-of-day> o'clock", -5.354224998486333),
+                                    ("dayhour", -2.680076349059804),
+                                    ("daymonth", -3.5624655292582776),
+                                    ("u <named-day><named-month> <day-of-month> (ordinal)",
+                                     -5.354224998486333),
+                                    ("monthyear", -3.6494769062479073),
+                                    ("christmasyear", -5.354224998486333),
+                                    ("u <named-day><time-of-day> o'clock", -5.354224998486333),
+                                    ("<time> <part-of-day>u <named-day>", -3.9679306373664422),
+                                    ("intersectat <time-of-day>", -5.354224998486333),
+                                    ("day before yesterdayat <time-of-day>", -4.661077817926388),
+                                    ("between <time-of-day> and <time-of-day> (interval)u <named-day>",
+                                     -4.661077817926388),
+                                    ("between <datetime> and <datetime> (interval)u <named-day>",
+                                     -4.661077817926388),
+                                    ("last <cycle>u <named-day>", -5.354224998486333),
+                                    ("<time-of-day> o'clocktonight", -5.354224998486333),
+                                    ("from <datetime> - <datetime> (interval)u <named-day>",
+                                     -5.354224998486333),
+                                    ("today<time> <part-of-day>", -4.948759890378168),
+                                    ("todayat <time-of-day>", -5.354224998486333),
+                                    ("from <time-of-day> - <time-of-day> (interval)u <named-day>",
+                                     -5.354224998486333),
+                                    ("next <cycle>u <named-day>", -5.354224998486333),
+                                    ("named-dayhh:mm", -5.354224998486333),
+                                    ("dayday", -3.1029331998798373),
+                                    ("hourhour", -5.354224998486333),
+                                    ("intersect<named-month> <day-of-month> (non ordinal)",
+                                     -4.948759890378168),
+                                    ("intersectnamed-month", -4.437934266612178),
+                                    ("dayyear", -3.8501476017100584),
+                                    ("named-dayu <named-month>", -4.948759890378168),
+                                    ("<time-of-day> o'clocktomorrow", -5.354224998486333),
+                                    ("<time-of-day> - <time-of-day> (interval)u <named-day>",
+                                     -4.437934266612178),
+                                    ("<day-of-month>(ordinal) <named-month>year",
+                                     -4.948759890378168),
+                                    ("<datetime> - <datetime> (interval)u <named-day>",
+                                     -4.437934266612178),
+                                    ("absorption of , after named day<day-of-month>(ordinal) <named-month>",
+                                     -3.9679306373664422),
+                                    ("day after tomorrow<time> <part-of-day>", -5.354224998486333),
+                                    ("minutemonth", -4.437934266612178),
+                                    ("day after tomorrowat <time-of-day>", -4.948759890378168),
+                                    ("absorption of , after named day<named-month> <day-of-month> (ordinal)",
+                                     -4.437934266612178),
+                                    ("named-day<time> timezone", -5.354224998486333),
+                                    ("named-monthyear", -3.8501476017100584),
+                                    ("absorption of , after named day<named-month> <day-of-month> (non ordinal)",
+                                     -4.948759890378168),
+                                    ("this <cycle>u <named-day>", -4.948759890378168),
+                                    ("last <day-of-week> <time>year", -4.948759890378168),
+                                    ("dd.mmat <time-of-day>", -4.948759890378168),
+                                    ("intersect<day-of-month>(ordinal) <named-month>",
+                                     -4.437934266612178),
+                                    ("intersect<named-month> <day-of-month> (ordinal)",
+                                     -4.948759890378168),
+                                    ("named-daynext <cycle>", -5.354224998486333),
+                                    ("named-dayintersect", -5.354224998486333),
+                                    ("day before yesterday<time-of-day> o'clock",
+                                     -5.354224998486333),
+                                    ("<time> <part-of-day><named-day> <day-of-month> (ordinal)",
+                                     -4.437934266612178),
+                                    ("dayminute", -3.274783456806497),
+                                    ("u <named-day>at <time-of-day>", -4.948759890378168),
+                                    ("<time> <part-of-day>intersect", -3.4824228215847413),
+                                    ("u <named-day><time> <part-of-day>", -4.948759890378168),
+                                    ("intersectyear", -5.354224998486333),
+                                    ("<ordinal> <cycle> of <time>year", -5.354224998486333),
+                                    ("minuteday", -2.0583881324820035),
+                                    ("absorption of , after named dayintersect",
+                                     -5.354224998486333),
+                                    ("named-daybetween <time-of-day> and <time-of-day> (interval)",
+                                     -4.661077817926388),
+                                    ("<time> <part-of-day>intersect by \",\"", -4.437934266612178),
+                                    ("named-day<time> <part-of-day>", -4.437934266612178),
+                                    ("named-dayat <time-of-day>", -4.948759890378168),
+                                    ("named-daybetween <datetime> and <datetime> (interval)",
+                                     -4.661077817926388),
+                                    ("intersect by \",\"at <time-of-day>", -4.437934266612178),
+                                    ("absorption of , after named dayintersect by \",\"",
+                                     -4.255612709818223),
+                                    ("yearat <time-of-day>", -5.354224998486333),
+                                    ("<time> <part-of-day>absorption of , after named day",
+                                     -4.661077817926388),
+                                    ("todaytonight", -5.354224998486333),
+                                    ("weekday", -4.437934266612178),
+                                    ("named-day<time-of-day> o'clock", -5.354224998486333),
+                                    ("dayweek", -5.354224998486333),
+                                    ("weekyear", -4.948759890378168),
+                                    ("<named-day> <day-of-month> (ordinal)named-month",
+                                     -3.8501476017100584),
+                                    ("u <named-day><named-month> <day-of-month> (non ordinal)",
+                                     -5.354224998486333),
+                                    ("u <named-month>year", -4.948759890378168),
+                                    ("last <cycle> of <time>year", -4.948759890378168),
+                                    ("<day-of-month> (non ordinal) <named-month>year",
+                                     -5.354224998486333),
+                                    ("yearminute", -5.354224998486333)],
+                               n = 158},
+                   koData =
+                     ClassData{prior = -0.906900991524316, unseen = -5.7745515455444085,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("since <time-of-day>by <time>", -4.385146762010125),
+                                    ("year<time-of-day> - <time-of-day> (interval)",
+                                     -5.07829394257007),
+                                    ("hourday", -4.162003210695915),
+                                    ("dayhour", -3.1323837935147574),
+                                    ("named-monthnamed-day", -3.979681653901961),
+                                    ("daymonth", -2.9382277790737996),
+                                    ("monthday", -3.69199958145018),
+                                    ("monthyear", -3.4688560301359703),
+                                    ("yearhour", -4.672828834461907),
+                                    ("named-month<hour-of-day> <integer> (as relative minutes)",
+                                     -5.07829394257007),
+                                    ("hh:mmu <named-day>", -3.979681653901961),
+                                    ("last <time>today", -4.385146762010125),
+                                    ("intersectat <time-of-day>", -5.07829394257007),
+                                    ("named-daysince <time-of-day>", -4.162003210695915),
+                                    ("until <time-of-day>u <named-day>", -5.07829394257007),
+                                    ("since <time-of-day>u <named-day>", -4.672828834461907),
+                                    ("<time> <part-of-day>named-day", -5.07829394257007),
+                                    ("dayday", -3.979681653901961),
+                                    ("<time> <part-of-day>at <time-of-day>", -5.07829394257007),
+                                    ("named-monthnamed-month", -4.385146762010125),
+                                    ("monthmonth", -4.162003210695915),
+                                    ("hourhour", -5.07829394257007),
+                                    ("intersectnamed-month", -4.672828834461907),
+                                    ("dayyear", -4.385146762010125),
+                                    ("named-dayu <named-month>", -4.162003210695915),
+                                    ("<time-of-day> - <time-of-day> (interval)u <named-day>",
+                                     -5.07829394257007),
+                                    ("year<hour-of-day> <integer> (as relative minutes)",
+                                     -4.672828834461907),
+                                    ("monthminute", -4.672828834461907),
+                                    ("absorption of , after named day<day-of-month>(ordinal) <named-month>",
+                                     -5.07829394257007),
+                                    ("minutemonth", -4.672828834461907),
+                                    ("absorption of , after named day<named-month> <day-of-month> (ordinal)",
+                                     -5.07829394257007),
+                                    ("intersect<time-of-day> - <time-of-day> (interval)",
+                                     -5.07829394257007),
+                                    ("named-monthyear", -4.385146762010125),
+                                    ("absorption of , after named day<named-month> <day-of-month> (non ordinal)",
+                                     -4.385146762010125),
+                                    ("absorption of , after named daynamed-month",
+                                     -3.979681653901961),
+                                    ("intersect by \",\"<time-of-day> - <time-of-day> (interval)",
+                                     -4.162003210695915),
+                                    ("named-dayafter <time-of-day>", -4.162003210695915),
+                                    ("named-dayintersect", -4.385146762010125),
+                                    ("dayminute", -3.0633909220278057),
+                                    ("<time> <part-of-day>intersect", -4.672828834461907),
+                                    ("intersectyear", -4.162003210695915),
+                                    ("after <time-of-day>by <time>", -4.385146762010125),
+                                    ("minuteday", -3.0633909220278057),
+                                    ("absorption of , after named dayintersect", -5.07829394257007),
+                                    ("named-daybetween <time-of-day> and <time-of-day> (interval)",
+                                     -4.672828834461907),
+                                    ("named-monthnumeral to|till|before <integer> (hour-of-day)",
+                                     -5.07829394257007),
+                                    ("<time> <part-of-day>intersect by \",\"", -5.07829394257007),
+                                    ("named-day<time> <part-of-day>", -4.672828834461907),
+                                    ("named-daybetween <datetime> and <datetime> (interval)",
+                                     -4.385146762010125),
+                                    ("intersect by \",\"at <time-of-day>", -4.162003210695915),
+                                    ("absorption of , after named dayintersect by \",\"",
+                                     -5.07829394257007),
+                                    ("yearat <time-of-day>", -5.07829394257007),
+                                    ("named-month<day-of-month> (non ordinal) <named-month>",
+                                     -4.672828834461907),
+                                    ("<named-month> <day-of-month> (non ordinal)named-month",
+                                     -4.672828834461907),
+                                    ("u <named-day>named-month", -5.07829394257007),
+                                    ("<named-day> <day-of-month> (ordinal)named-month",
+                                     -5.07829394257007),
+                                    ("after <time-of-day>u <named-day>", -4.672828834461907),
+                                    ("named-monthintersect", -5.07829394257007),
+                                    ("hh:mmby <time>", -4.385146762010125),
+                                    ("u <named-month>year", -3.979681653901961),
+                                    ("minutesecond", -3.4688560301359703),
+                                    ("yearminute", -4.672828834461907)],
+                               n = 107}}),
+       ("early morning",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<ordinal> <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.7047480922384253),
+                                    ("ordinals (first..19th)week (grain)named-month",
+                                     -1.7047480922384253),
+                                    ("ordinals (first..19th)day (grain)named-month",
+                                     -1.7047480922384253),
+                                    ("ordinals (first..19th)week (grain)intersect",
+                                     -1.7047480922384253),
+                                    ("weekmonth", -1.2992829841302609)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("season",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("from <datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.252762968495368),
+                                    ("hh:mmhh:mm", -1.252762968495368)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -1.5040773967762742),
+                                    ("minuteminute", -1.5040773967762742),
+                                    ("minutehour", -1.5040773967762742),
+                                    ("hh:mmintersect", -1.5040773967762742)],
+                               n = 2}}),
+       ("day before yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("next <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.7047480922384253),
+                                    ("month (grain)", -1.9924301646902063),
+                                    ("year (grain)", -2.3978952727983707),
+                                    ("week (grain)", -1.7047480922384253),
+                                    ("quarter", -2.3978952727983707), ("year", -2.3978952727983707),
+                                    ("month", -1.9924301646902063),
+                                    ("quarter (grain)", -2.3978952727983707)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("number.number hours",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("from <time-of-day> - <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.0986122886681098),
+                                    ("hh:mmhh:mm", -1.0986122886681098)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -1.0986122886681098),
+                                    ("minutehour", -1.0986122886681098)],
+                               n = 1}}),
+       ("integer 21..99",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods =
+                                 HashMap.fromList [("integer (numeric)integer (numeric)", 0.0)],
+                               n = 1}}),
+       ("yyyy-mm-dd",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (latent)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 2}}),
+       ("prije <duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.13353139262452263,
+                               unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6094379124341003), ("day", -1.8971199848858813),
+                                    ("year", -2.3025850929940455),
+                                    ("<integer> <unit-of-duration>", -0.916290731874155),
+                                    ("month", -2.3025850929940455)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -2.0794415416798357,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> <unit-of-duration>", -1.3862943611198906),
+                                    ("hour", -1.3862943611198906)],
+                               n = 1}}),
+       ("evening|night",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dd/mm/yyyy",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<ordinal> quarter <year>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("quarteryear", -0.8472978603872037),
+                                    ("ordinals (first..19th)quarter (grain)year",
+                                     -1.252762968495368),
+                                    ("ordinal (digits)quarter (grain)year", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("after lunch",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh:mm:ss",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("numeral to|till|before <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = -1.791759469228055, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (0..19)noon", -0.916290731874155),
+                                    ("hour", -0.916290731874155)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.1823215567939546, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.7731898882334817),
+                                    ("integer (numeric)time-of-day (latent)", -0.7731898882334817)],
+                               n = 5}}),
+       ("quarter to|till|before <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("noon", -0.6931471805599453), ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("a pair",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("nth <time> of <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -0.7884573603642702),
+                                    ("ordinals (first..19th)named-dayintersect",
+                                     -1.0116009116784799),
+                                    ("ordinals (first..19th)named-daynamed-month",
+                                     -1.7047480922384253)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.8472978603872037,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -0.8109302162163288),
+                                    ("ordinals (first..19th)named-daynamed-month",
+                                     -0.8109302162163288)],
+                               n = 3}}),
+       ("<hour-of-day> quarter",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1}}),
+       ("named-month",
+        Classifier{okData =
+                     ClassData{prior = -0.25593337413720063,
+                               unseen = -3.912023005428146,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 48},
+                   koData =
+                     ClassData{prior = -1.488077055429833, unseen = -2.772588722239781,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 14}}),
+       ("week (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.258096538021482,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 24},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("valentine's day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <day-of-week> <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.6739764335716716),
+                                    ("daymonth", -0.8266785731844679),
+                                    ("named-dayu <named-month>", -1.6739764335716716),
+                                    ("named-dayintersect", -1.6739764335716716)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("now",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.2231435513142097, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("evening|night", -1.8718021769015913),
+                                    ("afternoon", -1.466337068793427),
+                                    ("hour", -0.9555114450274363),
+                                    ("morning", -1.8718021769015913)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -1.6094379124341003,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -1.252762968495368),
+                                    ("late night", -1.252762968495368)],
+                               n = 1}}),
+       ("<day-of-month>(ordinal) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -4.652001563489282e-2,
+                               unseen = -3.828641396489095,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)named-month", -0.916290731874155),
+                                    ("month", -0.7156200364120039),
+                                    ("ordinals (first..19th)named-month", -2.1972245773362196)],
+                               n = 21},
+                   koData =
+                     ClassData{prior = -3.0910424533583156, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)named-month", -0.916290731874155),
+                                    ("month", -0.916290731874155)],
+                               n = 1}}),
+       ("numbers i",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods =
+                                 HashMap.fromList [("integer (20..90)integer (0..19)", 0.0)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("numbers prefix with -, negative or minus",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 11}}),
+       ("new year's eve",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("tomorrow",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Mother's Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> after next",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -1.3862943611198906),
+                                    ("day", -1.3862943611198906),
+                                    ("named-day", -1.3862943611198906),
+                                    ("month", -1.3862943611198906)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("by <time>",
+        Classifier{okData =
+                     ClassData{prior = -2.70805020110221, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -1.6094379124341003),
+                                    ("midnight|EOD|end of day", -1.6094379124341003)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -6.899287148695143e-2,
+                               unseen = -3.6109179126442243,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> <part-of-day>", -2.4849066497880004),
+                                    ("intersect", -2.890371757896165),
+                                    ("time-of-day (latent)", -1.6376087894007967),
+                                    ("hh:mm", -2.1972245773362196), ("noon", -2.4849066497880004),
+                                    ("hour", -1.1856236656577395), ("minute", -1.9740810260220096)],
+                               n = 14}}),
+       ("half an hour",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("afternoon",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1780538303479458,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 22},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<duration> from now",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("year", -1.252762968495368),
+                                    ("<integer> <unit-of-duration>", -0.8472978603872037),
+                                    ("minute", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.295836866004329,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.8718021769015913),
+                                    ("month (grain)", -2.5649493574615367),
+                                    ("year (grain)", -2.5649493574615367),
+                                    ("week (grain)", -1.8718021769015913),
+                                    ("day", -2.5649493574615367), ("quarter", -2.159484249353372),
+                                    ("year", -2.5649493574615367), ("month", -2.5649493574615367),
+                                    ("quarter (grain)", -2.159484249353372),
+                                    ("day (grain)", -2.5649493574615367)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("minute (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 10},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> half",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("about <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.7047480922384253),
+                                    ("time-of-day (latent)", -1.0116009116784799),
+                                    ("hour", -0.7884573603642702)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.8472978603872037,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.8109302162163288),
+                                    ("hour", -0.8109302162163288)],
+                               n = 3}}),
+       ("time-of-day (latent)",
+        Classifier{okData =
+                     ClassData{prior = -0.9444616088408514, unseen = -3.713572066704308,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.2231435513142097),
+                                    ("integer (0..19)", -2.0794415416798357)],
+                               n = 35},
+                   koData =
+                     ClassData{prior = -0.49247648509779407,
+                               unseen = -4.110873864173311,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.4307829160924542),
+                                    ("integer (20..90)", -2.995732273553991),
+                                    ("few", -3.4011973816621555),
+                                    ("integer (0..19)", -1.455287232606842),
+                                    ("a pair", -3.4011973816621555)],
+                               n = 55}}),
+       ("for <duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.11441035117774422,
+                               unseen = -4.3694478524670215,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.9704144655697013),
+                                    ("three-quarters of an hour", -2.7472709142554916),
+                                    ("number.number hours", -3.6635616461296463),
+                                    ("second", -3.6635616461296463), ("day", -2.5649493574615367),
+                                    ("half an hour", -2.5649493574615367),
+                                    ("year", -3.6635616461296463),
+                                    ("<integer> <unit-of-duration>", -1.221214610760442),
+                                    ("hour", -2.4107986776342782), ("month", -3.6635616461296463),
+                                    ("minute", -1.5234954826333758),
+                                    ("about <duration>", -3.6635616461296463)],
+                               n = 33},
+                   koData =
+                     ClassData{prior = -2.2246235515243336, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> <unit-of-duration>", -1.3862943611198906),
+                                    ("hour", -1.3862943611198906)],
+                               n = 4}}),
+       ("year",
+        Classifier{okData =
+                     ClassData{prior = -0.2006706954621511, unseen = -2.995732273553991,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 18},
+                   koData =
+                     ClassData{prior = -1.7047480922384253, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 4}}),
+       ("<integer> <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.9808292530117262,
+                               unseen = -4.5217885770490405,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.5649493574615367),
+                                    ("integer (0..19)year (grain)", -3.1245651453969594),
+                                    ("integer (numeric)day (grain)", -2.431417964837014),
+                                    ("integer (0..19)hour (grain)", -3.817712325956905),
+                                    ("second", -3.817712325956905),
+                                    ("a pairhour (grain)", -3.817712325956905),
+                                    ("integer (numeric)year (grain)", -3.817712325956905),
+                                    ("day", -2.3136349291806306), ("year", -2.9014215940827497),
+                                    ("integer (numeric)week (grain)", -3.817712325956905),
+                                    ("integer (0..19)month (grain)", -3.41224721784874),
+                                    ("integer (0..19)second (grain)", -3.817712325956905),
+                                    ("hour", -2.5649493574615367), ("month", -3.41224721784874),
+                                    ("integer (numeric)minute (grain)", -2.7191000372887952),
+                                    ("integer (0..19)minute (grain)", -3.817712325956905),
+                                    ("fewhour (grain)", -3.817712325956905),
+                                    ("minute", -2.5649493574615367),
+                                    ("integer (numeric)hour (grain)", -3.1245651453969594),
+                                    ("integer (0..19)day (grain)", -3.817712325956905),
+                                    ("integer (0..19)week (grain)", -2.7191000372887952)],
+                               n = 33},
+                   koData =
+                     ClassData{prior = -0.4700036292457356, unseen = -4.912654885736052,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.959364629383116),
+                                    ("integer (0..19)year (grain)", -3.8066624897703196),
+                                    ("integer (numeric)day (grain)", -3.518980417318539),
+                                    ("fewday (grain)", -4.212127597878484),
+                                    ("integer (0..19)hour (grain)", -2.959364629383116),
+                                    ("second", -3.295836866004329),
+                                    ("numbers ihour (grain)", -3.8066624897703196),
+                                    ("integer (numeric)second (grain)", -3.8066624897703196),
+                                    ("integer (numeric)year (grain)", -3.8066624897703196),
+                                    ("day", -2.959364629383116), ("year", -3.295836866004329),
+                                    ("integer (numeric)week (grain)", -3.295836866004329),
+                                    ("integer (0..19)month (grain)", -3.8066624897703196),
+                                    ("integer (0..19)second (grain)", -3.8066624897703196),
+                                    ("hour", -1.5730702682632256), ("month", -3.295836866004329),
+                                    ("integer (numeric)minute (grain)", -3.8066624897703196),
+                                    ("integer (0..19)minute (grain)", -3.8066624897703196),
+                                    ("integer (numeric)month (grain)", -3.8066624897703196),
+                                    ("minute", -3.295836866004329),
+                                    ("integer (numeric)hour (grain)", -1.9095425048844386),
+                                    ("integer (0..19)day (grain)", -3.8066624897703196),
+                                    ("integer (0..19)week (grain)", -3.8066624897703196)],
+                               n = 55}}),
+       ("ordinals (first..19th)",
+        Classifier{okData =
+                     ClassData{prior = -6.899287148695143e-2,
+                               unseen = -2.772588722239781,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 14},
+                   koData =
+                     ClassData{prior = -2.70805020110221, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("<duration> after <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> <unit-of-duration>christmas", -1.7047480922384253),
+                                    ("yearday", -1.7047480922384253),
+                                    ("minutesecond", -1.7047480922384253),
+                                    ("<integer> <unit-of-duration>now", -1.7047480922384253)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("quarter of an hourtime-of-day (latent)", -1.7047480922384253),
+                                    ("minutehour", -1.2992829841302609),
+                                    ("quarter of an hour<time> <part-of-day>",
+                                     -1.7047480922384253)],
+                               n = 2}}),
+       ("intersect by \",\"",
+        Classifier{okData =
+                     ClassData{prior = -0.35667494393873245,
+                               unseen = -4.820281565605037,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("u <named-day><day-of-month>(ordinal) <named-month>",
+                                     -4.119037174812472),
+                                    ("<named-month> <day-of-month> (ordinal)intersect",
+                                     -4.119037174812472),
+                                    ("u <named-day><named-month> <day-of-month> (ordinal)",
+                                     -4.119037174812472),
+                                    ("intersecthh:mm", -4.119037174812472),
+                                    ("intersect by \",\"year", -3.713572066704308),
+                                    ("dayday", -2.0395956331326364),
+                                    ("intersect by \",\"hh:mm", -3.202746442938317),
+                                    ("intersect by \",\"intersect by \",\"", -4.119037174812472),
+                                    ("named-dayintersect by \",\"", -3.0204248861443626),
+                                    ("intersect<named-month> <day-of-month> (non ordinal)",
+                                     -4.119037174812472),
+                                    ("named-day<named-month> <day-of-month> (ordinal)",
+                                     -3.4258899942525267),
+                                    ("dayyear", -2.7327428136925813),
+                                    ("<named-month> <day-of-month> (ordinal)intersect by \",\"",
+                                     -4.119037174812472),
+                                    ("<day-of-month>(ordinal) <named-month>year",
+                                     -4.119037174812472),
+                                    ("named-day<named-month> <day-of-month> (non ordinal)",
+                                     -4.119037174812472),
+                                    ("intersect<day-of-month>(ordinal) <named-month>",
+                                     -3.713572066704308),
+                                    ("intersect<named-month> <day-of-month> (ordinal)",
+                                     -4.119037174812472),
+                                    ("intersect by \",\"intersect", -4.119037174812472),
+                                    ("named-dayintersect", -4.119037174812472),
+                                    ("intersectintersect by \",\"", -4.119037174812472),
+                                    ("dayminute", -2.0395956331326364),
+                                    ("intersectyear", -3.713572066704308),
+                                    ("minuteday", -3.202746442938317),
+                                    ("yearhh:mm", -4.119037174812472),
+                                    ("intersectintersect", -4.119037174812472),
+                                    ("named-day<day-of-month>(ordinal) <named-month>",
+                                     -2.866274206317104),
+                                    ("u <named-day><named-month> <day-of-month> (non ordinal)",
+                                     -4.119037174812472),
+                                    ("<named-month> <day-of-month> (ordinal)year",
+                                     -3.713572066704308),
+                                    ("yearminute", -4.119037174812472)],
+                               n = 42},
+                   koData =
+                     ClassData{prior = -1.2039728043259361, unseen = -4.330733340286331,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -2.70805020110221),
+                                    ("hourday", -3.624340932976365),
+                                    ("<named-month> <day-of-month> (ordinal)intersect",
+                                     -3.624340932976365),
+                                    ("dayhour", -2.5257286443082556),
+                                    ("daymonth", -2.5257286443082556),
+                                    ("monthyear", -3.624340932976365),
+                                    ("dayday", -2.5257286443082556),
+                                    ("named-dayintersect by \",\"", -3.624340932976365),
+                                    ("named-day<named-month> <day-of-month> (ordinal)",
+                                     -3.624340932976365),
+                                    ("intersectnamed-month", -3.624340932976365),
+                                    ("minutemonth", -3.624340932976365),
+                                    ("at <time-of-day><day-of-month>(ordinal) <named-month>",
+                                     -3.624340932976365),
+                                    ("named-monthyear", -3.624340932976365),
+                                    ("named-day<named-month> <day-of-month> (non ordinal)",
+                                     -2.9311937524164198),
+                                    ("intersect by \",\"intersect", -3.624340932976365),
+                                    ("named-dayintersect", -3.624340932976365),
+                                    ("intersectintersect", -3.624340932976365),
+                                    ("named-day<day-of-month>(ordinal) <named-month>",
+                                     -3.624340932976365),
+                                    ("u <named-day>named-month", -3.624340932976365)],
+                               n = 18}}),
+       ("hh:mm",
+        Classifier{okData =
+                     ClassData{prior = -2.7398974188114388e-2,
+                               unseen = -3.6375861597263857,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 36},
+                   koData =
+                     ClassData{prior = -3.6109179126442243,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("named-day",
+        Classifier{okData =
+                     ClassData{prior = -1.4815085785140587e-2,
+                               unseen = -4.23410650459726,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 67},
+                   koData =
+                     ClassData{prior = -4.219507705176107, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("quarter of an hour",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -0.8472978603872037,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3}}),
+       ("second (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("u <named-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.5263605246161616,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("absorption of , after named day", -2.3978952727983707),
+                                    ("day", -0.723918839226699),
+                                    ("named-day", -0.8574502318512216)],
+                               n = 15},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-day> <day-of-month> (ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -0.10536051565782628,
+                               unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("absorption of , after named dayordinal (digits)",
+                                     -1.0116009116784799),
+                                    ("named-dayordinal (digits)", -2.3978952727983707),
+                                    ("day", -0.7884573603642702),
+                                    ("u <named-day>ordinal (digits)", -2.3978952727983707)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -2.3025850929940455,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("absorption of , after named dayordinal (digits)",
+                                     -1.0986122886681098),
+                                    ("day", -1.0986122886681098)],
+                               n = 1}}),
+       ("last <time>",
+        Classifier{okData =
+                     ClassData{prior = -1.7047480922384253, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("evening|night", -2.0149030205422647),
+                                    ("day", -1.6094379124341003),
+                                    ("named-day", -1.6094379124341003),
+                                    ("hour", -1.6094379124341003),
+                                    ("week-end", -2.0149030205422647)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.2006706954621511, unseen = -3.784189633918261,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day> o'clock", -3.068052935133617),
+                                    ("time-of-day (latent)", -0.8708283577973976),
+                                    ("hour", -0.8167611365271219)],
+                               n = 18}}),
+       ("since <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -2.772588722239781, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mm", -1.7047480922384253),
+                                    ("minute", -1.7047480922384253)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -6.453852113757118e-2,
+                               unseen = -3.6888794541139363,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> <part-of-day>", -2.9704144655697013),
+                                    ("intersect", -1.8718021769015913),
+                                    ("second", -2.0541237336955462),
+                                    ("numeral to|till|before <integer> (hour-of-day)",
+                                     -2.9704144655697013),
+                                    ("now", -2.9704144655697013), ("hh:mm", -2.277267285009756),
+                                    ("<datetime> - <datetime> (interval)", -2.5649493574615367),
+                                    ("<time-of-day> - <time-of-day> (interval)",
+                                     -2.5649493574615367),
+                                    ("minute", -1.1786549963416462)],
+                               n = 15}}),
+       ("noon",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("until <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -1.2039728043259361, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day> o'clock", -1.8718021769015913),
+                                    ("time-of-day (latent)", -1.466337068793427),
+                                    ("hour", -1.1786549963416462)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -0.35667494393873245,
+                               unseen = -3.0910424533583156,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -2.3513752571634776),
+                                    ("hh:mm", -1.6582280766035324), ("noon", -1.9459101490553135),
+                                    ("hour", -1.4350845252893227),
+                                    ("midnight|EOD|end of day", -2.3513752571634776),
+                                    ("minute", -1.6582280766035324)],
+                               n = 7}}),
+       ("<integer> and an half hours",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("integer (0..19)", 0.0)],
+                               n = 1}}),
+       ("after <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("<integer> <unit-of-duration>", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("late night",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -0.8472978603872037,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3}}),
+       ("midnight|EOD|end of day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("next <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.25131442828090605,
+                               unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -2.3025850929940455),
+                                    ("day", -1.0498221244986778),
+                                    ("named-day", -1.0498221244986778),
+                                    ("month", -2.3025850929940455)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -1.5040773967762742,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day> o'clock", -1.2039728043259361),
+                                    ("hour", -1.2039728043259361)],
+                               n = 2}}),
+       ("last <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.3862943611198906),
+                                    ("month (grain)", -2.3025850929940455),
+                                    ("year (grain)", -1.8971199848858813),
+                                    ("week (grain)", -1.3862943611198906),
+                                    ("year", -1.8971199848858813), ("month", -2.3025850929940455)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("christmas",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("new year's day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("next n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.007333185232471,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.6026896854443837),
+                                    ("integer (0..19)year (grain)", -3.295836866004329),
+                                    ("integer (numeric)day (grain)", -3.295836866004329),
+                                    ("fewday (grain)", -3.295836866004329),
+                                    ("integer (0..19)hour (grain)", -3.295836866004329),
+                                    ("second", -2.890371757896165),
+                                    ("integer (numeric)second (grain)", -3.295836866004329),
+                                    ("integer (numeric)year (grain)", -3.295836866004329),
+                                    ("day", -2.6026896854443837), ("year", -2.890371757896165),
+                                    ("integer (numeric)week (grain)", -2.890371757896165),
+                                    ("integer (0..19)month (grain)", -3.295836866004329),
+                                    ("integer (0..19)second (grain)", -3.295836866004329),
+                                    ("hour", -2.890371757896165), ("month", -2.890371757896165),
+                                    ("integer (numeric)minute (grain)", -3.295836866004329),
+                                    ("integer (0..19)minute (grain)", -3.295836866004329),
+                                    ("integer (numeric)month (grain)", -3.295836866004329),
+                                    ("minute", -2.890371757896165),
+                                    ("integer (numeric)hour (grain)", -3.295836866004329),
+                                    ("integer (0..19)day (grain)", -3.295836866004329),
+                                    ("integer (0..19)week (grain)", -3.295836866004329)],
+                               n = 16},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -3.1354942159291497,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("halloween day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("by the end of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("today", -2.0794415416798357),
+                                    ("next <cycle>", -2.0794415416798357),
+                                    ("day", -1.3862943611198906),
+                                    ("this <cycle>", -1.6739764335716716),
+                                    ("month", -1.6739764335716716),
+                                    ("this <time>", -2.0794415416798357)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("in <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.343805421853684,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.9444389791664407),
+                                    ("three-quarters of an hour", -2.7212954278522306),
+                                    ("number.number hours", -3.6375861597263857),
+                                    ("second", -3.6375861597263857), ("day", -2.538973871058276),
+                                    ("half an hour", -2.538973871058276),
+                                    ("year", -3.6375861597263857),
+                                    ("<integer> <unit-of-duration>", -1.2396908869280152),
+                                    ("hour", -2.384823191231018), ("month", -3.6375861597263857),
+                                    ("minute", -1.55814461804655),
+                                    ("about <duration>", -3.6375861597263857)],
+                               n = 32},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -3.4011973816621555,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("since <time-of-day>hh:mm", -2.6741486494265287),
+                                    ("minuteminute", -1.4213856809311607),
+                                    ("hh:mmhh:mm", -1.7578579175523736),
+                                    ("dayday", -2.268683541318364),
+                                    ("<named-month> <day-of-month> (non ordinal)<named-month> <day-of-month> (non ordinal)",
+                                     -2.268683541318364),
+                                    ("after <time-of-day>hh:mm", -2.6741486494265287)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -3.4011973816621555,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -2.268683541318364),
+                                    ("minuteminute", -1.7578579175523736),
+                                    ("after <time-of-day>intersect", -2.6741486494265287),
+                                    ("year<hour-of-day> <integer> (as relative minutes)",
+                                     -2.268683541318364),
+                                    ("hh:mmintersect", -2.268683541318364),
+                                    ("since <time-of-day>intersect", -2.6741486494265287),
+                                    ("<named-month> <day-of-month> (non ordinal)named-month",
+                                     -2.268683541318364),
+                                    ("yearminute", -2.268683541318364)],
+                               n = 8}}),
+       ("<time-of-day> - <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.9808292530117262,
+                               unseen = -3.1780538303479458,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("since <time-of-day>hh:mm", -2.4423470353692043),
+                                    ("minuteminute", -1.1895840668738362),
+                                    ("hh:mmhh:mm", -1.5260563034950494),
+                                    ("after <time-of-day>hh:mm", -2.4423470353692043)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.4700036292457356,
+                               unseen = -3.4657359027997265,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("since <time-of-day>time-of-day (latent)", -2.740840023925201),
+                                    ("hh:mmtime-of-day (latent)", -1.824549292051046),
+                                    ("hourhour", -2.740840023925201),
+                                    ("minutehour", -1.1314021114911006),
+                                    ("numeral to|till|before <integer> (hour-of-day)time-of-day (latent)",
+                                     -2.0476928433652555),
+                                    ("at <time-of-day>time-of-day (latent)", -2.740840023925201),
+                                    ("after <time-of-day>time-of-day (latent)",
+                                     -2.740840023925201)],
+                               n = 10}}),
+       ("quarter after|past (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.110873864173311,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.70805020110221),
+                                    ("integer (0..19)year (grain)", -3.4011973816621555),
+                                    ("integer (numeric)day (grain)", -2.995732273553991),
+                                    ("integer (0..19)hour (grain)", -3.4011973816621555),
+                                    ("second", -2.995732273553991),
+                                    ("numbers ihour (grain)", -2.995732273553991),
+                                    ("integer (numeric)second (grain)", -3.4011973816621555),
+                                    ("integer (numeric)year (grain)", -3.4011973816621555),
+                                    ("day", -2.70805020110221), ("year", -2.995732273553991),
+                                    ("integer (numeric)week (grain)", -2.995732273553991),
+                                    ("integer (0..19)month (grain)", -3.4011973816621555),
+                                    ("integer (0..19)second (grain)", -3.4011973816621555),
+                                    ("hour", -2.3025850929940455), ("month", -2.995732273553991),
+                                    ("integer (numeric)minute (grain)", -3.4011973816621555),
+                                    ("integer (0..19)minute (grain)", -3.4011973816621555),
+                                    ("integer (numeric)month (grain)", -3.4011973816621555),
+                                    ("minute", -2.995732273553991),
+                                    ("integer (numeric)hour (grain)", -2.995732273553991),
+                                    ("integer (0..19)day (grain)", -3.4011973816621555),
+                                    ("integer (0..19)week (grain)", -3.4011973816621555)],
+                               n = 19},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -3.1354942159291497,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("nth <time> after <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayday", -0.916290731874155),
+                                    ("ordinals (first..19th)named-dayintersect",
+                                     -0.916290731874155)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayday", -0.916290731874155),
+                                    ("ordinals (first..19th)named-daychristmas",
+                                     -0.916290731874155)],
+                               n = 1}}),
+       ("<named-month> <day-of-month> (non ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -0.6061358035703156, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-monthinteger (numeric)", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.7884573603642702,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-monthinteger (numeric)", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 5}}),
+       ("numeral after|past (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.6931471805599453),
+                                    ("integer (20..90)time-of-day (latent)", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month> (non ordinal) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 2}}),
+       ("this|next <day-of-week>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinal (digits)",
+        Classifier{okData =
+                     ClassData{prior = -3.7740327982847086e-2,
+                               unseen = -3.332204510175204,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 26},
+                   koData =
+                     ClassData{prior = -3.295836866004329, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("quarter (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.540445040947149),
+                                    ("week (grain)named-month", -1.9459101490553135),
+                                    ("day (grain)intersect", -1.9459101490553135),
+                                    ("weekmonth", -1.540445040947149),
+                                    ("day (grain)named-month", -1.9459101490553135),
+                                    ("week (grain)intersect", -1.9459101490553135)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month>(ordinal) <named-month> year",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)named-month", -1.0986122886681098),
+                                    ("month", -0.8109302162163288),
+                                    ("ordinals (first..19th)named-month", -1.5040773967762742)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("morning",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("half <integer> (HR style hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("week-end",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("after <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -2.890371757896165, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mm", -1.9459101490553135),
+                                    ("minute", -1.9459101490553135)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -5.715841383994864e-2,
+                               unseen = -3.8501476017100584,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> <part-of-day>", -3.1354942159291497),
+                                    ("<time-of-day> o'clock", -3.1354942159291497),
+                                    ("intersect", -2.03688192726104),
+                                    ("second", -2.2192034840549946),
+                                    ("numeral to|till|before <integer> (hour-of-day)",
+                                     -3.1354942159291497),
+                                    ("now", -3.1354942159291497),
+                                    ("time-of-day (latent)", -3.1354942159291497),
+                                    ("hh:mm", -2.4423470353692043), ("hour", -2.7300291078209855),
+                                    ("<datetime> - <datetime> (interval)", -2.7300291078209855),
+                                    ("<time-of-day> - <time-of-day> (interval)",
+                                     -2.7300291078209855),
+                                    ("minute", -1.3437347467010947)],
+                               n = 17}}),
+       ("day (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.9444389791664407,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 17},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dd.mm",
+        Classifier{okData =
+                     ClassData{prior = -1.8718021769015913,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -0.1670540846631662,
+                               unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11}}),
+       ("day after tomorrow",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<month> dd-dd (interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("about <duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.5108256237659907, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("half an hour", -1.6094379124341003),
+                                    ("quarter of an hour", -1.0986122886681098),
+                                    ("minute", -0.7621400520468967)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.916290731874155, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("quarter of an hour", -0.7884573603642702),
+                                    ("minute", -0.7884573603642702)],
+                               n = 4}}),
+       ("u <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.7884573603642702,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.6061358035703156, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 6}}),
+       ("<hour-of-day> <integer> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)integer (numeric)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 4}}),
+       ("this <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.1670540846631662,
+                               unseen = -3.4965075614664802,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("today", -2.772588722239781), ("season", -2.0794415416798357),
+                                    ("evening|night", -2.772588722239781),
+                                    ("day", -1.5198257537444133), ("afternoon", -2.772588722239781),
+                                    ("named-day", -2.367123614131617),
+                                    ("hour", -1.6739764335716716), ("morning", -2.772588722239781),
+                                    ("week-end", -2.367123614131617)],
+                               n = 11},
+                   koData =
+                     ClassData{prior = -1.8718021769015913, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.9459101490553135),
+                                    ("named-day", -1.9459101490553135),
+                                    ("hour", -1.9459101490553135),
+                                    ("late night", -1.9459101490553135)],
+                               n = 2}}),
+       ("<named-month> <day-of-month> (ordinal)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("month", -0.6931471805599453),
+                                    ("named-monthordinal (digits)", -0.6931471805599453)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}})]
diff --git a/Duckling/Ranking/Classifiers/ID.hs b/Duckling/Ranking/Classifiers/ID.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/ID.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.ID (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers = HashMap.fromList []
diff --git a/Duckling/Ranking/Classifiers/IT.hs b/Duckling/Ranking/Classifiers/IT.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/IT.hs
@@ -0,0 +1,2085 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.IT (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers
+  = HashMap.fromList
+      [("<time-of-day> - <time-of-day> <day-of-month> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hourhourday", -1.6094379124341003),
+                                    ("minuteminuteday", -1.6094379124341003),
+                                    ("hh(:|h)mm (time-of-day)hh(:|h)mm (time-of-day)named-day",
+                                     -1.6094379124341003),
+                                    ("<integer> (latent time-of-day)<integer> (latent time-of-day)named-day",
+                                     -1.6094379124341003)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)<integer> (latent time-of-day)named-month",
+                                     -1.3862943611198906),
+                                    ("hourhourmonth", -1.3862943611198906)],
+                               n = 1}}),
+       ("midnight",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the <cycle> next",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.3862943611198906),
+                                    ("semaine (grain)", -1.3862943611198906),
+                                    ("mois (grain)", -1.3862943611198906),
+                                    ("month", -1.3862943611198906)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> timezone",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.0986122886681098),
+                                    ("hh(:|h)mm (time-of-day)", -1.791759469228055),
+                                    ("hour", -1.3862943611198906), ("minute", -1.3862943611198906)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (numeric)",
+        Classifier{okData =
+                     ClassData{prior = -0.5510646669461725, unseen = -5.030437921392435,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 151},
+                   koData =
+                     ClassData{prior = -0.8588143024487628, unseen = -4.727387818712341,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 111}}),
+       ("the day before yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("lunch",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dd[/-]mm",
+        Classifier{okData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -0.916290731874155, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("today",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("at <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.6325225587435105,
+                               unseen = -4.7535901911063645,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("midnight", -4.051784947803305),
+                                    ("<time> timezone", -4.051784947803305),
+                                    ("<integer 0 12> del <part of day>", -2.665490586683414),
+                                    ("<integer> (latent time-of-day)", -1.5668782980153044),
+                                    ("hh(:|h)mm (time-of-day)", -2.1058747987479913),
+                                    ("<hour-of-day> and <relative minutes>", -4.051784947803305),
+                                    ("noon", -3.6463198396951406),
+                                    ("hh <relative-minutes> del pomeriggio(time-of-day)",
+                                     -3.6463198396951406),
+                                    ("<hour-of-day> minus quart", -4.051784947803305),
+                                    ("hour", -1.2185716037470886), ("minute", -1.8004931491968097)],
+                               n = 51},
+                   koData =
+                     ClassData{prior = -0.7576857016975165,
+                               unseen = -4.6443908991413725,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("una", -3.9415818076696905),
+                                    ("<integer> (latent time-of-day)", -1.0512100497735257),
+                                    ("hh(:|h)mm (time-of-day)", -2.842969519001581),
+                                    ("<hour-of-day> and <relative minutes>", -3.536116699561526),
+                                    ("<hour-of-day> minus <integer> (as relative minutes)",
+                                     -3.9415818076696905),
+                                    ("noon", -3.9415818076696905), ("hour", -0.99714282850325),
+                                    ("minute", -2.4375044108934163)],
+                               n = 45}}),
+       ("ann\233e (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.833213344056216,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 15},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dopo le <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -1.3862943611198906, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("midnight", -2.0149030205422647),
+                                    ("<integer> (latent time-of-day)", -1.0986122886681098),
+                                    ("hour", -0.916290731874155)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.2876820724517809, unseen = -3.58351893845611,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("midnight", -2.8622008809294686),
+                                    ("<integer> (latent time-of-day)", -1.0704414117014134),
+                                    ("hh(:|h)mm (time-of-day)", -2.169053700369523),
+                                    ("hour", -0.9903987040278769), ("minute", -2.169053700369523)],
+                               n = 15}}),
+       ("seconde (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("stanotte",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3}}),
+       ("dal <integer> al <integer> (interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("number (0..19)number (0..19)", -0.6931471805599453),
+                                    ("integer (numeric)integer (numeric)", -0.6931471805599453)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer 0 12> del <part of day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.2876820724517809),
+                                    ("number (0..19)", -1.3862943611198906)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("una",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("semaine (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.4657359027997265,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 30},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("il <day-of-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.3364722366212129,
+                               unseen = -2.4849066497880004,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -1.252762968495368, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 4}}),
+       ("mois (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.258096538021482,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 24},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<ordinal> quarter",
+        Classifier{okData =
+                     ClassData{prior = -0.916290731874155, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (primo..10)trimestre (grain)", -0.6931471805599453),
+                                    ("quarter", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (primo..10)trimestre (grain)", -0.6931471805599453),
+                                    ("quarter", -0.6931471805599453)],
+                               n = 3}}),
+       ("in|entro <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.252762968495368),
+                                    ("<integer> <unit-of-duration>", -0.8472978603872037),
+                                    ("minute", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<cycle> next",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.2039728043259361),
+                                    ("semaine (grain)", -1.2039728043259361),
+                                    ("mois (grain)", -1.6094379124341003),
+                                    ("month", -1.6094379124341003)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("epifania",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <time> ",
+        Classifier{okData =
+                     ClassData{prior = -2.8622008809294686,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.7047480922384253),
+                                    ("named-day", -1.7047480922384253),
+                                    ("hour", -1.7047480922384253),
+                                    ("week-end", -1.7047480922384253)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -5.8840500022933465e-2,
+                               unseen = -4.304065093204169,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)", -0.9582549309731873),
+                                    ("intersect by \"di\", \"della\", \"del\"",
+                                     -3.1918471524802814),
+                                    ("day", -2.344549292093078), ("named-day", -3.1918471524802814),
+                                    ("hour", -0.9582549309731873),
+                                    ("two time tokens separated by `di`", -3.1918471524802814)],
+                               n = 33}}),
+       ("<ordinal> <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.7047480922384253),
+                                    ("weekmonth", -1.2992829841302609),
+                                    ("ordinals (primo..10)jour (grain)named-month",
+                                     -1.7047480922384253),
+                                    ("ordinals (primo..10)semaine (grain)named-month",
+                                     -1.7047480922384253),
+                                    ("ordinals (primo..10)semaine (grain)two time tokens in a row",
+                                     -1.7047480922384253)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("season",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> last",
+        Classifier{okData =
+                     ClassData{prior = -1.791759469228055, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.252762968495368), ("named-day", -1.252762968495368)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.1823215567939546, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.6094379124341003),
+                                    ("<integer> (latent time-of-day)", -1.3217558399823195),
+                                    ("hour", -0.916290731874155)],
+                               n = 5}}),
+       ("the <ordinal> <cycle> <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("quarteryear", -0.6931471805599453),
+                                    ("ordinals (primo..10)trimestre (grain)year (1000-2100 not latent)",
+                                     -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("verso <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.6931471805599453),
+                                    ("evening", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("weekday", -0.9808292530117262),
+                                    ("semaine (grain)<day-of-month> <named-month>",
+                                     -0.9808292530117262)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("semaine (grain)<integer> (latent time-of-day)",
+                                     -0.9808292530117262),
+                                    ("weekhour", -0.9808292530117262)],
+                               n = 2}}),
+       ("yyyy-mm-dd",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("in the <part-of-day> of <dim time>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hourday", -0.9808292530117262),
+                                    ("morningnamed-day", -1.3862943611198906),
+                                    ("eveningtomorrow", -1.3862943611198906)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hourday", -0.9808292530117262),
+                                    ("nighttomorrow", -0.9808292530117262)],
+                               n = 2}}),
+       ("yesterday",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("<ordinal> quarter <year>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("quarteryear", -0.6931471805599453),
+                                    ("ordinals (primo..10)trimestre (grain)year (1000-2100 not latent)",
+                                     -0.6931471805599453)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("festa della repubblica",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("in/after <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.4849066497880004),
+                                    ("un quarto d'ora", -2.4849066497880004),
+                                    ("<integer> <unit-of-duration>", -1.5686159179138452),
+                                    ("une <unit-of-duration>", -2.4849066497880004),
+                                    ("hour", -2.0794415416798357),
+                                    ("mezz'ora", -2.4849066497880004),
+                                    ("minute", -1.3862943611198906),
+                                    ("tre quarti d'ora", -2.4849066497880004)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("i|le n <cycle> passati|passate",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.219507705176107,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("number (21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99)heure (grain)",
+                                     -3.5115454388310208),
+                                    ("number (0..19)heure (grain)", -3.5115454388310208),
+                                    ("number (0..19)jour (grain)", -2.8183982582710754),
+                                    ("second", -3.1060803307228566),
+                                    ("integer (numeric)ann\233e (grain)", -3.1060803307228566),
+                                    ("integer (numeric)seconde (grain)", -3.5115454388310208),
+                                    ("integer (numeric)jour (grain)", -3.1060803307228566),
+                                    ("day", -2.412933150162911), ("year", -2.5952547069568657),
+                                    ("number (0..19)seconde (grain)", -3.5115454388310208),
+                                    ("number (0..19)ann\233e (grain)", -3.1060803307228566),
+                                    ("hour", -2.2587824703356527),
+                                    ("number (0..19)mois (grain)", -3.1060803307228566),
+                                    ("month", -2.412933150162911),
+                                    ("integer (numeric)minute (grain)", -3.5115454388310208),
+                                    ("integer (numeric)mois (grain)", -2.8183982582710754),
+                                    ("minute", -3.1060803307228566),
+                                    ("number (0..19)minute (grain)", -3.5115454388310208),
+                                    ("integer (numeric)heure (grain)", -2.5952547069568657)],
+                               n = 24},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.995732273553991,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("un quarto d'ora",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("santo stefano",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("entro il <integer>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("stasera",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("<integer> (latent time-of-day)",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -3.8066624897703196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.3184537311185346),
+                                    ("number (0..19)", -1.3862943611198906)],
+                               n = 41},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -4.454347296253507,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.3317773923170052),
+                                    ("number (21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99)",
+                                     -3.3440389678222067),
+                                    ("number (0..19)", -1.3981288187668934)],
+                               n = 82}}),
+       ("nth <time> of <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.4700036292457356, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -0.7731898882334817),
+                                    ("ordinals (primo..10)named-daynamed-month",
+                                     -1.466337068793427),
+                                    ("ordinals (primo..10)named-daytwo time tokens in a row",
+                                     -1.1786549963416462)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.9808292530117262,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -0.8109302162163288),
+                                    ("ordinals (primo..10)named-daynamed-month",
+                                     -0.8109302162163288)],
+                               n = 3}}),
+       ("a quart to <integer> (as hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("noon", -0.6931471805599453), ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> notte",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("new year's eve", -1.8718021769015913),
+                                    ("tomorrow", -0.9555114450274363),
+                                    ("day", -0.7731898882334817)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("named-month",
+        Classifier{okData =
+                     ClassData{prior = -1.680711831638129e-2,
+                               unseen = -4.110873864173311,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 59},
+                   koData =
+                     ClassData{prior = -4.0943445622221, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("intersect by \"di\", \"della\", \"del\"",
+        Classifier{okData =
+                     ClassData{prior = -0.41616039722491244,
+                               unseen = -4.442651256490317,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh(:|h)mm (time-of-day)named-day", -3.332204510175204),
+                                    ("named-daynamed-month", -3.332204510175204),
+                                    ("hourday", -2.4849066497880004),
+                                    ("day of month (1st)named-month", -3.7376696182833684),
+                                    ("daymonth", -2.3513752571634776),
+                                    ("named-daytwo time tokens in a row", -3.7376696182833684),
+                                    ("at <time-of-day>named-day", -3.332204510175204),
+                                    ("hh(:|h)mm (time-of-day)<day-of-month> <named-month>",
+                                     -3.7376696182833684),
+                                    ("two time tokens in a rowtwo time tokens in a row",
+                                     -3.7376696182833684),
+                                    ("dalle <time-of-day> alle <time-of-day> (interval)named-day",
+                                     -3.044522437723423),
+                                    ("<integer 0 12> del <part of day>tomorrow",
+                                     -3.7376696182833684),
+                                    ("hh(:|h)mm (time-of-day)two time tokens in a row",
+                                     -2.8213788864092133),
+                                    ("hh(:|h)mm (time-of-day)<named-day> <day-of-month>",
+                                     -3.332204510175204),
+                                    ("at <time-of-day>two time tokens in a row",
+                                     -3.332204510175204),
+                                    ("minuteday", -1.6582280766035324),
+                                    ("at <time-of-day><named-day> <day-of-month>",
+                                     -3.7376696182833684),
+                                    ("dayweek", -3.044522437723423),
+                                    ("two time tokens in a rownamed-month", -3.332204510175204),
+                                    ("<dim time> al <part-of-day>tomorrow", -3.7376696182833684),
+                                    ("named-daythis <cycle>", -3.044522437723423),
+                                    ("tra il <datetime> e il <datetime> (interval)named-day",
+                                     -3.332204510175204)],
+                               n = 31},
+                   koData =
+                     ClassData{prior = -1.0775588794702773, unseen = -4.007333185232471,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh(:|h)mm (time-of-day)named-day", -2.890371757896165),
+                                    ("named-daynamed-month", -2.379546134130174),
+                                    ("hourday", -2.1972245773362196),
+                                    ("daymonth", -1.9095425048844386),
+                                    ("named-daytwo time tokens in a row", -2.6026896854443837),
+                                    ("at <time-of-day>named-day", -2.1972245773362196),
+                                    ("stanottetomorrow", -2.890371757896165),
+                                    ("minuteday", -2.379546134130174)],
+                               n = 16}}),
+       ("<hour-of-day> and a quart",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 2}}),
+       ("commemorazione dei defunti",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("valentine's day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh(:|h)mm (time-of-day)",
+        Classifier{okData =
+                     ClassData{prior = -0.13353139262452263,
+                               unseen = -3.784189633918261,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 42},
+                   koData =
+                     ClassData{prior = -2.0794415416798357,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6}}),
+       ("EOY|End of year",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.4595323293784402, unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("afternoon", -2.639057329615259),
+                                    ("hour", -0.7672551527136672), ("evening", -1.1349799328389845),
+                                    ("morning", -1.9459101490553135)],
+                               n = 12},
+                   koData =
+                     ClassData{prior = -0.9985288301111273,
+                               unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.8109302162163288), ("evening", -1.791759469228055),
+                                    ("morning", -1.0986122886681098)],
+                               n = 7}}),
+       ("circa per le <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer 0 12> del <part of day>", -1.5040773967762742),
+                                    ("<integer> (latent time-of-day)", -1.0986122886681098),
+                                    ("hour", -0.8109302162163288)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -1.3862943611198906, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)", -0.916290731874155),
+                                    ("hour", -0.916290731874155)],
+                               n = 1}}),
+       ("christmas eve",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("festa del lavoro",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("il week-end del <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("<day-of-month> <named-month>", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("numbers prefix with -, negative or minus",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.2231435513142097),
+                                    ("number (0..19)", -1.6094379124341003)],
+                               n = 13}}),
+       ("entro le <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.11778303565638351,
+                               unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("il <day-of-month>", -2.639057329615259),
+                                    ("<integer> (latent time-of-day)", -2.2335922215070942),
+                                    ("named-month", -2.639057329615259),
+                                    ("hh(:|h)mm (time-of-day)", -2.639057329615259),
+                                    ("EOY|End of year", -2.639057329615259),
+                                    ("day", -2.639057329615259), ("year", -2.639057329615259),
+                                    ("EOM|End of month", -2.639057329615259),
+                                    ("noon", -2.639057329615259), ("hour", -1.9459101490553135),
+                                    ("month", -2.2335922215070942), ("minute", -2.639057329615259)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -2.1972245773362196, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)", -1.9459101490553135),
+                                    ("hour", -1.9459101490553135)],
+                               n = 1}}),
+       ("new year's eve",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("tomorrow",
+        Classifier{okData =
+                     ClassData{prior = -5.406722127027582e-2,
+                               unseen = -2.995732273553991,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 18},
+                   koData =
+                     ClassData{prior = -2.9444389791664407,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("Mother's Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("domattina",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("number (20..90)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinals (primo..10)",
+        Classifier{okData =
+                     ClassData{prior = -1.0185695809945732, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 13},
+                   koData =
+                     ClassData{prior = -0.4480247225269604,
+                               unseen = -3.2188758248682006,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 23}}),
+       ("the <ordinal> <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -0.6931471805599453),
+                                    ("ordinals (primo..10)jour (grain)named-month",
+                                     -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("gli <n> ultimi <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -0.8109302162163288),
+                                    ("number (0..19)semaine (grain)", -1.5040773967762742),
+                                    ("integer (numeric)semaine (grain)", -1.0986122886681098)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("afternoon",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3}}),
+       ("tra il <integer> e il <integer> (interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList [("integer (numeric)integer (numeric)", 0.0)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.2809338454620642),
+                                    ("ann\233e (grain)", -2.1972245773362196),
+                                    ("semaine (grain)", -1.2809338454620642),
+                                    ("quarter", -2.1972245773362196), ("year", -2.1972245773362196),
+                                    ("trimestre (grain)", -2.1972245773362196)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.3862943611198906),
+                                    ("semaine (grain)", -1.3862943611198906)],
+                               n = 1}}),
+       ("minute (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("il <cycle> dopo <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthday", -1.5040773967762742),
+                                    ("dayday", -1.5040773967762742),
+                                    ("mois (grain)two time tokens in a row", -1.5040773967762742),
+                                    ("jour (grain)tomorrow", -1.5040773967762742)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthday", -1.252762968495368),
+                                    ("mois (grain)christmas", -1.252762968495368)],
+                               n = 1}}),
+       ("night",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4}}),
+       ("tra il <datetime> e il <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.8109302162163288, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.466337068793427),
+                                    ("at <time-of-day>at <time-of-day>", -0.9555114450274363),
+                                    ("hourhour", -1.466337068793427)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.587786664902119, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)<integer> (latent time-of-day)",
+                                     -1.3217558399823195),
+                                    ("at <time-of-day>at <time-of-day>", -1.6094379124341003),
+                                    ("hourhour", -1.3217558399823195),
+                                    ("minutehour", -1.6094379124341003)],
+                               n = 5}}),
+       ("gli ultimi <n> <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.343805421853684,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -3.2321210516182215),
+                                    ("number (21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99)heure (grain)",
+                                     -3.6375861597263857),
+                                    ("number (0..19)heure (grain)", -2.9444389791664407),
+                                    ("number (0..19)jour (grain)", -3.2321210516182215),
+                                    ("second", -3.2321210516182215),
+                                    ("integer (numeric)ann\233e (grain)", -2.9444389791664407),
+                                    ("integer (numeric)seconde (grain)", -3.6375861597263857),
+                                    ("integer (numeric)jour (grain)", -3.2321210516182215),
+                                    ("day", -2.7212954278522306), ("year", -2.538973871058276),
+                                    ("number (0..19)seconde (grain)", -3.6375861597263857),
+                                    ("number (0..19)ann\233e (grain)", -3.2321210516182215),
+                                    ("number (0..19)semaine (grain)", -3.6375861597263857),
+                                    ("hour", -2.2512917986064953),
+                                    ("number (0..19)mois (grain)", -2.9444389791664407),
+                                    ("month", -2.538973871058276),
+                                    ("integer (numeric)minute (grain)", -3.6375861597263857),
+                                    ("integer (numeric)mois (grain)", -3.2321210516182215),
+                                    ("minute", -3.2321210516182215),
+                                    ("number (0..19)minute (grain)", -3.6375861597263857),
+                                    ("integer (numeric)semaine (grain)", -3.6375861597263857),
+                                    ("integer (numeric)heure (grain)", -2.9444389791664407)],
+                               n = 27},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -3.1354942159291497,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("le idi di <named-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the <ordinal> quarter",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (primo..10)trimestre (grain)", -0.6931471805599453),
+                                    ("quarter", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (primo..10)trimestre (grain)", -0.6931471805599453),
+                                    ("quarter", -0.6931471805599453)],
+                               n = 2}}),
+       ("<dim time> <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.5389965007326869, unseen = -3.295836866004329,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -1.1786549963416462),
+                                    ("<day-of-month> <named-month>morning", -2.5649493574615367),
+                                    ("tomorrowevening", -2.5649493574615367),
+                                    ("named-daymorning", -2.159484249353372),
+                                    ("tomorrowmorning", -2.5649493574615367),
+                                    ("tomorrowlunch", -2.5649493574615367),
+                                    ("yesterdayevening", -2.5649493574615367)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -0.8754687373538999,
+                               unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -1.9924301646902063),
+                                    ("tomorrownight", -2.3978952727983707),
+                                    ("monthhour", -1.7047480922384253),
+                                    ("named-monththis <part-of-day>", -1.9924301646902063),
+                                    ("tomorrowstanotte", -2.3978952727983707),
+                                    ("named-monthmorning", -2.3978952727983707)],
+                               n = 5}}),
+       ("<hour-of-day> and <relative minutes>",
+        Classifier{okData =
+                     ClassData{prior = -1.0116009116784799, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>number (20..90)", -1.8718021769015913),
+                                    ("<integer> (latent time-of-day)integer (numeric)",
+                                     -1.466337068793427),
+                                    ("<integer> (latent time-of-day)number (20..90)",
+                                     -1.8718021769015913),
+                                    ("hour", -0.9555114450274363)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.45198512374305727,
+                               unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>number (20..90)", -1.845826690498331),
+                                    ("<integer> (latent time-of-day)integer (numeric)",
+                                     -2.2512917986064953),
+                                    ("<integer> (latent time-of-day)number (20..90)",
+                                     -1.845826690498331),
+                                    ("hour", -0.8649974374866046),
+                                    ("<integer> (latent time-of-day)number (0..19)",
+                                     -1.845826690498331)],
+                               n = 7}}),
+       ("last <day-of-week> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.0986122886681098),
+                                    ("daymonth", -0.8109302162163288),
+                                    ("named-daytwo time tokens in a row", -1.5040773967762742)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-day> <day-of-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.295836866004329,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-dayinteger (numeric)", -0.6931471805599453),
+                                    ("day", -0.6931471805599453)],
+                               n = 12},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = -2.9444389791664407,
+                               unseen = -3.4339872044851463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.70805020110221),
+                                    ("number (0..19)semaine (grain)", -2.70805020110221),
+                                    ("hour", -2.70805020110221),
+                                    ("integer (numeric)minute (grain)", -2.3025850929940455),
+                                    ("minute", -2.3025850929940455),
+                                    ("integer (numeric)heure (grain)", -2.70805020110221)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -5.406722127027582e-2,
+                               unseen = -5.117993812416755,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -3.3202283191284883),
+                                    ("number (21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99)heure (grain)",
+                                     -4.013375499688434),
+                                    ("number (0..19)heure (grain)", -3.3202283191284883),
+                                    ("number (0..19)jour (grain)", -3.16607763930123),
+                                    ("second", -3.0325462466767075),
+                                    ("integer (numeric)ann\233e (grain)", -3.16607763930123),
+                                    ("integer (numeric)seconde (grain)", -3.7256934272366524),
+                                    ("integer (numeric)jour (grain)", -3.16607763930123),
+                                    ("day", -2.5470384308950065), ("year", -2.627081138568543),
+                                    ("number (0..19)seconde (grain)", -3.5025498759224427),
+                                    ("number (0..19)ann\233e (grain)", -3.3202283191284883),
+                                    ("number (0..19)semaine (grain)", -4.013375499688434),
+                                    ("hour", -2.278774444300327),
+                                    ("number (0..19)mois (grain)", -3.0325462466767075),
+                                    ("month", -2.403937587254333),
+                                    ("integer (numeric)minute (grain)", -3.5025498759224427),
+                                    ("integer (numeric)mois (grain)", -3.0325462466767075),
+                                    ("minute", -3.0325462466767075),
+                                    ("number (0..19)minute (grain)", -3.7256934272366524),
+                                    ("integer (numeric)semaine (grain)", -3.7256934272366524),
+                                    ("integer (numeric)heure (grain)", -2.8094026953624978)],
+                               n = 72}}),
+       ("immacolata concezione",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<dim time> al <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.3746934494414107,
+                               unseen = -3.6375861597263857,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -1.413693335308005),
+                                    ("<integer> (latent time-of-day)afternoon", -2.917770732084279),
+                                    ("hh(:|h)mm (time-of-day)afternoon", -2.917770732084279),
+                                    ("<day-of-month> <named-month>morning", -2.512305623976115),
+                                    ("il <day-of-month> <named-month>morning", -2.917770732084279),
+                                    ("hourhour", -2.917770732084279),
+                                    ("two time tokens in a rowmorning", -2.917770732084279),
+                                    ("minutehour", -2.512305623976115),
+                                    ("tomorrowevening", -2.917770732084279),
+                                    ("named-daymorning", -2.512305623976115),
+                                    ("il <time>morning", -2.917770732084279),
+                                    ("hh(:|h)mm (time-of-day)morning", -2.917770732084279)],
+                               n = 11},
+                   koData =
+                     ClassData{prior = -1.1631508098056809, unseen = -3.258096538021482,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -2.5257286443082556),
+                                    ("<integer> (latent time-of-day)afternoon", -2.120263536200091),
+                                    ("tomorrownight", -2.5257286443082556),
+                                    ("monthhour", -2.120263536200091),
+                                    ("hourhour", -2.120263536200091),
+                                    ("named-monthmorning", -2.120263536200091)],
+                               n = 5}}),
+       ("named-day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.290459441148391,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 71},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dal <datetime> al <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayday", -0.6931471805599453),
+                                    ("tomorrownamed-day", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dalle <time-of-day> alle <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.35667494393873245,
+                               unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("midnight<integer> (latent time-of-day)", -2.3978952727983707),
+                                    ("minuteminute", -1.7047480922384253),
+                                    ("hh(:|h)mm (time-of-day)hh(:|h)mm (time-of-day)",
+                                     -1.7047480922384253),
+                                    ("<integer> (latent time-of-day)<integer> (latent time-of-day)",
+                                     -1.9924301646902063),
+                                    ("hourhour", -1.4816045409242156),
+                                    ("<integer> (latent time-of-day)una", -2.3978952727983707)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -1.2039728043259361, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minutehour", -1.252762968495368),
+                                    ("hh(:|h)mm (time-of-day)<integer> (latent time-of-day)",
+                                     -1.252762968495368)],
+                               n = 3}}),
+       ("ferragosto",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("number (21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("day of month (1st)",
+        Classifier{okData =
+                     ClassData{prior = -9.53101798043249e-2,
+                               unseen = -2.4849066497880004,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 10},
+                   koData =
+                     ClassData{prior = -2.3978952727983707,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("number (0..19)",
+        Classifier{okData =
+                     ClassData{prior = -0.14058195062118944,
+                               unseen = -4.007333185232471,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 53},
+                   koData =
+                     ClassData{prior = -2.031432322493475, unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 8}}),
+       ("year (1000-2100 not latent)",
+        Classifier{okData =
+                     ClassData{prior = -0.2231435513142097, unseen = -2.890371757896165,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 16},
+                   koData =
+                     ClassData{prior = -1.6094379124341003, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 4}}),
+       ("il <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.1823215567939546, unseen = -4.060443010546419,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dd[/-]mm", -3.349904087274605),
+                                    ("<ordinal> quarter", -3.349904087274605),
+                                    ("<cycle> next", -3.349904087274605),
+                                    ("<ordinal> <cycle> of <time>", -3.349904087274605),
+                                    ("<ordinal> quarter <year>", -3.349904087274605),
+                                    ("day", -1.3350010667323402), ("quarter", -2.9444389791664407),
+                                    ("<dim time> al <part-of-day>", -3.349904087274605),
+                                    ("named-day", -3.349904087274605),
+                                    ("day of month (1st)", -3.349904087274605),
+                                    ("two time tokens in a row", -2.6567569067146595),
+                                    ("hour", -2.9444389791664407), ("month", -3.349904087274605),
+                                    ("next <time>", -2.9444389791664407),
+                                    ("<day-of-month> <named-month>", -2.097141118779237),
+                                    ("minute", -3.349904087274605),
+                                    ("week-end", -3.349904087274605)],
+                               n = 20},
+                   koData =
+                     ClassData{prior = -1.791759469228055, unseen = -3.258096538021482,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<ordinal> quarter", -2.5257286443082556),
+                                    ("day", -2.5257286443082556), ("quarter", -2.5257286443082556),
+                                    ("two time tokens in a row", -2.5257286443082556),
+                                    ("hour", -2.120263536200091),
+                                    ("<day-of-month> <named-month>", -2.5257286443082556),
+                                    ("week-end", -2.5257286443082556)],
+                               n = 4}}),
+       ("entro <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -3.1780538303479458,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -2.4423470353692043),
+                                    ("il <day-of-month>", -2.4423470353692043),
+                                    ("named-month", -2.4423470353692043),
+                                    ("EOY|End of year", -2.4423470353692043),
+                                    ("day", -2.4423470353692043), ("year", -2.4423470353692043),
+                                    ("EOM|End of month", -2.4423470353692043),
+                                    ("noon", -2.4423470353692043), ("hour", -2.4423470353692043),
+                                    ("month", -2.03688192726104), ("minute", -2.4423470353692043)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -1.9459101490553135, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.8718021769015913),
+                                    ("hour", -1.8718021769015913)],
+                               n = 1}}),
+       ("trimestre (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ognissanti",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> minus <integer> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("noonnumber (0..19)", -1.0986122886681098),
+                                    ("at <time-of-day>number (0..19)", -1.5040773967762742),
+                                    ("hour", -0.8109302162163288)],
+                               n = 3}}),
+       ("une <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("heure (grain)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("next <cycle> ",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.7047480922384253),
+                                    ("ann\233e (grain)", -2.3978952727983707),
+                                    ("semaine (grain)", -1.7047480922384253),
+                                    ("mois (grain)", -2.3978952727983707),
+                                    ("quarter", -1.9924301646902063), ("year", -2.3978952727983707),
+                                    ("trimestre (grain)", -1.9924301646902063),
+                                    ("month", -2.3978952727983707)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("EOM|End of month",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh quart del pomeriggio(time-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("two time tokens in a row",
+        Classifier{okData =
+                     ClassData{prior = -0.3708595789306889, unseen = -5.955837369464831,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("il <time>named-month", -5.260096153727839),
+                                    ("hh(:|h)mm (time-of-day)named-day", -5.260096153727839),
+                                    ("intersect by \"di\", \"della\", \"del\"year (1000-2100 not latent)",
+                                     -4.854631045619675),
+                                    ("named-monthyear (1000-2100 not latent)", -3.5553480614894135),
+                                    ("day of month (1st)named-month", -4.566948973167894),
+                                    ("dayhour", -3.245193133185574),
+                                    ("daymonth", -2.4875074314880576),
+                                    ("day of month (1st)intersect by \"di\", \"della\", \"del\"",
+                                     -4.566948973167894),
+                                    ("monthyear", -3.5553480614894135),
+                                    ("day of month (1st)two time tokens in a row",
+                                     -4.161483865059729),
+                                    ("named-dayentro <time>", -5.260096153727839),
+                                    ("named-daytwo time tokens in a row", -4.854631045619675),
+                                    ("<ordinal> <cycle> of <time>year (1000-2100 not latent)",
+                                     -5.260096153727839),
+                                    ("christmasyear (1000-2100 not latent)", -4.854631045619675),
+                                    ("<named-day> <day-of-month>two time tokens in a row",
+                                     -4.854631045619675),
+                                    ("tra il <integer> e il <integer> (interval)named-month",
+                                     -4.854631045619675),
+                                    ("tomorrowdopo <time>", -5.260096153727839),
+                                    ("at <time-of-day>named-day", -5.260096153727839),
+                                    ("intersect by \"di\", \"della\", \"del\"<day-of-month> <named-month>",
+                                     -4.566948973167894),
+                                    ("entro il <integer>named-month", -5.260096153727839),
+                                    ("day of month (1st)named-day", -4.161483865059729),
+                                    ("dayday", -2.695146796266302),
+                                    ("named-daydalle <time-of-day> alle <time-of-day> (interval)",
+                                     -4.854631045619675),
+                                    ("il <time>year (1000-2100 not latent)", -5.260096153727839),
+                                    ("intersect by \"di\", \"della\", \"del\"named-month",
+                                     -4.566948973167894),
+                                    ("dayyear", -3.120029990231568),
+                                    ("tomorrowdalle <time-of-day> alle <time-of-day> (interval)",
+                                     -5.260096153727839),
+                                    ("hh(:|h)mm (time-of-day)two time tokens in a row",
+                                     -4.854631045619675),
+                                    ("dd[/-]mmat <time-of-day>", -5.260096153727839),
+                                    ("tomorrowdopo le <time-of-day>", -4.854631045619675),
+                                    ("monthminute", -4.566948973167894),
+                                    ("minutemonth", -3.7560187569515646),
+                                    ("named-day<time> timezone", -5.260096153727839),
+                                    ("hh(:|h)mm (time-of-day)<named-day> <day-of-month>",
+                                     -5.260096153727839),
+                                    ("il <day-of-month> <named-month>at <time-of-day>",
+                                     -5.260096153727839),
+                                    ("named-dayin <named-month>", -4.566948973167894),
+                                    ("named-day<day-of-month> <named-month>", -3.6506582412937383),
+                                    ("<day-of-month> <named-month>at <time-of-day>",
+                                     -4.566948973167894),
+                                    ("il <day-of-month> <named-month>year (1000-2100 not latent)",
+                                     -5.260096153727839),
+                                    ("two time tokens separated by `di`year (1000-2100 not latent)",
+                                     -4.854631045619675),
+                                    ("last <cycle> of <time>year (1000-2100 not latent)",
+                                     -4.566948973167894),
+                                    ("day of month (1st)two time tokens separated by `di`",
+                                     -4.566948973167894),
+                                    ("dal <integer> al <integer> (interval)named-month",
+                                     -4.854631045619675),
+                                    ("il <day-of-month>named-month", -4.007333185232471),
+                                    ("the day after tomorrowat <time-of-day>", -4.854631045619675),
+                                    ("two time tokens in a rowyear (1000-2100 not latent)",
+                                     -4.566948973167894),
+                                    ("il <day-of-month>two time tokens in a row",
+                                     -4.854631045619675),
+                                    ("at <time-of-day>two time tokens in a row",
+                                     -4.854631045619675),
+                                    ("dayminute", -2.8622008809294686),
+                                    ("two time tokens in a rowat <time-of-day>",
+                                     -4.161483865059729),
+                                    ("minuteday", -3.0628715763916197),
+                                    ("two time tokens separated by `di`<day-of-month> <named-month>",
+                                     -4.566948973167894),
+                                    ("il <time>at <time-of-day>", -5.260096153727839),
+                                    ("named-dayat <time-of-day>", -4.854631045619675),
+                                    ("last <day-of-week> of <time>year (1000-2100 not latent)",
+                                     -5.260096153727839),
+                                    ("immacolata concezioneat <time-of-day>", -5.260096153727839),
+                                    ("at <time-of-day><named-day> <day-of-month>",
+                                     -5.260096153727839),
+                                    ("two time tokens separated by `di`named-month",
+                                     -4.566948973167894),
+                                    ("weekyear", -4.854631045619675),
+                                    ("named-dayentro le <time-of-day>", -5.260096153727839),
+                                    ("two time tokens in a rownamed-month", -4.854631045619675),
+                                    ("tomorrowat <time-of-day>", -5.260096153727839),
+                                    ("two time tokens in a row<day-of-month> <named-month>",
+                                     -4.854631045619675),
+                                    ("named-daytra il <datetime> e il <datetime> (interval)",
+                                     -4.854631045619675),
+                                    ("two time tokens in a rowin <named-month>",
+                                     -4.566948973167894),
+                                    ("<named-day> <day-of-month>named-month", -3.6506582412937383),
+                                    ("the day after tomorrowentro le <time-of-day>",
+                                     -5.260096153727839),
+                                    ("<day-of-month> <named-month>year (1000-2100 not latent)",
+                                     -4.854631045619675),
+                                    ("named-monthat <time-of-day>", -4.566948973167894),
+                                    ("commemorazione dei defuntiat <time-of-day>",
+                                     -5.260096153727839),
+                                    ("<datetime> - <datetime> (interval)named-day",
+                                     -5.260096153727839)],
+                               n = 147},
+                   koData =
+                     ClassData{prior = -1.1716374236829996,
+                               unseen = -5.4116460518550396,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>prossimi <unit-of-duration>",
+                                     -4.308559482792009),
+                                    ("hh(:|h)mm (time-of-day)named-day", -4.7140245909001735),
+                                    ("intersect by \"di\", \"della\", \"del\"year (1000-2100 not latent)",
+                                     -4.020877410340228),
+                                    ("named-monthyear (1000-2100 not latent)", -4.7140245909001735),
+                                    ("dayhour", -2.188295946591918),
+                                    ("daymonth", -4.7140245909001735),
+                                    ("monthday", -4.308559482792009),
+                                    ("monthyear", -4.7140245909001735),
+                                    ("named-daytwo time tokens in a row", -4.020877410340228),
+                                    ("tra il <datetime> e il <datetime> (interval)named-month",
+                                     -4.308559482792009),
+                                    ("<named-day> <day-of-month>two time tokens in a row",
+                                     -4.020877410340228),
+                                    ("named-month<day-of-month> <named-month>", -4.308559482792009),
+                                    ("monthhour", -3.615412302232064),
+                                    ("hourmonth", -4.308559482792009),
+                                    ("dayday", -4.7140245909001735),
+                                    ("named-daydalle <time-of-day> alle <time-of-day> (interval)",
+                                     -4.7140245909001735),
+                                    ("named-monththis <part-of-day>", -4.308559482792009),
+                                    ("dayyear", -3.2099471941238993),
+                                    ("tomorrowdalle <time-of-day> alle <time-of-day> (interval)",
+                                     -4.7140245909001735),
+                                    ("tomorrowstanotte", -4.7140245909001735),
+                                    ("year (1000-2100 not latent)<hour-of-day> <integer> (as relative minutes)",
+                                     -4.308559482792009),
+                                    ("the <cycle> of <time>named-month", -4.308559482792009),
+                                    ("dd[/-]mmat <time-of-day>", -4.7140245909001735),
+                                    ("tomorrowdopo le <time-of-day>", -4.308559482792009),
+                                    ("monthminute", -4.7140245909001735),
+                                    ("minutemonth", -4.308559482792009),
+                                    ("named-daydopo <time>", -4.7140245909001735),
+                                    ("il <day-of-month> <named-month>at <time-of-day>",
+                                     -4.7140245909001735),
+                                    ("dopo <time>at <time-of-day>", -4.7140245909001735),
+                                    ("<day-of-month> <named-month>at <time-of-day>",
+                                     -3.7977338590260183),
+                                    ("weekmonth", -4.308559482792009),
+                                    ("two time tokens separated by `di`year (1000-2100 not latent)",
+                                     -4.020877410340228),
+                                    ("hourweek", -4.308559482792009),
+                                    ("il <day-of-month>named-month", -4.7140245909001735),
+                                    ("the day after tomorrowat <time-of-day>", -4.7140245909001735),
+                                    ("<hour-of-day> <integer> (as relative minutes)named-month",
+                                     -4.7140245909001735),
+                                    ("il <day-of-month>two time tokens in a row",
+                                     -4.7140245909001735),
+                                    ("dayminute", -2.9222651216721185),
+                                    ("two time tokens in a rowat <time-of-day>",
+                                     -3.3277302297802827),
+                                    ("minuteday", -4.308559482792009),
+                                    ("il <time>at <time-of-day>", -4.7140245909001735),
+                                    ("named-daydopo le <time-of-day>", -4.020877410340228),
+                                    ("named-dayat <time-of-day>", -4.308559482792009),
+                                    ("<hour-of-day> <integer> (as relative minutes)named-day",
+                                     -4.7140245909001735),
+                                    ("tomorrowat <time-of-day>", -4.7140245909001735),
+                                    ("named-daytra il <datetime> e il <datetime> (interval)",
+                                     -4.7140245909001735),
+                                    ("dopo <time>year (1000-2100 not latent)", -4.308559482792009),
+                                    ("<hour-of-day> and <relative minutes>named-month",
+                                     -4.7140245909001735),
+                                    ("named-monthat <time-of-day>", -3.7977338590260183),
+                                    ("yearminute", -4.308559482792009)],
+                               n = 66}}),
+       ("heure (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.0910424533583156,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 20},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the day after tomorrow",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("noon",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("jour (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.995732273553991,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 18},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh <relative-minutes> del pomeriggio(time-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>number (20..90)", -1.2992829841302609),
+                                    ("<integer> (latent time-of-day)number (20..90)",
+                                     -1.2992829841302609),
+                                    ("hour", -0.7884573603642702)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> minus quart",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.5040773967762742),
+                                    ("noon", -1.0986122886681098), ("hour", -0.8109302162163288)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("evening",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 13},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dd[/-]mm[/-]yyyy",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("next <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("season", -2.0794415416798357), ("day", -0.8266785731844679),
+                                    ("named-day", -1.1631508098056809),
+                                    ("il <time>", -2.0794415416798357)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.0986122886681098),
+                                    ("named-day", -1.0986122886681098)],
+                               n = 1}}),
+       ("christmas",
+        Classifier{okData =
+                     ClassData{prior = -0.2231435513142097, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -1.6094379124341003,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("new year's day",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("next n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.304065093204169,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.498699971920336),
+                                    ("number (0..19)heure (grain)", -3.597312260588446),
+                                    ("number (0..19)jour (grain)", -3.597312260588446),
+                                    ("second", -2.9041650800285006),
+                                    ("integer (numeric)ann\233e (grain)", -3.597312260588446),
+                                    ("integer (numeric)seconde (grain)", -3.597312260588446),
+                                    ("integer (numeric)jour (grain)", -3.1918471524802814),
+                                    ("day", -2.9041650800285006), ("year", -2.9041650800285006),
+                                    ("number (0..19)seconde (grain)", -3.1918471524802814),
+                                    ("number (0..19)ann\233e (grain)", -3.1918471524802814),
+                                    ("number (0..19)semaine (grain)", -3.1918471524802814),
+                                    ("hour", -2.9041650800285006),
+                                    ("number (0..19)mois (grain)", -2.9041650800285006),
+                                    ("month", -2.344549292093078),
+                                    ("integer (numeric)minute (grain)", -3.1918471524802814),
+                                    ("integer (numeric)mois (grain)", -2.9041650800285006),
+                                    ("minute", -2.9041650800285006),
+                                    ("number (0..19)minute (grain)", -3.597312260588446),
+                                    ("integer (numeric)semaine (grain)", -2.9041650800285006),
+                                    ("integer (numeric)heure (grain)", -3.1918471524802814)],
+                               n = 26},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -3.0910424533583156,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("entro il <duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -0.9808292530117262),
+                                    ("semaine (grain)", -0.9808292530117262)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("mois (grain)", -0.9808292530117262),
+                                    ("month", -0.9808292530117262)],
+                               n = 2}}),
+       ("prossimi <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.9924301646902063),
+                                    ("ann\233e (grain)", -2.3978952727983707),
+                                    ("semaine (grain)", -1.9924301646902063),
+                                    ("mois (grain)", -1.9924301646902063),
+                                    ("day", -1.9924301646902063), ("year", -2.3978952727983707),
+                                    ("jour (grain)", -1.9924301646902063),
+                                    ("month", -1.9924301646902063)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dopo <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.9555114450274363, unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.845826690498331),
+                                    ("day", -1.55814461804655), ("named-day", -2.2512917986064953),
+                                    ("day of month (1st)", -2.2512917986064953),
+                                    ("the day after tomorrow", -2.2512917986064953),
+                                    ("hour", -1.845826690498331)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.48550781578170077,
+                               unseen = -3.258096538021482,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("tomorrow", -1.8325814637483102), ("day", -1.1394342831883648),
+                                    ("two time tokens in a row", -1.8325814637483102),
+                                    ("hour", -2.5257286443082556),
+                                    ("christmas", -2.120263536200091)],
+                               n = 8}}),
+       ("festa della liberazione",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dal <integer",
+        Classifier{okData =
+                     ClassData{prior = -1.6094379124341003,
+                               unseen = -1.3862943611198906,
+                               likelihoods =
+                                 HashMap.fromList [("integer (numeric)", -0.40546510810816444)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.2231435513142097,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.6931471805599453),
+                                    ("number (0..19)", -0.6931471805599453)],
+                               n = 4}}),
+       ("halloween day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453, unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.55814461804655),
+                                    ("<day-of-month> <named-month><day-of-month> <named-month>",
+                                     -1.845826690498331),
+                                    ("hh(:|h)mm (time-of-day)hh(:|h)mm (time-of-day)",
+                                     -1.55814461804655),
+                                    ("dayday", -1.845826690498331)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.6931471805599453, unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthday", -1.845826690498331),
+                                    ("named-month<day-of-month> <named-month>", -1.845826690498331),
+                                    ("minuteminute", -2.2512917986064953),
+                                    ("hh(:|h)mm (time-of-day)two time tokens in a row",
+                                     -2.2512917986064953),
+                                    ("year (1000-2100 not latent)<hour-of-day> <integer> (as relative minutes)",
+                                     -1.845826690498331),
+                                    ("yearminute", -1.845826690498331)],
+                               n = 5}}),
+       ("<day-of-month> <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.1880522315029396, unseen = -4.127134385045092,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.7096764825111559),
+                                    ("month", -0.7096764825111559)],
+                               n = 29},
+                   koData =
+                     ClassData{prior = -1.7635885922613588, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("number (0..19)named-month", -2.0149030205422647),
+                                    ("integer (numeric)named-month", -0.916290731874155),
+                                    ("month", -0.7621400520468967)],
+                               n = 6}}),
+       ("in <named-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("mezz'ora",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> entro le <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -0.8472978603872037),
+                                    ("named-daynoon", -1.252762968495368),
+                                    ("the day after tomorrow<integer> (latent time-of-day)",
+                                     -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("nth <time> after <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayday", -0.916290731874155),
+                                    ("ordinals (primo..10)named-daytwo time tokens in a row",
+                                     -0.916290731874155)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayday", -0.916290731874155),
+                                    ("ordinals (primo..10)named-daychristmas", -0.916290731874155)],
+                               n = 1}}),
+       ("<part-of-day> of <dim time>",
+        Classifier{okData =
+                     ClassData{prior = -0.916290731874155, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hourday", -1.0986122886681098),
+                                    ("morningnamed-day", -2.0149030205422647),
+                                    ("morning<day-of-month> <named-month>", -1.6094379124341003),
+                                    ("eveningtomorrow", -2.0149030205422647)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.5108256237659907, unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hourday", -0.9985288301111273),
+                                    ("stanottetomorrow", -1.845826690498331),
+                                    ("nighttomorrow", -1.845826690498331),
+                                    ("afternoontomorrow", -1.845826690498331)],
+                               n = 6}}),
+       ("<dim time> del mattino",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.7047480922384253),
+                                    ("<integer> (latent time-of-day)", -1.7047480922384253),
+                                    ("hh(:|h)mm (time-of-day)", -1.7047480922384253),
+                                    ("hour", -1.2992829841302609), ("minute", -1.7047480922384253)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinal (digits)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("last <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.2809338454620642),
+                                    ("jour (grain)two time tokens in a row", -1.791759469228055),
+                                    ("weekmonth", -1.791759469228055),
+                                    ("semaine (grain)named-month", -2.1972245773362196),
+                                    ("semaine (grain)two time tokens in a row",
+                                     -2.1972245773362196),
+                                    ("jour (grain)named-month", -1.791759469228055)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("two time tokens separated by `di`",
+        Classifier{okData =
+                     ClassData{prior = -0.4274440148269396, unseen = -4.406719247264253,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh(:|h)mm (time-of-day)named-day", -3.295836866004329),
+                                    ("named-daynamed-month", -3.295836866004329),
+                                    ("hourday", -2.4485390056171257),
+                                    ("day of month (1st)named-month", -3.7013019741124937),
+                                    ("daymonth", -2.315007612992603),
+                                    ("named-daytwo time tokens in a row", -3.7013019741124937),
+                                    ("at <time-of-day>named-day", -3.295836866004329),
+                                    ("two time tokens in a rowtwo time tokens in a row",
+                                     -3.7013019741124937),
+                                    ("dalle <time-of-day> alle <time-of-day> (interval)named-day",
+                                     -3.0081547935525483),
+                                    ("<integer 0 12> del <part of day>tomorrow",
+                                     -3.7013019741124937),
+                                    ("hh(:|h)mm (time-of-day)two time tokens in a row",
+                                     -2.7850112422383386),
+                                    ("hh(:|h)mm (time-of-day)<named-day> <day-of-month>",
+                                     -3.295836866004329),
+                                    ("at <time-of-day>two time tokens in a row",
+                                     -3.295836866004329),
+                                    ("minuteday", -1.6863989535702288),
+                                    ("at <time-of-day><named-day> <day-of-month>",
+                                     -3.7013019741124937),
+                                    ("dayweek", -3.0081547935525483),
+                                    ("two time tokens in a rownamed-month", -3.295836866004329),
+                                    ("<dim time> al <part-of-day>tomorrow", -3.7013019741124937),
+                                    ("named-daythis <cycle>", -3.0081547935525483),
+                                    ("tra il <datetime> e il <datetime> (interval)named-day",
+                                     -3.295836866004329)],
+                               n = 30},
+                   koData =
+                     ClassData{prior = -1.0560526742493137,
+                               unseen = -3.9889840465642745,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh(:|h)mm (time-of-day)named-day", -2.871679624884012),
+                                    ("named-daynamed-month", -2.3608540011180215),
+                                    ("hourday", -2.178532444324067),
+                                    ("daymonth", -1.890850371872286),
+                                    ("named-daytwo time tokens in a row", -2.583997552432231),
+                                    ("at <time-of-day>named-day", -2.178532444324067),
+                                    ("stanottetomorrow", -2.871679624884012),
+                                    ("minuteday", -2.3608540011180215)],
+                               n = 16}}),
+       ("il <day-of-month> <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 1}}),
+       ("morning",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.833213344056216,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 15},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("week-end",
+        Classifier{okData =
+                     ClassData{prior = -0.13353139262452263,
+                               unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -2.0794415416798357,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("fino al <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh(:|h)mm (time-of-day)", -1.791759469228055),
+                                    ("EOY|End of year", -1.791759469228055),
+                                    ("year", -1.791759469228055),
+                                    ("EOM|End of month", -1.791759469228055),
+                                    ("month", -1.791759469228055), ("minute", -1.791759469228055)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("right now",
+        Classifier{okData =
+                     ClassData{prior = -0.8754687373538999,
+                               unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -0.5389965007326869,
+                               unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7}}),
+       ("tre quarti d'ora",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the <cycle> last",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.791759469228055),
+                                    ("ann\233e (grain)", -2.1972245773362196),
+                                    ("semaine (grain)", -1.791759469228055),
+                                    ("mois (grain)", -1.5040773967762742),
+                                    ("year", -2.1972245773362196), ("month", -1.5040773967762742)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("festa del pap\224",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> <integer> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = -1.6094379124341003,
+                               unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)integer (numeric)",
+                                     -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.2231435513142097,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)integer (numeric)",
+                                     -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 4}}),
+       ("dd-dd <month> (interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the last <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.8971199848858813),
+                                    ("semaine (grain)", -1.8971199848858813),
+                                    ("mois (grain)", -1.8971199848858813),
+                                    ("heure (grain)", -1.8971199848858813),
+                                    ("hour", -1.8971199848858813), ("month", -1.8971199848858813)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -1.0986122886681098, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.9459101490553135),
+                                    ("semaine (grain)", -1.9459101490553135),
+                                    ("day", -1.540445040947149),
+                                    ("jour (grain)", -1.540445040947149)],
+                               n = 3}}),
+       ("this <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.4965075614664802,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("season", -1.8562979903656263), ("day", -1.8562979903656263),
+                                    ("hour", -1.1631508098056809), ("evening", -2.772588722239781),
+                                    ("morning", -2.0794415416798357),
+                                    ("week-end", -1.6739764335716716)],
+                               n = 13},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}})]
diff --git a/Duckling/Ranking/Classifiers/JA.hs b/Duckling/Ranking/Classifiers/JA.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/JA.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.JA (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers = HashMap.fromList []
diff --git a/Duckling/Ranking/Classifiers/KO.hs b/Duckling/Ranking/Classifiers/KO.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/KO.hs
@@ -0,0 +1,1458 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.KO (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers
+  = HashMap.fromList
+      [("<time> timezone",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day> am|pm", -1.3862943611198906),
+                                    ("hh:mm", -1.3862943611198906), ("hour", -1.3862943611198906),
+                                    ("minute", -1.3862943611198906)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (numeric)",
+        Classifier{okData =
+                     ClassData{prior = -0.4267425065554492, unseen = -4.836281906951478,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 124},
+                   koData =
+                     ClassData{prior = -1.0573693301340605, unseen = -4.219507705176107,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 66}}),
+       ("lunch",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect 2 numbers",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.3862943611198906,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("compose by multiplicationinteger (21..99) - TYPE 2",
+                                     -0.40546510810816444)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.3862943611198906,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer - TYPE 1: powers of teninteger (21..99) - TYPE 2",
+                                     -0.40546510810816444)],
+                               n = 1}}),
+       ("the day after tomorrow - \45236\51068\47784\47000",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.8472978603872037, unseen = -4.143134726391533,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -1.1826954058786512),
+                                    ("day-of-weekmorning", -3.4339872044851463),
+                                    ("yesterdayevening|night", -3.4339872044851463),
+                                    ("mm/ddafternoon", -3.4339872044851463),
+                                    ("todayafternoon", -3.4339872044851463),
+                                    ("dayevening|night", -3.4339872044851463),
+                                    ("todayevening|night", -2.740840023925201),
+                                    ("daymorning", -3.4339872044851463),
+                                    ("intersectevening|night", -3.4339872044851463),
+                                    ("next <cycle>evening|night", -3.028522096376982),
+                                    ("tomorrowlunch", -3.4339872044851463),
+                                    ("intersectmorning", -3.4339872044851463),
+                                    ("the day before yesterday - \50634\44536\51228morning",
+                                     -3.4339872044851463),
+                                    ("tomorrowevening|night", -3.028522096376982),
+                                    ("next <cycle>lunch", -3.4339872044851463)],
+                               n = 18},
+                   koData =
+                     ClassData{prior = -0.5596157879354228, unseen = -4.31748811353631,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("year (latent)lunch", -3.6109179126442243),
+                                    ("dayhour", -1.8191584434161694),
+                                    ("yearhour", -1.906169820405799),
+                                    ("time-of-day (latent)evening|night", -3.6109179126442243),
+                                    ("year (latent)evening|night", -2.512305623976115),
+                                    ("hourhour", -2.917770732084279),
+                                    ("time-of-day (latent)lunch", -3.6109179126442243),
+                                    ("intersectafternoon", -2.10684051586795),
+                                    ("year (latent)afternoon", -3.20545280453606),
+                                    ("day-of-weekafternoon", -3.6109179126442243),
+                                    ("next <cycle>evening|night", -3.6109179126442243),
+                                    ("time-of-day (latent)afternoon", -3.6109179126442243),
+                                    ("year (latent)morning", -3.20545280453606),
+                                    ("tomorrowevening|night", -3.6109179126442243)],
+                               n = 24}}),
+       ("<time> nth <time> - 3\50900 \52395\51704 \54868\50836\51068",
+        Classifier{okData =
+                     ClassData{prior = -0.6190392084062235, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthday", -0.7537718023763802),
+                                    ("intersectordinals (\52395\48264\51704)day-of-week",
+                                     -0.8873031950009028),
+                                    ("monthordinals (\52395\48264\51704)day-of-week",
+                                     -2.1400661634962708)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -0.7731898882334817, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthday", -0.7621400520468967),
+                                    ("monthordinals (\52395\48264\51704)day-of-week",
+                                     -0.7621400520468967)],
+                               n = 6}}),
+       ("the day before yesterday - \50634\44536\51228",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("today",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("mm/dd",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("half - \48152",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7}}),
+       ("month (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> \47560\51648\47561 <day-of-week>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersectday-of-week", -1.5040773967762742),
+                                    ("monthday", -0.8109302162163288),
+                                    ("monthday-of-week", -1.0986122886681098)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer - TYPE 1",
+        Classifier{okData =
+                     ClassData{prior = -1.8523840910444898, unseen = -3.258096538021482,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 24},
+                   koData =
+                     ClassData{prior = -0.17062551703076334,
+                               unseen = -4.875197323201151,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 129}}),
+       ("fraction",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)integer (numeric)", -0.6359887667199967),
+                                    ("fractioninteger (numeric)", -1.4469189829363254),
+                                    ("integer (numeric)fraction", -1.4469189829363254)],
+                               n = 14}}),
+       ("hour (grain)",
+        Classifier{okData =
+                     ClassData{prior = -1.241713132308783, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 13},
+                   koData =
+                     ClassData{prior = -0.3409265869705932,
+                               unseen = -3.5263605246161616,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 32}}),
+       ("intersect",
+        Classifier{okData =
+                     ClassData{prior = -0.45983651189029134,
+                               unseen = -5.958424693029782,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect<hour-of-day> <integer> (as relative minutes)",
+                                     -5.262690188904886),
+                                    ("intersectday-of-week", -4.164077900236776),
+                                    ("year<time> \47560\51648\47561 <cycle>", -4.857225080796721),
+                                    ("next <cycle>day-of-week", -4.3463994570307305),
+                                    ("dayhour", -2.0240117367405053),
+                                    ("monthday", -2.4294768448486694),
+                                    ("yearhour", -3.5579420966664603),
+                                    ("<time> <part-of-day>time-of-day", -4.3463994570307305),
+                                    ("intersectam|pm <time-of-day>", -3.065465611568666),
+                                    ("next <cycle><time-of-day> am|pm", -5.262690188904886),
+                                    ("next <cycle>am|pm <time-of-day>", -4.857225080796721),
+                                    ("yearintersect", -2.96010509591084),
+                                    ("day-of-week<datetime> - <datetime> (interval)",
+                                     -4.3463994570307305),
+                                    ("day-of-week<time-of-day> - <time-of-day> (interval)",
+                                     -4.3463994570307305),
+                                    ("monthday with korean number - \51068\51068..\44396\51068",
+                                     -5.262690188904886),
+                                    ("dayday-of-week", -4.3463994570307305),
+                                    ("this <cycle>day-of-week", -4.857225080796721),
+                                    ("day-of-week<time> timezone", -5.262690188904886),
+                                    ("monthhour", -3.5579420966664603),
+                                    ("mm/ddam|pm <time-of-day>", -4.56954300834494),
+                                    ("todayam|pm <time-of-day>", -4.56954300834494),
+                                    ("the day before yesterday - \50634\44536\51228am|pm <time-of-day>",
+                                     -4.3463994570307305),
+                                    ("dayday", -3.653252276470785),
+                                    ("hourhour", -3.7586127921286114),
+                                    ("year<time> <ordinal> <cycle>", -5.262690188904886),
+                                    ("month<datetime> - <datetime> (interval)", -4.857225080796721),
+                                    ("dayam|pm <time-of-day>", -5.262690188904886),
+                                    ("day-of-weekam|pm <time-of-day>", -4.56954300834494),
+                                    ("time-of-dayafter <time-of-day>", -5.262690188904886),
+                                    ("monthminute", -4.857225080796721),
+                                    ("hourminute", -4.56954300834494),
+                                    ("intersectday", -4.164077900236776),
+                                    ("am|pm <time-of-day>after <time-of-day>", -5.262690188904886),
+                                    ("last <cycle>day-of-week", -4.56954300834494),
+                                    ("month<time> <part-of-day>", -4.857225080796721),
+                                    ("yearmonth", -3.122624025408615),
+                                    ("dayminute", -3.653252276470785),
+                                    ("today<date>\50640", -5.262690188904886),
+                                    ("mm/dd<date>\50640", -5.262690188904886),
+                                    ("<hour-of-day> <integer> (as relative minutes)seconds",
+                                     -5.262690188904886),
+                                    ("<time> <part-of-day><date>\50640", -4.857225080796721),
+                                    ("monthintersect", -3.653252276470785),
+                                    ("year<time> \47560\51648\47561 <day-of-week>",
+                                     -5.262690188904886),
+                                    ("intersectintersect", -3.4709307196768306),
+                                    ("weekday", -3.653252276470785),
+                                    ("intersectday with korean number - \51068\51068..\44396\51068",
+                                     -4.857225080796721),
+                                    ("yearday", -3.4709307196768306),
+                                    ("day-of-weekhh:mm", -4.857225080796721),
+                                    ("yearweek", -4.857225080796721),
+                                    ("minutesecond", -5.262690188904886),
+                                    ("<time> <part-of-day><hour-of-day> <integer> (as relative minutes)",
+                                     -4.857225080796721),
+                                    ("tomorrow<time-of-day> am|pm", -5.262690188904886),
+                                    ("tomorrowam|pm <time-of-day>", -4.857225080796721),
+                                    ("dayintersect", -4.857225080796721),
+                                    ("day-of-weektime-of-day", -5.262690188904886)],
+                               n = 173},
+                   koData =
+                     ClassData{prior = -0.9980075895468108, unseen = -5.616771097666572,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("year<time> \47560\51648\47561 <cycle>", -4.919980925828125),
+                                    ("day-of-weekintersect", -4.919980925828125),
+                                    ("dayhour", -2.2809235962128662),
+                                    ("daymonth", -4.5145158177199605),
+                                    ("monthday", -3.1282214566000697),
+                                    ("yearhour", -3.2152328335897),
+                                    ("<time> <part-of-day>time-of-day", -3.821368637160015),
+                                    ("houryear", -4.22683374526818),
+                                    ("day-of-weekafter <time-of-day>", -4.919980925828125),
+                                    ("after <time-of-day>by <time> - \44620\51648",
+                                     -4.919980925828125),
+                                    ("intersectam|pm <time-of-day>", -4.5145158177199605),
+                                    ("intersect<time> <part-of-day>", -4.00369019395397),
+                                    ("yearintersect", -3.0481787489265337),
+                                    ("day-of-week<time-of-day> - <time-of-day> (interval)",
+                                     -4.00369019395397),
+                                    ("intersectmonth", -4.919980925828125),
+                                    ("time-of-day<duration> ago", -4.919980925828125),
+                                    ("this <cycle>day-of-week", -4.5145158177199605),
+                                    ("intersecttime-of-day", -4.5145158177199605),
+                                    ("monthhour", -3.0481787489265337),
+                                    ("intersectlast <time>", -4.919980925828125),
+                                    ("dayday", -4.00369019395397),
+                                    ("hourhour", -2.9740707767728116),
+                                    ("month<datetime> - <datetime> (interval)", -4.00369019395397),
+                                    ("dayam|pm <time-of-day>", -4.5145158177199605),
+                                    ("day-of-weekam|pm <time-of-day>", -4.919980925828125),
+                                    ("hourminute", -4.22683374526818),
+                                    ("<time-of-day> - <time-of-day> (interval)last <time>",
+                                     -4.00369019395397),
+                                    ("intersectday", -4.5145158177199605),
+                                    ("<datetime> - <datetime> (interval)day", -4.00369019395397),
+                                    ("month<time> <part-of-day>", -4.5145158177199605),
+                                    ("yearmonth", -4.5145158177199605),
+                                    ("dayminute", -4.22683374526818),
+                                    ("<hour-of-day> <integer> (as relative minutes)seconds",
+                                     -4.22683374526818),
+                                    ("<time> <part-of-day><date>\50640", -4.919980925828125),
+                                    ("last <time>time-of-day", -4.919980925828125),
+                                    ("monthintersect", -3.3105430133940246),
+                                    ("hoursecond", -4.919980925828125),
+                                    ("year<time> <part-of-day>", -4.5145158177199605),
+                                    ("intersectintersect", -3.667217957332757),
+                                    ("weekday", -4.5145158177199605),
+                                    ("intersectday with korean number - \51068\51068..\44396\51068",
+                                     -4.5145158177199605),
+                                    ("day<time> <part-of-day>", -4.919980925828125),
+                                    ("yearday", -3.821368637160015),
+                                    ("day-of-weekhh:mm", -4.919980925828125),
+                                    ("<time-of-day> - <time-of-day> (interval)time-of-day",
+                                     -4.5145158177199605),
+                                    ("intersectafter <time-of-day>", -3.415903529051851),
+                                    ("minutesecond", -4.22683374526818),
+                                    ("<time> <part-of-day><hour-of-day> <integer> (as relative minutes)",
+                                     -4.5145158177199605),
+                                    ("dayintersect", -4.919980925828125),
+                                    ("mm/ddafter <time-of-day>", -4.919980925828125),
+                                    ("todayafter <time-of-day>", -4.919980925828125)],
+                               n = 101}}),
+       ("season",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (grain)",
+        Classifier{okData =
+                     ClassData{prior = -1.0608719606852628,
+                               unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -0.42488319396526597,
+                               unseen = -2.9444389791664407,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 17}}),
+       ("next <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.12516314295400605,
+                               unseen = -3.7612001156935624,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.791759469228055),
+                                    ("month (grain)", -3.044522437723423),
+                                    ("year (grain)", -3.044522437723423),
+                                    ("week (grain)", -1.791759469228055),
+                                    ("day", -1.791759469228055), ("quarter", -3.044522437723423),
+                                    ("year", -3.044522437723423), ("month", -3.044522437723423),
+                                    ("quarter (grain)", -3.044522437723423),
+                                    ("day (grain)", -1.791759469228055)],
+                               n = 15},
+                   koData =
+                     ClassData{prior = -2.1400661634962708, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -2.0794415416798357),
+                                    ("minute (grain)", -2.0794415416798357),
+                                    ("minute", -2.0794415416798357),
+                                    ("day (grain)", -2.0794415416798357)],
+                               n = 2}}),
+       ("this <day-of-week>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("day-of-week", -0.6931471805599453)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("yyyy-mm-dd",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (latent)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -3.5263605246161616,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.9315582040049435),
+                                    ("integer - TYPE 1", -0.8574502318512216),
+                                    ("integer (21..99) - TYPE 2", -2.803360380906535),
+                                    ("integer (1..4) - for ordinals", -2.1102132003465894)],
+                               n = 29}}),
+       ("mm/dd/yyyy",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("evening|night",
+        Classifier{okData =
+                     ClassData{prior = -0.13353139262452263,
+                               unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -2.0794415416798357,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("Memorial Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (20..90) - TYPE 2 and ordinals",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh:mm:ss",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("by <time> - \44620\51648",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("am|pm <time-of-day>", -1.252762968495368),
+                                    ("time-of-day", -1.252762968495368),
+                                    ("hour", -0.8472978603872037)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-month>\50640",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("month", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (21..99) - TYPE 2",
+        Classifier{okData =
+                     ClassData{prior = -0.587786664902119, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (20..90) - TYPE 2 and ordinalsinteger (1..4) - for ordinals",
+                                     -1.6094379124341003),
+                                    ("integer - TYPE 1: powers of teninteger - TYPE 1",
+                                     -1.2039728043259361),
+                                    ("compose by multiplicationinteger - TYPE 1",
+                                     -1.2039728043259361)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.8109302162163288,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)integer (numeric)", -1.5040773967762742),
+                                    ("integer - TYPE 1: powers of teninteger - TYPE 1",
+                                     -1.0986122886681098),
+                                    ("integer (numeric)integer - TYPE 1", -1.5040773967762742)],
+                               n = 4}}),
+       ("Independence Movement Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("week (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.17589066646366416,
+                               unseen = -3.332204510175204,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 26},
+                   koData =
+                     ClassData{prior = -1.824549292051046, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5}}),
+       ("<year> <1..4>quarter",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("yearinteger (numeric)quarter (grain)", -0.6931471805599453),
+                                    ("yearquarter", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("now",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4}}),
+       ("christmas eve",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Liberation Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("numbers prefix with -, \47560\51060\45320\49828, or \47560\51060\45208\49828",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 7}}),
+       ("in|during the <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("afternoon", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("tomorrow",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("National Foundation Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinals (\52395\48264\51704)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (1..4) - for ordinals", -0.35667494393873245),
+                                    ("integer (1..10) - TYPE 2", -1.2039728043259361)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("day",
+        Classifier{okData =
+                     ClassData{prior = -0.2657031657330056, unseen = -3.258096538021482,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -8.338160893905101e-2),
+                                    ("integer - TYPE 1", -2.5257286443082556)],
+                               n = 23},
+                   koData =
+                     ClassData{prior = -1.455287232606842, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.25131442828090605),
+                                    ("integer - TYPE 1", -1.5040773967762742)],
+                               n = 7}}),
+       ("half an hour",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour (grain)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 7}}),
+       ("afternoon",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("integer (1..4) - for ordinals",
+        Classifier{okData =
+                     ClassData{prior = -3.7740327982847086e-2,
+                               unseen = -3.332204510175204,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 26},
+                   koData =
+                     ClassData{prior = -3.295836866004329, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("<time> <ordinal> <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthordinals (\52395\48264\51704)week (grain)",
+                                     -1.252762968495368),
+                                    ("monthweek", -0.8472978603872037),
+                                    ("intersectordinals (\52395\48264\51704)week (grain)",
+                                     -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.5108256237659907, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.3862943611198906),
+                                    ("year (grain)", -2.3025850929940455),
+                                    ("week (grain)", -1.3862943611198906),
+                                    ("quarter", -2.3025850929940455), ("year", -2.3025850929940455),
+                                    ("quarter (grain)", -2.3025850929940455)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.916290731874155, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.3862943611198906),
+                                    ("week (grain)", -1.3862943611198906),
+                                    ("minute (grain)", -2.0794415416798357),
+                                    ("minute", -2.0794415416798357)],
+                               n = 4}}),
+       ("minute (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.6359887667199967,
+                               unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -0.7537718023763802,
+                               unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 8}}),
+       ("about <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.252762968495368),
+                                    ("time-of-day", -1.252762968495368),
+                                    ("hour", -0.8472978603872037)],
+                               n = 2}}),
+       ("time-of-day (latent)",
+        Classifier{okData =
+                     ClassData{prior = -1.3596261140377293, unseen = -3.332204510175204,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.6567795363890705),
+                                    ("integer - TYPE 1", -2.6026896854443837),
+                                    ("integer (1..4) - for ordinals", -1.6863989535702288),
+                                    ("integer (1..10) - TYPE 2", -2.6026896854443837)],
+                               n = 19},
+                   koData =
+                     ClassData{prior = -0.2967319079716989,
+                               unseen = -4.1588830833596715,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -1.052092273033217),
+                                    ("integer - TYPE 1", -0.924258901523332),
+                                    ("integer (20..90) - TYPE 2 and ordinals", -3.4499875458315876),
+                                    ("integer (21..99) - TYPE 2", -3.4499875458315876),
+                                    ("integer (1..4) - for ordinals", -2.3513752571634776),
+                                    ("integer - TYPE 1: powers of ten", -3.044522437723423),
+                                    ("compose by multiplication", -3.4499875458315876)],
+                               n = 55}}),
+       ("year",
+        Classifier{okData =
+                     ClassData{prior = -1.006804739414987, unseen = -3.295836866004329,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.3677247801253174),
+                                    ("intersect 2 numbers", -2.5649493574615367),
+                                    ("integer (21..99) - TYPE 2", -2.5649493574615367)],
+                               n = 19},
+                   koData =
+                     ClassData{prior = -0.4547361571149472, unseen = -3.713572066704308,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.5533852381847867),
+                                    ("intersect 2 numbers", -2.3025850929940455),
+                                    ("integer - TYPE 1", -2.0794415416798357),
+                                    ("fraction", -2.995732273553991),
+                                    ("integer - TYPE 1: powers of ten", -2.5902671654458267),
+                                    ("compose by multiplication", -2.995732273553991)],
+                               n = 33}}),
+       ("<integer> <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = -1.5955488002734333,
+                               unseen = -4.5217885770490405,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.7191000372887952),
+                                    ("integer - TYPE 1year (grain)", -3.41224721784874),
+                                    ("integer (numeric)day (grain)", -2.5649493574615367),
+                                    ("second", -3.817712325956905),
+                                    ("integer - TYPE 1minute (grain)", -2.9014215940827497),
+                                    ("integer (1..4) - for ordinalshour (grain)",
+                                     -3.41224721784874),
+                                    ("integer (numeric)second (grain)", -3.817712325956905),
+                                    ("integer (numeric)year (grain)", -3.41224721784874),
+                                    ("day", -2.5649493574615367), ("year", -2.9014215940827497),
+                                    ("integer (numeric)week (grain)", -2.7191000372887952),
+                                    ("hour", -2.3136349291806306),
+                                    ("integer (numeric)minute (grain)", -3.817712325956905),
+                                    ("few \47751hour (grain)", -2.9014215940827497),
+                                    ("minute", -2.7191000372887952),
+                                    ("integer (numeric)hour (grain)", -3.41224721784874)],
+                               n = 29},
+                   koData =
+                     ClassData{prior = -0.22664618186541183,
+                               unseen = -5.568344503761097,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -3.955082494888593),
+                                    ("integer - TYPE 1quarter (grain)", -4.871373226762748),
+                                    ("integer - TYPE 1day (grain)", -4.178226046202803),
+                                    ("integer - TYPE 1year (grain)", -4.465908118654584),
+                                    ("integer (numeric)day (grain)", -2.4290261913935436),
+                                    ("intersect 2 numbersyear (grain)", -4.465908118654584),
+                                    ("integer (numeric)quarter (grain)", -4.871373226762748),
+                                    ("compose by multiplicationminute (grain)", -4.871373226762748),
+                                    ("second", -3.7727609380946383),
+                                    ("integer - TYPE 1minute (grain)", -3.7727609380946383),
+                                    ("integer (1..4) - for ordinalsmonth (grain)",
+                                     -4.465908118654584),
+                                    ("integer (1..4) - for ordinalshour (grain)",
+                                     -2.7313070632664775),
+                                    ("integer (numeric)second (grain)", -4.871373226762748),
+                                    ("integer (numeric)year (grain)", -2.6741486494265287),
+                                    ("integer (21..99) - TYPE 2hour (grain)", -4.871373226762748),
+                                    ("integer - TYPE 1week (grain)", -4.465908118654584),
+                                    ("day", -2.3064238693012116), ("quarter", -4.465908118654584),
+                                    ("year", -2.4290261913935436),
+                                    ("integer (21..99) - TYPE 2minute (grain)", -4.178226046202803),
+                                    ("integer (numeric)week (grain)", -4.465908118654584),
+                                    ("integer (1..10) - TYPE 2hour (grain)", -4.871373226762748),
+                                    ("hour", -1.900958761193047), ("month", -3.955082494888593),
+                                    ("integer - TYPE 1second (grain)", -4.465908118654584),
+                                    ("integer (numeric)minute (grain)", -3.955082494888593),
+                                    ("integer (numeric)month (grain)", -4.465908118654584),
+                                    ("integer (21..99) - TYPE 2year (grain)", -4.871373226762748),
+                                    ("minute", -2.856470206220483),
+                                    ("integer - TYPE 1: powers of tenminute (grain)",
+                                     -4.871373226762748),
+                                    ("integer (numeric)hour (grain)", -2.5199979695992702),
+                                    ("integer (21..99) - TYPE 2second (grain)",
+                                     -4.465908118654584)],
+                               n = 114}}),
+       ("<time-of-day> am|pm",
+        Classifier{okData =
+                     ClassData{prior = -0.3746934494414107, unseen = -3.295836866004329,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.6486586255873816),
+                                    ("hh:mm", -1.1786549963416462), ("hour", -1.6486586255873816),
+                                    ("minute", -1.1786549963416462)],
+                               n = 11},
+                   koData =
+                     ClassData{prior = -1.1631508098056809, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.8472978603872037),
+                                    ("hour", -0.8472978603872037)],
+                               n = 5}}),
+       ("a day - \54616\47336",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("am|pm <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.23262229526875347,
+                               unseen = -4.543294782270004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.824549292051046),
+                                    ("<date>\50640", -3.146305132033365),
+                                    ("time-of-day", -1.536867219599265),
+                                    ("hour", -0.8437200390393196),
+                                    ("<time-of-day>\51060\51204", -3.4339872044851463),
+                                    ("<hour-of-day> half (as relative minutes)",
+                                     -3.8394523125933104),
+                                    ("minute", -3.146305132033365),
+                                    ("after <time-of-day>", -3.8394523125933104),
+                                    ("<hour-of-day> <integer> (as relative minutes)",
+                                     -3.4339872044851463)],
+                               n = 42},
+                   koData =
+                     ClassData{prior = -1.5723966407537513,
+                               unseen = -3.4657359027997265,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.2367626271489267),
+                                    ("time-of-day", -2.0476928433652555),
+                                    ("hour", -0.9490805546971459)],
+                               n = 11}}),
+       ("hh:mm",
+        Classifier{okData =
+                     ClassData{prior = -4.445176257083381e-2,
+                               unseen = -3.1780538303479458,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 22},
+                   koData =
+                     ClassData{prior = -3.1354942159291497,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("integer - TYPE 1: powers of ten",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("day with korean number - \51068\51068..\44396\51068",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("second (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("Hangul Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> (hour-of-day) relative minutes \51204",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.916290731874155),
+                                    ("time-of-dayinteger (21..99) - TYPE 2", -0.916290731874155)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)integer - TYPE 1", -0.916290731874155),
+                                    ("hour", -0.916290731874155)],
+                               n = 1}}),
+       ("<duration> ago",
+        Classifier{okData =
+                     ClassData{prior = -0.45198512374305727,
+                               unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.8971199848858813), ("day", -1.6094379124341003),
+                                    ("year", -1.8971199848858813),
+                                    ("<integer> <unit-of-duration>", -0.916290731874155)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -1.0116009116784799, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> <unit-of-duration>", -1.0296194171811581),
+                                    ("hour", -1.540445040947149), ("minute", -1.540445040947149)],
+                               n = 4}}),
+       ("Constitution Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <time>",
+        Classifier{okData =
+                     ClassData{prior = -2.833213344056216, unseen = -3.1780538303479458,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -2.03688192726104), ("hour", -2.03688192726104),
+                                    ("week-end", -2.03688192726104),
+                                    ("day-of-week", -2.03688192726104)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -6.0624621816434854e-2,
+                               unseen = -4.969813299576001,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -3.8642323415917974),
+                                    ("year (latent)", -1.967112356705916),
+                                    ("second", -3.8642323415917974),
+                                    ("time-of-day (latent)", -1.9183221925364844),
+                                    ("year", -1.967112356705916),
+                                    ("<duration> ago", -3.8642323415917974),
+                                    ("time-of-day", -2.477937980471907),
+                                    ("hour", -1.1786549963416462), ("seconds", -3.8642323415917974),
+                                    ("<datetime> - <datetime> (interval)", -3.8642323415917974),
+                                    ("<time-of-day>\51060\51204", -3.8642323415917974),
+                                    ("<time-of-day> - <time-of-day> (interval)",
+                                     -3.353406717825807)],
+                               n = 64}}),
+       ("<date>\50640",
+        Classifier{okData =
+                     ClassData{prior = -7.410797215372185e-2,
+                               unseen = -3.4657359027997265,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -1.824549292051046), ("day", -2.3353749158170367),
+                                    ("am|pm <time-of-day>", -2.0476928433652555),
+                                    ("<duration> ago", -2.740840023925201),
+                                    ("time-of-day", -2.0476928433652555),
+                                    ("hour", -1.0360919316867756), ("month", -2.740840023925201)],
+                               n = 13},
+                   koData =
+                     ClassData{prior = -2.639057329615259, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -1.5040773967762742),
+                                    ("hour", -1.5040773967762742)],
+                               n = 1}}),
+       ("integer (1..10) - TYPE 2",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("time-of-day",
+        Classifier{okData =
+                     ClassData{prior = -0.5328045304847658,
+                               unseen = -3.4965075614664802,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.5753641449035618),
+                                    ("integer (1..4) - for ordinals", -1.1631508098056809),
+                                    ("integer (1..10) - TYPE 2", -2.772588722239781)],
+                               n = 27},
+                   koData =
+                     ClassData{prior = -0.8842024173226546,
+                               unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -1.3862943611198906),
+                                    ("integer (21..99) - TYPE 2", -2.4849066497880004),
+                                    ("integer (1..4) - for ordinals", -0.8754687373538999),
+                                    ("few \47751", -1.5686159179138452)],
+                               n = 19}}),
+       ("noon",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> and an half hours",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList [("integer (1..4) - for ordinals", 0.0)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("after <duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.5040773967762742),
+                                    ("<integer> <unit-of-duration>", -0.8109302162163288),
+                                    ("hour", -1.0986122886681098)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -1.3862943611198906, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> <unit-of-duration>", -0.916290731874155),
+                                    ("hour", -0.916290731874155)],
+                               n = 1}}),
+       ("month",
+        Classifier{okData =
+                     ClassData{prior = -5.5569851154810765e-2,
+                               unseen = -3.6375861597263857,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -5.5569851154810765e-2),
+                                    ("integer - TYPE 1", -2.917770732084279)],
+                               n = 35},
+                   koData =
+                     ClassData{prior = -2.917770732084279, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList [("integer - TYPE 1", -0.2876820724517809)],
+                               n = 2}}),
+       ("midnight|EOD|end of day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("next <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.3862943611198906), ("month", -1.3862943611198906),
+                                    ("day-of-week", -1.3862943611198906)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day", -1.252762968495368),
+                                    ("hour", -1.252762968495368)],
+                               n = 1}}),
+       ("last <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.45198512374305727,
+                               unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.2992829841302609),
+                                    ("month (grain)", -2.3978952727983707),
+                                    ("year (grain)", -2.3978952727983707),
+                                    ("week (grain)", -1.2992829841302609),
+                                    ("year", -2.3978952727983707), ("month", -2.3978952727983707)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -1.0116009116784799, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6739764335716716),
+                                    ("week (grain)", -1.6739764335716716),
+                                    ("day", -1.6739764335716716),
+                                    ("day (grain)", -1.6739764335716716)],
+                               n = 4}}),
+       ("christmas",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("next n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.4965075614664802,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.367123614131617),
+                                    ("integer - TYPE 1year (grain)", -2.772588722239781),
+                                    ("integer - TYPE 1minute (grain)", -2.772588722239781),
+                                    ("integer (1..4) - for ordinalsmonth (grain)",
+                                     -2.772588722239781),
+                                    ("integer (numeric)year (grain)", -2.772588722239781),
+                                    ("integer - TYPE 1week (grain)", -2.772588722239781),
+                                    ("year", -2.367123614131617),
+                                    ("integer (numeric)week (grain)", -2.772588722239781),
+                                    ("hour", -2.772588722239781), ("month", -2.367123614131617),
+                                    ("integer (numeric)minute (grain)", -2.772588722239781),
+                                    ("integer (numeric)month (grain)", -2.772588722239781),
+                                    ("minute", -2.367123614131617),
+                                    ("integer (numeric)hour (grain)", -2.772588722239781)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("seconds",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.916290731874155),
+                                    ("integer (21..99) - TYPE 2", -0.916290731874155)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -1.252762968495368),
+                                    ("integer - TYPE 1", -0.8472978603872037),
+                                    ("integer (21..99) - TYPE 2", -1.252762968495368)],
+                               n = 4}}),
+       ("<time> \47560\51648\47561 <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthday", -1.540445040947149),
+                                    ("monthday (grain)", -1.9459101490553135),
+                                    ("monthweek", -1.540445040947149),
+                                    ("intersectweek (grain)", -1.9459101490553135),
+                                    ("intersectday (grain)", -1.9459101490553135),
+                                    ("monthweek (grain)", -1.9459101490553135)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthday", -1.2039728043259361),
+                                    ("monthday (grain)", -1.6094379124341003),
+                                    ("intersectday (grain)", -1.6094379124341003)],
+                               n = 2}}),
+       ("in <duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.17589066646366416,
+                               unseen = -4.1588830833596715,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -3.044522437723423), ("second", -3.4499875458315876),
+                                    ("day", -2.5336968139574325), ("year", -3.044522437723423),
+                                    ("<integer> <unit-of-duration>", -1.1986957472250923),
+                                    ("a day - \54616\47336", -3.044522437723423),
+                                    ("<integer> and an half hours", -2.3513752571634776),
+                                    ("hour", -2.1972245773362196), ("minute", -1.6582280766035324),
+                                    ("about <duration>", -3.4499875458315876)],
+                               n = 26},
+                   koData =
+                     ClassData{prior = -1.824549292051046, unseen = -3.0910424533583156,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("half an hour", -1.252762968495368),
+                                    ("minute", -1.252762968495368)],
+                               n = 5}}),
+       ("<datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.46262352194811296,
+                               unseen = -3.8501476017100584,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersecthh:mm", -2.7300291078209855),
+                                    ("intersectam|pm <time-of-day>", -2.7300291078209855),
+                                    ("minuteminute", -1.749199854809259),
+                                    ("hh:mmhh:mm", -2.03688192726104),
+                                    ("dayday", -1.8827312474337816),
+                                    ("hourhour", -2.2192034840549946),
+                                    ("intersectday", -2.7300291078209855),
+                                    ("am|pm <time-of-day>am|pm <time-of-day>", -2.7300291078209855),
+                                    ("intersectintersect", -2.7300291078209855)],
+                               n = 17},
+                   koData =
+                     ClassData{prior = -0.9932517730102834,
+                               unseen = -3.4965075614664802,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.8562979903656263),
+                                    ("time-of-dayam|pm <time-of-day>", -2.367123614131617),
+                                    ("intersectmonth", -2.367123614131617),
+                                    ("dayday", -2.367123614131617),
+                                    ("hourhour", -1.8562979903656263),
+                                    ("last <time>am|pm <time-of-day>", -2.367123614131617),
+                                    ("dayintersect", -2.367123614131617)],
+                               n = 10}}),
+       ("<time-of-day>\51060\51204",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("am|pm <time-of-day>", -1.540445040947149),
+                                    ("time-of-day", -1.540445040947149),
+                                    ("hour", -1.0296194171811581)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.8472978603872037,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.791759469228055),
+                                    ("hour", -1.791759469228055), ("minute", -1.3862943611198906),
+                                    ("<hour-of-day> <integer> (as relative minutes)",
+                                     -1.3862943611198906)],
+                               n = 3}}),
+       ("New Year's Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<1..4> quarter",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer - TYPE 1quarter (grain)", -0.916290731874155),
+                                    ("quarter", -0.916290731874155)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)quarter (grain)", -0.916290731874155),
+                                    ("quarter", -0.916290731874155)],
+                               n = 1}}),
+       ("Children's Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> - <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.9444616088408514, unseen = -3.258096538021482,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.4271163556401458),
+                                    ("hh:mmhh:mm", -1.4271163556401458),
+                                    ("hourhour", -2.120263536200091),
+                                    ("am|pm <time-of-day>am|pm <time-of-day>", -2.120263536200091)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -0.49247648509779407,
+                               unseen = -3.5263605246161616,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-dayam|pm <time-of-day>", -2.3978952727983707),
+                                    ("time-of-daytime-of-day (latent)", -2.803360380906535),
+                                    ("hh:mmtime-of-day (latent)", -1.7047480922384253),
+                                    ("am|pm <time-of-day>time-of-day (latent)", -2.803360380906535),
+                                    ("am|pm <time-of-day><time-of-day>\51060\51204",
+                                     -2.803360380906535),
+                                    ("hourhour", -1.550597412411167),
+                                    ("time-of-day<time-of-day>\51060\51204", -2.803360380906535),
+                                    ("minutehour", -1.7047480922384253)],
+                               n = 11}}),
+       ("last n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.713572066704308,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.5902671654458267), ("second", -2.5902671654458267),
+                                    ("integer - TYPE 1minute (grain)", -2.995732273553991),
+                                    ("integer (1..4) - for ordinalsmonth (grain)",
+                                     -2.995732273553991),
+                                    ("integer (1..4) - for ordinalshour (grain)",
+                                     -2.995732273553991),
+                                    ("integer (numeric)second (grain)", -2.995732273553991),
+                                    ("integer (21..99) - TYPE 2hour (grain)", -2.995732273553991),
+                                    ("integer - TYPE 1week (grain)", -2.995732273553991),
+                                    ("integer (numeric)week (grain)", -2.995732273553991),
+                                    ("hour", -2.0794415416798357), ("month", -2.5902671654458267),
+                                    ("integer - TYPE 1second (grain)", -2.995732273553991),
+                                    ("integer (numeric)minute (grain)", -2.995732273553991),
+                                    ("integer (numeric)month (grain)", -2.995732273553991),
+                                    ("minute", -2.5902671654458267),
+                                    ("integer (numeric)hour (grain)", -2.5902671654458267)],
+                               n = 12},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.833213344056216,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> half (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("quarter (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("morning",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("week-end",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("after <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -1.5040773967762742, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("lunch", -1.8718021769015913),
+                                    ("time-of-day (latent)", -1.8718021769015913),
+                                    ("am|pm <time-of-day>", -1.8718021769015913),
+                                    ("time-of-day", -1.8718021769015913),
+                                    ("hour", -0.9555114450274363)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.25131442828090605,
+                               unseen = -3.5263605246161616,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.7884573603642702),
+                                    ("hour", -0.7884573603642702)],
+                               n = 14}}),
+       ("day (grain)",
+        Classifier{okData =
+                     ClassData{prior = -1.1727202608218315, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 13},
+                   koData =
+                     ClassData{prior = -0.37037378829689427,
+                               unseen = -3.4339872044851463,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 29}}),
+       ("compose by multiplication",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer - TYPE 1integer - TYPE 1: powers of ten", 0.0)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("about <duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> and an half hours", -1.0986122886681098),
+                                    ("minute", -1.0986122886681098)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> <unit-of-duration>", -1.0986122886681098),
+                                    ("hour", -1.0986122886681098)],
+                               n = 1}}),
+       ("<hour-of-day> <integer> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-daycompose by multiplication", -1.8718021769015913),
+                                    ("time-of-dayinteger (numeric)", -1.8718021769015913),
+                                    ("hour", -1.1786549963416462),
+                                    ("time-of-dayinteger (21..99) - TYPE 2", -1.8718021769015913)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)integer (21..99) - TYPE 2",
+                                     -2.2512917986064953),
+                                    ("time-of-day (latent)integer - TYPE 1", -1.55814461804655),
+                                    ("time-of-day (latent)integer - TYPE 1: powers of ten",
+                                     -2.2512917986064953),
+                                    ("hour", -0.9985288301111273),
+                                    ("time-of-dayinteger (21..99) - TYPE 2", -2.2512917986064953)],
+                               n = 6}}),
+       ("few \47751",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("season", -1.466337068793427), ("day", -1.466337068793427),
+                                    ("hour", -1.466337068793427), ("morning", -1.8718021769015913),
+                                    ("week-end", -1.8718021769015913)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("day-of-week",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.713572066704308,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 39},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("within <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -0.6931471805599453),
+                                    ("<integer> <unit-of-duration>", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}})]
diff --git a/Duckling/Ranking/Classifiers/MY.hs b/Duckling/Ranking/Classifiers/MY.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/MY.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.MY (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers = HashMap.fromList []
diff --git a/Duckling/Ranking/Classifiers/NB.hs b/Duckling/Ranking/Classifiers/NB.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/NB.hs
@@ -0,0 +1,1603 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.NB (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers
+  = HashMap.fromList
+      [("<time> timezone",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.0910424533583156,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.4350845252893227),
+                                    ("<time-of-day> o'clock", -2.3513752571634776),
+                                    ("hh:mm", -1.6582280766035324), ("hour", -1.6582280766035324),
+                                    ("minute", -1.252762968495368)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (numeric)",
+        Classifier{okData =
+                     ClassData{prior = -0.7546493803177391, unseen = -4.912654885736052,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 134},
+                   koData =
+                     ClassData{prior = -0.6352093434537263, unseen = -5.030437921392435,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 151}}),
+       ("the day before yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Father's Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("lunch",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -1.0986122886681098),
+                                    ("tomorrowevening", -2.0149030205422647),
+                                    ("named-daymorning", -2.0149030205422647),
+                                    ("tomorrowlunch", -2.0149030205422647),
+                                    ("yesterdayevening", -2.0149030205422647)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.8472978603872037, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("yearhour", -1.1786549963416462),
+                                    ("year (latent)in|during the <part-of-day>",
+                                     -1.1786549963416462)],
+                               n = 3}}),
+       ("dd/mm",
+        Classifier{okData =
+                     ClassData{prior = -0.2231435513142097,
+                               unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 8},
+                   koData =
+                     ClassData{prior = -1.6094379124341003,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("today",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("at <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.2657031657330056, unseen = -4.605170185988091,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> timezone", -3.4965075614664802),
+                                    ("time-of-day (latent)", -1.2278240201481159),
+                                    ("relative minutes after|past <integer> (hour-of-day)",
+                                     -3.4965075614664802),
+                                    ("hh:mm", -2.030170492673053),
+                                    ("<time-of-day> sharp", -3.4965075614664802),
+                                    ("hour", -1.1611326456494435), ("minute", -1.7619065060783738)],
+                               n = 46},
+                   koData =
+                     ClassData{prior = -1.455287232606842, unseen = -3.58351893845611,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.8472978603872037),
+                                    ("hour", -0.8472978603872037)],
+                               n = 14}}),
+       ("absorption of , after named day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("tonight",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("on <date>",
+        Classifier{okData =
+                     ClassData{prior = -0.3313571359544425, unseen = -4.189654742026425,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("on <date>", -3.481240089335692),
+                                    ("the <day-of-month> (non ordinal)", -3.0757749812275272),
+                                    ("<day-of-month>(ordinal) <named-month>", -2.228477120840324),
+                                    ("day", -0.807091439909163),
+                                    ("the <day-of-month> (ordinal)", -3.481240089335692),
+                                    ("named-day", -1.8718021769015913),
+                                    ("<day-of-month> (ordinal)", -1.8718021769015913)],
+                               n = 28},
+                   koData =
+                     ClassData{prior = -1.2656663733312759,
+                               unseen = -3.4657359027997265,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("on <date>", -2.3353749158170367),
+                                    ("time-of-day (latent)", -1.1314021114911006),
+                                    ("hour", -0.9490805546971459)],
+                               n = 11}}),
+       ("integer (0..19)",
+        Classifier{okData =
+                     ClassData{prior = -0.2231435513142097,
+                               unseen = -3.6375861597263857,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 36},
+                   koData =
+                     ClassData{prior = -1.6094379124341003,
+                               unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9}}),
+       ("between <time-of-day> and <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -0.9808292530117262),
+                                    ("hh:mmhh:mm", -0.9808292530117262)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -0.9808292530117262),
+                                    ("minutehour", -0.9808292530117262)],
+                               n = 2}}),
+       ("between <datetime> and <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.916290731874155, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.0986122886681098),
+                                    ("hh:mmhh:mm", -1.0986122886681098)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -1.2992829841302609),
+                                    ("minuteminute", -1.7047480922384253),
+                                    ("minutehour", -1.2992829841302609),
+                                    ("hh:mmintersect", -1.7047480922384253)],
+                               n = 3}}),
+       ("month (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 10},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> more <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)minute (grain)", -1.252762968495368),
+                                    ("integer (0..19)minute (grain)", -1.252762968495368),
+                                    ("minute", -0.8472978603872037)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> o'clock",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hour (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.10536051565782628,
+                               unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -2.3025850929940455,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("<ordinal> quarter",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)quarter (grain)", -1.252762968495368),
+                                    ("quarter", -0.8472978603872037),
+                                    ("ordinals (first..31st)quarter (grain)", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)quarter (grain)", -1.252762968495368),
+                                    ("quarter", -0.8472978603872037),
+                                    ("ordinals (first..31st)quarter (grain)", -1.252762968495368)],
+                               n = 2}}),
+       ("Last year",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect",
+        Classifier{okData =
+                     ClassData{prior = -0.4392032477400145, unseen = -5.860786223465865,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<datetime> - <datetime> (interval)on <date>",
+                                     -4.0661736852554045),
+                                    ("<time-of-day> - <time-of-day> (interval)on <date>",
+                                     -4.0661736852554045),
+                                    ("hourday", -5.1647859739235145),
+                                    ("dayhour", -2.9134941753170187),
+                                    ("daymonth", -2.9134941753170187),
+                                    ("monthyear", -3.0853444322436783),
+                                    ("intersecthh:mm", -5.1647859739235145),
+                                    ("the <day-of-month> (ordinal)named-month", -3.912023005428146),
+                                    ("intersect by \"of\", \"from\", \"'s\"year",
+                                     -4.75932086581535),
+                                    ("last <day-of-week> of <time>year", -4.75932086581535),
+                                    ("todayat <time-of-day>", -4.75932086581535),
+                                    ("dayday", -3.0247198104272432),
+                                    ("dd/mmat <time-of-day>", -4.248495242049359),
+                                    ("intersect by \",\"hh:mm", -4.248495242049359),
+                                    ("intersectnamed-month", -4.75932086581535),
+                                    ("dayyear", -3.2929837970219227),
+                                    ("named-daythis <time>", -4.0661736852554045),
+                                    ("tomorrow<time-of-day> sharp", -4.75932086581535),
+                                    ("<day-of-month>(ordinal) <named-month>year",
+                                     -4.75932086581535),
+                                    ("named-day<time> timezone", -4.471638793363569),
+                                    ("named-monthyear", -3.0853444322436783),
+                                    ("absorption of , after named day<named-month> <day-of-month> (non ordinal)",
+                                     -4.0661736852554045),
+                                    ("on <date>named-month", -3.912023005428146),
+                                    ("tomorrowuntil <time-of-day>", -4.75932086581535),
+                                    ("absorption of , after named day<day-of-month> (non ordinal) <named-month>",
+                                     -4.471638793363569),
+                                    ("after <time-of-day>at <time-of-day>", -4.75932086581535),
+                                    ("intersect by \",\"<day-of-month> (non ordinal) <named-month>",
+                                     -4.75932086581535),
+                                    ("named-day<day-of-month> (non ordinal) <named-month>",
+                                     -5.1647859739235145),
+                                    ("named-day<named-month> <day-of-month> (non ordinal)",
+                                     -5.1647859739235145),
+                                    ("named-dayfrom <datetime> - <datetime> (interval)",
+                                     -4.75932086581535),
+                                    ("named-daynext <cycle>", -5.1647859739235145),
+                                    ("named-dayintersect", -4.75932086581535),
+                                    ("named-dayfrom <time-of-day> - <time-of-day> (interval)",
+                                     -4.75932086581535),
+                                    ("tomorrowafter <time-of-day>", -4.75932086581535),
+                                    ("from <time-of-day> - <time-of-day> (interval)on <date>",
+                                     -4.248495242049359),
+                                    ("dayminute", -2.8134107167600364),
+                                    ("from <datetime> - <datetime> (interval)on <date>",
+                                     -4.471638793363569),
+                                    ("<ordinal> <cycle> of <time>year", -4.75932086581535),
+                                    ("minuteday", -2.5620962884791303),
+                                    ("absorption of , after named dayintersect",
+                                     -5.1647859739235145),
+                                    ("named-daybetween <time-of-day> and <time-of-day> (interval)",
+                                     -5.1647859739235145),
+                                    ("named-dayon <date>", -4.75932086581535),
+                                    ("named-dayat <time-of-day>", -4.75932086581535),
+                                    ("yearhh:mm", -5.1647859739235145),
+                                    ("named-daybetween <datetime> and <datetime> (interval)",
+                                     -5.1647859739235145),
+                                    ("absorption of , after named dayintersect by \",\"",
+                                     -4.75932086581535),
+                                    ("dd/mmyear", -4.75932086581535),
+                                    ("at <time-of-day>on <date>", -5.1647859739235145),
+                                    ("between <time-of-day> and <time-of-day> (interval)on <date>",
+                                     -5.1647859739235145),
+                                    ("between <datetime> and <datetime> (interval)on <date>",
+                                     -5.1647859739235145),
+                                    ("dayweek", -4.0661736852554045),
+                                    ("weekyear", -4.248495242049359),
+                                    ("hh:mmtomorrow", -4.471638793363569),
+                                    ("tomorrowat <time-of-day>", -3.912023005428146),
+                                    ("named-daythis <cycle>", -4.471638793363569),
+                                    ("named-daythe <day-of-month> (ordinal)", -5.1647859739235145),
+                                    ("at <time-of-day>tomorrow", -4.75932086581535),
+                                    ("last <cycle> of <time>year", -4.248495242049359),
+                                    ("<day-of-month> (non ordinal) <named-month>year",
+                                     -4.75932086581535),
+                                    ("yearminute", -5.1647859739235145)],
+                               n = 136},
+                   koData =
+                     ClassData{prior = -1.034370019939756, unseen = -5.43372200355424,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -4.736198448394496),
+                                    ("dayhour", -3.2321210516182215),
+                                    ("daymonth", -2.133508762950112),
+                                    ("monthday", -3.8199077165203406),
+                                    ("monthyear", -3.8199077165203406),
+                                    ("intersect by \"of\", \"from\", \"'s\"year",
+                                     -3.4834354798991276),
+                                    ("named-dayhh:mm", -4.736198448394496),
+                                    ("dd/mmat <time-of-day>", -3.8199077165203406),
+                                    ("hourhour", -3.4834354798991276),
+                                    ("dayyear", -3.4834354798991276),
+                                    ("named-daythis <time>", -2.596132284898225),
+                                    ("year<hour-of-day> <integer> (as relative minutes)",
+                                     -4.330733340286331),
+                                    ("minutemonth", -4.330733340286331),
+                                    ("named-monthyear", -3.8199077165203406),
+                                    ("in|during the <part-of-day>until <time-of-day>",
+                                     -4.330733340286331),
+                                    ("absorption of , after named daynamed-month",
+                                     -3.6375861597263857),
+                                    ("named-dayfrom <datetime> - <datetime> (interval)",
+                                     -4.736198448394496),
+                                    ("<hour-of-day> <integer> (as relative minutes)named-month",
+                                     -4.330733340286331),
+                                    ("named-dayfrom <time-of-day> - <time-of-day> (interval)",
+                                     -4.736198448394496),
+                                    ("until <time-of-day>on <date>", -4.736198448394496),
+                                    ("dayminute", -3.349904087274605),
+                                    ("minuteday", -3.2321210516182215),
+                                    ("named-daybetween <time-of-day> and <time-of-day> (interval)",
+                                     -4.736198448394496),
+                                    ("named-dayon <date>", -4.736198448394496),
+                                    ("named-dayat <time-of-day>", -3.8199077165203406),
+                                    ("hh:mmon <date>", -3.349904087274605),
+                                    ("named-daybetween <datetime> and <datetime> (interval)",
+                                     -4.736198448394496),
+                                    ("named-month<day-of-month> (non ordinal) <named-month>",
+                                     -3.8199077165203406),
+                                    ("<named-month> <day-of-month> (non ordinal)named-month",
+                                     -3.8199077165203406),
+                                    ("this <part-of-day>until <time-of-day>", -4.330733340286331),
+                                    ("<hour-of-day> <integer> (as relative minutes)year",
+                                     -4.330733340286331),
+                                    ("tomorrownoon", -4.736198448394496),
+                                    ("this <time>until <time-of-day>", -4.330733340286331),
+                                    ("minuteyear", -4.330733340286331),
+                                    ("yearminute", -4.330733340286331)],
+                               n = 75}}),
+       ("<ordinal> <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.7346010553881064),
+                                    ("ordinals (first..31st)week (grain)intersect",
+                                     -1.7346010553881064),
+                                    ("ordinals (first..31st)week (grain)named-month",
+                                     -1.7346010553881064),
+                                    ("weekmonth", -1.2237754316221157),
+                                    ("ordinals (first..31st)day (grain)named-month",
+                                     -1.7346010553881064)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("season",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.833213344056216,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 15},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("from <datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.8754687373538999, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.2237754316221157),
+                                    ("time-of-day (latent)time-of-day (latent)",
+                                     -2.1400661634962708),
+                                    ("hh:mmhh:mm", -1.2237754316221157),
+                                    ("hourhour", -2.1400661634962708)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.5389965007326869,
+                               unseen = -3.0910424533583156,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -1.4350845252893227),
+                                    ("minuteminute", -1.6582280766035324),
+                                    ("minutehour", -1.4350845252893227),
+                                    ("hh:mmintersect", -1.6582280766035324)],
+                               n = 7}}),
+       ("next <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6094379124341003),
+                                    ("month (grain)", -2.3025850929940455),
+                                    ("year (grain)", -2.3025850929940455),
+                                    ("week (grain)", -1.6094379124341003),
+                                    ("quarter", -2.3025850929940455), ("year", -2.3025850929940455),
+                                    ("month", -2.3025850929940455),
+                                    ("quarter (grain)", -2.3025850929940455)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("number.number hours",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("from <time-of-day> - <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.6061358035703156,
+                               unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.0986122886681098),
+                                    ("time-of-day (latent)time-of-day (latent)",
+                                     -2.1972245773362196),
+                                    ("hh:mmhh:mm", -1.0986122886681098),
+                                    ("hourhour", -2.1972245773362196)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.7884573603642702, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -0.9808292530117262),
+                                    ("minutehour", -0.9808292530117262)],
+                               n = 5}}),
+       ("yyyy-mm-dd",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (latent)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 3}}),
+       ("dd/mm/yyyy",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<ordinal> quarter <year>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..31st)quarter (grain)year",
+                                     -1.252762968495368),
+                                    ("quarteryear", -0.8472978603872037),
+                                    ("ordinal (digits)quarter (grain)year", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("after lunch",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh:mm:ss",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the <cycle> after <time>",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("year (grain)christmas eve", -0.6931471805599453),
+                                    ("yearday", -0.6931471805599453)],
+                               n = 2}}),
+       ("quarter to|till|before <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("a pair",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("nth <time> of <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228, unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -0.7472144018302211),
+                                    ("ordinals (first..31st)named-dayintersect",
+                                     -0.9985288301111273),
+                                    ("ordinals (first..31st)named-daynamed-month",
+                                     -1.845826690498331)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -0.8472978603872037, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -0.7621400520468967),
+                                    ("ordinals (first..31st)named-daynamed-month",
+                                     -0.7621400520468967)],
+                               n = 6}}),
+       ("the <day-of-month> (non ordinal)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinals (first..31st)",
+        Classifier{okData =
+                     ClassData{prior = -5.129329438755058e-2,
+                               unseen = -3.044522437723423,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 19},
+                   koData =
+                     ClassData{prior = -2.995732273553991, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("named-month",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.189654742026425,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 64},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("week (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.367295829986474,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 27},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("now",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4}}),
+       ("this <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.8472978603872037), ("evening", -1.252762968495368),
+                                    ("morning", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -3.332204510175204,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.7308875085427924),
+                                    ("morning", -0.7308875085427924)],
+                               n = 12}}),
+       ("christmas eve",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month>(ordinal) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -9.53101798043249e-2,
+                               unseen = -3.1780538303479458,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..31st)named-month", -1.749199854809259),
+                                    ("ordinal (digits)named-month", -1.0560526742493137),
+                                    ("month", -0.7375989431307791)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -2.3978952727983707, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..31st)named-month", -0.916290731874155),
+                                    ("month", -0.916290731874155)],
+                               n = 1}}),
+       ("numbers prefix with -, negative or minus",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -3.2188758248682006,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 23}}),
+       ("in|during the <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("afternoon", -1.3862943611198906),
+                                    ("hour", -0.9808292530117262),
+                                    ("evening", -1.3862943611198906)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.7672551527136672),
+                                    ("morning", -0.7672551527136672)],
+                               n = 12}}),
+       ("new year's eve",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("tomorrow",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.772588722239781,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 14},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<cycle> after <time>",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("year (grain)christmas eve", -0.6931471805599453),
+                                    ("yearday", -0.6931471805599453)],
+                               n = 2}}),
+       ("Mother's Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> after next",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -1.791759469228055),
+                                    ("day", -1.0986122886681098),
+                                    ("named-day", -1.0986122886681098),
+                                    ("month", -1.791759469228055)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("half an hour",
+        Classifier{okData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -0.916290731874155, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("the <day-of-month> (ordinal)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..31st)", -1.0116009116784799),
+                                    ("ordinal (digits)", -0.45198512374305727)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("afternoon",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<duration> from now",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("second", -1.4469189829363254), ("year", -2.1400661634962708),
+                                    ("<integer> <unit-of-duration>", -1.2237754316221157),
+                                    ("a <unit-of-duration>", -1.7346010553881064),
+                                    ("minute", -1.7346010553881064)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.11778303565638351,
+                               unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.5686159179138452),
+                                    ("year (grain)", -2.0794415416798357),
+                                    ("week (grain)", -1.5686159179138452),
+                                    ("day", -2.4849066497880004), ("quarter", -2.4849066497880004),
+                                    ("year", -2.0794415416798357),
+                                    ("quarter (grain)", -2.4849066497880004),
+                                    ("day (grain)", -2.4849066497880004)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -2.1972245773362196,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.6094379124341003),
+                                    ("day (grain)", -1.6094379124341003)],
+                               n = 1}}),
+       ("minute (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.833213344056216,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 15},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("about <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.8472978603872037,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.916290731874155),
+                                    ("hour", -0.916290731874155)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -0.5596157879354228,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.3862943611198906),
+                                    ("hour", -1.3862943611198906),
+                                    ("<hour-of-day> half (as relative minutes)",
+                                     -1.3862943611198906),
+                                    ("minute", -1.3862943611198906)],
+                               n = 4}}),
+       ("christmas days",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("time-of-day (latent)",
+        Classifier{okData =
+                     ClassData{prior = -0.99714282850325, unseen = -3.713572066704308,
+                               likelihoods =
+                                 HashMap.fromList [("integer (numeric)", -2.5317807984289897e-2)],
+                               n = 38},
+                   koData =
+                     ClassData{prior = -0.46034171833399856,
+                               unseen = -4.219507705176107,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.27286698666664033),
+                                    ("integer (0..19)", -1.4321038971511848)],
+                               n = 65}}),
+       ("year",
+        Classifier{okData =
+                     ClassData{prior = -0.1431008436406733, unseen = -3.332204510175204,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 26},
+                   koData =
+                     ClassData{prior = -2.0149030205422647, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 4}}),
+       ("last <day-of-week> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.0986122886681098),
+                                    ("daymonth", -0.7621400520468967),
+                                    ("named-dayintersect", -1.6094379124341003)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.579818495252942, unseen = -4.672828834461907,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.466214516775848),
+                                    ("integer (0..19)year (grain)", -3.5648268054439574),
+                                    ("integer (numeric)day (grain)", -2.871679624884012),
+                                    ("integer (0..19)hour (grain)", -3.970291913552122),
+                                    ("second", -3.2771447329921766),
+                                    ("integer (numeric)second (grain)", -3.970291913552122),
+                                    ("a pairhour (grain)", -3.970291913552122),
+                                    ("integer (numeric)year (grain)", -3.5648268054439574),
+                                    ("day", -2.2655438213136967), ("year", -3.054001181677967),
+                                    ("integer (numeric)week (grain)", -3.2771447329921766),
+                                    ("integer (0..19)month (grain)", -3.970291913552122),
+                                    ("integer (0..19)second (grain)", -3.5648268054439574),
+                                    ("hour", -2.871679624884012), ("month", -3.5648268054439574),
+                                    ("integer (numeric)minute (grain)", -2.717528945056754),
+                                    ("integer (0..19)minute (grain)", -3.054001181677967),
+                                    ("integer (numeric)month (grain)", -3.970291913552122),
+                                    ("minute", -2.2655438213136967),
+                                    ("integer (numeric)hour (grain)", -3.2771447329921766),
+                                    ("integer (0..19)day (grain)", -2.871679624884012),
+                                    ("integer (0..19)week (grain)", -2.871679624884012)],
+                               n = 42},
+                   koData =
+                     ClassData{prior = -0.8209805520698302, unseen = -4.48863636973214,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.6855773452501515),
+                                    ("integer (0..19)year (grain)", -3.378724525810097),
+                                    ("integer (numeric)day (grain)", -3.0910424533583156),
+                                    ("integer (0..19)hour (grain)", -3.784189633918261),
+                                    ("second", -2.867898902044106),
+                                    ("integer (numeric)second (grain)", -3.378724525810097),
+                                    ("integer (numeric)year (grain)", -3.0910424533583156),
+                                    ("day", -2.6855773452501515), ("year", -2.6855773452501515),
+                                    ("integer (numeric)week (grain)", -3.378724525810097),
+                                    ("integer (0..19)month (grain)", -3.0910424533583156),
+                                    ("integer (0..19)second (grain)", -3.378724525810097),
+                                    ("hour", -2.6855773452501515), ("month", -2.6855773452501515),
+                                    ("integer (numeric)minute (grain)", -3.378724525810097),
+                                    ("integer (0..19)minute (grain)", -3.378724525810097),
+                                    ("integer (numeric)month (grain)", -3.378724525810097),
+                                    ("minute", -2.867898902044106),
+                                    ("integer (numeric)hour (grain)", -2.867898902044106),
+                                    ("integer (0..19)day (grain)", -3.378724525810097),
+                                    ("integer (0..19)week (grain)", -3.0910424533583156)],
+                               n = 33}}),
+       ("<duration> after <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("a <unit-of-duration>christmas eve", -1.0986122886681098),
+                                    ("yearday", -0.8109302162163288),
+                                    ("<integer> <unit-of-duration>christmas eve",
+                                     -1.5040773967762742)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("relative minutes after|past <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.6931471805599453),
+                                    ("integer (numeric)time-of-day (latent)", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("a <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.4965075614664802,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.367123614131617),
+                                    ("year (grain)", -2.367123614131617),
+                                    ("second", -1.8562979903656263),
+                                    ("week (grain)", -2.367123614131617),
+                                    ("day", -2.772588722239781),
+                                    ("minute (grain)", -2.367123614131617),
+                                    ("year", -2.367123614131617),
+                                    ("second (grain)", -1.8562979903656263),
+                                    ("minute", -2.367123614131617),
+                                    ("day (grain)", -2.772588722239781)],
+                               n = 11},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect by \",\"",
+        Classifier{okData =
+                     ClassData{prior = -0.1590646946296874, unseen = -4.442651256490317,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>named-day", -3.7376696182833684),
+                                    ("intersect by \",\"year", -3.7376696182833684),
+                                    ("hh:mmintersect by \",\"", -3.7376696182833684),
+                                    ("dayday", -2.032921526044943),
+                                    ("hh:mmnamed-day", -3.7376696182833684),
+                                    ("named-dayintersect by \",\"", -3.332204510175204),
+                                    ("dayyear", -2.8213788864092133),
+                                    ("<named-month> <day-of-month> (non ordinal)intersect",
+                                     -3.7376696182833684),
+                                    ("intersect by \",\"<day-of-month> (non ordinal) <named-month>",
+                                     -3.332204510175204),
+                                    ("<named-month> <day-of-month> (non ordinal)named-day",
+                                     -3.7376696182833684),
+                                    ("named-day<day-of-month> (non ordinal) <named-month>",
+                                     -3.044522437723423),
+                                    ("named-day<named-month> <day-of-month> (non ordinal)",
+                                     -2.639057329615259),
+                                    ("hh:mmintersect", -3.7376696182833684),
+                                    ("intersect by \",\"intersect", -3.7376696182833684),
+                                    ("named-dayintersect", -3.7376696182833684),
+                                    ("at <time-of-day>intersect", -3.7376696182833684),
+                                    ("dayminute", -2.639057329615259),
+                                    ("intersectyear", -3.7376696182833684),
+                                    ("minuteday", -2.032921526044943),
+                                    ("hh:mmabsorption of , after named day", -3.7376696182833684),
+                                    ("at <time-of-day>intersect by \",\"", -3.7376696182833684),
+                                    ("at <time-of-day>absorption of , after named day",
+                                     -3.7376696182833684),
+                                    ("intersectintersect", -3.7376696182833684),
+                                    ("<named-month> <day-of-month> (non ordinal)year",
+                                     -3.332204510175204)],
+                               n = 29},
+                   koData =
+                     ClassData{prior = -1.916922612182061, unseen = -3.6109179126442243,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.791759469228055),
+                                    ("daymonth", -1.791759469228055)],
+                               n = 5}}),
+       ("hh:mm",
+        Classifier{okData =
+                     ClassData{prior = -2.197890671877523e-2,
+                               unseen = -3.8501476017100584,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 45},
+                   koData =
+                     ClassData{prior = -3.828641396489095, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("quarter after|past <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("named-day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.290459441148391,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 71},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("second (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> sharp",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.2992829841302609),
+                                    ("time-of-day (latent)", -1.2992829841302609),
+                                    ("hour", -0.7884573603642702)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect by \"of\", \"from\", \"'s\"",
+        Classifier{okData =
+                     ClassData{prior = -1.0116009116784799,
+                               unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.4816045409242156),
+                                    ("daymonth", -1.1451323043030026),
+                                    ("named-daylast <cycle>", -2.3978952727983707),
+                                    ("named-daynext <cycle>", -2.3978952727983707),
+                                    ("named-dayintersect", -1.9924301646902063),
+                                    ("dayweek", -1.9924301646902063)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -0.45198512374305727,
+                               unseen = -3.5553480614894135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.329135947279942),
+                                    ("daymonth", -0.8183103235139514),
+                                    ("named-dayintersect", -1.580450375560848)],
+                               n = 14}}),
+       ("<duration> ago",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.5553480614894135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.580450375560848), ("day", -1.916922612182061),
+                                    ("year", -2.4277482359480516),
+                                    ("<integer> <unit-of-duration>", -0.8873031950009028),
+                                    ("a <unit-of-duration>", -2.833213344056216),
+                                    ("month", -2.4277482359480516)],
+                               n = 14},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <time>",
+        Classifier{okData =
+                     ClassData{prior = -1.6739764335716716,
+                               unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.2809338454620642),
+                                    ("named-day", -1.2809338454620642),
+                                    ("hour", -1.791759469228055), ("week-end", -1.791759469228055)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.2076393647782445, unseen = -4.07753744390572,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.8632184332102),
+                                    ("time-of-day (latent)", -1.1160040313799788),
+                                    ("named-day", -2.451005098112319),
+                                    ("intersect by \"of\", \"from\", \"'s\"", -2.451005098112319),
+                                    ("hour", -1.1160040313799788)],
+                               n = 26}}),
+       ("<day-of-month> (ordinal)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..31st)", -1.0116009116784799),
+                                    ("ordinal (digits)", -0.45198512374305727)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the day after tomorrow",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("noon",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("until <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -0.8873031950009028),
+                                    ("hour", -0.8873031950009028)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -1.5040773967762742),
+                                    ("hh:mm", -1.5040773967762742),
+                                    ("minute", -1.0986122886681098)],
+                               n = 2}}),
+       ("<integer> and an half hours",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.6931471805599453),
+                                    ("integer (0..19)", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("after <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("<integer> <unit-of-duration>", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("evening",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("decimal number",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("next <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -1.791759469228055),
+                                    ("day", -1.0986122886681098),
+                                    ("named-day", -1.0986122886681098),
+                                    ("month", -1.791759469228055)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -1.791759469228055),
+                                    ("day", -1.0986122886681098),
+                                    ("named-day", -1.0986122886681098),
+                                    ("month", -1.791759469228055)],
+                               n = 4}}),
+       ("last <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.45198512374305727,
+                               unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.7047480922384253),
+                                    ("month (grain)", -1.9924301646902063),
+                                    ("year (grain)", -1.9924301646902063),
+                                    ("week (grain)", -1.7047480922384253),
+                                    ("year", -1.9924301646902063), ("month", -1.9924301646902063)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -1.0116009116784799, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6739764335716716),
+                                    ("week (grain)", -1.6739764335716716),
+                                    ("day", -1.6739764335716716),
+                                    ("day (grain)", -1.6739764335716716)],
+                               n = 4}}),
+       ("christmas",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("new year's day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("next n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.912023005428146,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.793208009442517),
+                                    ("integer (0..19)year (grain)", -3.1986731175506815),
+                                    ("integer (numeric)day (grain)", -3.1986731175506815),
+                                    ("integer (0..19)hour (grain)", -3.1986731175506815),
+                                    ("second", -2.793208009442517),
+                                    ("integer (numeric)second (grain)", -3.1986731175506815),
+                                    ("integer (numeric)year (grain)", -3.1986731175506815),
+                                    ("day", -2.793208009442517), ("year", -2.793208009442517),
+                                    ("integer (numeric)week (grain)", -3.1986731175506815),
+                                    ("integer (0..19)month (grain)", -3.1986731175506815),
+                                    ("integer (0..19)second (grain)", -3.1986731175506815),
+                                    ("hour", -2.793208009442517), ("month", -2.793208009442517),
+                                    ("integer (numeric)minute (grain)", -3.1986731175506815),
+                                    ("integer (0..19)minute (grain)", -3.1986731175506815),
+                                    ("integer (numeric)month (grain)", -3.1986731175506815),
+                                    ("minute", -2.793208009442517),
+                                    ("integer (numeric)hour (grain)", -3.1986731175506815),
+                                    ("integer (0..19)day (grain)", -3.1986731175506815),
+                                    ("integer (0..19)week (grain)", -3.1986731175506815)],
+                               n = 14},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -3.0910424533583156,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("in <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.418840607796598,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -3.0204248861443626),
+                                    ("<integer> more <unit-of-duration>", -3.3081069585961433),
+                                    ("number.number hours", -3.713572066704308),
+                                    ("second", -2.797281334830153), ("day", -2.6149597780361984),
+                                    ("half an hour", -3.713572066704308),
+                                    ("<integer> <unit-of-duration>", -1.3156767939059373),
+                                    ("a <unit-of-duration>", -2.46080909820894),
+                                    ("<integer> and an half hours", -3.3081069585961433),
+                                    ("hour", -2.6149597780361984), ("minute", -1.4622802680978126),
+                                    ("about <duration>", -3.3081069585961433)],
+                               n = 35},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.8362480242006186,
+                               unseen = -3.6888794541139363,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.466337068793427),
+                                    ("hh:mmhh:mm", -1.466337068793427),
+                                    ("dayday", -1.8718021769015913),
+                                    ("<day-of-month> (non ordinal) <named-month><day-of-month> (non ordinal) <named-month>",
+                                     -1.8718021769015913)],
+                               n = 13},
+                   koData =
+                     ClassData{prior = -0.5679840376059393, unseen = -3.871201010907891,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthday", -2.0583881324820035),
+                                    ("minuteminute", -1.9042374526547454),
+                                    ("hh:mmhh:mm", -3.1570004211501135),
+                                    ("dayyear", -2.751535313041949),
+                                    ("year<hour-of-day> <integer> (as relative minutes)",
+                                     -2.751535313041949),
+                                    ("hh:mmintersect", -2.0583881324820035),
+                                    ("dd/mmyear", -2.751535313041949),
+                                    ("named-month<day-of-month> (non ordinal) <named-month>",
+                                     -2.0583881324820035),
+                                    ("<hour-of-day> <integer> (as relative minutes)year",
+                                     -2.751535313041949),
+                                    ("minuteyear", -2.751535313041949),
+                                    ("yearminute", -2.751535313041949)],
+                               n = 17}}),
+       ("<time-of-day> - <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.8109302162163288, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -0.7985076962177716),
+                                    ("hh:mmhh:mm", -0.7985076962177716)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -0.587786664902119, unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -0.8754687373538999),
+                                    ("minuteminute", -2.4849066497880004),
+                                    ("hh:mmhh:mm", -2.4849066497880004),
+                                    ("minutehour", -0.8754687373538999)],
+                               n = 10}}),
+       ("last n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.04305126783455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.639057329615259),
+                                    ("integer (0..19)year (grain)", -3.332204510175204),
+                                    ("integer (numeric)day (grain)", -2.9267394020670396),
+                                    ("second", -2.9267394020670396),
+                                    ("integer (numeric)second (grain)", -3.332204510175204),
+                                    ("integer (numeric)year (grain)", -2.9267394020670396),
+                                    ("day", -2.639057329615259), ("year", -2.639057329615259),
+                                    ("integer (numeric)week (grain)", -3.332204510175204),
+                                    ("integer (0..19)month (grain)", -2.9267394020670396),
+                                    ("integer (0..19)second (grain)", -3.332204510175204),
+                                    ("hour", -2.9267394020670396), ("month", -2.639057329615259),
+                                    ("integer (numeric)minute (grain)", -3.332204510175204),
+                                    ("integer (0..19)minute (grain)", -3.332204510175204),
+                                    ("integer (numeric)month (grain)", -3.332204510175204),
+                                    ("minute", -2.9267394020670396),
+                                    ("integer (numeric)hour (grain)", -2.9267394020670396),
+                                    ("integer (0..19)day (grain)", -3.332204510175204),
+                                    ("integer (0..19)week (grain)", -2.9267394020670396)],
+                               n = 18},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -3.044522437723423,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-month> <day-of-month> (non ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -0.4418327522790392, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-monthinteger (numeric)", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -1.0296194171811581,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-monthinteger (numeric)", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 5}}),
+       ("<day-of-month> (non ordinal) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -3.6635616461296463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 18},
+                   koData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 3}}),
+       ("this|next <day-of-week>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 3}}),
+       ("<hour-of-day> half (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("about <time-of-day>", -1.7346010553881064),
+                                    ("time-of-day (latent)", -1.041453874828161),
+                                    ("hour", -0.7537718023763802)],
+                               n = 7}}),
+       ("ordinal (digits)",
+        Classifier{okData =
+                     ClassData{prior = -8.701137698962981e-2,
+                               unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -2.4849066497880004,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("quarter (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.4816045409242156),
+                                    ("week (grain)named-month", -1.9924301646902063),
+                                    ("day (grain)intersect", -1.9924301646902063),
+                                    ("weekmonth", -1.4816045409242156),
+                                    ("day (grain)named-month", -1.9924301646902063),
+                                    ("week (grain)intersect", -1.9924301646902063)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month>(ordinal) <named-month> year",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..31st)named-month", -1.0986122886681098),
+                                    ("ordinal (digits)named-month", -1.5040773967762742),
+                                    ("month", -0.8109302162163288)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("morning",
+        Classifier{okData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12}}),
+       ("week-end",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("after <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.7537718023763802, unseen = -3.332204510175204,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.6863989535702288),
+                                    ("intersect", -2.1972245773362196),
+                                    ("tomorrow", -2.1972245773362196), ("day", -2.1972245773362196),
+                                    ("hour", -1.349926716949016)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -0.6359887667199967,
+                               unseen = -3.4011973816621555,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("this <part-of-day>", -2.268683541318364),
+                                    ("christmas eve", -2.268683541318364),
+                                    ("in|during the <part-of-day>", -2.268683541318364),
+                                    ("day", -2.268683541318364), ("hh:mm", -2.6741486494265287),
+                                    ("hour", -1.4213856809311607), ("minute", -2.6741486494265287),
+                                    ("this <time>", -2.268683541318364)],
+                               n = 9}}),
+       ("day (grain)",
+        Classifier{okData =
+                     ClassData{prior = -4.8790164169432056e-2,
+                               unseen = -3.0910424533583156,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 20},
+                   koData =
+                     ClassData{prior = -3.044522437723423, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("<month> dd-dd (interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("about <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("half an hour", -0.6931471805599453),
+                                    ("minute", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> <integer> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)integer (numeric)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 9}}),
+       ("this <time>",
+        Classifier{okData =
+                     ClassData{prior = -1.2443240998495033,
+                               unseen = -3.8501476017100584,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -3.1354942159291497),
+                                    ("intersect", -2.7300291078209855),
+                                    ("season", -2.2192034840549946),
+                                    ("next <cycle>", -3.1354942159291497),
+                                    ("named-month", -2.7300291078209855),
+                                    ("day", -1.8827312474337816),
+                                    ("christmas days", -2.7300291078209855),
+                                    ("hour", -1.8827312474337816), ("evening", -3.1354942159291497),
+                                    ("month", -2.2192034840549946),
+                                    ("morning", -3.1354942159291497),
+                                    ("week-end", -2.2192034840549946)],
+                               n = 17},
+                   koData =
+                     ClassData{prior = -0.3398678256223512, unseen = -4.574710978503383,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -1.9993988340062996),
+                                    ("named-month", -1.6199092123013958),
+                                    ("hour", -1.9993988340062996), ("month", -1.1303609869826898),
+                                    ("morning", -1.9993988340062996)],
+                               n = 42}}),
+       ("within <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -0.6931471805599453),
+                                    ("<integer> <unit-of-duration>", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}})]
diff --git a/Duckling/Ranking/Classifiers/NL.hs b/Duckling/Ranking/Classifiers/NL.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/NL.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.NL (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers = HashMap.fromList []
diff --git a/Duckling/Ranking/Classifiers/PL.hs b/Duckling/Ranking/Classifiers/PL.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/PL.hs
@@ -0,0 +1,2307 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.PL (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers
+  = HashMap.fromList
+      [("five",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (numeric)",
+        Classifier{okData =
+                     ClassData{prior = -0.5997731097824868, unseen = -4.875197323201151,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 129},
+                   koData =
+                     ClassData{prior = -0.7961464200320919, unseen = -4.68213122712422,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 106}}),
+       ("exactly <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<ordinal> (as hour)", -1.3862943611198906),
+                                    ("<integer> (latent time-of-day)", -1.791759469228055),
+                                    ("hour", -0.8754687373538999),
+                                    ("<time-of-day> popo\322udniu/wieczorem/w nocy",
+                                     -1.791759469228055)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<cycle> before <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayday", -0.6931471805599453),
+                                    ("day (grain)yesterday", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Father's Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<ordinal> <cycle> <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.258096538021482,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -2.120263536200091),
+                                    ("quarteryear", -2.5257286443082556),
+                                    ("third ordinalday (grain)on <date>", -2.5257286443082556),
+                                    ("first ordinalweek (grain)named-month", -2.5257286443082556),
+                                    ("weekmonth", -1.4271163556401458),
+                                    ("ordinal (digits)quarter (grain)year", -2.5257286443082556),
+                                    ("first ordinalweek (grain)intersect", -2.120263536200091),
+                                    ("third ordinalday (grain)named-month", -2.5257286443082556),
+                                    ("first ordinalweek (grain)on <date>", -2.120263536200091)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.7392382877602123, unseen = -5.003946305945459,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<ordinal> (as hour)after <time-of-day>", -3.6109179126442243),
+                                    ("dayhour", -3.0513021247088017),
+                                    ("<ordinal> (as hour)evening|night", -2.5993170009657445),
+                                    ("<ordinal> (as hour)on <date>", -4.304065093204169),
+                                    ("yesterdayevening|night", -4.304065093204169),
+                                    ("hourhour", -1.1470646720540565),
+                                    ("after <time-of-day>after <time-of-day>", -3.898599985096005),
+                                    ("until <time-of-day>morning", -3.898599985096005),
+                                    ("until <time-of-day>after <time-of-day>", -4.304065093204169),
+                                    ("minutehour", -4.304065093204169),
+                                    ("named-daymorning", -4.304065093204169),
+                                    ("todayevening|night", -4.304065093204169),
+                                    ("at <time-of-day>evening|night", -4.304065093204169),
+                                    ("named-dayevening|night", -4.304065093204169),
+                                    ("intersecton <date>", -4.304065093204169),
+                                    ("<integer> (latent time-of-day)this <part-of-day>",
+                                     -4.304065093204169),
+                                    ("hh:mmon <date>", -4.304065093204169),
+                                    ("<integer> (latent time-of-day)morning", -3.20545280453606),
+                                    ("at <time-of-day>on <date>", -4.304065093204169),
+                                    ("intersectmorning", -3.0513021247088017),
+                                    ("<integer> (latent time-of-day)evening|night",
+                                     -3.0513021247088017),
+                                    ("from <datetime> - <datetime> (interval)morning",
+                                     -3.898599985096005),
+                                    ("from <time-of-day> - <time-of-day> (interval)morning",
+                                     -4.304065093204169),
+                                    ("on <date>morning", -4.304065093204169),
+                                    ("at <time-of-day>morning", -3.898599985096005),
+                                    ("tomorrowevening|night", -3.898599985096005)],
+                               n = 53},
+                   koData =
+                     ClassData{prior = -0.6490871907659148,
+                               unseen = -5.0689042022202315,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -1.971552579668651),
+                                    ("yearhour", -1.9271008170978172),
+                                    ("year (latent)on <date>", -4.3694478524670215),
+                                    ("<day-of-month> (ordinal)on <date>", -4.3694478524670215),
+                                    ("<time-of-day> - <time-of-day> (interval)morning",
+                                     -4.3694478524670215),
+                                    ("<day-of-month> (ordinal)evening|night", -2.6646997602285962),
+                                    ("by the end of <time>morning", -4.3694478524670215),
+                                    ("year (latent)evening|night", -3.1166848839716534),
+                                    ("hourhour", -2.760009940032921),
+                                    ("after <time-of-day>after <time-of-day>", -3.963982744358857),
+                                    ("<day-of-month> (ordinal)morning", -3.963982744358857),
+                                    ("<day-of-month> (ordinal)after <time-of-day>",
+                                     -3.270835563798912),
+                                    ("until <time-of-day>morning", -3.4531571205928664),
+                                    ("until <time-of-day>after <time-of-day>", -4.3694478524670215),
+                                    ("about <time-of-day>after <time-of-day>", -4.3694478524670215),
+                                    ("by <time>morning", -4.3694478524670215),
+                                    ("<integer> (latent time-of-day)after <time-of-day>",
+                                     -3.963982744358857),
+                                    ("<integer> (latent time-of-day)morning", -4.3694478524670215),
+                                    ("secondhour", -3.1166848839716534),
+                                    ("intersectmorning", -3.4531571205928664),
+                                    ("from <datetime> - <datetime> (interval)morning",
+                                     -3.963982744358857),
+                                    ("year (latent)morning", -2.6646997602285962),
+                                    ("year (latent)after <time-of-day>", -3.963982744358857),
+                                    ("at <time-of-day>after <time-of-day>", -4.3694478524670215)],
+                               n = 58}}),
+       ("today",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("mm/dd",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4}}),
+       ("at <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.14571181118139365,
+                               unseen = -4.718498871295094,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<ordinal> (as hour)", -1.5740359853831845),
+                                    ("<integer> (latent time-of-day)", -2.3116349285139637),
+                                    ("about <time-of-day>", -4.0163830207523885),
+                                    ("hh:mm", -3.6109179126442243),
+                                    ("<time-of-day> rano", -2.917770732084279),
+                                    ("hour", -0.8383291904044432),
+                                    ("<time-of-day> popo\322udniu/wieczorem/w nocy",
+                                     -2.2246235515243336),
+                                    ("minute", -3.100092288878234)],
+                               n = 51},
+                   koData =
+                     ClassData{prior = -1.9980959022258835, unseen = -3.258096538021482,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<ordinal> (as hour)", -1.8325814637483102),
+                                    ("<integer> (latent time-of-day)", -1.8325814637483102),
+                                    ("relative minutes after|past <integer> (hour-of-day)",
+                                     -2.5257286443082556),
+                                    ("hour", -1.1394342831883648),
+                                    ("<time-of-day> popo\322udniu/wieczorem/w nocy",
+                                     -2.5257286443082556),
+                                    ("minute", -2.5257286443082556)],
+                               n = 8}}),
+       ("absorption of , after named day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.891820298110627,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 23},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("11th ordinal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("on <date>",
+        Classifier{okData =
+                     ClassData{prior = -7.79615414697118e-2, unseen = -4.51085950651685,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.890371757896165),
+                                    ("<time> <part-of-day>", -3.8066624897703196),
+                                    ("intersect", -2.0149030205422647),
+                                    ("next <cycle>", -3.1135153092103742),
+                                    ("named-month", -2.1972245773362196),
+                                    ("half to|till|before <integer> (hour-of-day)",
+                                     -3.8066624897703196),
+                                    ("day", -2.70805020110221), ("afternoon", -3.1135153092103742),
+                                    ("this <cycle>", -2.890371757896165),
+                                    ("year", -3.1135153092103742),
+                                    ("named-day", -2.890371757896165),
+                                    ("hour", -2.3025850929940455), ("month", -1.666596326274049),
+                                    ("minute", -3.8066624897703196),
+                                    ("this <time>", -3.8066624897703196)],
+                               n = 37},
+                   koData =
+                     ClassData{prior = -2.5902671654458267,
+                               unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("noon", -1.7047480922384253), ("hour", -1.7047480922384253)],
+                               n = 3}}),
+       ("8th ordinal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("month (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> o'clock",
+        Classifier{okData =
+                     ClassData{prior = -0.3184537311185346, unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<ordinal> (as hour)", -0.8649974374866046),
+                                    ("<integer> (latent time-of-day)", -2.2512917986064953),
+                                    ("hour", -0.7472144018302211)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -1.2992829841302609,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<ordinal> (as hour)", -1.5040773967762742),
+                                    ("<integer> (latent time-of-day)", -1.0986122886681098),
+                                    ("hour", -0.8109302162163288)],
+                               n = 3}}),
+       ("on a named-day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("nth <time> <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.4989911661189879, unseen = -3.951243718581427,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("second ordinalnamed-dayintersect", -2.833213344056216),
+                                    ("first ordinalnamed-dayon <date>", -2.833213344056216),
+                                    ("daymonth", -1.4469189829363254),
+                                    ("dayyear", -1.9859154836690123),
+                                    ("third ordinalnamed-dayintersect", -2.833213344056216),
+                                    ("third ordinalintersectyear", -2.833213344056216),
+                                    ("third ordinalnamed-dayon <date>", -3.2386784521643803),
+                                    ("first ordinalnamed-daynamed-month", -3.2386784521643803),
+                                    ("first ordinalintersectyear", -2.833213344056216),
+                                    ("first ordinalnamed-dayintersect", -2.833213344056216),
+                                    ("second ordinalnamed-dayon <date>", -3.2386784521643803),
+                                    ("second ordinalintersectyear", -2.833213344056216)],
+                               n = 17},
+                   koData =
+                     ClassData{prior = -0.9343092373768334,
+                               unseen = -3.6888794541139363,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("third ordinalnamed-daynamed-month", -2.9704144655697013),
+                                    ("first ordinalnamed-dayon <date>", -2.9704144655697013),
+                                    ("daymonth", -1.717651497074333),
+                                    ("14th ordinalnamed-month<integer> (latent time-of-day)",
+                                     -2.5649493574615367),
+                                    ("monthhour", -1.8718021769015913),
+                                    ("second ordinalnamed-daynamed-month", -2.9704144655697013),
+                                    ("third ordinalnamed-dayon <date>", -2.9704144655697013),
+                                    ("first ordinalnamed-daynamed-month", -2.9704144655697013),
+                                    ("ordinal (digits)named-month<integer> (latent time-of-day)",
+                                     -2.277267285009756),
+                                    ("second ordinalnamed-dayon <date>", -2.9704144655697013)],
+                               n = 11}}),
+       ("<ordinal> (as hour)",
+        Classifier{okData =
+                     ClassData{prior = -0.5773153650348236, unseen = -4.454347296253507,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("11th ordinal", -3.056356895370426),
+                                    ("8th ordinal", -3.3440389678222067),
+                                    ("21st ordinal no space", -3.7495040759303713),
+                                    ("20th ordinal", -3.7495040759303713),
+                                    ("third ordinal", -1.8035939268750578),
+                                    ("16th ordinal", -3.3440389678222067),
+                                    ("18th ordinal", -3.3440389678222067),
+                                    ("fifth ordinal", -3.7495040759303713),
+                                    ("seventh ordinal", -3.3440389678222067),
+                                    ("19th ordinal", -3.3440389678222067),
+                                    ("21-29th ordinal", -3.3440389678222067),
+                                    ("sixth ordinal", -3.3440389678222067),
+                                    ("15th ordinal", -3.3440389678222067),
+                                    ("second ordinal", -2.245426679154097),
+                                    ("ordinal (digits)", -2.3632097148104805),
+                                    ("10th ordinal", -2.496741107435003),
+                                    ("9th ordinal", -2.3632097148104805),
+                                    ("23rd ordinal no space", -3.7495040759303713)],
+                               n = 64},
+                   koData =
+                     ClassData{prior = -0.8241754429663495, unseen = -4.276666119016055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("8th ordinal", -3.164067588373206),
+                                    ("20th ordinal", -3.164067588373206),
+                                    ("third ordinal", -2.316769727986002),
+                                    ("14th ordinal", -3.164067588373206),
+                                    ("13th ordinal", -3.56953269648137),
+                                    ("15th ordinal", -2.8763855159214247),
+                                    ("second ordinal", -2.8763855159214247),
+                                    ("ordinal (digits)", -1.1716374236829996),
+                                    ("first ordinal", -1.864784604242945)],
+                               n = 50}}),
+       ("hour (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.8472978603872037,
+                               unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -0.5596157879354228, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12}}),
+       ("21st ordinal no space",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<ordinal> quarter",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("quarter", -0.916290731874155),
+                                    ("third ordinalquarter (grain)", -0.916290731874155)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)quarter (grain)", -0.916290731874155),
+                                    ("quarter", -0.916290731874155)],
+                               n = 1}}),
+       ("intersect",
+        Classifier{okData =
+                     ClassData{prior = -0.7200546334798696, unseen = -6.364750756851911,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<datetime> - <datetime> (interval)on <date>",
+                                     -5.264415814872355),
+                                    ("mm/dd<time-of-day> popo\322udniu/wieczorem/w nocy",
+                                     -5.66988092298052),
+                                    ("<hour-of-day> - <hour-of-day> (interval)on <date>",
+                                     -5.264415814872355),
+                                    ("named-daynamed-month", -4.7535901911063645),
+                                    ("<time-of-day> - <time-of-day> (interval)on <date>",
+                                     -5.264415814872355),
+                                    ("hourday", -3.8781214537524646),
+                                    ("dayhour", -2.6741486494265287),
+                                    ("daymonth", -3.8781214537524646),
+                                    ("<time-of-day> popo\322udniu/wieczorem/w nocyabsorption of , after named day",
+                                     -4.7535901911063645),
+                                    ("monthyear", -3.1849742731925192),
+                                    ("from <hour-of-day> - <hour-of-day> (interval)on a named-day",
+                                     -5.66988092298052),
+                                    ("from <time-of-day> - <time-of-day> (interval)on a named-day",
+                                     -5.66988092298052),
+                                    ("absorption of , after named dayintersect by \",\" 2",
+                                     -5.264415814872355),
+                                    ("intersecthh:mm", -5.264415814872355),
+                                    ("from <datetime> - <datetime> (interval)on a named-day",
+                                     -5.66988092298052),
+                                    ("at <time-of-day>intersect by \",\" 2", -5.264415814872355),
+                                    ("named-daylast <cycle>", -5.264415814872355),
+                                    ("named-day<time-of-day> rano", -5.66988092298052),
+                                    ("on a named-dayat <time-of-day>", -5.264415814872355),
+                                    ("at <time-of-day>named-day", -5.264415814872355),
+                                    ("at <time-of-day>on a named-day", -5.66988092298052),
+                                    ("<time> <part-of-day>on a named-day", -5.264415814872355),
+                                    ("on a named-day<time> <part-of-day>", -5.66988092298052),
+                                    ("last <day-of-week> of <time>year", -5.66988092298052),
+                                    ("today<time> <part-of-day>", -5.66988092298052),
+                                    ("todayat <time-of-day>", -5.66988092298052),
+                                    ("on <date>at <time-of-day>", -5.264415814872355),
+                                    ("dayday", -2.995732273553991),
+                                    ("on <date><time> <part-of-day>", -5.66988092298052),
+                                    ("intersect by \",\"hh:mm", -4.4171179544851515),
+                                    ("mm/ddat <time-of-day>", -4.976733742420574),
+                                    ("last <cycle> <time>year", -4.7535901911063645),
+                                    ("intersect<named-month> <day-of-month> (non ordinal)",
+                                     -4.976733742420574),
+                                    ("intersect<day-of-month> (non ordinal) <named-month>",
+                                     -4.976733742420574),
+                                    ("dayyear", -3.654977902438255),
+                                    ("<day-of-month>(ordinal) <named-month>year",
+                                     -5.264415814872355),
+                                    ("day-after-tomorrow (single-word)at <time-of-day>",
+                                     -5.66988092298052),
+                                    ("absorption of , after named day<day-of-month>(ordinal) <named-month>",
+                                     -4.165803526204246),
+                                    ("tomorrow<time-of-day> popo\322udniu/wieczorem/w nocy",
+                                     -5.66988092298052),
+                                    ("named-monthyear", -3.529814759484249),
+                                    ("absorption of , after named day<named-month> <day-of-month> (non ordinal)",
+                                     -4.283586561860629),
+                                    ("tomorrowuntil <time-of-day>", -5.264415814872355),
+                                    ("absorption of , after named day<day-of-month> (non ordinal) <named-month>",
+                                     -4.165803526204246),
+                                    ("<time-of-day> popo\322udniu/wieczorem/w nocyintersect by \",\"",
+                                     -4.7535901911063645),
+                                    ("named-dayin <duration>", -5.66988092298052),
+                                    ("last <day-of-week> <time>year", -5.264415814872355),
+                                    ("<time-of-day> ranoon <date>", -5.264415814872355),
+                                    ("named-dayfrom <datetime> - <datetime> (interval)",
+                                     -4.283586561860629),
+                                    ("named-daynext <cycle>", -4.7535901911063645),
+                                    ("named-dayintersect", -5.264415814872355),
+                                    ("named-dayfrom <time-of-day> - <time-of-day> (interval)",
+                                     -4.976733742420574),
+                                    ("on <date><time-of-day> rano", -5.66988092298052),
+                                    ("<time-of-day> popo\322udniu/wieczorem/w nocytomorrow",
+                                     -5.66988092298052),
+                                    ("from <time-of-day> - <time-of-day> (interval)on <date>",
+                                     -5.66988092298052),
+                                    ("at <time-of-day>intersect", -5.264415814872355),
+                                    ("dayminute", -3.318505665817042),
+                                    ("intersect by \",\" 2hh:mm", -4.4171179544851515),
+                                    ("<time-of-day> ranoon a named-day", -5.264415814872355),
+                                    ("from <hour-of-day> - <hour-of-day> (interval)on <date>",
+                                     -5.66988092298052),
+                                    ("from <datetime> - <datetime> (interval)on <date>",
+                                     -5.66988092298052),
+                                    ("intersectyear", -5.264415814872355),
+                                    ("on a named-day<time-of-day> rano", -5.66988092298052),
+                                    ("<ordinal> <cycle> of <time>year", -5.66988092298052),
+                                    ("minuteday", -2.3556949183079943),
+                                    ("absorption of , after named dayintersect",
+                                     -5.264415814872355),
+                                    ("named-dayon <date>", -4.060443010546419),
+                                    ("named-day<time> <part-of-day>", -4.7535901911063645),
+                                    ("named-dayat <time-of-day>", -5.264415814872355),
+                                    ("yearhh:mm", -5.66988092298052),
+                                    ("at <time-of-day>intersect by \",\"", -5.264415814872355),
+                                    ("absorption of , after named dayintersect by \",\"",
+                                     -5.264415814872355),
+                                    ("tomorrowexactly <time-of-day>", -4.976733742420574),
+                                    ("at <time-of-day>absorption of , after named day",
+                                     -5.264415814872355),
+                                    ("at <time-of-day>on <date>", -5.66988092298052),
+                                    ("on <date>year", -4.283586561860629),
+                                    ("dayweek", -3.7239707739252066),
+                                    ("<time> <part-of-day>on <date>", -5.264415814872355),
+                                    ("weekyear", -4.4171179544851515),
+                                    ("named-day<day-of-month>(ordinal) <named-month>",
+                                     -4.976733742420574),
+                                    ("<ordinal> <cycle> <time>year", -5.264415814872355),
+                                    ("<time-of-day> popo\322udniu/wieczorem/w nocyintersect by \",\" 2",
+                                     -4.7535901911063645),
+                                    ("tomorrowat <time-of-day>", -5.66988092298052),
+                                    ("named-daythis <cycle>", -5.264415814872355),
+                                    ("tomorrow<time> <part-of-day>", -5.66988092298052),
+                                    ("<time-of-day> popo\322udniu/wieczorem/w nocyintersect",
+                                     -4.7535901911063645),
+                                    ("<named-month> <day-of-month> (ordinal)year",
+                                     -5.66988092298052),
+                                    ("<time-of-day> popo\322udniu/wieczorem/w nocynamed-day",
+                                     -4.7535901911063645),
+                                    ("tomorrow<time-of-day> rano", -5.66988092298052),
+                                    ("<datetime> - <datetime> (interval)on a named-day",
+                                     -5.264415814872355),
+                                    ("last <cycle> of <time>year", -5.264415814872355),
+                                    ("<named-month> <day-of-month> (non ordinal)year",
+                                     -5.66988092298052),
+                                    ("<time-of-day> - <time-of-day> (interval)on a named-day",
+                                     -5.264415814872355),
+                                    ("<day-of-month> (non ordinal) <named-month>year",
+                                     -5.264415814872355),
+                                    ("<hour-of-day> - <hour-of-day> (interval)on a named-day",
+                                     -5.264415814872355),
+                                    ("yearminute", -5.66988092298052)],
+                               n = 220},
+                   koData =
+                     ClassData{prior = -0.6669448081659212, unseen = -6.405228458030842,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<ordinal> (as hour)<named-month> <day-of-month> (non ordinal)",
+                                     -4.61181472870676),
+                                    ("<time-of-day> ranoby <time>", -5.304961909266705),
+                                    ("intersect by \",\"named-month", -4.206349620598596),
+                                    ("named-daynamed-month", -5.304961909266705),
+                                    ("hourday", -2.7926562852905903),
+                                    ("dayhour", -3.107737331930486),
+                                    ("daymonth", -3.312531744576499),
+                                    ("monthday", -5.304961909266705),
+                                    ("monthyear", -4.794136285500715),
+                                    ("named-month<hour-of-day> <integer> (as relative minutes)",
+                                     -5.71042701737487),
+                                    ("at <time-of-day>intersect by \",\" 2", -5.71042701737487),
+                                    ("houryear", -3.570360853878599),
+                                    ("<time> <part-of-day>until <time-of-day>", -5.304961909266705),
+                                    ("<ordinal> (as hour)intersect", -3.145477659913333),
+                                    ("monthhour", -4.61181472870676),
+                                    ("<time> <part-of-day><time> <part-of-day>",
+                                     -5.017279836814924),
+                                    ("hourmonth", -2.0860860843985045),
+                                    ("mm/ddat <time-of-day>", -5.71042701737487),
+                                    ("hourhour", -4.794136285500715),
+                                    ("<time> <part-of-day>by <time>", -5.304961909266705),
+                                    ("intersectnamed-month", -3.4591352187683744),
+                                    ("dayyear", -4.457664048879502),
+                                    ("<time-of-day> ranoby the end of <time>", -5.304961909266705),
+                                    ("year<hour-of-day> <integer> (as relative minutes)",
+                                     -5.304961909266705),
+                                    ("intersect by \",\" 2named-month", -4.206349620598596),
+                                    ("monthminute", -5.304961909266705),
+                                    ("minutemonth", -5.017279836814924),
+                                    ("named-monthyear", -4.794136285500715),
+                                    ("absorption of , after named daynamed-month",
+                                     -4.324132656254979),
+                                    ("<time-of-day> popo\322udniu/wieczorem/w nocyintersect by \",\"",
+                                     -5.304961909266705),
+                                    ("after <time-of-day>at <time-of-day>", -5.71042701737487),
+                                    ("hh:mmby the end of <time>", -5.71042701737487),
+                                    ("<ordinal> (as hour)named-month", -3.036278367948341),
+                                    ("daysecond", -5.304961909266705),
+                                    ("<time> <part-of-day>by the end of <time>",
+                                     -5.304961909266705),
+                                    ("named-month<ordinal> (as hour)", -4.794136285500715),
+                                    ("<time> <part-of-day><time-of-day> rano", -5.71042701737487),
+                                    ("named-dayfrom <datetime> - <datetime> (interval)",
+                                     -5.71042701737487),
+                                    ("named-dayintersect", -4.100989104940769),
+                                    ("named-dayfrom <time-of-day> - <time-of-day> (interval)",
+                                     -5.71042701737487),
+                                    ("<time-of-day> rano<time> <part-of-day>", -5.304961909266705),
+                                    ("at <time-of-day>intersect", -5.71042701737487),
+                                    ("dayminute", -5.304961909266705),
+                                    ("<named-month> <day-of-month> (non ordinal)by <time>",
+                                     -5.71042701737487),
+                                    ("intersecton <date>", -4.324132656254979),
+                                    ("intersectyear", -3.312531744576499),
+                                    ("minuteday", -3.7645168683195562),
+                                    ("absorption of , after named dayintersect",
+                                     -4.206349620598596),
+                                    ("<ordinal> (as hour)year", -5.71042701737487),
+                                    ("named-dayon <date>", -4.794136285500715),
+                                    ("hh:mmon <date>", -5.304961909266705),
+                                    ("absorption of , after named day<ordinal> (as hour)",
+                                     -4.206349620598596),
+                                    ("at <time-of-day>intersect by \",\"", -5.71042701737487),
+                                    ("<named-month> <day-of-month> (non ordinal)by the end of <time>",
+                                     -5.71042701737487),
+                                    ("tomorrowexactly <time-of-day>", -5.71042701737487),
+                                    ("hoursecond", -3.8386248404732783),
+                                    ("named-month<day-of-month> (non ordinal) <named-month>",
+                                     -5.304961909266705),
+                                    ("named-day<ordinal> (as hour)", -5.017279836814924),
+                                    ("<ordinal> (as hour)named-day", -4.206349620598596),
+                                    ("named-monthrelative minutes to|till|before <integer> (hour-of-day)",
+                                     -5.71042701737487),
+                                    ("<named-month> <day-of-month> (non ordinal)named-month",
+                                     -5.304961909266705),
+                                    ("intersectintersect", -4.457664048879502),
+                                    ("hh:mmon a named-day", -5.304961909266705),
+                                    ("named-monthintersect", -5.71042701737487),
+                                    ("<time-of-day> popo\322udniu/wieczorem/w nocyintersect by \",\" 2",
+                                     -5.304961909266705),
+                                    ("hh:mmby <time>", -5.71042701737487),
+                                    ("tomorrowat <time-of-day>", -5.71042701737487),
+                                    ("minutesecond", -5.304961909266705),
+                                    ("<time-of-day> popo\322udniu/wieczorem/w nocyintersect",
+                                     -5.304961909266705),
+                                    ("yearminute", -5.304961909266705)],
+                               n = 232}}),
+       ("half after|past <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<ordinal> (as hour)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("twenty",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("20th ordinal",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("a few",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<ordinal> <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.7047480922384253),
+                                    ("first ordinalweek (grain)named-month", -1.7047480922384253),
+                                    ("weekmonth", -1.2992829841302609),
+                                    ("first ordinalweek (grain)intersect", -1.7047480922384253),
+                                    ("third ordinalday (grain)named-month", -1.7047480922384253)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("season",
+        Classifier{okData =
+                     ClassData{prior = -1.0116009116784799, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -0.45198512374305727,
+                               unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7}}),
+       ("year (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.890371757896165,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 16},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("from <datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.7537718023763802,
+                               unseen = -3.4339872044851463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> <part-of-day><integer> (latent time-of-day)",
+                                     -2.70805020110221),
+                                    ("minuteminute", -2.3025850929940455),
+                                    ("<time> <part-of-day><time> <part-of-day>", -2.70805020110221),
+                                    ("<time-of-day> rano<time-of-day> rano", -2.70805020110221),
+                                    ("hh:mmhh:mm", -2.3025850929940455),
+                                    ("hourhour", -1.455287232606842),
+                                    ("<time> <part-of-day><time-of-day> rano", -2.70805020110221),
+                                    ("<time-of-day> rano<time> <part-of-day>", -2.70805020110221),
+                                    ("<time-of-day> rano<integer> (latent time-of-day)",
+                                     -2.70805020110221)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -0.6359887667199967,
+                               unseen = -3.4965075614664802,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> <part-of-day><integer> (latent time-of-day)",
+                                     -2.772588722239781),
+                                    ("houryear", -2.772588722239781),
+                                    ("minuteminute", -2.367123614131617),
+                                    ("<time> <part-of-day><time> <part-of-day>",
+                                     -2.367123614131617),
+                                    ("hourhour", -1.8562979903656263),
+                                    ("minutehour", -2.367123614131617),
+                                    ("hh:mmintersect", -2.367123614131617),
+                                    ("<time> <part-of-day>year (latent)", -2.772588722239781),
+                                    ("<time> <part-of-day><time-of-day> rano", -2.772588722239781),
+                                    ("hh:mm<integer> (latent time-of-day)", -2.367123614131617)],
+                               n = 9}}),
+       ("from <hour-of-day> - <hour-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -0.6931471805599453),
+                                    ("hh:mmhh:mm", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("next <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -6.453852113757118e-2,
+                               unseen = -3.7612001156935624,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.540445040947149),
+                                    ("month (grain)", -3.044522437723423),
+                                    ("year (grain)", -2.639057329615259),
+                                    ("second", -3.044522437723423),
+                                    ("week (grain)", -1.540445040947149),
+                                    ("quarter", -2.3513752571634776), ("year", -2.639057329615259),
+                                    ("second (grain)", -3.044522437723423),
+                                    ("month", -3.044522437723423),
+                                    ("quarter (grain)", -2.3513752571634776)],
+                               n = 15},
+                   koData =
+                     ClassData{prior = -2.772588722239781, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minute (grain)", -1.9459101490553135),
+                                    ("minute", -1.9459101490553135)],
+                               n = 1}}),
+       ("number.number hours",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("from <time-of-day> - <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.6094379124341003),
+                                    ("<time-of-day> rano<time-of-day> rano", -2.0149030205422647),
+                                    ("hh:mmhh:mm", -1.6094379124341003),
+                                    ("hourhour", -1.6094379124341003),
+                                    ("<time-of-day> rano<integer> (latent time-of-day)",
+                                     -2.0149030205422647)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minutehour", -1.2992829841302609),
+                                    ("hh:mm<integer> (latent time-of-day)", -1.2992829841302609)],
+                               n = 2}}),
+       ("integer 21..99",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods =
+                                 HashMap.fromList [("integer (numeric)integer (numeric)", 0.0)],
+                               n = 1}}),
+       ("yyyy-mm-dd",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (latent)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -3.295836866004329,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -8.004270767353637e-2),
+                                    ("one", -2.5649493574615367)],
+                               n = 24}}),
+       ("mm/dd/yyyy",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("evening|night",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1780538303479458,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 22},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("third ordinal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 19},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<ordinal> quarter <year>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("quarteryear", -0.6931471805599453),
+                                    ("ordinal (digits)quarter (grain)year", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh:mm:ss",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect by \",\" 2",
+        Classifier{okData =
+                     ClassData{prior = -0.46430560813109784, unseen = -4.74493212836325,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect by \",\"year", -4.04305126783455),
+                                    ("intersect by \",\" 2intersect", -4.04305126783455),
+                                    ("dayday", -1.4781019103730135),
+                                    ("named-dayintersect by \",\"", -3.6375861597263857),
+                                    ("intersect<named-month> <day-of-month> (non ordinal)",
+                                     -3.349904087274605),
+                                    ("intersect<day-of-month> (non ordinal) <named-month>",
+                                     -3.349904087274605),
+                                    ("dayyear", -2.9444389791664407),
+                                    ("<named-month> <day-of-month> (non ordinal)intersect",
+                                     -4.04305126783455),
+                                    ("named-day<day-of-month> (non ordinal) <named-month>",
+                                     -2.538973871058276),
+                                    ("named-day<named-month> <day-of-month> (non ordinal)",
+                                     -2.6567569067146595),
+                                    ("intersect by \",\"intersect", -4.04305126783455),
+                                    ("named-dayintersect", -3.6375861597263857),
+                                    ("intersect by \",\" 2year", -4.04305126783455),
+                                    ("named-dayintersect by \",\" 2", -3.6375861597263857),
+                                    ("dayminute", -2.538973871058276),
+                                    ("intersectyear", -4.04305126783455),
+                                    ("minuteday", -2.790288299339182),
+                                    ("intersectintersect", -4.04305126783455),
+                                    ("named-day<day-of-month>(ordinal) <named-month>",
+                                     -2.538973871058276),
+                                    ("<named-month> <day-of-month> (non ordinal)year",
+                                     -3.6375861597263857)],
+                               n = 44},
+                   koData =
+                     ClassData{prior = -0.9903987040278769,
+                               unseen = -4.3694478524670215,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -2.277267285009756),
+                                    ("dayhour", -1.5234954826333758),
+                                    ("daymonth", -2.277267285009756),
+                                    ("intersectnamed-month", -2.9704144655697013),
+                                    ("minutemonth", -2.9704144655697013),
+                                    ("named-dayintersect", -2.159484249353372),
+                                    ("named-day<ordinal> (as hour)", -2.159484249353372)],
+                               n = 26}}),
+       ("16th ordinal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("quarter to|till|before <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<ordinal> (as hour)", -1.791759469228055),
+                                    ("<integer> (latent time-of-day)", -1.791759469228055),
+                                    ("noon", -1.3862943611198906), ("hour", -0.8754687373538999)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> (latent time-of-day)",
+        Classifier{okData =
+                     ClassData{prior = -0.505548566665147, unseen = -3.7376696182833684,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -7.598590697792199e-2),
+                                    ("fifteen", -3.0204248861443626)],
+                               n = 38},
+                   koData =
+                     ClassData{prior = -0.924258901523332, unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.15415067982725836),
+                                    ("one", -2.639057329615259), ("fifteen", -2.639057329615259)],
+                               n = 25}}),
+       ("nth <time> of <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("second ordinalnamed-dayintersect", -2.0149030205422647),
+                                    ("daymonth", -1.0986122886681098),
+                                    ("third ordinalnamed-dayintersect", -2.0149030205422647),
+                                    ("first ordinalnamed-daynamed-month", -2.0149030205422647),
+                                    ("first ordinalnamed-dayintersect", -2.0149030205422647)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.8472978603872037, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("third ordinalnamed-daynamed-month", -1.8718021769015913),
+                                    ("daymonth", -1.1786549963416462),
+                                    ("second ordinalnamed-daynamed-month", -1.8718021769015913),
+                                    ("first ordinalnamed-daynamed-month", -1.8718021769015913)],
+                               n = 3}}),
+       ("named-month",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.51085950651685,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 89},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("18th ordinal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("week (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.784189633918261,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 42},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("fifth ordinal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("valentine's day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <day-of-week> <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.791759469228055),
+                                    ("daymonth", -0.9444616088408514),
+                                    ("named-dayintersect", -1.791759469228055),
+                                    ("named-dayon <date>", -1.791759469228055)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-dayin <duration>", -1.3862943611198906),
+                                    ("dayminute", -1.3862943611198906)],
+                               n = 1}}),
+       ("now",
+        Classifier{okData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -0.916290731874155, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("<unit-of-duration> as a duration",
+        Classifier{okData =
+                     ClassData{prior = -2.3608540011180215,
+                               unseen = -3.8501476017100584,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6314168191528755),
+                                    ("hour (grain)", -2.7300291078209855),
+                                    ("second", -2.4423470353692043),
+                                    ("week (grain)", -1.6314168191528755),
+                                    ("day", -3.1354942159291497),
+                                    ("minute (grain)", -3.1354942159291497),
+                                    ("second (grain)", -2.4423470353692043),
+                                    ("hour", -2.7300291078209855), ("minute", -3.1354942159291497),
+                                    ("day (grain)", -3.1354942159291497)],
+                               n = 15},
+                   koData =
+                     ClassData{prior = -9.909090264423089e-2,
+                               unseen = -5.720311776607412,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.161679639916808),
+                                    ("month (grain)", -3.2321210516182215),
+                                    ("hour (grain)", -2.7212954278522306),
+                                    ("year (grain)", -2.8838143573500057),
+                                    ("second", -2.8838143573500057),
+                                    ("week (grain)", -2.161679639916808),
+                                    ("day", -2.498151876538021), ("quarter", -3.5198031240700023),
+                                    ("minute (grain)", -2.8838143573500057),
+                                    ("year", -2.8838143573500057),
+                                    ("second (grain)", -2.8838143573500057),
+                                    ("hour", -2.7212954278522306), ("month", -3.2321210516182215),
+                                    ("quarter (grain)", -3.5198031240700023),
+                                    ("minute", -2.8838143573500057),
+                                    ("day (grain)", -2.498151876538021)],
+                               n = 144}}),
+       ("this <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("on <date>", -1.5040773967762742),
+                                    ("evening|night", -1.0986122886681098),
+                                    ("hour", -0.8109302162163288)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("christmas eve",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month>(ordinal) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.19671029424605427,
+                               unseen = -4.007333185232471,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("third ordinalnamed-month", -3.295836866004329),
+                                    ("8th ordinalnamed-month", -2.890371757896165),
+                                    ("first ordinalnamed-month", -2.890371757896165),
+                                    ("15th ordinalnamed-month", -2.890371757896165),
+                                    ("ordinal (digits)named-month", -1.2163953243244932),
+                                    ("month", -0.8109302162163288),
+                                    ("13th ordinalnamed-month", -3.295836866004329)],
+                               n = 23},
+                   koData =
+                     ClassData{prior = -1.7227665977411035,
+                               unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("14th ordinalnamed-month", -1.791759469228055),
+                                    ("ordinal (digits)named-month", -1.5040773967762742),
+                                    ("month", -1.0986122886681098)],
+                               n = 5}}),
+       ("<duration> hence",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -3.295836866004329,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.3121863889661687),
+                                    ("<unit-of-duration> as a duration", -1.8718021769015913),
+                                    ("day", -2.159484249353372), ("year", -2.5649493574615367),
+                                    ("<integer> <unit-of-duration>", -1.1786549963416462),
+                                    ("month", -2.5649493574615367)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -1.0986122886681098, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.0794415416798357),
+                                    ("<unit-of-duration> as a duration", -0.9808292530117262),
+                                    ("day", -1.6739764335716716), ("year", -2.0794415416798357),
+                                    ("month", -2.0794415416798357)],
+                               n = 5}}),
+       ("numbers prefix with -, negative or minus",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 11}}),
+       ("new year's eve",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("tomorrow",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3}}),
+       ("<cycle> after <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day (grain)tomorrow", -0.6931471805599453),
+                                    ("dayday", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("Mother's Day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("half to|till|before <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<ordinal> (as hour)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> after next",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -1.3862943611198906),
+                                    ("day", -1.3862943611198906),
+                                    ("named-day", -1.3862943611198906),
+                                    ("month", -1.3862943611198906)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("two",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("by <time>",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -3.6635616461296463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> <part-of-day>", -2.9444389791664407),
+                                    ("<ordinal> (as hour)", -2.538973871058276),
+                                    ("year (latent)", -2.9444389791664407),
+                                    ("<integer> (latent time-of-day)", -2.0281482472922856),
+                                    ("day", -2.538973871058276), ("year", -2.9444389791664407),
+                                    ("hh:mm", -2.9444389791664407),
+                                    ("<day-of-month> (ordinal)", -2.538973871058276),
+                                    ("noon", -2.9444389791664407),
+                                    ("<time-of-day> rano", -2.9444389791664407),
+                                    ("hour", -1.3350010667323402), ("minute", -2.9444389791664407)],
+                               n = 13}}),
+       ("seventh ordinal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("half an hour",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("one",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("afternoon",
+        Classifier{okData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -0.916290731874155, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4}}),
+       ("<duration> from now",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("second", -1.9459101490553135),
+                                    ("<unit-of-duration> as a duration", -1.540445040947149),
+                                    ("day", -1.9459101490553135), ("year", -1.9459101490553135),
+                                    ("<integer> <unit-of-duration>", -1.540445040947149),
+                                    ("minute", -1.9459101490553135)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<unit-of-duration> as a duration", -1.2039728043259361),
+                                    ("year", -1.6094379124341003), ("minute", -1.6094379124341003)],
+                               n = 2}}),
+       ("this <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.8066624897703196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.5869650565820417),
+                                    ("year (grain)", -1.9924301646902063),
+                                    ("week (grain)", -1.5869650565820417),
+                                    ("day", -2.6855773452501515), ("quarter", -2.3978952727983707),
+                                    ("year", -1.9924301646902063),
+                                    ("quarter (grain)", -2.3978952727983707),
+                                    ("day (grain)", -2.6855773452501515)],
+                               n = 18},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("minute (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.3483066942682157, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12},
+                   koData =
+                     ClassData{prior = -1.2237754316221157,
+                               unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5}}),
+       ("last <cycle> <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.540445040947149),
+                                    ("week (grain)named-month", -2.639057329615259),
+                                    ("day (grain)intersect", -2.2335922215070942),
+                                    ("day (grain)on <date>", -2.2335922215070942),
+                                    ("weekmonth", -1.540445040947149),
+                                    ("day (grain)named-month", -2.639057329615259),
+                                    ("week (grain)intersect", -2.2335922215070942),
+                                    ("week (grain)on <date>", -2.2335922215070942)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("about <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.11778303565638351,
+                               unseen = -3.1780538303479458,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -2.4423470353692043),
+                                    ("<ordinal> (as hour)", -1.5260563034950494),
+                                    ("<integer> (latent time-of-day)", -2.03688192726104),
+                                    ("hour", -0.9382696385929302),
+                                    ("<time-of-day> popo\322udniu/wieczorem/w nocy",
+                                     -2.4423470353692043)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -2.1972245773362196,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("relative minutes after|past <integer> (hour-of-day)",
+                                     -1.5040773967762742),
+                                    ("minute", -1.5040773967762742)],
+                               n = 1}}),
+       ("year",
+        Classifier{okData =
+                     ClassData{prior = -0.14842000511827333,
+                               unseen = -3.295836866004329,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 25},
+                   koData =
+                     ClassData{prior = -1.9810014688665833, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 4}}),
+       ("last <day-of-week> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.252762968495368),
+                                    ("daymonth", -0.8472978603872037),
+                                    ("named-dayintersect", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.7404000654104909, unseen = -4.59511985013459,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.2823823856765264),
+                                    ("threemonth (grain)", -3.4863551900024623),
+                                    ("fifteenminute (grain)", -3.891820298110627),
+                                    ("a fewhour (grain)", -3.891820298110627),
+                                    ("integer (numeric)day (grain)", -2.793208009442517),
+                                    ("twoweek (grain)", -3.891820298110627),
+                                    ("fiveday (grain)", -3.891820298110627),
+                                    ("oneweek (grain)", -3.1986731175506815),
+                                    ("oneminute (grain)", -3.891820298110627),
+                                    ("integer (numeric)year (grain)", -3.891820298110627),
+                                    ("day", -2.505525936990736), ("year", -3.1986731175506815),
+                                    ("integer (numeric)week (grain)", -3.1986731175506815),
+                                    ("oneday (grain)", -3.891820298110627),
+                                    ("hour", -3.1986731175506815), ("month", -3.4863551900024623),
+                                    ("threeweek (grain)", -3.4863551900024623),
+                                    ("integer (numeric)minute (grain)", -2.793208009442517),
+                                    ("minute", -2.505525936990736),
+                                    ("integer (numeric)hour (grain)", -3.4863551900024623),
+                                    ("twoyear (grain)", -3.4863551900024623)],
+                               n = 31},
+                   koData =
+                     ClassData{prior = -0.6480267452794757, unseen = -4.653960350157523,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.8526314299133175),
+                                    ("threemonth (grain)", -3.951243718581427),
+                                    ("threehour (grain)", -3.951243718581427),
+                                    ("integer (numeric)day (grain)", -3.258096538021482),
+                                    ("twoweek (grain)", -3.951243718581427),
+                                    ("twominute (grain)", -3.951243718581427),
+                                    ("second", -2.6984807500860595),
+                                    ("threeday (grain)", -3.951243718581427),
+                                    ("threeyear (grain)", -3.951243718581427),
+                                    ("integer (numeric)second (grain)", -3.0349529867072724),
+                                    ("twomonth (grain)", -3.951243718581427),
+                                    ("onehour (grain)", -3.951243718581427),
+                                    ("integer (numeric)year (grain)", -3.545778610473263),
+                                    ("threesecond (grain)", -3.951243718581427),
+                                    ("day", -2.6984807500860595), ("year", -3.0349529867072724),
+                                    ("threeminute (grain)", -3.951243718581427),
+                                    ("integer (numeric)week (grain)", -3.258096538021482),
+                                    ("twoday (grain)", -3.951243718581427),
+                                    ("hour", -2.6984807500860595), ("month", -3.258096538021482),
+                                    ("threeweek (grain)", -3.951243718581427),
+                                    ("integer (numeric)minute (grain)", -3.545778610473263),
+                                    ("a fewday (grain)", -3.951243718581427),
+                                    ("integer (numeric)month (grain)", -3.951243718581427),
+                                    ("minute", -3.0349529867072724),
+                                    ("twosecond (grain)", -3.951243718581427),
+                                    ("integer (numeric)hour (grain)", -3.258096538021482),
+                                    ("fifteenhour (grain)", -3.951243718581427),
+                                    ("twoyear (grain)", -3.951243718581427)],
+                               n = 34}}),
+       ("19th ordinal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("thanksgiving day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<duration> after <time>",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayday", -0.6931471805599453),
+                                    ("<unit-of-duration> as a durationtomorrow",
+                                     -0.6931471805599453)],
+                               n = 1}}),
+       ("relative minutes after|past <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)<integer> (latent time-of-day)",
+                                     -1.7047480922384253),
+                                    ("fifteen<ordinal> (as hour)", -1.7047480922384253),
+                                    ("hour", -1.0116009116784799),
+                                    ("integer (numeric)<ordinal> (as hour)", -1.7047480922384253)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -0.916290731874155, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)noon", -1.0986122886681098),
+                                    ("hour", -1.0986122886681098)],
+                               n = 2}}),
+       ("intersect by \",\"",
+        Classifier{okData =
+                     ClassData{prior = -0.46430560813109784, unseen = -4.74493212836325,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect by \",\"year", -4.04305126783455),
+                                    ("intersect by \",\" 2intersect", -4.04305126783455),
+                                    ("dayday", -1.4781019103730135),
+                                    ("named-dayintersect by \",\"", -3.6375861597263857),
+                                    ("intersect<named-month> <day-of-month> (non ordinal)",
+                                     -3.349904087274605),
+                                    ("intersect<day-of-month> (non ordinal) <named-month>",
+                                     -3.349904087274605),
+                                    ("dayyear", -2.9444389791664407),
+                                    ("<named-month> <day-of-month> (non ordinal)intersect",
+                                     -4.04305126783455),
+                                    ("named-day<day-of-month> (non ordinal) <named-month>",
+                                     -2.538973871058276),
+                                    ("named-day<named-month> <day-of-month> (non ordinal)",
+                                     -2.6567569067146595),
+                                    ("intersect by \",\"intersect", -4.04305126783455),
+                                    ("named-dayintersect", -3.6375861597263857),
+                                    ("intersect by \",\" 2year", -4.04305126783455),
+                                    ("named-dayintersect by \",\" 2", -3.6375861597263857),
+                                    ("dayminute", -2.538973871058276),
+                                    ("intersectyear", -4.04305126783455),
+                                    ("minuteday", -2.790288299339182),
+                                    ("intersectintersect", -4.04305126783455),
+                                    ("named-day<day-of-month>(ordinal) <named-month>",
+                                     -2.538973871058276),
+                                    ("<named-month> <day-of-month> (non ordinal)year",
+                                     -3.6375861597263857)],
+                               n = 44},
+                   koData =
+                     ClassData{prior = -0.9903987040278769,
+                               unseen = -4.3694478524670215,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -2.277267285009756),
+                                    ("dayhour", -1.5234954826333758),
+                                    ("daymonth", -2.277267285009756),
+                                    ("intersectnamed-month", -2.9704144655697013),
+                                    ("minutemonth", -2.9704144655697013),
+                                    ("named-dayintersect", -2.159484249353372),
+                                    ("named-day<ordinal> (as hour)", -2.159484249353372)],
+                               n = 26}}),
+       ("hh:mm",
+        Classifier{okData =
+                     ClassData{prior = -4.652001563489282e-2,
+                               unseen = -3.1354942159291497,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 21},
+                   koData =
+                     ClassData{prior = -3.0910424533583156,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("quarter after|past <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("14th ordinal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("21-29th ordinal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("second ordinal", -0.6931471805599453),
+                                    ("first ordinal", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("named-day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.584967478670572,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 96},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<duration> before <time>",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayday", -0.6931471805599453),
+                                    ("<unit-of-duration> as a durationyesterday",
+                                     -0.6931471805599453)],
+                               n = 1}}),
+       ("second (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.9985288301111273,
+                               unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -0.4595323293784402, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12}}),
+       ("13th ordinal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect by \"of\", \"from\", \"'s\"",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daylast <cycle>", -0.6931471805599453),
+                                    ("dayweek", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<duration> ago",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -3.295836866004329,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.3121863889661687),
+                                    ("<unit-of-duration> as a duration", -1.8718021769015913),
+                                    ("day", -2.159484249353372), ("year", -2.5649493574615367),
+                                    ("<integer> <unit-of-duration>", -1.1786549963416462),
+                                    ("month", -2.5649493574615367)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -1.0986122886681098, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.0794415416798357),
+                                    ("<unit-of-duration> as a duration", -0.9808292530117262),
+                                    ("day", -1.6739764335716716), ("year", -2.0794415416798357),
+                                    ("month", -2.0794415416798357)],
+                               n = 5}}),
+       ("last <time>",
+        Classifier{okData =
+                     ClassData{prior = -1.6094379124341003, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.1631508098056809),
+                                    ("named-day", -1.1631508098056809)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.2231435513142097, unseen = -3.713572066704308,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day> o'clock", -2.5902671654458267),
+                                    ("intersect", -2.3025850929940455),
+                                    ("year (latent)", -2.0794415416798357),
+                                    ("<integer> (latent time-of-day)", -2.0794415416798357),
+                                    ("day", -1.742969305058623), ("year", -2.0794415416798357),
+                                    ("named-day", -2.3025850929940455),
+                                    ("hour", -1.742969305058623)],
+                               n = 16}}),
+       ("sixth ordinal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month> (ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("11th ordinal", -2.2335922215070942),
+                                    ("8th ordinal", -2.639057329615259),
+                                    ("third ordinal", -2.2335922215070942),
+                                    ("16th ordinal", -2.639057329615259),
+                                    ("second ordinal", -1.7227665977411035),
+                                    ("ordinal (digits)", -2.2335922215070942),
+                                    ("10th ordinal", -1.7227665977411035),
+                                    ("9th ordinal", -1.7227665977411035)],
+                               n = 20}}),
+       ("noon",
+        Classifier{okData =
+                     ClassData{prior = -1.791759469228055, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -0.1823215567939546,
+                               unseen = -2.4849066497880004,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 10}}),
+       ("until <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -3.6375861597263857,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> <part-of-day>", -2.2246235515243336),
+                                    ("<ordinal> (as hour)", -2.512305623976115),
+                                    ("<integer> (latent time-of-day)", -2.512305623976115),
+                                    ("<time-of-day> rano", -2.512305623976115),
+                                    ("hour", -1.213022639845854),
+                                    ("<time-of-day> popo\322udniu/wieczorem/w nocy",
+                                     -2.917770732084279)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -4.060443010546419,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> <part-of-day>", -2.2512917986064953),
+                                    ("<ordinal> (as hour)", -2.9444389791664407),
+                                    ("year (latent)", -2.6567569067146595),
+                                    ("yesterday", -3.349904087274605),
+                                    ("<integer> (latent time-of-day)", -3.349904087274605),
+                                    ("day", -2.9444389791664407), ("year", -2.6567569067146595),
+                                    ("hh:mm", -3.349904087274605),
+                                    ("<day-of-month> (ordinal)", -3.349904087274605),
+                                    ("noon", -2.9444389791664407),
+                                    ("<time-of-day> rano", -3.349904087274605),
+                                    ("hour", -1.3350010667323402),
+                                    ("<datetime> - <datetime> (interval)", -3.349904087274605),
+                                    ("<time-of-day> - <time-of-day> (interval)",
+                                     -3.349904087274605),
+                                    ("<hour-of-day> - <hour-of-day> (interval)",
+                                     -3.349904087274605),
+                                    ("minute", -3.349904087274605)],
+                               n = 20}}),
+       ("<integer> and an half hours",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> rano",
+        Classifier{okData =
+                     ClassData{prior = -0.10008345855698253,
+                               unseen = -3.828641396489095,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -2.0149030205422647),
+                                    ("<ordinal> (as hour)", -2.70805020110221),
+                                    ("<integer> (latent time-of-day)", -1.5040773967762742),
+                                    ("hh:mm", -3.1135153092103742),
+                                    ("until <time-of-day>", -2.70805020110221),
+                                    ("hour", -0.8622235106038793), ("minute", -3.1135153092103742)],
+                               n = 19},
+                   koData =
+                     ClassData{prior = -2.3513752571634776,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)", -1.7047480922384253),
+                                    ("until <time-of-day>", -1.7047480922384253),
+                                    ("hour", -1.2992829841302609)],
+                               n = 2}}),
+       ("after <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("<integer> <unit-of-duration>", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("decimal number",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("next <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -2.4849066497880004),
+                                    ("day", -0.8754687373538999),
+                                    ("named-day", -0.8754687373538999),
+                                    ("month", -2.4849066497880004)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -3.4965075614664802,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6739764335716716),
+                                    ("month (grain)", -1.6739764335716716),
+                                    ("year (grain)", -2.367123614131617),
+                                    ("week (grain)", -1.6739764335716716),
+                                    ("year", -2.367123614131617), ("month", -1.6739764335716716)],
+                               n = 12},
+                   koData =
+                     ClassData{prior = -1.3862943611198906, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6739764335716716),
+                                    ("week (grain)", -1.6739764335716716),
+                                    ("day", -1.6739764335716716),
+                                    ("day (grain)", -1.6739764335716716)],
+                               n = 4}}),
+       ("christmas",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("next n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.912023005428146,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.793208009442517),
+                                    ("threemonth (grain)", -3.1986731175506815),
+                                    ("threehour (grain)", -3.1986731175506815),
+                                    ("integer (numeric)day (grain)", -3.1986731175506815),
+                                    ("second", -2.793208009442517),
+                                    ("threeday (grain)", -3.1986731175506815),
+                                    ("threeyear (grain)", -3.1986731175506815),
+                                    ("integer (numeric)second (grain)", -3.1986731175506815),
+                                    ("integer (numeric)year (grain)", -3.1986731175506815),
+                                    ("threesecond (grain)", -3.1986731175506815),
+                                    ("day", -2.505525936990736), ("year", -2.793208009442517),
+                                    ("threeminute (grain)", -3.1986731175506815),
+                                    ("integer (numeric)week (grain)", -3.1986731175506815),
+                                    ("hour", -2.793208009442517), ("month", -3.1986731175506815),
+                                    ("threeweek (grain)", -3.1986731175506815),
+                                    ("integer (numeric)minute (grain)", -3.1986731175506815),
+                                    ("a fewday (grain)", -3.1986731175506815),
+                                    ("minute", -2.793208009442517),
+                                    ("integer (numeric)hour (grain)", -3.1986731175506815)],
+                               n = 14},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -3.0910424533583156,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("15th ordinal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("halloween day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("by the end of <time>",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -3.6635616461296463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> <part-of-day>", -2.9444389791664407),
+                                    ("<ordinal> (as hour)", -2.538973871058276),
+                                    ("year (latent)", -2.9444389791664407),
+                                    ("<integer> (latent time-of-day)", -2.0281482472922856),
+                                    ("day", -2.538973871058276), ("year", -2.9444389791664407),
+                                    ("hh:mm", -2.9444389791664407),
+                                    ("<day-of-month> (ordinal)", -2.538973871058276),
+                                    ("noon", -2.9444389791664407),
+                                    ("<time-of-day> rano", -2.9444389791664407),
+                                    ("hour", -1.3350010667323402), ("minute", -2.9444389791664407)],
+                               n = 13}}),
+       ("in <duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.2231435513142097, unseen = -4.0943445622221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.691243082785829),
+                                    ("number.number hours", -3.3843902633457743),
+                                    ("second", -2.9789251552376097),
+                                    ("<unit-of-duration> as a duration", -1.9980959022258835),
+                                    ("day", -2.9789251552376097),
+                                    ("half an hour", -3.3843902633457743),
+                                    ("<integer> <unit-of-duration>", -1.5125880864441827),
+                                    ("<integer> and an half hours", -3.3843902633457743),
+                                    ("hour", -2.2857779746776643), ("minute", -1.5125880864441827),
+                                    ("about <duration>", -2.9789251552376097)],
+                               n = 24},
+                   koData =
+                     ClassData{prior = -1.6094379124341003,
+                               unseen = -3.1780538303479458,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.749199854809259), ("second", -2.03688192726104),
+                                    ("<unit-of-duration> as a duration", -1.5260563034950494),
+                                    ("<integer> <unit-of-duration>", -2.03688192726104),
+                                    ("minute", -2.4423470353692043)],
+                               n = 6}}),
+       ("<datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -1.6739764335716716,
+                               unseen = -3.4657359027997265,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.824549292051046),
+                                    ("hh:mmhh:mm", -1.824549292051046),
+                                    ("dayday", -2.3353749158170367),
+                                    ("<named-month> <day-of-month> (non ordinal)<named-month> <day-of-month> (non ordinal)",
+                                     -2.3353749158170367)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.2076393647782445, unseen = -4.276666119016055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -3.164067588373206),
+                                    ("about <time-of-day>noon", -3.56953269648137),
+                                    ("minuteminute", -2.4709204078132605),
+                                    ("<time> <part-of-day><time> <part-of-day>",
+                                     -2.8763855159214247),
+                                    ("<time-of-day> rano<time-of-day> rano", -3.56953269648137),
+                                    ("until <time-of-day>noon", -3.56953269648137),
+                                    ("hh:mmhh:mm", -3.56953269648137),
+                                    ("hourhour", -1.3723081191451507),
+                                    ("year<hour-of-day> <integer> (as relative minutes)",
+                                     -3.164067588373206),
+                                    ("after <time-of-day>noon", -3.164067588373206),
+                                    ("hh:mmintersect", -2.653241964607215),
+                                    ("<time> <part-of-day><time-of-day> rano", -3.164067588373206),
+                                    ("at <time-of-day>noon", -3.56953269648137),
+                                    ("<time-of-day> rano<time> <part-of-day>", -3.56953269648137),
+                                    ("<ordinal> (as hour)noon", -2.4709204078132605),
+                                    ("<named-month> <day-of-month> (non ordinal)named-month",
+                                     -3.164067588373206),
+                                    ("yearminute", -3.164067588373206)],
+                               n = 26}}),
+       ("second ordinal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> popo\322udniu/wieczorem/w nocy",
+        Classifier{okData =
+                     ClassData{prior = -1.5037877364540559e-2,
+                               unseen = -4.962844630259907,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("exactly <time-of-day>", -4.2626798770413155),
+                                    ("at <time-of-day>", -2.4709204078132605),
+                                    ("<ordinal> (as hour)", -1.5218398531161146),
+                                    ("<integer> (latent time-of-day)", -2.01138807843482),
+                                    ("about <time-of-day>", -4.2626798770413155),
+                                    ("hh:mm", -3.857214768933151),
+                                    ("until <time-of-day>", -4.2626798770413155),
+                                    ("hour", -0.812692331209728), ("minute", -3.3463891451671604),
+                                    ("after <time-of-day>", -3.857214768933151)],
+                               n = 66},
+                   koData =
+                     ClassData{prior = -4.204692619390966, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.791759469228055),
+                                    ("hour", -1.791759469228055)],
+                               n = 1}}),
+       ("fifteen",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> - <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -1.749199854809259, unseen = -3.0910424533583156,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.4350845252893227),
+                                    ("hh:mmhh:mm", -1.4350845252893227)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.19105523676270922,
+                               unseen = -3.951243718581427,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("about <time-of-day>noon", -3.2386784521643803),
+                                    ("relative minutes to|till|before <integer> (hour-of-day)<integer> (latent time-of-day)",
+                                     -3.2386784521643803),
+                                    ("minuteminute", -3.2386784521643803),
+                                    ("<time-of-day> rano<time-of-day> rano", -3.2386784521643803),
+                                    ("until <time-of-day>noon", -3.2386784521643803),
+                                    ("hh:mmhh:mm", -3.2386784521643803),
+                                    ("hourhour", -1.3668762752627892),
+                                    ("minutehour", -1.9859154836690123),
+                                    ("after <time-of-day>noon", -2.833213344056216),
+                                    ("at <time-of-day>noon", -3.2386784521643803),
+                                    ("<ordinal> (as hour)noon", -2.1400661634962708),
+                                    ("<time-of-day> rano<integer> (latent time-of-day)",
+                                     -3.2386784521643803),
+                                    ("hh:mm<integer> (latent time-of-day)", -2.1400661634962708)],
+                               n = 19}}),
+       ("<hour-of-day> - <hour-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -1.6094379124341003, unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.3350010667323402),
+                                    ("hh:mmhh:mm", -1.3350010667323402)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.2231435513142097, unseen = -3.784189633918261,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("about <time-of-day>noon", -3.068052935133617),
+                                    ("minuteminute", -3.068052935133617),
+                                    ("<time-of-day> rano<time-of-day> rano", -3.068052935133617),
+                                    ("until <time-of-day>noon", -3.068052935133617),
+                                    ("hh:mmhh:mm", -3.068052935133617),
+                                    ("hourhour", -0.9886113934537812),
+                                    ("after <time-of-day>noon", -2.662587827025453),
+                                    ("at <time-of-day>noon", -3.068052935133617),
+                                    ("<ordinal> (as hour)noon", -1.9694406464655074),
+                                    ("<integer> (latent time-of-day)noon", -2.662587827025453),
+                                    ("<integer> (latent time-of-day)<ordinal> (as hour)",
+                                     -2.662587827025453)],
+                               n = 16}}),
+       ("last n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.9889840465642745,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.583997552432231),
+                                    ("integer (numeric)day (grain)", -2.871679624884012),
+                                    ("twoweek (grain)", -3.2771447329921766),
+                                    ("twominute (grain)", -3.2771447329921766),
+                                    ("second", -2.871679624884012),
+                                    ("integer (numeric)second (grain)", -3.2771447329921766),
+                                    ("twomonth (grain)", -3.2771447329921766),
+                                    ("onehour (grain)", -3.2771447329921766),
+                                    ("integer (numeric)year (grain)", -3.2771447329921766),
+                                    ("day", -2.583997552432231), ("year", -2.871679624884012),
+                                    ("integer (numeric)week (grain)", -2.871679624884012),
+                                    ("twoday (grain)", -3.2771447329921766),
+                                    ("hour", -2.871679624884012), ("month", -2.871679624884012),
+                                    ("integer (numeric)minute (grain)", -3.2771447329921766),
+                                    ("integer (numeric)month (grain)", -3.2771447329921766),
+                                    ("minute", -2.871679624884012),
+                                    ("twosecond (grain)", -3.2771447329921766),
+                                    ("integer (numeric)hour (grain)", -3.2771447329921766),
+                                    ("twoyear (grain)", -3.2771447329921766)],
+                               n = 16},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -3.0910424533583156,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-month> <day-of-month> (non ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -0.3364722366212129,
+                               unseen = -3.7612001156935624,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-monthinteger (numeric)", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 20},
+                   koData =
+                     ClassData{prior = -1.252762968495368, unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-monthinteger (numeric)", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 8}}),
+       ("<day-of-month> (non ordinal) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.1431008436406733, unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 13},
+                   koData =
+                     ClassData{prior = -2.0149030205422647,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 2}}),
+       ("this|next <day-of-week>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("three",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinal (digits)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.4011973816621555,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 28},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("quarter (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.540445040947149),
+                                    ("week (grain)named-month", -1.9459101490553135),
+                                    ("day (grain)intersect", -1.9459101490553135),
+                                    ("weekmonth", -1.540445040947149),
+                                    ("day (grain)named-month", -1.9459101490553135),
+                                    ("week (grain)intersect", -1.9459101490553135)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month>(ordinal) <named-month> year",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("14th ordinalnamed-month", -1.791759469228055),
+                                    ("third ordinalnamed-month", -2.1972245773362196),
+                                    ("ordinal (digits)named-month", -1.2809338454620642),
+                                    ("month", -0.8109302162163288)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("morning",
+        Classifier{okData =
+                     ClassData{prior = -0.6190392084062235,
+                               unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -0.7731898882334817,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6}}),
+       ("relative minutes to|till|before <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)<integer> (latent time-of-day)",
+                                     -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 2}}),
+       ("week-end",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("10th ordinal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("after <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -1.4403615823901665,
+                               unseen = -3.4657359027997265,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> <part-of-day>", -2.3353749158170367),
+                                    ("<ordinal> (as hour)", -2.3353749158170367),
+                                    ("afternoon", -2.0476928433652555),
+                                    ("hour", -1.1314021114911006),
+                                    ("<time-of-day> popo\322udniu/wieczorem/w nocy",
+                                     -2.3353749158170367)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -0.2702903297399117, unseen = -4.276666119016055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> <part-of-day>", -3.164067588373206),
+                                    ("<ordinal> (as hour)", -2.8763855159214247),
+                                    ("intersect", -3.56953269648137),
+                                    ("tomorrow", -2.653241964607215), ("day", -2.316769727986002),
+                                    ("afternoon", -2.653241964607215),
+                                    ("<day-of-month> (ordinal)", -3.164067588373206),
+                                    ("noon", -2.1832383353614793), ("hour", -1.08462604669337),
+                                    ("<datetime> - <datetime> (interval)", -3.164067588373206),
+                                    ("<time-of-day> - <time-of-day> (interval)",
+                                     -3.164067588373206),
+                                    ("<hour-of-day> - <hour-of-day> (interval)",
+                                     -3.164067588373206)],
+                               n = 29}}),
+       ("day (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.12783337150988489,
+                               unseen = -3.1780538303479458,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 22},
+                   koData =
+                     ClassData{prior = -2.120263536200091, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3}}),
+       ("9th ordinal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("first ordinal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 10},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<month> dd-dd (interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("about <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("half an hour", -0.6931471805599453),
+                                    ("minute", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("day-after-tomorrow (single-word)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> <integer> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<ordinal> (as hour)twenty", -1.3862943611198906),
+                                    ("hour", -0.9808292530117262),
+                                    ("<ordinal> (as hour)fifteen", -1.3862943611198906)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)integer (numeric)",
+                                     -0.8754687373538999),
+                                    ("hour", -0.8754687373538999)],
+                               n = 4}}),
+       ("23rd ordinal no space",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.6286086594223742,
+                               unseen = -3.7612001156935624,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("on <date>", -3.044522437723423),
+                                    ("season", -2.128231705849268),
+                                    ("evening|night", -3.044522437723423),
+                                    ("day", -1.252762968495368), ("named-day", -1.6582280766035324),
+                                    ("hour", -1.9459101490553135),
+                                    ("week-end", -2.3513752571634776)],
+                               n = 16},
+                   koData =
+                     ClassData{prior = -0.7621400520468967,
+                               unseen = -3.6635616461296463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("on <date>", -2.9444389791664407),
+                                    ("evening|night", -2.9444389791664407),
+                                    ("named-month", -1.2396908869280152),
+                                    ("day", -2.538973871058276), ("hour", -2.538973871058276),
+                                    ("month", -1.2396908869280152),
+                                    ("<named-month> <day-of-month> (non ordinal)",
+                                     -2.538973871058276)],
+                               n = 14}}),
+       ("<named-month> <day-of-month> (ordinal)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-monthfirst ordinal", -1.791759469228055),
+                                    ("named-month15th ordinal", -1.791759469228055),
+                                    ("month", -0.8754687373538999),
+                                    ("named-monthordinal (digits)", -1.3862943611198906)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("within <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -0.6931471805599453),
+                                    ("<integer> <unit-of-duration>", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}})]
diff --git a/Duckling/Ranking/Classifiers/PT.hs b/Duckling/Ranking/Classifiers/PT.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/PT.hs
@@ -0,0 +1,1279 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.PT (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers
+  = HashMap.fromList
+      [("<cycle> (que vem)",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.540445040947149),
+                                    ("semana (grain)", -1.540445040947149),
+                                    ("mes (grain)", -1.9459101490553135),
+                                    ("year", -1.9459101490553135),
+                                    ("ano (grain)", -1.9459101490553135),
+                                    ("month", -1.9459101490553135)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.8472978603872037,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.791759469228055),
+                                    ("semana (grain)", -1.791759469228055),
+                                    ("mes (grain)", -1.791759469228055),
+                                    ("year", -1.791759469228055),
+                                    ("ano (grain)", -1.791759469228055),
+                                    ("month", -1.791759469228055)],
+                               n = 3}}),
+       ("midnight",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<dim time> da tarde",
+        Classifier{okData =
+                     ClassData{prior = -0.24686007793152578, unseen = -4.0943445622221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<hour-of-day> and half", -2.468099531471619),
+                                    ("time-of-day (latent)", -2.9789251552376097),
+                                    ("<hour-of-day> and <relative minutes>", -2.1316272948504063),
+                                    ("hour", -2.468099531471619),
+                                    ("<hour-of-day> and quinze", -2.468099531471619),
+                                    ("minute", -0.9864949905474035),
+                                    ("\224s <time-of-day>", -1.7749523509116738)],
+                               n = 25},
+                   koData =
+                     ClassData{prior = -1.5198257537444133,
+                               unseen = -3.1780538303479458,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("year (latent)", -1.3437347467010947),
+                                    ("time-of-day (latent)", -2.03688192726104),
+                                    ("year", -1.3437347467010947), ("hour", -2.03688192726104)],
+                               n = 7}}),
+       ("de <year>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> timezone",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.252762968495368),
+                                    ("hour", -0.8472978603872037),
+                                    ("\224s <time-of-day>", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (numeric)",
+        Classifier{okData =
+                     ClassData{prior = -0.6427163269330534, unseen = -4.143134726391533,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 61},
+                   koData =
+                     ClassData{prior = -0.7462570058738938, unseen = -4.04305126783455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 55}}),
+       ("the day before yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh(:|.|h)mm (time-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-month|named-day> past",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("proximo <cycle> ",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.540445040947149),
+                                    ("semana (grain)", -1.540445040947149),
+                                    ("mes (grain)", -1.9459101490553135),
+                                    ("year", -1.9459101490553135),
+                                    ("ano (grain)", -1.9459101490553135),
+                                    ("month", -1.9459101490553135)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.8472978603872037,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.791759469228055),
+                                    ("semana (grain)", -1.791759469228055),
+                                    ("mes (grain)", -1.791759469228055),
+                                    ("year", -1.791759469228055),
+                                    ("ano (grain)", -1.791759469228055),
+                                    ("month", -1.791759469228055)],
+                               n = 3}}),
+       ("dd[/-]mm",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 10},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect by `da` or `de`",
+        Classifier{okData =
+                     ClassData{prior = -0.6443570163905132, unseen = -4.31748811353631,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day of month (1st)named-month", -3.20545280453606),
+                                    ("daymonth", -3.20545280453606),
+                                    ("<day-of-month> de <named-month>two time tokens separated by \",\"2",
+                                     -3.6109179126442243),
+                                    ("<day-of-month> de <named-month>intersect",
+                                     -3.6109179126442243),
+                                    ("<day-of-month> de <named-month>two time tokens separated by \",\"",
+                                     -3.6109179126442243),
+                                    ("dayday", -2.917770732084279),
+                                    ("dayyear", -1.6650077635889111),
+                                    ("named-dayproximo <cycle> ", -3.6109179126442243),
+                                    ("dd-dd <month>(interval)year", -3.6109179126442243),
+                                    ("named-day<cycle> (que vem)", -3.6109179126442243),
+                                    ("two time tokens separated by \",\"2year", -3.20545280453606),
+                                    ("intersectyear", -3.6109179126442243),
+                                    ("<day-of-month> de <named-month>year", -2.2246235515243336),
+                                    ("two time tokens separated by \",\"year", -3.20545280453606),
+                                    ("dayweek", -2.917770732084279),
+                                    ("named-day<cycle> passado", -3.6109179126442243)],
+                               n = 21},
+                   koData =
+                     ClassData{prior = -0.7444404749474959,
+                               unseen = -4.2626798770413155,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<hour-of-day> <integer> (as relative minutes)intersect",
+                                     -3.5553480614894135),
+                                    ("monthday", -2.8622008809294686),
+                                    ("monthyear", -2.05127066471314),
+                                    ("hourmonth", -3.1498829533812494),
+                                    ("monthmonth", -3.5553480614894135),
+                                    ("dayyear", -3.5553480614894135),
+                                    ("minutemonth", -2.8622008809294686),
+                                    ("named-monthyear", -2.05127066471314),
+                                    ("de <datetime> - <datetime> (interval)named-month",
+                                     -3.5553480614894135),
+                                    ("\224s <time-of-day>named-month", -3.1498829533812494),
+                                    ("<hour-of-day> <integer> (as relative minutes)named-month",
+                                     -3.5553480614894135),
+                                    ("<day-of-month> de <named-month>year", -3.5553480614894135),
+                                    ("intersect by `da` or `de`year", -3.5553480614894135),
+                                    ("named-monthtwo time tokens separated by \",\"2",
+                                     -3.5553480614894135),
+                                    ("named-monthintersect", -3.5553480614894135),
+                                    ("<hour-of-day> <integer> (as relative minutes)intersect by `da` or `de`",
+                                     -3.5553480614894135),
+                                    ("minuteyear", -3.5553480614894135),
+                                    ("named-monthtwo time tokens separated by \",\"",
+                                     -3.5553480614894135)],
+                               n = 19}}),
+       ("<hour-of-day> and half",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.4816045409242156),
+                                    ("noon", -2.3978952727983707), ("hour", -0.7884573603642702),
+                                    ("\224s <time-of-day>", -1.4816045409242156)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("semana (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 13},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("two time tokens separated by \",\"",
+        Classifier{okData =
+                     ClassData{prior = -0.1670540846631662,
+                               unseen = -3.4965075614664802,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersectnamed-day", -2.772588722239781),
+                                    ("intersect by `da` or `de`named-day", -2.772588722239781),
+                                    ("dayday", -1.1631508098056809),
+                                    ("de <year>named-day", -2.772588722239781),
+                                    ("named-dayintersect", -2.367123614131617),
+                                    ("named-dayintersect by `da` or `de`", -2.367123614131617),
+                                    ("yearnamed-day", -2.772588722239781),
+                                    ("named-day<day-of-month> de <named-month>",
+                                     -2.0794415416798357),
+                                    ("yearday", -2.367123614131617)],
+                               n = 11},
+                   koData =
+                     ClassData{prior = -1.8718021769015913, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthday", -1.540445040947149),
+                                    ("intersectnamed-day", -1.9459101490553135),
+                                    ("intersect by `da` or `de`named-day", -1.9459101490553135)],
+                               n = 2}}),
+       ("ordinals (primeiro..10)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -3.4657359027997265,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 30}}),
+       ("<day-of-month> de <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -7.410797215372185e-2,
+                               unseen = -4.02535169073515,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("number (0..15)named-month", -1.927891643552635),
+                                    ("integer (numeric)named-month", -1.0116009116784799),
+                                    ("month", -0.7114963192281418)],
+                               n = 26},
+                   koData =
+                     ClassData{prior = -2.639057329615259, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.8472978603872037),
+                                    ("month", -0.8472978603872037)],
+                               n = 2}}),
+       ("<time-of-day> <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.33270575382573614,
+                               unseen = -4.584967478670572,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<hour-of-day> and quinzeafternoon", -2.9652730660692823),
+                                    ("<day-of-month> de <named-month>morning", -3.8815637979434374),
+                                    ("dayhour", -3.4760986898352733),
+                                    ("\224s <time-of-day>morning", -3.188416617383492),
+                                    ("hourhour", -1.8021222562636017),
+                                    ("<hour-of-day> and halfafternoon", -2.9652730660692823),
+                                    ("minutehour", -1.483668525145067),
+                                    ("time-of-day (latent)morning", -3.188416617383492),
+                                    ("time-of-day (latent)evening", -3.4760986898352733),
+                                    ("\224s <time-of-day>afternoon", -2.2721258855093374),
+                                    ("dia <day-of-month> de <named-month>morning",
+                                     -3.8815637979434374),
+                                    ("time-of-day (latent)afternoon", -3.4760986898352733),
+                                    ("<hour-of-day> and <relative minutes>afternoon",
+                                     -2.62880082944807),
+                                    ("intersectmorning", -3.8815637979434374),
+                                    ("\224s <time-of-day>evening", -3.4760986898352733)],
+                               n = 38},
+                   koData =
+                     ClassData{prior = -1.262241712449912, unseen = -3.951243718581427,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("yearhour", -1.4469189829363254),
+                                    ("monthhour", -3.2386784521643803),
+                                    ("hourhour", -2.5455312716044354),
+                                    ("time-of-day (latent)morning", -3.2386784521643803),
+                                    ("year (latent)afternoon", -2.1400661634962708),
+                                    ("named-monthmorning", -3.2386784521643803),
+                                    ("year (latent)evening", -2.833213344056216),
+                                    ("time-of-day (latent)afternoon", -2.833213344056216),
+                                    ("year (latent)morning", -2.322387720290225)],
+                               n = 15}}),
+       ("de <datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthday", -1.791759469228055),
+                                    ("monthyear", -1.791759469228055),
+                                    ("monthhour", -1.791759469228055),
+                                    ("named-monthyear (latent)", -1.791759469228055),
+                                    ("named-monthtime-of-day (latent)", -1.791759469228055),
+                                    ("named-month<day-of-month> de <named-month>",
+                                     -1.791759469228055)],
+                               n = 3}}),
+       ("<time-of-day> horas",
+        Classifier{okData =
+                     ClassData{prior = -0.7731898882334817,
+                               unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.5040773967762742),
+                                    ("hour", -0.9444616088408514),
+                                    ("depois das <time-of-day>", -1.791759469228055),
+                                    ("\224s <time-of-day>", -2.1972245773362196)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.6190392084062235, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.2039728043259361),
+                                    ("<hour-of-day> and <relative minutes>", -2.3025850929940455),
+                                    ("hour", -1.0498221244986778), ("minute", -2.3025850929940455),
+                                    ("\224s <time-of-day>", -2.3025850929940455)],
+                               n = 7}}),
+       ("intersect",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -5.062595033026967,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<day-of-month> de <named-month>in the <part-of-day>",
+                                     -3.9576335166801986),
+                                    ("dayhour", -2.4171884757330493),
+                                    ("<day-of-month> de <named-month>two time tokens separated by \",\"2",
+                                     -4.363098624788362),
+                                    ("nowquinze para as <hour-of-day> (as relative minutes)",
+                                     -4.363098624788362),
+                                    ("intersectnamed-day", -4.363098624788362),
+                                    ("now\224s <time-of-day>", -3.9576335166801986),
+                                    ("<day-of-month> de <named-month>intersect",
+                                     -4.363098624788362),
+                                    ("now<hour-of-day> and 3/4", -4.363098624788362),
+                                    ("intersect by `da` or `de`named-day", -4.363098624788362),
+                                    ("<day-of-month> de <named-month>two time tokens separated by \",\"",
+                                     -4.363098624788362),
+                                    ("dayday", -2.8590212280120886),
+                                    ("hourhour", -4.363098624788362),
+                                    ("named-day\224s <time-of-day>", -4.363098624788362),
+                                    ("dayyear", -2.348195604246098),
+                                    ("minutehour", -3.1103356562929947),
+                                    ("<hour-of-day> and quinzein the <part-of-day>",
+                                     -3.9576335166801986),
+                                    ("de <year>named-day", -4.363098624788362),
+                                    ("now<hour-of-day> and <relative minutes>", -4.363098624788362),
+                                    ("named-dayin the <part-of-day>", -4.363098624788362),
+                                    ("tomorrow<time-of-day> horas", -3.9576335166801986),
+                                    ("now<integer> para as <hour-of-day> (as relative minutes)",
+                                     -4.363098624788362),
+                                    ("\224s <time-of-day>in the <part-of-day>",
+                                     -3.6699514442284173),
+                                    ("named-dayintersect", -4.363098624788362),
+                                    ("named-dayamanh\227 pela <part-of-day>", -4.363098624788362),
+                                    ("dayminute", -3.1103356562929947),
+                                    ("named-dayintersect by `da` or `de`", -4.363098624788362),
+                                    ("yearnamed-day", -4.363098624788362),
+                                    ("dd-dd <month>(interval)de <year>", -4.363098624788362),
+                                    ("named-day<day-of-month> de <named-month>",
+                                     -4.363098624788362),
+                                    ("named-day<time-of-day> <part-of-day>", -4.363098624788362),
+                                    ("dia <day-of-month> de <named-month>in the <part-of-day>",
+                                     -3.9576335166801986),
+                                    ("yearday", -3.9576335166801986),
+                                    ("named-day<dim time> da manha", -4.363098624788362),
+                                    ("two time tokens separated by \",\"de <year>",
+                                     -3.9576335166801986),
+                                    ("dd[/-]mmyear", -4.363098624788362),
+                                    ("<day-of-month> de <named-month>de <year>",
+                                     -2.976804263668472),
+                                    ("tomorrowdepois das <time-of-day>", -3.9576335166801986),
+                                    ("<hour-of-day> and <relative minutes>in the <part-of-day>",
+                                     -3.9576335166801986),
+                                    ("two time tokens separated by \",\"2de <year>",
+                                     -3.9576335166801986),
+                                    ("intersectde <year>", -4.363098624788362)],
+                               n = 50},
+                   koData =
+                     ClassData{prior = -1.0986122886681098, unseen = -4.68213122712422,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hourday", -3.979681653901961),
+                                    ("dayhour", -3.5742165457937967),
+                                    ("monthday", -2.8810693652338513),
+                                    ("named-month\224s <time-of-day>", -3.979681653901961),
+                                    ("monthyear", -2.4756042571256867),
+                                    ("intersectnamed-day", -3.979681653901961),
+                                    ("now\224s <time-of-day>", -3.5742165457937967),
+                                    ("<time-of-day> am|pm<day-of-month> de <named-month>",
+                                     -3.979681653901961),
+                                    ("intersect by `da` or `de`named-day", -3.979681653901961),
+                                    ("monthhour", -3.2865344733420154),
+                                    ("dayyear", -3.979681653901961),
+                                    ("now<hour-of-day> and <relative minutes>", -3.979681653901961),
+                                    ("daysecond", -3.979681653901961),
+                                    ("named-dayright now", -3.979681653901961),
+                                    ("dayminute", -3.5742165457937967),
+                                    ("named-monthde <year>", -2.4756042571256867),
+                                    ("named-monthin the <part-of-day>", -3.5742165457937967),
+                                    ("named-monthtwo time tokens separated by \",\"2",
+                                     -3.979681653901961),
+                                    ("intersect by `da` or `de`de <year>", -3.979681653901961),
+                                    ("named-monthintersect", -3.979681653901961),
+                                    ("<day-of-month> de <named-month>\224s <time-of-day>",
+                                     -3.979681653901961),
+                                    ("<hour-of-day> <integer> (as relative minutes)year",
+                                     -3.979681653901961),
+                                    ("<day-of-month> de <named-month>de <year>",
+                                     -3.979681653901961),
+                                    ("minuteyear", -3.5742165457937967),
+                                    ("named-monthtwo time tokens separated by \",\"",
+                                     -3.979681653901961)],
+                               n = 25}}),
+       ("season",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("minutos (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("two time tokens separated by \",\"2",
+        Classifier{okData =
+                     ClassData{prior = -0.1670540846631662,
+                               unseen = -3.4965075614664802,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersectnamed-day", -2.772588722239781),
+                                    ("intersect by `da` or `de`named-day", -2.772588722239781),
+                                    ("dayday", -1.1631508098056809),
+                                    ("de <year>named-day", -2.772588722239781),
+                                    ("named-dayintersect", -2.367123614131617),
+                                    ("named-dayintersect by `da` or `de`", -2.367123614131617),
+                                    ("yearnamed-day", -2.772588722239781),
+                                    ("named-day<day-of-month> de <named-month>",
+                                     -2.0794415416798357),
+                                    ("yearday", -2.367123614131617)],
+                               n = 11},
+                   koData =
+                     ClassData{prior = -1.8718021769015913, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthday", -1.540445040947149),
+                                    ("intersectnamed-day", -1.9459101490553135),
+                                    ("intersect by `da` or `de`named-day", -1.9459101490553135)],
+                               n = 2}}),
+       ("year (latent)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -1.3217558399823195),
+                                    ("number (0..15)", -0.5108256237659907),
+                                    ("number (20..90)", -2.0149030205422647)],
+                               n = 12}}),
+       ("n[ao] <date>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dia <day-of-month> (non ordinal)", -1.252762968495368),
+                                    ("day", -0.8472978603872037),
+                                    ("named-day", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("yesterday",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("amanh\227 pela <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.3184537311185346,
+                               unseen = -4.6443908991413725,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<hour-of-day> and quinzeafternoon", -3.0252910757955354),
+                                    ("<day-of-month> de <named-month>morning", -3.9415818076696905),
+                                    ("dayhour", -3.0252910757955354),
+                                    ("\224s <time-of-day>morning", -3.248434627109745),
+                                    ("hourhour", -1.8621402659898547),
+                                    ("<hour-of-day> and halfafternoon", -3.0252910757955354),
+                                    ("minutehour", -1.54368653487132),
+                                    ("tomorrowevening", -3.9415818076696905),
+                                    ("time-of-day (latent)morning", -3.248434627109745),
+                                    ("time-of-day (latent)evening", -3.536116699561526),
+                                    ("\224s <time-of-day>afternoon", -2.33214389523559),
+                                    ("dia <day-of-month> de <named-month>morning",
+                                     -3.9415818076696905),
+                                    ("yesterdayevening", -3.9415818076696905),
+                                    ("time-of-day (latent)afternoon", -3.536116699561526),
+                                    ("<hour-of-day> and <relative minutes>afternoon",
+                                     -2.6888188391743224),
+                                    ("intersectmorning", -3.9415818076696905),
+                                    ("\224s <time-of-day>evening", -3.536116699561526)],
+                               n = 40},
+                   koData =
+                     ClassData{prior = -1.2992829841302609,
+                               unseen = -3.9889840465642745,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("yearhour", -1.4853852637641216),
+                                    ("monthhour", -3.2771447329921766),
+                                    ("hourhour", -2.583997552432231),
+                                    ("time-of-day (latent)morning", -3.2771447329921766),
+                                    ("year (latent)afternoon", -2.178532444324067),
+                                    ("named-monthmorning", -3.2771447329921766),
+                                    ("year (latent)evening", -2.871679624884012),
+                                    ("time-of-day (latent)afternoon", -2.871679624884012),
+                                    ("year (latent)morning", -2.3608540011180215)],
+                               n = 15}}),
+       ("dentro de <duration>",
+        Classifier{okData =
+                     ClassData{prior = -2.772588722239781, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> <unit-of-duration>", -1.6094379124341003),
+                                    ("hour", -1.6094379124341003)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -6.453852113757118e-2,
+                               unseen = -3.6635616461296463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.9444389791664407), ("second", -2.9444389791664407),
+                                    ("day", -2.538973871058276), ("year", -2.538973871058276),
+                                    ("<integer> <unit-of-duration>", -0.8649974374866046),
+                                    ("hour", -2.2512917986064953), ("month", -2.9444389791664407),
+                                    ("minute", -1.845826690498331)],
+                               n = 15}}),
+       ("dia <day-of-month> (non ordinal)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("named-month",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.4339872044851463,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 29},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("now",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dia <day-of-month> de <named-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.6931471805599453),
+                                    ("evening", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("number (0..15)",
+        Classifier{okData =
+                     ClassData{prior = -1.9048194970694474e-2,
+                               unseen = -3.9889840465642745,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 52},
+                   koData =
+                     ClassData{prior = -3.970291913552122, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("antes das <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("midnight", -1.2992829841302609),
+                                    ("noon", -1.2992829841302609), ("hour", -0.7884573603642702)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("numbers prefix with -, negative or minus",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 4}}),
+       ("dd-dd <month>(interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<cycle> passado",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.540445040947149),
+                                    ("semana (grain)", -1.540445040947149),
+                                    ("mes (grain)", -1.9459101490553135),
+                                    ("year", -1.9459101490553135),
+                                    ("ano (grain)", -1.9459101490553135),
+                                    ("month", -1.9459101490553135)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("tomorrow",
+        Classifier{okData =
+                     ClassData{prior = -0.3364722366212129,
+                               unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -1.252762968495368, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("mes (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> para as <hour-of-day> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)noon", -1.7047480922384253),
+                                    ("hour", -0.7884573603642702),
+                                    ("number (0..15)noon", -1.0116009116784799)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("number (20..90)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<dim time> da manha",
+        Classifier{okData =
+                     ClassData{prior = -0.2006706954621511, unseen = -3.332204510175204,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<day-of-month> de <named-month>", -2.6026896854443837),
+                                    ("intersect", -2.6026896854443837),
+                                    ("dia <day-of-month> de <named-month>", -2.6026896854443837),
+                                    ("day", -2.1972245773362196),
+                                    ("time-of-day (latent)", -1.9095425048844386),
+                                    ("hour", -1.2163953243244932),
+                                    ("\224s <time-of-day>", -1.9095425048844386)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -1.7047480922384253, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -1.8718021769015913),
+                                    ("time-of-day (latent)", -1.8718021769015913),
+                                    ("hour", -1.8718021769015913), ("month", -1.8718021769015913)],
+                               n = 2}}),
+       ("em <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.6635616461296463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.9444389791664407), ("second", -2.9444389791664407),
+                                    ("day", -2.538973871058276), ("year", -2.538973871058276),
+                                    ("<integer> <unit-of-duration>", -0.8649974374866046),
+                                    ("hour", -2.2512917986064953), ("month", -2.9444389791664407),
+                                    ("minute", -1.845826690498331)],
+                               n = 15},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("passados n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)dia (grain)", -1.791759469228055),
+                                    ("integer (numeric)mes (grain)", -1.791759469228055),
+                                    ("integer (numeric)ano (grain)", -1.791759469228055),
+                                    ("day", -1.791759469228055), ("year", -1.791759469228055),
+                                    ("month", -1.791759469228055)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("afternoon",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> and 3/4",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.2992829841302609),
+                                    ("hour", -0.7884573603642702),
+                                    ("\224s <time-of-day>", -1.2992829841302609)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("time-of-day (latent)",
+        Classifier{okData =
+                     ClassData{prior = -0.43286408229627876,
+                               unseen = -3.332204510175204,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -1.0986122886681098),
+                                    ("number (0..15)", -0.46262352194811296)],
+                               n = 24},
+                   koData =
+                     ClassData{prior = -1.0459685551826876, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.8266785731844679),
+                                    ("number (0..15)", -0.8266785731844679),
+                                    ("number (20..90)", -2.0794415416798357)],
+                               n = 13}}),
+       ("<hour-of-day> and <relative minutes>",
+        Classifier{okData =
+                     ClassData{prior = -0.2719337154836418, unseen = -3.713572066704308,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("\224s <time-of-day>number (0..15)", -2.3025850929940455),
+                                    ("time-of-day (latent)number (21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99)",
+                                     -2.5902671654458267),
+                                    ("\224s <time-of-day>number (20..90)", -2.5902671654458267),
+                                    ("\224s <time-of-day>number (21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99)",
+                                     -2.5902671654458267),
+                                    ("time-of-day (latent)number (0..15)", -2.0794415416798357),
+                                    ("hour", -0.8556661100577202),
+                                    ("time-of-day (latent)number (20..90)", -2.5902671654458267),
+                                    ("noonnumber (0..15)", -2.995732273553991)],
+                               n = 16},
+                   koData =
+                     ClassData{prior = -1.4350845252893227,
+                               unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("\224s <time-of-day>number (20..90)", -1.791759469228055),
+                                    ("time-of-day (latent)number (0..15)", -2.1972245773362196),
+                                    ("hour", -1.0986122886681098),
+                                    ("time-of-day (latent)number (20..90)", -1.791759469228055)],
+                               n = 5}}),
+       ("year",
+        Classifier{okData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -0.916290731874155, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 6}}),
+       ("<integer> <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.6443570163905132, unseen = -4.174387269895637,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.772588722239781),
+                                    ("number (0..15)ano (grain)", -3.0602707946915624),
+                                    ("integer (numeric)hora (grain)", -3.4657359027997265),
+                                    ("integer (numeric)dia (grain)", -3.4657359027997265),
+                                    ("number (0..15)segundo (grain)", -3.4657359027997265),
+                                    ("second", -3.4657359027997265),
+                                    ("integer (numeric)ano (grain)", -3.4657359027997265),
+                                    ("integer (numeric)minutos (grain)", -2.772588722239781),
+                                    ("number (21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99)hora (grain)",
+                                     -3.4657359027997265),
+                                    ("day", -3.0602707946915624), ("year", -2.772588722239781),
+                                    ("number (0..15)mes (grain)", -3.0602707946915624),
+                                    ("number (0..15)hora (grain)", -2.772588722239781),
+                                    ("hour", -2.367123614131617), ("month", -3.0602707946915624),
+                                    ("number (0..15)dia (grain)", -3.4657359027997265),
+                                    ("number (0..15)minutos (grain)", -3.0602707946915624),
+                                    ("minute", -2.367123614131617),
+                                    ("number (0..15)semana (grain)", -2.772588722239781)],
+                               n = 21},
+                   koData =
+                     ClassData{prior = -0.7444404749474959, unseen = -4.110873864173311,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.995732273553991),
+                                    ("number (0..15)ano (grain)", -3.4011973816621555),
+                                    ("integer (numeric)hora (grain)", -2.70805020110221),
+                                    ("integer (numeric)dia (grain)", -2.995732273553991),
+                                    ("integer (numeric)mes (grain)", -3.4011973816621555),
+                                    ("second", -2.995732273553991),
+                                    ("integer (numeric)semana (grain)", -3.4011973816621555),
+                                    ("integer (numeric)ano (grain)", -2.995732273553991),
+                                    ("integer (numeric)minutos (grain)", -2.995732273553991),
+                                    ("day", -2.995732273553991),
+                                    ("integer (numeric)segundo (grain)", -2.995732273553991),
+                                    ("year", -2.70805020110221),
+                                    ("number (0..15)mes (grain)", -2.995732273553991),
+                                    ("number (0..15)hora (grain)", -2.995732273553991),
+                                    ("hour", -2.3025850929940455), ("month", -2.70805020110221),
+                                    ("minute", -2.995732273553991),
+                                    ("number (0..15)semana (grain)", -3.4011973816621555)],
+                               n = 19}}),
+       ("proximas n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("number (0..15)ano (grain)", -2.4849066497880004),
+                                    ("integer (numeric)hora (grain)", -2.4849066497880004),
+                                    ("integer (numeric)dia (grain)", -2.4849066497880004),
+                                    ("second", -2.4849066497880004),
+                                    ("integer (numeric)minutos (grain)", -2.4849066497880004),
+                                    ("day", -2.4849066497880004),
+                                    ("integer (numeric)segundo (grain)", -2.4849066497880004),
+                                    ("year", -2.4849066497880004),
+                                    ("number (0..15)mes (grain)", -2.4849066497880004),
+                                    ("hour", -2.4849066497880004), ("month", -2.4849066497880004),
+                                    ("minute", -2.4849066497880004)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> am|pm",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.252762968495368),
+                                    ("hour", -0.8472978603872037),
+                                    ("\224s <time-of-day>", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -1.0986122886681098, unseen = -1.791759469228055,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.916290731874155),
+                                    ("hour", -0.916290731874155)],
+                               n = 1}}),
+       ("n proximas <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.791759469228055),
+                                    ("integer (numeric)mes (grain)", -1.791759469228055),
+                                    ("integer (numeric)semana (grain)", -1.791759469228055),
+                                    ("integer (numeric)ano (grain)", -1.791759469228055),
+                                    ("year", -1.791759469228055), ("month", -1.791759469228055)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("vespera de ano novo",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("named-day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.189654742026425,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 64},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("number (21..29 31..39 41..49 51..59 61..69 71..79 81..89 91..99)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList [("number (20..90)number (0..15)", 0.0)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("day of month (1st)",
+        Classifier{okData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4}}),
+       ("ano (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("este <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.916290731874155, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6094379124341003),
+                                    ("semana (grain)", -1.6094379124341003),
+                                    ("year", -1.6094379124341003),
+                                    ("ano (grain)", -1.6094379124341003)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.0986122886681098),
+                                    ("dia (grain)", -1.0986122886681098)],
+                               n = 3}}),
+       ("<named-month|named-day> next",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<part-of-day> dessa semana",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the day after tomorrow",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ano novo",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("dd[/-.]mm[/-.]yyyy",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("noon",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("n <cycle> (proximo|que vem)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.791759469228055),
+                                    ("integer (numeric)semana (grain)", -1.791759469228055),
+                                    ("integer (numeric)ano (grain)", -1.791759469228055),
+                                    ("year", -1.791759469228055),
+                                    ("number (0..15)mes (grain)", -1.791759469228055),
+                                    ("month", -1.791759469228055)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("evening",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> and quinze",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.3862943611198906),
+                                    ("noon", -2.3025850929940455), ("hour", -0.7985076962177716),
+                                    ("\224s <time-of-day>", -1.6094379124341003)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("depois das <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day> horas", -1.6739764335716716),
+                                    ("time-of-day (latent)", -1.6739764335716716),
+                                    ("noon", -1.6739764335716716), ("hour", -0.8266785731844679)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.916290731874155, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.9459101490553135),
+                                    ("dayday", -1.9459101490553135),
+                                    ("hh(:|.|h)mm (time-of-day)hh(:|.|h)mm (time-of-day)",
+                                     -1.9459101490553135),
+                                    ("<day-of-month> de <named-month><day-of-month> de <named-month>",
+                                     -1.9459101490553135)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.5108256237659907, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthday", -2.0794415416798357),
+                                    ("dayyear", -2.0794415416798357),
+                                    ("named-month<day-of-month> de <named-month>",
+                                     -2.0794415416798357),
+                                    ("dd[/-]mmyear", -2.0794415416798357),
+                                    ("<hour-of-day> <integer> (as relative minutes)year",
+                                     -2.0794415416798357),
+                                    ("minuteyear", -2.0794415416798357)],
+                               n = 3}}),
+       ("segundo (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dia (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -0.8472978603872037,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3}}),
+       ("in the <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("afternoon", -1.8718021769015913),
+                                    ("hour", -0.7731898882334817),
+                                    ("morning", -0.9555114450274363)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this|next <day-of-week>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("natal",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("morning",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("week-end",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hora (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3}}),
+       ("right now",
+        Classifier{okData =
+                     ClassData{prior = -0.1823215567939546,
+                               unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -1.791759469228055, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("quinze para as <hour-of-day> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("noon", -0.6931471805599453), ("hour", -0.6931471805599453)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("\224s <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.16507975035944858,
+                               unseen = -4.499809670330265,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> timezone", -3.7954891891721947),
+                                    ("<hour-of-day> and half", -2.8791984572980396),
+                                    ("<time-of-day> horas", -3.7954891891721947),
+                                    ("<hour-of-day> and 3/4", -3.39002408106403),
+                                    ("time-of-day (latent)", -1.4441139320087168),
+                                    ("<hour-of-day> and <relative minutes>", -2.409194828052304),
+                                    ("<time-of-day> am|pm", -3.7954891891721947),
+                                    ("hour", -1.3105825393841943),
+                                    ("<hour-of-day> and quinze", -3.1023420086122493),
+                                    ("minute", -1.6554230256759237)],
+                               n = 39},
+                   koData =
+                     ClassData{prior = -1.8827312474337816, unseen = -3.258096538021482,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("midnight", -2.5257286443082556),
+                                    ("<time-of-day> horas", -2.5257286443082556),
+                                    ("time-of-day (latent)", -1.8325814637483102),
+                                    ("<hour-of-day> and <relative minutes>", -2.120263536200091),
+                                    ("hour", -1.4271163556401458), ("minute", -2.120263536200091)],
+                               n = 7}}),
+       ("<hour-of-day> <integer> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)integer (numeric)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 2}}),
+       ("fazem <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6094379124341003), ("year", -2.0149030205422647),
+                                    ("<integer> <unit-of-duration>", -0.916290731874155),
+                                    ("hour", -2.0149030205422647), ("month", -2.0149030205422647)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("n <cycle> atras",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.791759469228055), ("second", -1.791759469228055),
+                                    ("integer (numeric)minutos (grain)", -1.791759469228055),
+                                    ("integer (numeric)segundo (grain)", -1.791759469228055),
+                                    ("minute", -1.791759469228055),
+                                    ("number (0..15)semana (grain)", -1.791759469228055)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.5263605246161616,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("season", -2.1102132003465894),
+                                    ("dia <day-of-month> (non ordinal)", -2.1102132003465894),
+                                    ("day", -1.1939224684724346),
+                                    ("named-day", -2.1102132003465894),
+                                    ("hour", -1.8870696490323797), ("evening", -2.3978952727983707),
+                                    ("week-end", -2.3978952727983707)],
+                               n = 13},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [], n = 0}})]
diff --git a/Duckling/Ranking/Classifiers/RO.hs b/Duckling/Ranking/Classifiers/RO.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/RO.hs
@@ -0,0 +1,265 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.RO (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers
+  = HashMap.fromList
+      [("integer (numeric)",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("absorption of , after named day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("acum",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect",
+        Classifier{okData =
+                     ClassData{prior = -0.13353139262452263,
+                               unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.9924301646902063),
+                                    ("dayday", -1.2992829841302609),
+                                    ("<named-day> <day-of-month> (number)named-month",
+                                     -1.9924301646902063),
+                                    ("absorption of , after named day<day-of-month>(number) <named-month>",
+                                     -1.9924301646902063),
+                                    ("absorption of , after named day<day-of-month> (non ordinal) <named-month>",
+                                     -1.9924301646902063),
+                                    ("named-daythe <day-of-month> (number)", -2.3978952727983707)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -2.0794415416798357,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month<hour-of-day> <integer> (as relative minutes)",
+                                     -1.6094379124341003),
+                                    ("monthminute", -1.6094379124341003)],
+                               n = 1}}),
+       ("<named-day> <day-of-month> (number)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-dayinteger (numeric)", -1.2992829841302609),
+                                    ("day", -0.7884573603642702),
+                                    ("absorption of , after named dayinteger (numeric)",
+                                     -1.2992829841302609)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("named-month",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("numbers prefix with - or minus",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 1}}),
+       ("craciun",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the <day-of-month> (number)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("time-of-day (latent)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 1}}),
+       ("ieri",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect by \",\"",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayday", -0.7884573603642702),
+                                    ("named-day<day-of-month>(number) <named-month>",
+                                     -1.2992829841302609),
+                                    ("named-day<day-of-month> (non ordinal) <named-month>",
+                                     -1.2992829841302609)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("azi",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("named-day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1780538303479458,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 22},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-day> pe <day-of-month> (number)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-dayinteger (numeric)", -0.6931471805599453),
+                                    ("day", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (0..10)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> (aceasta|acesta|[a\259]sta)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-month> <day-of-month> (non ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-monthinteger (numeric)", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 1}}),
+       ("<day-of-month> (non ordinal) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -1.0116009116784799),
+                                    ("integer (0..10)named-month", -1.7047480922384253),
+                                    ("month", -0.7884573603642702)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this|next <day-of-week>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("maine",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<month> dd-dd (interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> <integer> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)integer (numeric)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1}}),
+       ("<day-of-month>(number) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -1.0116009116784799),
+                                    ("integer (0..10)named-month", -1.7047480922384253),
+                                    ("month", -0.7884573603642702)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}})]
diff --git a/Duckling/Ranking/Classifiers/RU.hs b/Duckling/Ranking/Classifiers/RU.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/RU.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.RU (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers = HashMap.fromList []
diff --git a/Duckling/Ranking/Classifiers/SV.hs b/Duckling/Ranking/Classifiers/SV.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/SV.hs
@@ -0,0 +1,1596 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.SV (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers
+  = HashMap.fromList
+      [("<time> timezone",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.2039728043259361),
+                                    ("hh:mm", -1.6094379124341003), ("hour", -1.6094379124341003),
+                                    ("minute", -1.2039728043259361)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (numeric)",
+        Classifier{okData =
+                     ClassData{prior = -0.7672551527136672, unseen = -4.882801922586371,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 130},
+                   koData =
+                     ClassData{prior = -0.6241543090729939,
+                               unseen = -5.0238805208462765,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 150}}),
+       ("the day before yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("lunch",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("integer (20..90)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -0.9555114450274363),
+                                    ("tomorrowevening", -1.8718021769015913),
+                                    ("named-daymorning", -1.8718021769015913),
+                                    ("tomorrowlunch", -1.8718021769015913),
+                                    ("yesterdayevening", -1.8718021769015913)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("couple, a pair",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dd/mm",
+        Classifier{okData =
+                     ClassData{prior = -0.2231435513142097,
+                               unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 8},
+                   koData =
+                     ClassData{prior = -1.6094379124341003,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("today",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("at <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.2607262624632527, unseen = -4.624972813284271,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time> timezone", -3.5165082281731497),
+                                    ("time-of-day (latent)", -1.2139231351791042),
+                                    ("relative minutes after|past <integer> (hour-of-day)",
+                                     -3.5165082281731497),
+                                    ("hh:mm", -2.0501711593797225),
+                                    ("<time-of-day> sharp", -3.5165082281731497),
+                                    ("hour", -1.149384614041533), ("minute", -1.7819071727850433)],
+                               n = 47},
+                   koData =
+                     ClassData{prior = -1.4718165345580525, unseen = -3.58351893845611,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.8472978603872037),
+                                    ("hour", -0.8472978603872037)],
+                               n = 14}}),
+       ("absorption of , after named day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("tonight",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("on <date>",
+        Classifier{okData =
+                     ClassData{prior = -0.45198512374305727,
+                               unseen = -4.430816798843313,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("on <date>", -3.3202283191284883),
+                                    ("the <day-of-month> (non ordinal)", -3.3202283191284883),
+                                    ("<day-of-month>(ordinal) <named-month>", -2.339399066116762),
+                                    ("day", -0.8634925463071842),
+                                    ("the <day-of-month> (ordinal)", -3.3202283191284883),
+                                    ("afternoon", -3.7256934272366524),
+                                    ("named-day", -2.1162555148025524),
+                                    ("<day-of-month> (ordinal)", -1.9339339580085977),
+                                    ("hour", -3.7256934272366524),
+                                    ("<day-of-month> (non ordinal) <named-month>",
+                                     -3.7256934272366524)],
+                               n = 35},
+                   koData =
+                     ClassData{prior = -1.0116009116784799,
+                               unseen = -3.9889840465642745,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("on <date>", -2.3608540011180215),
+                                    ("year (latent)", -1.7730673362159024),
+                                    ("time-of-day (latent)", -1.7730673362159024),
+                                    ("year", -1.5723966407537513), ("hour", -1.5723966407537513)],
+                               n = 20}}),
+       ("integer (0..19)",
+        Classifier{okData =
+                     ClassData{prior = -0.2803019651541584, unseen = -3.58351893845611,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 34},
+                   koData =
+                     ClassData{prior = -1.4087672169719492,
+                               unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11}}),
+       ("between <time-of-day> and <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -0.9808292530117262),
+                                    ("hh:mmhh:mm", -0.9808292530117262)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -0.9808292530117262),
+                                    ("minutehour", -0.9808292530117262)],
+                               n = 2}}),
+       ("between <datetime> and <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -1.252762968495368, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.2992829841302609),
+                                    ("hh:mmhh:mm", -1.2992829841302609)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.3364722366212129, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mm<day-of-month> (ordinal)", -1.7346010553881064),
+                                    ("hh:mmtime-of-day (latent)", -1.7346010553881064),
+                                    ("minuteminute", -2.1400661634962708),
+                                    ("minutehour", -1.7346010553881064),
+                                    ("hh:mmintersect", -2.1400661634962708),
+                                    ("minuteday", -1.7346010553881064)],
+                               n = 5}}),
+       ("month (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> more <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)minute (grain)", -1.252762968495368),
+                                    ("integer (0..19)minute (grain)", -1.252762968495368),
+                                    ("minute", -0.8472978603872037)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> o'clock",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hour (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.11778303565638351,
+                               unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 8},
+                   koData =
+                     ClassData{prior = -2.1972245773362196,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("<ordinal> quarter",
+        Classifier{okData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)quarter (grain)", -1.0986122886681098),
+                                    ("quarter", -0.8109302162163288),
+                                    ("ordinals (first..31st)quarter (grain)", -1.5040773967762742)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -0.916290731874155, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)quarter (grain)", -1.252762968495368),
+                                    ("quarter", -0.8472978603872037),
+                                    ("ordinals (first..31st)quarter (grain)", -1.252762968495368)],
+                               n = 2}}),
+       ("Last year",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect",
+        Classifier{okData =
+                     ClassData{prior = -0.4382549309311553, unseen = -6.003887067106539,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<datetime> - <datetime> (interval)on <date>",
+                                     -4.209655408733095),
+                                    ("<time-of-day> - <time-of-day> (interval)on <date>",
+                                     -4.209655408733095),
+                                    ("hourday", -5.308267697401205),
+                                    ("dayhour", -3.0569758987947098),
+                                    ("daymonth", -2.956892440237727),
+                                    ("monthyear", -3.29336467685894),
+                                    ("intersecthh:mm", -4.9028025892930405),
+                                    ("named-daylast <cycle>", -5.308267697401205),
+                                    ("the <day-of-month> (ordinal)named-month",
+                                     -3.9219733362813143),
+                                    ("intersect by \"of\", \"from\", \"'s\"year",
+                                     -4.9028025892930405),
+                                    ("last <day-of-week> of <time>year", -5.308267697401205),
+                                    ("todayat <time-of-day>", -4.9028025892930405),
+                                    ("dayday", -2.6341190479746763),
+                                    ("dd/mmat <time-of-day>", -4.39197696552705),
+                                    ("intersect by \",\"hh:mm", -3.804190300624931),
+                                    ("named-day<named-month> <day-of-month> (ordinal)",
+                                     -5.308267697401205),
+                                    ("intersectnamed-month", -4.9028025892930405),
+                                    ("dayyear", -3.362357548345891),
+                                    ("named-daythis <time>", -4.209655408733095),
+                                    ("tomorrow<time-of-day> sharp", -4.9028025892930405),
+                                    ("<day-of-month>(ordinal) <named-month>year",
+                                     -4.39197696552705),
+                                    ("absorption of , after named day<day-of-month>(ordinal) <named-month>",
+                                     -4.61512051684126),
+                                    ("absorption of , after named day<named-month> <day-of-month> (ordinal)",
+                                     -4.209655408733095),
+                                    ("named-day<time> timezone", -4.61512051684126),
+                                    ("named-monthyear", -3.29336467685894),
+                                    ("absorption of , after named day<named-month> <day-of-month> (non ordinal)",
+                                     -4.209655408733095),
+                                    ("on <date>named-month", -3.9219733362813143),
+                                    ("tomorrowuntil <time-of-day>", -4.9028025892930405),
+                                    ("absorption of , after named day<day-of-month> (non ordinal) <named-month>",
+                                     -4.61512051684126),
+                                    ("after <time-of-day>at <time-of-day>", -4.9028025892930405),
+                                    ("intersect by \",\"<day-of-month> (non ordinal) <named-month>",
+                                     -4.9028025892930405),
+                                    ("named-day<day-of-month> (non ordinal) <named-month>",
+                                     -5.308267697401205),
+                                    ("named-day<named-month> <day-of-month> (non ordinal)",
+                                     -5.308267697401205),
+                                    ("named-dayfrom <datetime> - <datetime> (interval)",
+                                     -4.9028025892930405),
+                                    ("named-daynext <cycle>", -5.308267697401205),
+                                    ("named-dayintersect", -4.9028025892930405),
+                                    ("named-dayfrom <time-of-day> - <time-of-day> (interval)",
+                                     -4.9028025892930405),
+                                    ("tomorrowafter <time-of-day>", -4.9028025892930405),
+                                    ("from <time-of-day> - <time-of-day> (interval)on <date>",
+                                     -4.39197696552705),
+                                    ("dayminute", -2.6692103677859462),
+                                    ("from <datetime> - <datetime> (interval)on <date>",
+                                     -4.61512051684126),
+                                    ("<ordinal> <cycle> of <time>year", -4.9028025892930405),
+                                    ("minuteday", -2.6341190479746763),
+                                    ("absorption of , after named dayintersect",
+                                     -4.9028025892930405),
+                                    ("named-daybetween <time-of-day> and <time-of-day> (interval)",
+                                     -5.308267697401205),
+                                    ("named-dayon <date>", -4.61512051684126),
+                                    ("named-dayat <time-of-day>", -4.9028025892930405),
+                                    ("yearhh:mm", -5.308267697401205),
+                                    ("named-daybetween <datetime> and <datetime> (interval)",
+                                     -5.308267697401205),
+                                    ("absorption of , after named dayintersect by \",\"",
+                                     -4.39197696552705),
+                                    ("dd/mmyear", -4.9028025892930405),
+                                    ("at <time-of-day>on <date>", -5.308267697401205),
+                                    ("between <time-of-day> and <time-of-day> (interval)on <date>",
+                                     -5.308267697401205),
+                                    ("between <datetime> and <datetime> (interval)on <date>",
+                                     -5.308267697401205),
+                                    ("dayweek", -4.055504728905837),
+                                    ("intersect by \",\"<day-of-month>(ordinal) <named-month>",
+                                     -4.9028025892930405),
+                                    ("weekyear", -4.39197696552705),
+                                    ("hh:mmtomorrow", -4.61512051684126),
+                                    ("named-day<day-of-month>(ordinal) <named-month>",
+                                     -5.308267697401205),
+                                    ("tomorrowat <time-of-day>", -4.055504728905837),
+                                    ("named-daythis <cycle>", -4.61512051684126),
+                                    ("named-daythe <day-of-month> (ordinal)", -5.308267697401205),
+                                    ("at <time-of-day>tomorrow", -4.9028025892930405),
+                                    ("last <cycle> of <time>year", -4.39197696552705),
+                                    ("<day-of-month> (non ordinal) <named-month>year",
+                                     -4.9028025892930405),
+                                    ("yearminute", -5.308267697401205)],
+                               n = 160},
+                   koData =
+                     ClassData{prior = -1.0360919316867756, unseen = -5.564520407322694,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -4.867534450455582),
+                                    ("dayhour", -3.258096538021482),
+                                    ("daymonth", -2.0343211063993665),
+                                    ("monthday", -3.3634570536793085),
+                                    ("monthyear", -3.951243718581427),
+                                    ("intersect by \"of\", \"from\", \"'s\"year",
+                                     -3.7689221617874726),
+                                    ("hourmonth", -4.462069342347418),
+                                    ("named-dayhh:mm", -4.867534450455582),
+                                    ("dayday", -4.867534450455582),
+                                    ("dd/mmat <time-of-day>", -3.951243718581427),
+                                    ("intersectnamed-month", -4.462069342347418),
+                                    ("dayyear", -3.481240089335692),
+                                    ("named-daythis <time>", -2.921624301400269),
+                                    ("year<hour-of-day> <integer> (as relative minutes)",
+                                     -4.462069342347418),
+                                    ("minutemonth", -4.462069342347418),
+                                    ("named-monthyear", -3.951243718581427),
+                                    ("on <date>named-month", -4.462069342347418),
+                                    ("absorption of , after named daynamed-month",
+                                     -3.7689221617874726),
+                                    ("named-dayfrom <datetime> - <datetime> (interval)",
+                                     -3.7689221617874726),
+                                    ("named-dayintersect", -4.462069342347418),
+                                    ("named-month<day-of-month>(ordinal) <named-month>",
+                                     -3.951243718581427),
+                                    ("<hour-of-day> <integer> (as relative minutes)named-month",
+                                     -4.462069342347418),
+                                    ("named-dayfrom <time-of-day> - <time-of-day> (interval)",
+                                     -4.867534450455582),
+                                    ("yearmonth", -4.867534450455582),
+                                    ("until <time-of-day>on <date>", -4.867534450455582),
+                                    ("<named-month> <day-of-month> (ordinal)named-month",
+                                     -3.951243718581427),
+                                    ("dayminute", -3.258096538021482),
+                                    ("minuteday", -3.3634570536793085),
+                                    ("named-daybetween <time-of-day> and <time-of-day> (interval)",
+                                     -4.867534450455582),
+                                    ("named-dayon <date>", -4.462069342347418),
+                                    ("named-dayat <time-of-day>", -3.951243718581427),
+                                    ("hh:mmon <date>", -3.481240089335692),
+                                    ("named-daybetween <datetime> and <datetime> (interval)",
+                                     -4.462069342347418),
+                                    ("<day-of-month>(ordinal) <named-month> yearnamed-month",
+                                     -3.951243718581427),
+                                    ("named-month<day-of-month> (non ordinal) <named-month>",
+                                     -3.951243718581427),
+                                    ("<named-month> <day-of-month> (non ordinal)named-month",
+                                     -3.951243718581427),
+                                    ("<hour-of-day> <integer> (as relative minutes)year",
+                                     -4.462069342347418),
+                                    ("minuteyear", -4.462069342347418),
+                                    ("yearminute", -4.462069342347418)],
+                               n = 88}}),
+       ("<ordinal> <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.7346010553881064),
+                                    ("ordinals (first..31st)week (grain)intersect",
+                                     -1.7346010553881064),
+                                    ("ordinals (first..31st)week (grain)named-month",
+                                     -1.7346010553881064),
+                                    ("weekmonth", -1.2237754316221157),
+                                    ("ordinals (first..31st)day (grain)named-month",
+                                     -1.7346010553881064)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("season",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("from <datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -1.4816045409242156,
+                               unseen = -3.4657359027997265,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.824549292051046),
+                                    ("time-of-day (latent)time-of-day (latent)",
+                                     -2.740840023925201),
+                                    ("hh:mmhh:mm", -1.824549292051046),
+                                    ("hourhour", -2.740840023925201)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.2578291093020998, unseen = -4.02535169073515,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hourday", -3.3141860046725258),
+                                    ("dayhour", -3.3141860046725258),
+                                    ("yearhour", -3.3141860046725258),
+                                    ("hh:mm<day-of-month> (ordinal)", -2.3978952727983707),
+                                    ("time-of-day (latent)<day-of-month> (ordinal)",
+                                     -3.3141860046725258),
+                                    ("<day-of-month> (ordinal)time-of-day (latent)",
+                                     -3.3141860046725258),
+                                    ("hh:mmtime-of-day (latent)", -2.3978952727983707),
+                                    ("minuteminute", -2.6210388241125804),
+                                    ("yearyear", -3.3141860046725258),
+                                    ("<day-of-month> (ordinal)<day-of-month> (ordinal)",
+                                     -3.3141860046725258),
+                                    ("dayday", -3.3141860046725258),
+                                    ("year (latent)year (latent)", -3.3141860046725258),
+                                    ("minutehour", -2.3978952727983707),
+                                    ("hh:mmintersect", -2.6210388241125804),
+                                    ("year (latent)time-of-day (latent)", -3.3141860046725258),
+                                    ("minuteday", -2.3978952727983707),
+                                    ("year (latent)<day-of-month> (ordinal)", -3.3141860046725258),
+                                    ("yearday", -3.3141860046725258)],
+                               n = 17}}),
+       ("next <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6094379124341003),
+                                    ("month (grain)", -2.3025850929940455),
+                                    ("year (grain)", -2.3025850929940455),
+                                    ("week (grain)", -1.6094379124341003),
+                                    ("quarter", -2.3025850929940455), ("year", -2.3025850929940455),
+                                    ("month", -2.3025850929940455),
+                                    ("quarter (grain)", -2.3025850929940455)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("number.number hours",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("from <time-of-day> - <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.6061358035703156,
+                               unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -1.0986122886681098),
+                                    ("time-of-day (latent)time-of-day (latent)",
+                                     -2.1972245773362196),
+                                    ("hh:mmhh:mm", -1.0986122886681098),
+                                    ("hourhour", -2.1972245773362196)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.7884573603642702, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -0.9808292530117262),
+                                    ("minutehour", -0.9808292530117262)],
+                               n = 5}}),
+       ("yyyy-mm-dd",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (latent)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -3.4965075614664802,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.3746934494414107),
+                                    ("integer (0..19)", -1.1631508098056809)],
+                               n = 30}}),
+       ("dd/mm/yyyy",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<ordinal> quarter <year>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..31st)quarter (grain)year",
+                                     -1.252762968495368),
+                                    ("quarteryear", -0.8472978603872037),
+                                    ("ordinal (digits)quarter (grain)year", -1.252762968495368)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("after lunch",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh:mm:ss",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("quarter to|till|before <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.0986122886681098),
+                                    ("noon", -1.5040773967762742), ("hour", -0.8109302162163288)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("nth <time> of <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228, unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -0.7472144018302211),
+                                    ("ordinals (first..31st)named-dayintersect",
+                                     -0.9985288301111273),
+                                    ("ordinals (first..31st)named-daynamed-month",
+                                     -1.845826690498331)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -0.8472978603872037, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -0.7621400520468967),
+                                    ("ordinals (first..31st)named-daynamed-month",
+                                     -0.7621400520468967)],
+                               n = 6}}),
+       ("the <day-of-month> (non ordinal)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinals (first..31st)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 19},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("named-month",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.174387269895637,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 63},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("week (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.295836866004329,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 25},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("now",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("this <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.6931471805599453),
+                                    ("morning", -0.6931471805599453)],
+                               n = 1}}),
+       ("<day-of-month>(ordinal) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.12921173148000623,
+                               unseen = -4.127134385045092,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..31st)named-month", -2.501435951739211),
+                                    ("ordinal (digits)named-month", -0.8527773261518292),
+                                    ("month", -0.7096764825111559)],
+                               n = 29},
+                   koData =
+                     ClassData{prior = -2.1102132003465894,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..31st)named-month", -1.7047480922384253),
+                                    ("ordinal (digits)named-month", -1.0116009116784799),
+                                    ("month", -0.7884573603642702)],
+                               n = 4}}),
+       ("numbers prefix with -, negative or minus",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -3.2188758248682006,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 23}}),
+       ("in|during the <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.6931471805599453),
+                                    ("morning", -0.6931471805599453)],
+                               n = 1}}),
+       ("new year's eve",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("tomorrow",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.833213344056216,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 15},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> after next",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -1.791759469228055),
+                                    ("day", -1.0986122886681098),
+                                    ("named-day", -1.0986122886681098),
+                                    ("month", -1.791759469228055)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("half an hour",
+        Classifier{okData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -0.916290731874155, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2}}),
+       ("the <day-of-month> (ordinal)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..31st)", -1.1786549963416462),
+                                    ("ordinal (digits)", -0.3677247801253174)],
+                               n = 11},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("afternoon",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<duration> from now",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("second", -1.2039728043259361),
+                                    ("<integer> <unit-of-duration>", -1.2039728043259361),
+                                    ("a <unit-of-duration>", -1.6094379124341003),
+                                    ("minute", -1.6094379124341003)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.3862943611198906),
+                                    ("year (grain)", -1.8971199848858813),
+                                    ("week (grain)", -1.3862943611198906),
+                                    ("quarter", -2.3025850929940455), ("year", -1.8971199848858813),
+                                    ("quarter (grain)", -2.3025850929940455)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("minute (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 13},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("about <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -0.916290731874155, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 2}}),
+       ("time-of-day (latent)",
+        Classifier{okData =
+                     ClassData{prior = -1.006804739414987, unseen = -3.713572066704308,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -5.129329438755058e-2),
+                                    ("integer (0..19)", -2.995732273553991)],
+                               n = 38},
+                   koData =
+                     ClassData{prior = -0.4547361571149472, unseen = -4.23410650459726,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.24921579162398486),
+                                    ("integer (0..19)", -1.5114575040738967)],
+                               n = 66}}),
+       ("year",
+        Classifier{okData =
+                     ClassData{prior = -0.14842000511827333,
+                               unseen = -3.295836866004329,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 25},
+                   koData =
+                     ClassData{prior = -1.9810014688665833, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 4}}),
+       ("last <day-of-week> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.0986122886681098),
+                                    ("daymonth", -0.8109302162163288),
+                                    ("named-dayintersect", -1.5040773967762742)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = -0.6375773294051346, unseen = -4.59511985013459,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.639057329615259),
+                                    ("integer (0..19)year (grain)", -3.891820298110627),
+                                    ("integer (numeric)day (grain)", -2.793208009442517),
+                                    ("couple, a pairhour (grain)", -3.891820298110627),
+                                    ("integer (0..19)hour (grain)", -3.891820298110627),
+                                    ("second", -3.1986731175506815),
+                                    ("integer (numeric)second (grain)", -3.891820298110627),
+                                    ("integer (numeric)year (grain)", -3.891820298110627),
+                                    ("day", -2.187072205872201), ("year", -3.4863551900024623),
+                                    ("integer (numeric)week (grain)", -3.1986731175506815),
+                                    ("integer (0..19)month (grain)", -3.891820298110627),
+                                    ("integer (0..19)second (grain)", -3.4863551900024623),
+                                    ("hour", -2.793208009442517), ("month", -3.4863551900024623),
+                                    ("integer (numeric)minute (grain)", -2.793208009442517),
+                                    ("integer (0..19)minute (grain)", -2.9755295662364714),
+                                    ("integer (numeric)month (grain)", -3.891820298110627),
+                                    ("minute", -2.2823823856765264),
+                                    ("integer (numeric)hour (grain)", -3.1986731175506815),
+                                    ("integer (0..19)day (grain)", -2.793208009442517),
+                                    ("integer (0..19)week (grain)", -3.1986731175506815)],
+                               n = 37},
+                   koData =
+                     ClassData{prior = -0.7519876805828788, unseen = -4.51085950651685,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.70805020110221),
+                                    ("integer (0..19)year (grain)", -3.4011973816621555),
+                                    ("integer (numeric)day (grain)", -3.1135153092103742),
+                                    ("integer (numeric)quarter (grain)", -3.8066624897703196),
+                                    ("integer (0..19)hour (grain)", -3.8066624897703196),
+                                    ("second", -2.890371757896165),
+                                    ("integer (numeric)second (grain)", -3.4011973816621555),
+                                    ("integer (numeric)year (grain)", -3.1135153092103742),
+                                    ("day", -2.70805020110221), ("quarter", -3.8066624897703196),
+                                    ("year", -2.70805020110221),
+                                    ("integer (numeric)week (grain)", -3.4011973816621555),
+                                    ("integer (0..19)month (grain)", -3.1135153092103742),
+                                    ("integer (0..19)second (grain)", -3.4011973816621555),
+                                    ("hour", -2.890371757896165), ("month", -2.70805020110221),
+                                    ("integer (numeric)minute (grain)", -3.4011973816621555),
+                                    ("integer (0..19)minute (grain)", -3.4011973816621555),
+                                    ("integer (numeric)month (grain)", -3.4011973816621555),
+                                    ("minute", -2.890371757896165),
+                                    ("integer (numeric)hour (grain)", -3.1135153092103742),
+                                    ("integer (0..19)day (grain)", -3.4011973816621555),
+                                    ("integer (0..19)week (grain)", -3.1135153092103742)],
+                               n = 33}}),
+       ("relative minutes after|past <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.8109302162163288),
+                                    ("integer (numeric)time-of-day (latent)", -1.0986122886681098),
+                                    ("integer (20..90)time-of-day (latent)", -1.5040773967762742)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("a <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.0794415416798357),
+                                    ("hour (grain)", -2.4849066497880004),
+                                    ("second", -2.0794415416798357),
+                                    ("week (grain)", -2.0794415416798357),
+                                    ("day", -2.4849066497880004),
+                                    ("minute (grain)", -2.4849066497880004),
+                                    ("second (grain)", -2.0794415416798357),
+                                    ("hour", -2.4849066497880004), ("minute", -2.4849066497880004),
+                                    ("day (grain)", -2.4849066497880004)],
+                               n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect by \",\"",
+        Classifier{okData =
+                     ClassData{prior = -8.855339734144506e-2,
+                               unseen = -4.948759890378168,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<named-month> <day-of-month> (ordinal)intersect",
+                                     -4.248495242049359),
+                                    ("at <time-of-day>named-day", -4.248495242049359),
+                                    ("intersect by \",\"year", -3.8430301339411947),
+                                    ("hh:mmintersect by \",\"", -3.8430301339411947),
+                                    ("dayday", -1.8971199848858813),
+                                    ("hh:mmnamed-day", -4.248495242049359),
+                                    ("named-dayintersect by \",\"", -3.332204510175204),
+                                    ("named-day<named-month> <day-of-month> (ordinal)",
+                                     -3.1498829533812494),
+                                    ("dayyear", -2.744417845273085),
+                                    ("<named-month> <day-of-month> (non ordinal)intersect",
+                                     -4.248495242049359),
+                                    ("intersect by \",\"<day-of-month> (non ordinal) <named-month>",
+                                     -3.8430301339411947),
+                                    ("<named-month> <day-of-month> (non ordinal)named-day",
+                                     -4.248495242049359),
+                                    ("named-day<day-of-month> (non ordinal) <named-month>",
+                                     -3.5553480614894135),
+                                    ("named-day<named-month> <day-of-month> (non ordinal)",
+                                     -3.1498829533812494),
+                                    ("hh:mmintersect", -3.8430301339411947),
+                                    ("intersect by \",\"intersect", -3.8430301339411947),
+                                    ("named-dayintersect", -3.8430301339411947),
+                                    ("at <time-of-day>intersect", -3.8430301339411947),
+                                    ("dayminute", -2.5437471498109336),
+                                    ("intersectyear", -3.8430301339411947),
+                                    ("minuteday", -2.108429078553088),
+                                    ("hh:mmabsorption of , after named day", -4.248495242049359),
+                                    ("at <time-of-day>intersect by \",\"", -3.8430301339411947),
+                                    ("at <time-of-day>absorption of , after named day",
+                                     -4.248495242049359),
+                                    ("intersectintersect", -3.8430301339411947),
+                                    ("intersect by \",\"<day-of-month>(ordinal) <named-month>",
+                                     -3.8430301339411947),
+                                    ("named-day<day-of-month>(ordinal) <named-month>",
+                                     -3.5553480614894135),
+                                    ("<named-month> <day-of-month> (ordinal)named-day",
+                                     -4.248495242049359),
+                                    ("<named-month> <day-of-month> (ordinal)year",
+                                     -3.8430301339411947),
+                                    ("<named-month> <day-of-month> (non ordinal)year",
+                                     -3.8430301339411947)],
+                               n = 54},
+                   koData =
+                     ClassData{prior = -2.468099531471619, unseen = -3.7612001156935624,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.9459101490553135),
+                                    ("daymonth", -1.9459101490553135)],
+                               n = 5}}),
+       ("hh:mm",
+        Classifier{okData =
+                     ClassData{prior = -2.197890671877523e-2,
+                               unseen = -3.8501476017100584,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 45},
+                   koData =
+                     ClassData{prior = -3.828641396489095, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("quarter after|past <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("named-day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.248495242049359,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 68},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("second (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> sharp",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.2992829841302609),
+                                    ("time-of-day (latent)", -1.2992829841302609),
+                                    ("hour", -0.7884573603642702)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect by \"of\", \"from\", \"'s\"",
+        Classifier{okData =
+                     ClassData{prior = -0.8649974374866046,
+                               unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.4816045409242156),
+                                    ("daymonth", -1.1451323043030026),
+                                    ("named-daylast <cycle>", -2.3978952727983707),
+                                    ("named-daynext <cycle>", -2.3978952727983707),
+                                    ("named-dayintersect", -1.9924301646902063),
+                                    ("dayweek", -1.9924301646902063)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -0.5465437063680699, unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -1.3862943611198906),
+                                    ("daymonth", -0.8472978603872037),
+                                    ("named-dayintersect", -1.540445040947149)],
+                               n = 11}}),
+       ("<duration> ago",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.4965075614664802,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6739764335716716), ("day", -1.8562979903656263),
+                                    ("year", -2.367123614131617),
+                                    ("<integer> <unit-of-duration>", -0.9007865453381898),
+                                    ("a <unit-of-duration>", -2.772588722239781),
+                                    ("month", -2.367123614131617)],
+                               n = 13},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <time>",
+        Classifier{okData =
+                     ClassData{prior = -2.8134107167600364, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.6094379124341003),
+                                    ("named-day", -1.6094379124341003),
+                                    ("hour", -2.0149030205422647),
+                                    ("week-end", -2.0149030205422647)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -6.187540371808753e-2,
+                               unseen = -4.6443908991413725,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("year (latent)", -1.7443572303334711),
+                                    ("day", -1.9956716586143772),
+                                    ("time-of-day (latent)", -1.7443572303334711),
+                                    ("year", -1.7443572303334711),
+                                    ("named-day", -3.536116699561526),
+                                    ("intersect by \"of\", \"from\", \"'s\"", -3.536116699561526),
+                                    ("<day-of-month> (ordinal)", -2.33214389523559),
+                                    ("hour", -1.7443572303334711)],
+                               n = 47}}),
+       ("<day-of-month> (ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -1.0360919316867756, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..31st)", -1.1786549963416462),
+                                    ("ordinal (digits)", -0.3677247801253174)],
+                               n = 11},
+                   koData =
+                     ClassData{prior = -0.4382549309311553,
+                               unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList [("ordinal (digits)", -4.652001563489282e-2)],
+                               n = 20}}),
+       ("the day after tomorrow",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("noon",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("until <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -0.8873031950009028),
+                                    ("hour", -0.8873031950009028)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -1.5040773967762742),
+                                    ("hh:mm", -1.5040773967762742),
+                                    ("minute", -1.0986122886681098)],
+                               n = 2}}),
+       ("<integer> and an half hours",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.6931471805599453),
+                                    ("integer (0..19)", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("after <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("<integer> <unit-of-duration>", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("evening",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("decimal number",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("next <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -1.791759469228055),
+                                    ("day", -1.0986122886681098),
+                                    ("named-day", -1.0986122886681098),
+                                    ("month", -1.791759469228055)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -1.791759469228055),
+                                    ("day", -1.0986122886681098),
+                                    ("named-day", -1.0986122886681098),
+                                    ("month", -1.791759469228055)],
+                               n = 4}}),
+       ("last <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.587786664902119, unseen = -2.9444389791664407,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.5040773967762742),
+                                    ("month (grain)", -2.1972245773362196),
+                                    ("year (grain)", -2.1972245773362196),
+                                    ("week (grain)", -1.5040773967762742),
+                                    ("year", -2.1972245773362196), ("month", -2.1972245773362196)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.8109302162163288, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6739764335716716),
+                                    ("week (grain)", -1.6739764335716716),
+                                    ("day", -1.6739764335716716),
+                                    ("day (grain)", -1.6739764335716716)],
+                               n = 4}}),
+       ("christmas",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("new year's day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("next n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.912023005428146,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.793208009442517),
+                                    ("integer (0..19)year (grain)", -3.1986731175506815),
+                                    ("integer (numeric)day (grain)", -3.1986731175506815),
+                                    ("integer (0..19)hour (grain)", -3.1986731175506815),
+                                    ("second", -2.793208009442517),
+                                    ("integer (numeric)second (grain)", -3.1986731175506815),
+                                    ("integer (numeric)year (grain)", -3.1986731175506815),
+                                    ("day", -2.793208009442517), ("year", -2.793208009442517),
+                                    ("integer (numeric)week (grain)", -3.1986731175506815),
+                                    ("integer (0..19)month (grain)", -3.1986731175506815),
+                                    ("integer (0..19)second (grain)", -3.1986731175506815),
+                                    ("hour", -2.793208009442517), ("month", -2.793208009442517),
+                                    ("integer (numeric)minute (grain)", -3.1986731175506815),
+                                    ("integer (0..19)minute (grain)", -3.1986731175506815),
+                                    ("integer (numeric)month (grain)", -3.1986731175506815),
+                                    ("minute", -2.793208009442517),
+                                    ("integer (numeric)hour (grain)", -3.1986731175506815),
+                                    ("integer (0..19)day (grain)", -3.1986731175506815),
+                                    ("integer (0..19)week (grain)", -3.1986731175506815)],
+                               n = 14},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -3.0910424533583156,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("in <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.3694478524670215,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -3.258096538021482),
+                                    ("<integer> more <unit-of-duration>", -3.258096538021482),
+                                    ("number.number hours", -3.6635616461296463),
+                                    ("second", -2.9704144655697013), ("day", -2.5649493574615367),
+                                    ("half an hour", -3.6635616461296463),
+                                    ("<integer> <unit-of-duration>", -1.3121863889661687),
+                                    ("a <unit-of-duration>", -2.5649493574615367),
+                                    ("<integer> and an half hours", -3.258096538021482),
+                                    ("hour", -2.4107986776342782), ("minute", -1.466337068793427),
+                                    ("about <duration>", -3.258096538021482)],
+                               n = 33},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<datetime> - <datetime> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.579818495252942, unseen = -4.304065093204169,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<day-of-month>(ordinal) <named-month><day-of-month> (non ordinal) <named-month>",
+                                     -2.498699971920336),
+                                    ("<day-of-month> (non ordinal) <named-month><day-of-month>(ordinal) <named-month>",
+                                     -2.498699971920336),
+                                    ("minuteminute", -2.093234863812172),
+                                    ("hh:mmhh:mm", -2.093234863812172),
+                                    ("dayday", -1.2459370034249682),
+                                    ("<day-of-month> (non ordinal) <named-month><day-of-month> (non ordinal) <named-month>",
+                                     -2.498699971920336),
+                                    ("<day-of-month>(ordinal) <named-month><day-of-month>(ordinal) <named-month>",
+                                     -2.498699971920336)],
+                               n = 28},
+                   koData =
+                     ClassData{prior = -0.8209805520698302, unseen = -4.127134385045092,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthday", -1.7129785913749407),
+                                    ("minuteminute", -2.164963715117998),
+                                    ("hh:mmhh:mm", -3.417726683613366),
+                                    ("dayyear", -3.012261575505202),
+                                    ("year<hour-of-day> <integer> (as relative minutes)",
+                                     -3.012261575505202),
+                                    ("hh:mmintersect", -2.3191143949452564),
+                                    ("named-month<day-of-month>(ordinal) <named-month>",
+                                     -2.3191143949452564),
+                                    ("dd/mmyear", -3.012261575505202),
+                                    ("named-month<day-of-month> (non ordinal) <named-month>",
+                                     -2.3191143949452564),
+                                    ("<hour-of-day> <integer> (as relative minutes)year",
+                                     -3.012261575505202),
+                                    ("minuteyear", -3.012261575505202),
+                                    ("yearminute", -3.012261575505202)],
+                               n = 22}}),
+       ("<time-of-day> - <time-of-day> (interval)",
+        Classifier{okData =
+                     ClassData{prior = -0.8109302162163288, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("minuteminute", -0.7985076962177716),
+                                    ("hh:mmhh:mm", -0.7985076962177716)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -0.587786664902119, unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mmtime-of-day (latent)", -0.8754687373538999),
+                                    ("minuteminute", -2.4849066497880004),
+                                    ("hh:mmhh:mm", -2.4849066497880004),
+                                    ("minutehour", -0.8754687373538999)],
+                               n = 10}}),
+       ("last n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.007333185232471,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.6026896854443837),
+                                    ("integer (0..19)year (grain)", -3.295836866004329),
+                                    ("integer (numeric)day (grain)", -2.890371757896165),
+                                    ("second", -2.890371757896165),
+                                    ("integer (numeric)second (grain)", -3.295836866004329),
+                                    ("integer (numeric)year (grain)", -2.890371757896165),
+                                    ("day", -2.6026896854443837), ("year", -2.6026896854443837),
+                                    ("integer (numeric)week (grain)", -3.295836866004329),
+                                    ("integer (0..19)month (grain)", -2.890371757896165),
+                                    ("integer (0..19)second (grain)", -3.295836866004329),
+                                    ("hour", -3.295836866004329), ("month", -2.6026896854443837),
+                                    ("integer (numeric)minute (grain)", -3.295836866004329),
+                                    ("integer (0..19)minute (grain)", -3.295836866004329),
+                                    ("integer (numeric)month (grain)", -3.295836866004329),
+                                    ("minute", -2.890371757896165),
+                                    ("integer (numeric)hour (grain)", -3.295836866004329),
+                                    ("integer (0..19)day (grain)", -3.295836866004329),
+                                    ("integer (0..19)week (grain)", -2.890371757896165)],
+                               n = 17},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -3.044522437723423,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-month> <day-of-month> (non ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -0.4418327522790392, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-monthinteger (numeric)", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -1.0296194171811581,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-monthinteger (numeric)", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 5}}),
+       ("<day-of-month> (non ordinal) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.1466034741918754, unseen = -3.713572066704308,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 19},
+                   koData =
+                     ClassData{prior = -1.9924301646902063,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 3}}),
+       ("this|next <day-of-week>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 3}}),
+       ("ordinal (digits)",
+        Classifier{okData =
+                     ClassData{prior = -1.9212175364649418,
+                               unseen = -3.7612001156935624,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 41},
+                   koData =
+                     ClassData{prior = -0.158326051237739, unseen = -5.484796933490655,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 239}}),
+       ("quarter (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.4816045409242156),
+                                    ("week (grain)named-month", -1.9924301646902063),
+                                    ("day (grain)intersect", -1.9924301646902063),
+                                    ("weekmonth", -1.4816045409242156),
+                                    ("day (grain)named-month", -1.9924301646902063),
+                                    ("week (grain)intersect", -1.9924301646902063)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month>(ordinal) <named-month> year",
+        Classifier{okData =
+                     ClassData{prior = -0.587786664902119, unseen = -2.639057329615259,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinals (first..31st)named-month", -1.466337068793427),
+                                    ("ordinal (digits)named-month", -1.1786549963416462),
+                                    ("month", -0.7731898882334817)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.8109302162163288,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("ordinal (digits)named-month", -0.7884573603642702),
+                                    ("month", -0.7884573603642702)],
+                               n = 4}}),
+       ("morning",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("week-end",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("after <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = -0.8109302162163288, unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("at <time-of-day>", -1.7227665977411035),
+                                    ("intersect", -2.2335922215070942),
+                                    ("tomorrow", -2.2335922215070942), ("day", -2.2335922215070942),
+                                    ("hour", -1.3862943611198906)],
+                               n = 8},
+                   koData =
+                     ClassData{prior = -0.587786664902119, unseen = -3.4965075614664802,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("lunch", -2.772588722239781),
+                                    ("year (latent)", -2.0794415416798357),
+                                    ("day", -2.367123614131617),
+                                    ("time-of-day (latent)", -2.0794415416798357),
+                                    ("year", -2.0794415416798357), ("hh:mm", -2.772588722239781),
+                                    ("<day-of-month> (ordinal)", -2.367123614131617),
+                                    ("hour", -1.8562979903656263), ("minute", -2.772588722239781)],
+                               n = 10}}),
+       ("day (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.044522437723423,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 19},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<month> dd-dd (interval)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-month", -0.6931471805599453),
+                                    ("month", -0.6931471805599453)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("about <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("half an hour", -0.6931471805599453),
+                                    ("minute", -0.6931471805599453)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> <integer> (as relative minutes)",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -3.1354942159291497,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)integer (numeric)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 10}}),
+       ("this <time>",
+        Classifier{okData =
+                     ClassData{prior = -1.2992829841302609,
+                               unseen = -3.6375861597263857,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.917770732084279), ("intersect", -2.512305623976115),
+                                    ("season", -2.001480000210124),
+                                    ("next <cycle>", -2.917770732084279),
+                                    ("named-month", -2.512305623976115),
+                                    ("day", -2.001480000210124), ("hour", -2.2246235515243336),
+                                    ("month", -2.001480000210124),
+                                    ("week-end", -2.2246235515243336)],
+                               n = 12},
+                   koData =
+                     ClassData{prior = -0.3184537311185346, unseen = -4.356708826689592,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("intersect", -1.8588987720656835),
+                                    ("named-month", -1.5105920777974677),
+                                    ("day", -3.6506582412937383),
+                                    ("time-of-day (latent)", -3.245193133185574),
+                                    ("<day-of-month> (ordinal)", -3.6506582412937383),
+                                    ("noon", -3.6506582412937383), ("hour", -2.7343675094195836),
+                                    ("month", -1.0116009116784799),
+                                    ("morning", -3.6506582412937383)],
+                               n = 32}}),
+       ("<named-month> <day-of-month> (ordinal)",
+        Classifier{okData =
+                     ClassData{prior = -0.4418327522790392, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("month", -0.6931471805599453),
+                                    ("named-monthordinal (digits)", -0.6931471805599453)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -1.0296194171811581,
+                               unseen = -2.5649493574615367,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("month", -0.6931471805599453),
+                                    ("named-monthordinal (digits)", -0.6931471805599453)],
+                               n = 5}}),
+       ("within <duration>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -0.6931471805599453),
+                                    ("<integer> <unit-of-duration>", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}})]
diff --git a/Duckling/Ranking/Classifiers/TR.hs b/Duckling/Ranking/Classifiers/TR.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/TR.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.TR (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers = HashMap.fromList []
diff --git a/Duckling/Ranking/Classifiers/UK.hs b/Duckling/Ranking/Classifiers/UK.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/UK.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.UK (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers = HashMap.fromList []
diff --git a/Duckling/Ranking/Classifiers/VI.hs b/Duckling/Ranking/Classifiers/VI.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/VI.hs
@@ -0,0 +1,906 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.VI (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers
+  = HashMap.fromList
+      [("<time> timezone",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day> am|pm", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (numeric)",
+        Classifier{okData =
+                     ClassData{prior = -0.4986303506721723, unseen = -4.61512051684126,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 99},
+                   koData =
+                     ClassData{prior = -0.9348671174470905, unseen = -4.189654742026425,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 64}}),
+       ("exactly <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day gi\7901", -1.6094379124341003),
+                                    ("<time-of-day> s\225ng|chi\7873u|t\7889i",
+                                     -1.6094379124341003),
+                                    ("time-of-day (latent)", -1.6094379124341003),
+                                    ("hour", -0.916290731874155)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ng\224y h\244m qua",
+        Classifier{okData =
+                     ClassData{prior = -0.5596157879354228, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -0.8472978603872037,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3}}),
+       ("lunch",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ng\224y dd/mm",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("dd/mm",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5}}),
+       ("<part-of-day> <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.7884573603642702,
+                               unseen = -3.5553480614894135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hourday", -1.128465251817791),
+                                    ("noonh\244m nay", -2.833213344056216),
+                                    ("lunchng\224y mai", -2.4277482359480516),
+                                    ("morningnamed-day", -2.833213344056216),
+                                    ("noonng\224y h\244m qua", -2.4277482359480516),
+                                    ("noonng\224y mai", -2.4277482359480516),
+                                    ("afternoonng\224y dd/mm", -2.833213344056216),
+                                    ("afternoonng\224y mai", -2.833213344056216)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -0.6061358035703156,
+                               unseen = -3.6635616461296463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hourday", -1.072636802264849),
+                                    ("afternoon<day-of-month> (numeric with day symbol) <named-month>",
+                                     -2.538973871058276),
+                                    ("afternoongi\225ng sinh", -1.845826690498331),
+                                    ("afternoonng\224y dd/mm/yyyy", -2.9444389791664407),
+                                    ("noongi\225ng sinh", -2.9444389791664407),
+                                    ("afternoonng\224y dd/mm", -2.9444389791664407),
+                                    ("lunchgi\225ng sinh", -2.9444389791664407),
+                                    ("afternoonintersect", -2.9444389791664407)],
+                               n = 12}}),
+       ("tonight",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (0..19)",
+        Classifier{okData =
+                     ClassData{prior = -1.452252328911688, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -0.2666286632539486,
+                               unseen = -3.6375861597263857,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 36}}),
+       ("<day-of-week> t\7899i",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("month (grain)",
+        Classifier{okData =
+                     ClassData{prior = -1.4271163556401458,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -0.2744368457017603, unseen = -3.044522437723423,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 19}}),
+       ("ng\224y h\244m kia",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("gi\225ng sinh",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -3.4657359027997265,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 30}}),
+       ("hour (grain)",
+        Classifier{okData =
+                     ClassData{prior = -1.791759469228055, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -0.1823215567939546,
+                               unseen = -3.0910424533583156,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 20}}),
+       ("intersect",
+        Classifier{okData =
+                     ClassData{prior = -0.5070449009260846,
+                               unseen = -5.0106352940962555,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day> s\225ng|chi\7873u|t\7889ing\224y dd/mm",
+                                     -3.6176519448255684),
+                                    ("hourday", -3.6176519448255684),
+                                    ("dayhour", -4.310799125385514),
+                                    ("h\244m nay<time-of-day> s\225ng|chi\7873u|t\7889i",
+                                     -4.310799125385514),
+                                    ("monthyear", -3.3945083935113587),
+                                    ("time-of-day gi\7901<part-of-day> <time>", -4.310799125385514),
+                                    ("time-of-day gi\7901<time> n\224y", -4.310799125385514),
+                                    ("month (numeric with month symbol)year (numeric with year symbol)",
+                                     -3.6176519448255684),
+                                    ("dayday", -2.6060510331470885),
+                                    ("named-monthyear (numeric with year symbol)",
+                                     -4.310799125385514),
+                                    ("hourhour", -3.3945083935113587),
+                                    ("named-day<day-of-month> (numeric with day symbol) <named-month>",
+                                     -2.7013612129514133),
+                                    ("intersectyear (numeric with year symbol)",
+                                     -3.6176519448255684),
+                                    ("dayyear", -2.4389969484839225),
+                                    ("<ordinal> <cycle> of <time>year (numeric with year symbol)",
+                                     -4.310799125385514),
+                                    ("minutehour", -3.9053340172773496),
+                                    ("<time-of-day> s\225ng|chi\7873u|t\7889iintersect",
+                                     -3.9053340172773496),
+                                    ("time-of-day gi\7901<part-of-day> (h\244m )?nay",
+                                     -4.310799125385514),
+                                    ("<day-of-month> (numeric with day symbol) <named-month>year (numeric with year symbol)",
+                                     -3.2121868367174042),
+                                    ("named-daynext <cycle>", -3.6176519448255684),
+                                    ("named-dayintersect", -4.310799125385514),
+                                    ("<time-of-day> s\225ng|chi\7873u|t\7889i<day-of-month> (numeric with day symbol) <named-month>",
+                                     -3.3945083935113587),
+                                    ("time-of-day gi\7901tonight", -4.310799125385514),
+                                    ("minuteday", -2.8067217286092396),
+                                    ("hh:mm<part-of-day> <time>", -4.310799125385514),
+                                    ("<day-of-month> <named-month>year (numeric with year symbol)",
+                                     -3.2121868367174042),
+                                    ("at hh:mm<part-of-day> <time>", -4.310799125385514),
+                                    ("dayweek", -3.0580361568901457),
+                                    ("weekyear", -4.310799125385514),
+                                    ("<time-of-day> s\225ng|chi\7873u|t\7889ing\224y dd/mm/yyyy",
+                                     -4.310799125385514),
+                                    ("<time-of-day> s\225ng|chi\7873u|t\7889ing\224y mai",
+                                     -4.310799125385514),
+                                    ("named-daythis <cycle>", -3.6176519448255684),
+                                    ("seasonthis <cycle>", -4.310799125385514),
+                                    ("minuteyear", -3.9053340172773496)],
+                               n = 53},
+                   koData =
+                     ClassData{prior = -0.9219887529887928, unseen = -4.736198448394496,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day> s\225ng|chi\7873u|t\7889ing\224y dd/mm",
+                                     -4.034240638152395),
+                                    ("hourday", -2.2424811689243405),
+                                    ("dayhour", -4.034240638152395),
+                                    ("monthyear", -2.7814776696570274),
+                                    ("noonh\244m nay", -4.034240638152395),
+                                    ("houryear", -4.034240638152395),
+                                    ("month (numeric with month symbol)year (numeric with year symbol)",
+                                     -2.7814776696570274),
+                                    ("dayday", -2.5301632413761213),
+                                    ("dayyear", -4.034240638152395),
+                                    ("h\244m naytime-of-day gi\7901", -4.034240638152395),
+                                    ("minutehour", -3.628775530044231),
+                                    ("<part-of-day> <time>year (numeric with year symbol)",
+                                     -4.034240638152395),
+                                    ("<time-of-day> s\225ng|chi\7873u|t\7889igi\225ng sinh",
+                                     -2.424802725718295),
+                                    ("noonng\224y h\244m qua", -3.628775530044231),
+                                    ("noongi\225ng sinh", -4.034240638152395),
+                                    ("minuteday", -2.9356283494842854),
+                                    ("hh:mm<part-of-day> <time>", -4.034240638152395),
+                                    ("noonng\224y mai", -3.628775530044231),
+                                    ("<day-of-month> <named-month>year (numeric with year symbol)",
+                                     -4.034240638152395),
+                                    ("named-daygi\225ng sinh", -2.5301632413761213),
+                                    ("at hh:mm<part-of-day> <time>", -4.034240638152395)],
+                               n = 35}}),
+       ("time-of-day gi\7901",
+        Classifier{okData =
+                     ClassData{prior = -6.453852113757118e-2,
+                               unseen = -4.174387269895637,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("exactly <time-of-day>", -3.4657359027997265),
+                                    ("about <time-of-day>", -3.0602707946915624),
+                                    ("time-of-day (latent)", -0.8266785731844679),
+                                    ("hour", -0.7248958788745256)],
+                               n = 30},
+                   koData =
+                     ClassData{prior = -2.772588722239781, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.9808292530117262),
+                                    ("hour", -0.9808292530117262)],
+                               n = 2}}),
+       ("<ordinal> <cycle> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("daymonth", -1.3862943611198906),
+                                    ("day (grain)ordinalsnamed-month", -2.0794415416798357),
+                                    ("day (grain)ordinalsmonth (numeric with month symbol)",
+                                     -1.6739764335716716),
+                                    ("week (grain)ordinalsintersect", -2.0794415416798357),
+                                    ("weekmonth", -1.6739764335716716),
+                                    ("week (grain)ordinalsmonth (numeric with month symbol)",
+                                     -2.0794415416798357)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("season",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.5389965007326869, unseen = -2.772588722239781,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 14},
+                   koData =
+                     ClassData{prior = -0.8754687373538999,
+                               unseen = -2.4849066497880004,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 10}}),
+       ("quarter <number>",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("quarter (grain)integer (numeric)", -1.2992829841302609),
+                                    ("quarter (grain)integer (0..19)", -1.2992829841302609),
+                                    ("quarter", -0.7884573603642702)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -1.0986122886681098,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("quarter (grain)integer (numeric)", -0.8472978603872037),
+                                    ("quarter", -0.8472978603872037)],
+                               n = 2}}),
+       ("next <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -3.7612001156935624,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.9459101490553135),
+                                    ("month (grain)", -3.044522437723423),
+                                    ("year (grain)", -2.128231705849268),
+                                    ("week (grain)", -1.9459101490553135),
+                                    ("quarter", -2.3513752571634776), ("year", -2.128231705849268),
+                                    ("month", -3.044522437723423),
+                                    ("quarter (grain)", -2.3513752571634776)],
+                               n = 13},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -3.7612001156935624,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("month (grain)", -2.639057329615259),
+                                    ("hour (grain)", -2.639057329615259),
+                                    ("year (grain)", -2.639057329615259),
+                                    ("second", -2.3513752571634776), ("day", -2.639057329615259),
+                                    ("minute (grain)", -2.639057329615259),
+                                    ("year", -2.639057329615259),
+                                    ("second (grain)", -2.3513752571634776),
+                                    ("hour", -2.639057329615259), ("month", -2.639057329615259),
+                                    ("minute", -2.639057329615259),
+                                    ("day (grain)", -2.639057329615259)],
+                               n = 13}}),
+       ("ng\224y mai",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("quarter <number> <year>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("quarteryear", -1.0986122886681098),
+                                    ("quarter (grain)integer (numeric)year (numeric with year symbol)",
+                                     -1.0986122886681098)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("quarterhour", -1.0986122886681098),
+                                    ("quarter (grain)integer (numeric)time-of-day (latent)",
+                                     -1.0986122886681098)],
+                               n = 1}}),
+       ("dd/mm/yyyy",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("after lunch",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh:mm:ss",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> tr\432\7899c",
+        Classifier{okData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.5040773967762742),
+                                    ("named-day", -1.5040773967762742)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("gi\225ng sinh", -2.2512917986064953),
+                                    ("<hour-of-day> <integer>", -2.2512917986064953),
+                                    ("day", -2.2512917986064953),
+                                    ("time-of-day (latent)", -1.3350010667323402),
+                                    ("hour", -1.3350010667323402), ("minute", -2.2512917986064953)],
+                               n = 6}}),
+       ("ng\224y dd/mm/yyyy",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time> n\224y",
+        Classifier{okData =
+                     ClassData{prior = -0.6061358035703156, unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("season", -1.845826690498331),
+                                    ("<time-of-day> s\225ng|chi\7873u|t\7889i",
+                                     -2.2512917986064953),
+                                    ("day", -1.845826690498331), ("noon", -1.845826690498331),
+                                    ("hour", -1.3350010667323402),
+                                    ("morning", -2.2512917986064953)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.7884573603642702, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.4469189829363254),
+                                    ("noon", -1.7346010553881064), ("hour", -1.041453874828161)],
+                               n = 5}}),
+       ("b\226y gi\7901",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> <integer>",
+        Classifier{okData =
+                     ClassData{prior = -1.4469189829363254,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.7884573603642702),
+                                    ("time-of-day gi\7901integer (numeric)", -0.7884573603642702)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.2682639865946794,
+                               unseen = -3.4011973816621555,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)integer (0..19)", -0.7282385003712154),
+                                    ("hour", -0.7282385003712154)],
+                               n = 13}}),
+       ("<time> t\7899i",
+        Classifier{okData =
+                     ClassData{prior = -0.4700036292457356, unseen = -2.995732273553991,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("month (numeric with month symbol)", -2.2512917986064953),
+                                    ("day", -1.3350010667323402),
+                                    ("named-day", -1.3350010667323402),
+                                    ("month", -2.2512917986064953)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.9808292530117262, unseen = -2.772588722239781,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("gi\225ng sinh", -2.0149030205422647),
+                                    ("time-of-day gi\7901", -2.0149030205422647),
+                                    ("<hour-of-day> <integer>", -2.0149030205422647),
+                                    ("day", -2.0149030205422647), ("hour", -2.0149030205422647),
+                                    ("minute", -2.0149030205422647)],
+                               n = 3}}),
+       ("<time-of-day> s\225ng|chi\7873u|t\7889i",
+        Classifier{okData =
+                     ClassData{prior = -5.129329438755058e-2,
+                               unseen = -4.465908118654584,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("exactly <time-of-day>", -3.7612001156935624),
+                                    ("time-of-day gi\7901", -1.318853080324358),
+                                    ("<hour-of-day> <integer>", -3.355735007585398),
+                                    ("about <time-of-day>", -3.355735007585398),
+                                    ("at hh:mm", -3.068052935133617),
+                                    ("(hour-of-day) half", -3.355735007585398),
+                                    ("hh:mm", -2.5084371471981943), ("hour", -1.1962507582320256),
+                                    ("minute", -1.8152899666382492)],
+                               n = 38},
+                   koData =
+                     ClassData{prior = -2.995732273553991, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -1.540445040947149),
+                                    ("hour", -1.540445040947149)],
+                               n = 2}}),
+       ("named-month",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("month (numeric with month symbol)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.4339872044851463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.2231435513142097),
+                                    ("integer (0..19)", -1.6094379124341003)],
+                               n = 28},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("week (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 13},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (numeric with year symbol)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 11},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("quarter <number> of <year>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("quarteryear", -1.0986122886681098),
+                                    ("quarter (grain)integer (numeric)year (numeric with year symbol)",
+                                     -1.0986122886681098)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -1.9459101490553135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("quarterhour", -1.0986122886681098),
+                                    ("quarter (grain)integer (numeric)time-of-day (latent)",
+                                     -1.0986122886681098)],
+                               n = 1}}),
+       ("afternoon",
+        Classifier{okData =
+                     ClassData{prior = -0.916290731874155, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -0.5108256237659907,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3}}),
+       ("this <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.6109179126442243,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.6376087894007967),
+                                    ("year (grain)", -2.1972245773362196),
+                                    ("week (grain)", -1.6376087894007967),
+                                    ("day", -2.890371757896165), ("quarter", -1.9740810260220096),
+                                    ("year", -2.1972245773362196),
+                                    ("quarter (grain)", -1.9740810260220096),
+                                    ("day (grain)", -2.890371757896165)],
+                               n = 14},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("minute (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.2876820724517809,
+                               unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -1.3862943611198906,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("about <time-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day gi\7901", -1.6739764335716716),
+                                    ("<time-of-day> s\225ng|chi\7873u|t\7889i",
+                                     -1.6739764335716716),
+                                    ("time-of-day (latent)", -1.6739764335716716),
+                                    ("hour", -0.8266785731844679)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("time-of-day (latent)",
+        Classifier{okData =
+                     ClassData{prior = -0.6567795363890705,
+                               unseen = -3.4339872044851463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.10536051565782628),
+                                    ("integer (0..19)", -2.3025850929940455)],
+                               n = 28},
+                   koData =
+                     ClassData{prior = -0.7308875085427924, unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.4989911661189879),
+                                    ("integer (0..19)", -0.9343092373768334)],
+                               n = 26}}),
+       ("at hh:mm",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("(hour-of-day) half",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day gi\7901", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -2.995732273553991,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -5.225746673713201,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -3.42859635585027),
+                                    ("integer (0..19)year (grain)", -4.527208644518379),
+                                    ("integer (numeric)day (grain)", -3.0231312477421053),
+                                    ("integer (0..19)hour (grain)", -3.834061463958434),
+                                    ("second", -3.6109179126442243),
+                                    ("integer (numeric)second (grain)", -3.6109179126442243),
+                                    ("integer (numeric)year (grain)", -2.655406467616788),
+                                    ("day", -2.822460552279954), ("year", -2.581298495463066),
+                                    ("integer (numeric)week (grain)", -4.121743536410215),
+                                    ("integer (0..19)month (grain)", -4.527208644518379),
+                                    ("hour", -2.001480000210124), ("month", -2.042301994730379),
+                                    ("integer (numeric)minute (grain)", -3.6109179126442243),
+                                    ("integer (numeric)month (grain)", -2.084861609149175),
+                                    ("minute", -3.6109179126442243),
+                                    ("integer (numeric)hour (grain)", -2.129313371720009),
+                                    ("integer (0..19)day (grain)", -4.121743536410215),
+                                    ("integer (0..19)week (grain)", -3.834061463958434)],
+                               n = 83}}),
+       ("<time-of-day> am|pm",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("time-of-day (latent)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("h\244m nay",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh:mm",
+        Classifier{okData =
+                     ClassData{prior = -8.701137698962981e-2,
+                               unseen = -2.5649493574615367,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 11},
+                   koData =
+                     ClassData{prior = -2.4849066497880004,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("named-day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.4339872044851463,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 29},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("second (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect by \"of\", \"from\", \"'s\"",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3025850929940455,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynext <cycle>", -1.5040773967762742),
+                                    ("dayweek", -0.8109302162163288),
+                                    ("named-daythis <cycle>", -1.0986122886681098)],
+                               n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<hour-of-day> <integer> ph\250t",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.6931471805599453),
+                                    ("time-of-day gi\7901integer (numeric)", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("noon",
+        Classifier{okData =
+                     ClassData{prior = -0.7621400520468967,
+                               unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -0.6286086594223742,
+                               unseen = -2.3025850929940455,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 8}}),
+       ("ordinals",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("(hour-of-day) quarter",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hour", -0.6931471805599453),
+                                    ("time-of-day gi\7901integer (numeric)", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("last <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.7731898882334817, unseen = -3.295836866004329,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.5649493574615367),
+                                    ("month (grain)", -2.5649493574615367),
+                                    ("year (grain)", -1.6486586255873816),
+                                    ("week (grain)", -2.5649493574615367),
+                                    ("year", -1.6486586255873816), ("month", -2.5649493574615367)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.6190392084062235, unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("month (grain)", -2.2335922215070942),
+                                    ("hour (grain)", -2.639057329615259),
+                                    ("year (grain)", -2.639057329615259),
+                                    ("second", -2.639057329615259), ("day", -2.639057329615259),
+                                    ("minute (grain)", -2.639057329615259),
+                                    ("year", -2.639057329615259),
+                                    ("second (grain)", -2.639057329615259),
+                                    ("hour", -2.639057329615259), ("month", -2.2335922215070942),
+                                    ("minute", -2.639057329615259),
+                                    ("day (grain)", -2.639057329615259)],
+                               n = 7}}),
+       ("next n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.1431008436406733,
+                               unseen = -3.7612001156935624,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)day (grain)", -2.639057329615259),
+                                    ("second", -2.3513752571634776),
+                                    ("integer (numeric)second (grain)", -2.3513752571634776),
+                                    ("integer (numeric)year (grain)", -2.639057329615259),
+                                    ("day", -2.639057329615259), ("year", -2.639057329615259),
+                                    ("integer (0..19)month (grain)", -3.044522437723423),
+                                    ("hour", -2.639057329615259), ("month", -2.639057329615259),
+                                    ("integer (numeric)minute (grain)", -2.639057329615259),
+                                    ("integer (numeric)month (grain)", -3.044522437723423),
+                                    ("minute", -2.639057329615259),
+                                    ("integer (numeric)hour (grain)", -2.639057329615259)],
+                               n = 13},
+                   koData =
+                     ClassData{prior = -2.0149030205422647, unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.8971199848858813),
+                                    ("integer (numeric)week (grain)", -2.3025850929940455),
+                                    ("integer (0..19)week (grain)", -2.3025850929940455)],
+                               n = 2}}),
+       ("<day-of-month> <named-month>",
+        Classifier{okData =
+                     ClassData{prior = -0.10008345855698253,
+                               unseen = -3.7376696182833684,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)month (numeric with month symbol)",
+                                     -0.8232003088081431),
+                                    ("integer (numeric)named-month", -2.6149597780361984),
+                                    ("month", -0.7178397931503169)],
+                               n = 19},
+                   koData =
+                     ClassData{prior = -2.3513752571634776,
+                               unseen = -2.0794415416798357,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)month (numeric with month symbol)",
+                                     -0.8472978603872037),
+                                    ("month", -0.8472978603872037)],
+                               n = 2}}),
+       ("last n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.4657359027997265,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)day (grain)", -2.3353749158170367),
+                                    ("integer (0..19)hour (grain)", -2.740840023925201),
+                                    ("second", -2.740840023925201),
+                                    ("integer (numeric)second (grain)", -2.740840023925201),
+                                    ("integer (numeric)year (grain)", -2.740840023925201),
+                                    ("day", -2.3353749158170367), ("year", -2.740840023925201),
+                                    ("hour", -2.3353749158170367), ("month", -2.3353749158170367),
+                                    ("integer (numeric)minute (grain)", -2.740840023925201),
+                                    ("integer (numeric)month (grain)", -2.3353749158170367),
+                                    ("minute", -2.740840023925201),
+                                    ("integer (numeric)hour (grain)", -2.740840023925201)],
+                               n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("quarter (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 13},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("morning",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("day (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.6190392084062235,
+                               unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -0.7731898882334817,
+                               unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6}}),
+       ("<part-of-day> (h\244m )?nay",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("noon", -1.0116009116784799), ("hour", -0.7884573603642702),
+                                    ("morning", -1.7047480922384253)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<day-of-month> (numeric with day symbol) <named-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.784189633918261,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)month (numeric with month symbol)",
+                                     -0.8167611365271219),
+                                    ("integer (numeric)named-month", -2.662587827025453),
+                                    ("month", -0.7166776779701395)],
+                               n = 20},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}})]
diff --git a/Duckling/Ranking/Classifiers/ZH.hs b/Duckling/Ranking/Classifiers/ZH.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Classifiers/ZH.hs
@@ -0,0 +1,753 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+-----------------------------------------------------------------
+-- Auto-generated by regenClassifiers
+--
+-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+--  @generated
+-----------------------------------------------------------------
+{-# LANGUAGE OverloadedStrings #-}
+module Duckling.Ranking.Classifiers.ZH (classifiers) where
+import Prelude
+import Duckling.Ranking.Types
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+
+classifiers :: Classifiers
+classifiers
+  = HashMap.fromList
+      [("<time> timezone",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<time-of-day> am|pm", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (numeric)",
+        Classifier{okData =
+                     ClassData{prior = -0.5292593254548287,
+                               unseen = -3.8066624897703196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 43},
+                   koData =
+                     ClassData{prior = -0.8892620594862358,
+                               unseen = -3.4657359027997265,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 30}}),
+       ("the day before yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("today",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("mm/dd",
+        Classifier{okData =
+                     ClassData{prior = -1.6094379124341003,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -0.2231435513142097, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4}}),
+       ("absorption of , after named day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("tonight",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("month (grain)",
+        Classifier{okData =
+                     ClassData{prior = -1.0296194171811581,
+                               unseen = -3.0910424533583156,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 20},
+                   koData =
+                     ClassData{prior = -0.4418327522790392,
+                               unseen = -3.6375861597263857,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 36}}),
+       ("<time-of-day> o'clock",
+        Classifier{okData =
+                     ClassData{prior = -0.5108256237659907, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -0.916290731874155, unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)", -0.6931471805599453),
+                                    ("hour", -0.6931471805599453)],
+                               n = 4}}),
+       ("national day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hour (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect",
+        Classifier{okData =
+                     ClassData{prior = -0.4473122180436648, unseen = -4.553876891600541,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("children's day<part-of-day> <dim time>", -3.4446824936018943),
+                                    ("year (numeric with year symbol)<named-month> <day-of-month>",
+                                     -1.547562508716013),
+                                    ("dayday", -1.978345424808467),
+                                    ("hourhour", -2.9338568698359033),
+                                    ("hourminute", -3.4446824936018943),
+                                    ("absorption of , after named day<named-month> <day-of-month>",
+                                     -1.978345424808467),
+                                    ("dayminute", -3.4446824936018943),
+                                    ("tonight<time-of-day> o'clock", -2.9338568698359033),
+                                    ("<dim time> <part-of-day>relative minutes after|past <integer> (hour-of-day)",
+                                     -3.4446824936018943),
+                                    ("yearday", -1.547562508716013)],
+                               n = 39},
+                   koData =
+                     ClassData{prior = -1.0198314108149953, unseen = -4.110873864173311,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("children's day<part-of-day> <dim time>", -2.1484344131667874),
+                                    ("dayhour", -2.4849066497880004),
+                                    ("daymonth", -2.1484344131667874),
+                                    ("<dim time> <part-of-day><time-of-day> o'clock",
+                                     -2.995732273553991),
+                                    ("year (numeric with year symbol)named-month",
+                                     -2.1484344131667874),
+                                    ("hourhour", -2.995732273553991),
+                                    ("hourminute", -2.995732273553991),
+                                    ("absorption of , after named daynamed-month",
+                                     -2.1484344131667874),
+                                    ("yearmonth", -2.1484344131667874),
+                                    ("dayminute", -2.995732273553991),
+                                    ("<dim time> <part-of-day>relative minutes after|past <integer> (hour-of-day)",
+                                     -2.995732273553991)],
+                               n = 22}}),
+       ("year (grain)",
+        Classifier{okData =
+                     ClassData{prior = -1.2809338454620642,
+                               unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -0.325422400434628, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 13}}),
+       ("last tuesday, last july",
+        Classifier{okData =
+                     ClassData{prior = -1.3350010667323402, unseen = -3.332204510175204,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.8979415932059586),
+                                    ("named-day", -0.8979415932059586)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -0.3053816495511819,
+                               unseen = -4.1588830833596715,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)", -1.0076405104623831),
+                                    ("named-month", -3.4499875458315876),
+                                    ("month (numeric with month symbol)", -2.3513752571634776),
+                                    ("hour", -1.0076405104623831), ("month", -2.1972245773362196)],
+                               n = 28}}),
+       ("next <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.7884573603642702, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.540445040947149),
+                                    ("month (grain)", -1.252762968495368),
+                                    ("week (grain)", -1.540445040947149),
+                                    ("month", -1.252762968495368)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.6061358035703156, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -0.8266785731844679),
+                                    ("week (grain)", -0.8266785731844679)],
+                               n = 6}}),
+       ("last year",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <day-of-week>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.295836866004329,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 12},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("yyyy-mm-dd",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("mm/dd/yyyy",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("evening|night",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("yesterday",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("next year",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("hh:mm (time-of-day)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<integer> (latent time-of-day)",
+        Classifier{okData =
+                     ClassData{prior = -1.540445040947149, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -1.0296194171811581),
+                                    ("integer (0..10)", -0.4418327522790392)],
+                               n = 12},
+                   koData =
+                     ClassData{prior = -0.2411620568168881,
+                               unseen = -3.8501476017100584,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -2.2192034840549946),
+                                    ("integer (0..10)", -0.11506932978478723)],
+                               n = 44}}),
+       ("nth <time> of <time>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("monthday", -0.7884573603642702),
+                                    ("month (numeric with month symbol)ordinal (digits)named-day",
+                                     -1.2992829841302609),
+                                    ("named-monthordinal (digits)named-day", -1.2992829841302609)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("named-month",
+        Classifier{okData =
+                     ClassData{prior = -4.255961441879589e-2,
+                               unseen = -3.2188758248682006,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 23},
+                   koData =
+                     ClassData{prior = -3.1780538303479458,
+                               unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1}}),
+       ("month (numeric with month symbol)",
+        Classifier{okData =
+                     ClassData{prior = -0.15415067982725836,
+                               unseen = -3.6635616461296463,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("integer (numeric)", -0.9985288301111273),
+                                    ("integer (0..10)", -0.4595323293784402)],
+                               n = 36},
+                   koData =
+                     ClassData{prior = -1.9459101490553135,
+                               unseen = -2.1972245773362196,
+                               likelihoods =
+                                 HashMap.fromList [("integer (0..10)", -0.13353139262452263)],
+                               n = 6}}),
+       ("week (grain)",
+        Classifier{okData =
+                     ClassData{prior = -0.8754687373538999,
+                               unseen = -3.0910424533583156,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 20},
+                   koData =
+                     ClassData{prior = -0.5389965007326869,
+                               unseen = -3.4011973816621555,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 28}}),
+       ("valentine's day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("year (numeric with year symbol)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 13},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("now",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("numbers prefix with -, negative or minus",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("integer (numeric)", 0.0)],
+                               n = 4}}),
+       ("tomorrow",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this year",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 1},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("afternoon",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.9459101490553135,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 5},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.8754687373538999,
+                               unseen = -3.2188758248682006,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -0.9808292530117262),
+                                    ("year (grain)", -2.0794415416798357),
+                                    ("week (grain)", -0.9808292530117262),
+                                    ("year", -2.0794415416798357)],
+                               n = 10},
+                   koData =
+                     ClassData{prior = -0.5389965007326869,
+                               unseen = -3.4965075614664802,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -0.7576857016975165),
+                                    ("week (grain)", -0.7576857016975165)],
+                               n = 14}}),
+       ("minute (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 12},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<dim time> <part-of-day>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -3.9889840465642745,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayhour", -0.7514160886839211),
+                                    ("national dayevening|night", -2.871679624884012),
+                                    ("<named-month> <day-of-month>morning", -1.405342556090585),
+                                    ("named-daymorning", -1.7730673362159024),
+                                    ("children's dayafternoon", -2.871679624884012)],
+                               n = 24},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<part-of-day> <dim time>",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -3.5553480614894135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("tonight<integer> (latent time-of-day)", -1.916922612182061),
+                                    ("hourhour", -1.329135947279942),
+                                    ("afternoonrelative minutes after|past <integer> (hour-of-day)",
+                                     -1.916922612182061),
+                                    ("hourminute", -1.7346010553881064),
+                                    ("afternoonhh:mm (time-of-day)", -2.833213344056216),
+                                    ("tonight<time-of-day> o'clock", -1.916922612182061)],
+                               n = 13},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -3.5553480614894135,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hourhour", -1.2237754316221157),
+                                    ("afternoonrelative minutes after|past <integer> (hour-of-day)",
+                                     -1.916922612182061),
+                                    ("afternoon<time-of-day> o'clock", -1.916922612182061),
+                                    ("hourminute", -1.916922612182061),
+                                    ("afternoon<integer> (latent time-of-day)",
+                                     -1.7346010553881064)],
+                               n = 13}}),
+       ("<integer> <unit-of-duration>",
+        Classifier{okData =
+                     ClassData{prior = -infinity, unseen = -2.833213344056216,
+                               likelihoods = HashMap.fromList [], n = 0},
+                   koData =
+                     ClassData{prior = 0.0, unseen = -5.54907608489522,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.9802280870180256),
+                                    ("integer (0..10)month (grain)", -1.9075912847531766),
+                                    ("integer (0..10)hour (grain)", -2.9802280870180256),
+                                    ("second", -3.242592351485517),
+                                    ("integer (0..10)day (grain)", -3.4657359027997265),
+                                    ("integer (0..10)year (grain)", -3.7534179752515073),
+                                    ("integer (numeric)year (grain)", -2.906120114864304),
+                                    ("integer (0..10)second (grain)", -3.242592351485517),
+                                    ("day", -3.4657359027997265), ("year", -2.600738465313122),
+                                    ("integer (0..10)minute (grain)", -2.9802280870180256),
+                                    ("hour", -2.9802280870180256),
+                                    ("integer (0..10)week (grain)", -2.9802280870180256),
+                                    ("month", -1.6133518117552368),
+                                    ("integer (numeric)month (grain)", -2.906120114864304),
+                                    ("minute", -2.9802280870180256)],
+                               n = 120}}),
+       ("integer (11..19)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.639057329615259,
+                               likelihoods = HashMap.fromList [("integer (0..10)", 0.0)], n = 12},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<time-of-day> am|pm",
+        Classifier{okData =
+                     ClassData{prior = -0.4700036292457356, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("hh:mm (time-of-day)", -1.252762968495368),
+                                    ("<integer> (latent time-of-day)", -1.540445040947149),
+                                    ("hour", -1.540445040947149), ("minute", -1.252762968495368)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.9808292530117262,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)", -0.916290731874155),
+                                    ("hour", -0.916290731874155)],
+                               n = 3}}),
+       ("relative minutes after|past <integer> (hour-of-day)",
+        Classifier{okData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)integer (11..19)",
+                                     -0.7884573603642702),
+                                    ("hour", -0.7884573603642702)],
+                               n = 4},
+                   koData =
+                     ClassData{prior = -0.6931471805599453,
+                               unseen = -2.4849066497880004,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)integer (0..10)",
+                                     -0.7884573603642702),
+                                    ("hour", -0.7884573603642702)],
+                               n = 4}}),
+       ("army's day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("intersect by \",\"",
+        Classifier{okData =
+                     ClassData{prior = -0.40546510810816444,
+                               unseen = -3.367295829986474,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("dayday", -0.7672551527136672),
+                                    ("named-day<named-month> <day-of-month>", -0.7672551527136672)],
+                               n = 12},
+                   koData =
+                     ClassData{prior = -1.0986122886681098, unseen = -2.833213344056216,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("named-daynamed-month", -0.8266785731844679),
+                                    ("daymonth", -0.8266785731844679)],
+                               n = 6}}),
+       ("named-day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.406719247264253,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 80},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("second (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.3978952727983707,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 9},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("the day after tomorrow",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("next <time>",
+        Classifier{okData =
+                     ClassData{prior = -2.2512917986064953,
+                               unseen = -2.3978952727983707,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -1.2039728043259361),
+                                    ("named-day", -1.2039728043259361)],
+                               n = 2},
+                   koData =
+                     ClassData{prior = -0.11122563511022437,
+                               unseen = -3.713572066704308,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)", -1.1239300966523995),
+                                    ("month (numeric with month symbol)", -2.995732273553991),
+                                    ("day", -2.0794415416798357),
+                                    ("named-day", -2.0794415416798357),
+                                    ("hour", -1.1239300966523995), ("month", -2.995732273553991)],
+                               n = 17}}),
+       ("last <cycle>",
+        Classifier{okData =
+                     ClassData{prior = -0.9555114450274363, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -1.540445040947149),
+                                    ("month (grain)", -1.252762968495368),
+                                    ("week (grain)", -1.540445040947149),
+                                    ("month", -1.252762968495368)],
+                               n = 5},
+                   koData =
+                     ClassData{prior = -0.48550781578170077,
+                               unseen = -3.044522437723423,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -0.7985076962177716),
+                                    ("week (grain)", -0.7985076962177716)],
+                               n = 8}}),
+       ("christmas",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("new year's day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.6094379124341003,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 3},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("next n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.07753744390572,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.451005098112319),
+                                    ("integer (0..10)month (grain)", -2.6741486494265287),
+                                    ("integer (0..10)hour (grain)", -2.451005098112319),
+                                    ("second", -2.6741486494265287),
+                                    ("integer (0..10)day (grain)", -2.6741486494265287),
+                                    ("integer (0..10)year (grain)", -3.367295829986474),
+                                    ("integer (0..10)second (grain)", -2.6741486494265287),
+                                    ("day", -2.6741486494265287), ("year", -3.367295829986474),
+                                    ("integer (0..10)minute (grain)", -2.451005098112319),
+                                    ("hour", -2.451005098112319),
+                                    ("integer (0..10)week (grain)", -2.451005098112319),
+                                    ("month", -2.6741486494265287), ("minute", -2.451005098112319)],
+                               n = 22},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("children's day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.791759469228055,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 4},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("<named-month> <day-of-month>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.770684624465665,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("month (numeric with month symbol)integer (11..19)",
+                                     -2.5649493574615367),
+                                    ("named-monthinteger (11..19)", -2.5649493574615367),
+                                    ("named-monthinteger (numeric)", -3.6635616461296463),
+                                    ("month (numeric with month symbol)integer (numeric)",
+                                     -2.1231166051824975),
+                                    ("month", -0.7368222440626069),
+                                    ("month (numeric with month symbol)integer (0..10)",
+                                     -2.1231166051824975),
+                                    ("named-monthinteger (0..10)", -2.277267285009756)],
+                               n = 55},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.0794415416798357,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("integer (0..10)",
+        Classifier{okData =
+                     ClassData{prior = -0.5010216236693698,
+                               unseen = -4.8283137373023015,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 123},
+                   koData =
+                     ClassData{prior = -0.9311793443679057, unseen = -4.406719247264253,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 80}}),
+       ("last n <cycle>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -4.61512051684126,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("week", -2.995732273553991),
+                                    ("integer (0..10)month (grain)", -2.120263536200091),
+                                    ("integer (0..10)hour (grain)", -2.4079456086518722),
+                                    ("second", -2.659260036932778),
+                                    ("integer (0..10)day (grain)", -2.995732273553991),
+                                    ("integer (0..10)year (grain)", -3.506557897319982),
+                                    ("integer (0..10)second (grain)", -2.659260036932778),
+                                    ("day", -2.995732273553991), ("year", -3.506557897319982),
+                                    ("integer (0..10)minute (grain)", -2.4079456086518722),
+                                    ("hour", -2.4079456086518722),
+                                    ("integer (0..10)week (grain)", -2.995732273553991),
+                                    ("month", -2.120263536200091), ("minute", -2.4079456086518722)],
+                               n = 43},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -2.70805020110221,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this|next <day-of-week>",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.70805020110221,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.6931471805599453),
+                                    ("named-day", -0.6931471805599453)],
+                               n = 6},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -1.0986122886681098,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("ordinal (digits)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("integer (0..10)", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("morning",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.890371757896165,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 16},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("week-end",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("day (grain)",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -2.1972245773362196,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 7},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("labor day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("women's day",
+        Classifier{okData =
+                     ClassData{prior = 0.0, unseen = -1.3862943611198906,
+                               likelihoods = HashMap.fromList [("", 0.0)], n = 2},
+                   koData =
+                     ClassData{prior = -infinity, unseen = -0.6931471805599453,
+                               likelihoods = HashMap.fromList [], n = 0}}),
+       ("this <time>",
+        Classifier{okData =
+                     ClassData{prior = -0.35667494393873245,
+                               unseen = -3.5263605246161616,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("day", -0.9315582040049435),
+                                    ("named-day", -0.9315582040049435),
+                                    ("hour", -2.3978952727983707),
+                                    ("week-end", -2.3978952727983707)],
+                               n = 14},
+                   koData =
+                     ClassData{prior = -1.2039728043259361, unseen = -2.890371757896165,
+                               likelihoods =
+                                 HashMap.fromList
+                                   [("<integer> (latent time-of-day)", -0.8873031950009028),
+                                    ("hour", -0.8873031950009028)],
+                               n = 6}})]
diff --git a/Duckling/Ranking/Extraction.hs b/Duckling/Ranking/Extraction.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Extraction.hs
@@ -0,0 +1,45 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+module Duckling.Ranking.Extraction
+  ( extractFeatures
+  ) where
+
+import qualified Data.HashMap.Strict as HashMap
+import Data.Maybe
+import qualified Data.Text as Text
+import Prelude
+import TextShow (showt)
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Types (DurationData (DurationData))
+import qualified Duckling.Duration.Types as TDuration
+import Duckling.Ranking.Types
+import Duckling.Time.Types (TimeData (TimeData))
+import qualified Duckling.Time.Types as TTime
+import Duckling.Types
+
+
+-- | Feature extraction
+-- | Features:
+-- | 1) Concatenation of the names of the rules involved in parsing `Node`
+-- | 2) Concatenation of the grains for time-like dimensions
+extractFeatures :: Node -> BagOfFeatures
+extractFeatures node =
+  HashMap.fromList $ (featRules, 1) : [ (featGrain, 1) | not (null grains) ]
+  where
+    featRules = Text.concat $ mapMaybe rule (children node)
+    grains = mapMaybe (\x ->
+      case token x of
+        Token Duration (DurationData {TDuration.grain = g}) -> Just $ showt g
+        Token Time (TimeData {TTime.timeGrain = g}) -> Just $ showt g
+        Token TimeGrain g -> Just $ showt g
+        _ -> Nothing
+      ) $ children node
+    featGrain = Text.concat grains
diff --git a/Duckling/Ranking/Rank.hs b/Duckling/Ranking/Rank.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Rank.hs
@@ -0,0 +1,68 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+
+module Duckling.Ranking.Rank
+  ( rank
+  ) where
+
+import Control.Arrow ((***))
+import Control.Monad (join)
+import qualified Data.HashMap.Strict as HashMap
+import Data.HashSet (HashSet)
+import qualified Data.HashSet as HashSet
+import Data.Maybe
+import qualified Data.Set as Set
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Ranking.Extraction
+import Duckling.Ranking.Types
+import Duckling.Types
+
+classify :: Classifier -> BagOfFeatures -> (Class, Double)
+classify Classifier {..} feats = if okScore >= koScore
+  then (True, okScore)
+  else (False, koScore)
+  where
+    (okScore, koScore) = join (***) (p feats) (okData, koData)
+    p :: BagOfFeatures -> ClassData -> Double
+    p feats ClassData{..} =
+      prior + HashMap.foldrWithKey (\feat x res ->
+        res + fromIntegral x * HashMap.lookupDefault unseen feat likelihoods
+      ) 0.0 feats
+
+score :: Classifiers -> Node -> Double
+score classifiers node@Node {rule = Just rule, ..} =
+  case HashMap.lookup rule classifiers of
+    Just c -> let feats = extractFeatures node
+      in snd (classify c feats) + sum (map (score classifiers) children)
+    Nothing -> 0.0
+score _ Node {rule = Nothing} = 0.0
+
+-- | Return all superior candidates, as defined by the partial ordering
+winners :: Ord a => [a] -> [a]
+winners xs = filter (\x -> all ((/=) LT . compare x) xs) xs
+
+-- | Return a curated list of tokens
+rank
+  :: Classifiers
+  -> HashSet (Some Dimension)
+  -> [ResolvedToken]
+  -> [ResolvedToken]
+rank classifiers targets tokens =
+  Set.toList . Set.fromList
+  . map (\(Candidate token _ _) -> token)
+  . winners
+  $ map makeCandidate tokens
+  where
+    makeCandidate :: ResolvedToken -> Candidate
+    makeCandidate token@Resolved {node = n@Node {token = Token d _}} =
+      Candidate token (score classifiers n) $ HashSet.member (This d) targets
diff --git a/Duckling/Ranking/Types.hs b/Duckling/Ranking/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Ranking/Types.hs
@@ -0,0 +1,58 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Ranking.Types
+  ( Feature
+  , BagOfFeatures
+  , Class
+  , Datum
+  , Dataset
+
+  , Classifier(..)
+  , Classifiers
+  , ClassData(..)
+
+  , infinity
+  ) where
+import Data.HashMap.Strict (HashMap)
+import Data.Text (Text)
+import Prelude
+
+-- -----------------------------------------------------------------
+-- Aliases
+
+type Feature = Text
+type BagOfFeatures = HashMap Feature Int
+type Class = Bool
+type Datum = (BagOfFeatures, Class)
+type Dataset = HashMap Text [Datum]
+
+-- -----------------------------------------------------------------
+-- Classification
+
+data Classifier = Classifier
+  { okData :: ClassData
+  , koData :: ClassData
+  }
+  deriving (Eq, Show)
+
+type Classifiers = HashMap Text Classifier
+
+data ClassData = ClassData
+  { prior :: Double
+  , unseen :: Double
+  , likelihoods :: HashMap Feature Double
+  , n :: Int
+  }
+  deriving (Eq, Show)
+
+infinity :: Double
+infinity = 1 / 0
diff --git a/Duckling/Regex/Types.hs b/Duckling/Regex/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Regex/Types.hs
@@ -0,0 +1,30 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Duckling.Regex.Types
+  ( GroupMatch(..)
+  ) where
+
+import Control.DeepSeq
+import Data.Hashable
+import Data.Text (Text)
+import GHC.Generics
+import Prelude
+import Duckling.Resolve (Resolve(..))
+
+data GroupMatch = GroupMatch [Text]
+  deriving (Eq, Generic, Hashable, Ord, Show, NFData)
+
+instance Resolve GroupMatch where
+  type ResolvedValue GroupMatch = ()
+  resolve _ _ = Nothing
diff --git a/Duckling/Resolve.hs b/Duckling/Resolve.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Resolve.hs
@@ -0,0 +1,60 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Duckling.Resolve
+  ( Context(..)
+  , DucklingTime(..)
+  , Resolve(..)
+  , fromUTC
+  , fromZonedTime
+  , toUTC
+  ) where
+
+import Data.Aeson
+import Data.String
+import qualified Data.Time as Time
+import qualified Data.Time.LocalTime.TimeZone.Series as Series
+import Prelude
+
+import Duckling.Lang
+
+-- | Internal time reference.
+-- We work as if we were in UTC time and use `ZoneSeriesTime` to house the info.
+-- We convert to local time at resolution, using `fromUTC`.
+newtype DucklingTime = DucklingTime Series.ZoneSeriesTime
+  deriving (Eq, Show)
+
+data Context = Context
+  { referenceTime :: DucklingTime
+  , lang :: Lang
+  }
+  deriving (Eq, Show)
+
+class ToJSON (ResolvedValue a) => Resolve a where
+  type ResolvedValue a
+  resolve :: Context -> a -> Maybe (ResolvedValue a)
+
+fromZonedTime :: Time.ZonedTime -> DucklingTime
+fromZonedTime (Time.ZonedTime localTime timeZone) = DucklingTime $
+  Series.ZoneSeriesTime (toUTC localTime) (Series.TimeZoneSeries timeZone [])
+
+-- | Given a UTCTime and an TimeZone, build a ZonedTime (no conversion)
+fromUTC :: Time.UTCTime -> Time.TimeZone -> Time.ZonedTime
+fromUTC (Time.UTCTime day diffTime) timeZone = Time.ZonedTime localTime timeZone
+  where
+    localTime = Time.LocalTime day timeOfDay
+    timeOfDay = Time.timeToTimeOfDay diffTime
+
+-- | Given a LocalTime, build a UTCTime (no conversion)
+toUTC :: Time.LocalTime -> Time.UTCTime
+toUTC (Time.LocalTime day timeOfDay) = Time.UTCTime day diffTime
+  where
+    diffTime = Time.timeOfDayToTime timeOfDay
diff --git a/Duckling/Rules.hs b/Duckling/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules.hs
@@ -0,0 +1,95 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+
+module Duckling.Rules
+  ( allRules
+  , rulesFor
+  ) where
+
+import Data.HashSet (HashSet)
+import Prelude
+import qualified Data.HashSet as HashSet
+
+import Duckling.Dimensions
+import Duckling.Dimensions.Types
+import Duckling.Lang
+import qualified Duckling.Rules.AR as ARRules
+import qualified Duckling.Rules.Common as CommonRules
+import qualified Duckling.Rules.DA as DARules
+import qualified Duckling.Rules.DE as DERules
+import qualified Duckling.Rules.EN as ENRules
+import qualified Duckling.Rules.ES as ESRules
+import qualified Duckling.Rules.ET as ETRules
+import qualified Duckling.Rules.FR as FRRules
+import qualified Duckling.Rules.GA as GARules
+import qualified Duckling.Rules.HE as HERules
+import qualified Duckling.Rules.HR as HRRules
+import qualified Duckling.Rules.ID as IDRules
+import qualified Duckling.Rules.IT as ITRules
+import qualified Duckling.Rules.JA as JARules
+import qualified Duckling.Rules.KO as KORules
+import qualified Duckling.Rules.MY as MYRules
+import qualified Duckling.Rules.NB as NBRules
+import qualified Duckling.Rules.NL as NLRules
+import qualified Duckling.Rules.PL as PLRules
+import qualified Duckling.Rules.PT as PTRules
+import qualified Duckling.Rules.RO as RORules
+import qualified Duckling.Rules.RU as RURules
+import qualified Duckling.Rules.SV as SVRules
+import qualified Duckling.Rules.TR as TRRules
+import qualified Duckling.Rules.UK as UKRules
+import qualified Duckling.Rules.VI as VIRules
+import qualified Duckling.Rules.ZH as ZHRules
+import Duckling.Types
+
+-- | Returns the minimal set of rules required for `targets`.
+rulesFor :: Lang -> HashSet (Some Dimension) -> [Rule]
+rulesFor lang targets
+  | HashSet.null targets = allRules lang
+  | otherwise = [ rules | dims <- HashSet.toList $ explicitDimensions targets
+                        , rules <- rulesFor' lang dims ]
+
+-- | Returns all the rules for `lang`.
+-- We can't really use `allDimensions` as-is, since `TimeGrain` is not present.
+allRules :: Lang -> [Rule]
+allRules lang = concatMap (rulesFor' lang) . HashSet.toList .
+  explicitDimensions . HashSet.fromList $ allDimensions lang
+
+rulesFor' :: Lang -> Some Dimension -> [Rule]
+rulesFor' lang dim = CommonRules.rules dim ++ langRules lang dim
+
+langRules :: Lang -> Some Dimension -> [Rule]
+langRules AR = ARRules.rules
+langRules DA = DARules.rules
+langRules DE = DERules.rules
+langRules EN = ENRules.rules
+langRules ES = ESRules.rules
+langRules ET = ETRules.rules
+langRules FR = FRRules.rules
+langRules GA = GARules.rules
+langRules HE = HERules.rules
+langRules HR = HRRules.rules
+langRules ID = IDRules.rules
+langRules IT = ITRules.rules
+langRules JA = JARules.rules
+langRules KO = KORules.rules
+langRules MY = MYRules.rules
+langRules NB = NBRules.rules
+langRules NL = NLRules.rules
+langRules PL = PLRules.rules
+langRules PT = PTRules.rules
+langRules RO = RORules.rules
+langRules RU = RURules.rules
+langRules SV = SVRules.rules
+langRules TR = TRRules.rules
+langRules UK = UKRules.rules
+langRules VI = VIRules.rules
+langRules ZH = ZHRules.rules
diff --git a/Duckling/Rules/AR.hs b/Duckling/Rules/AR.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/AR.hs
@@ -0,0 +1,35 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.AR
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Numeral.AR.Rules as Numeral
+import qualified Duckling.Ordinal.AR.Rules as Ordinal
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = []
+rules (This Duration) = []
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = []
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = []
+rules (This Time) = []
+rules (This TimeGrain) = []
+rules (This Url) = []
+rules (This Volume) = []
diff --git a/Duckling/Rules/Common.hs b/Duckling/Rules/Common.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/Common.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.Common
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Distance.Rules as Distance
+import qualified Duckling.Duration.Rules as Duration
+import qualified Duckling.Email.Rules as Email
+import qualified Duckling.AmountOfMoney.Rules as AmountOfMoney
+import qualified Duckling.PhoneNumber.Rules as PhoneNumber
+import qualified Duckling.Temperature.Rules as Temperature
+import Duckling.Types
+import qualified Duckling.Url.Rules as Url
+import qualified Duckling.Volume.Rules as Volume
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = Distance.rules
+rules (This Duration) = Duration.rules
+rules (This Numeral) = []
+rules (This Email) = Email.rules
+rules (This AmountOfMoney) = AmountOfMoney.rules
+rules (This Ordinal) = []
+rules (This PhoneNumber) = PhoneNumber.rules
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = Temperature.rules
+rules (This Time) = []
+rules (This TimeGrain) = []
+rules (This Url) = Url.rules
+rules (This Volume) = Volume.rules
diff --git a/Duckling/Rules/DA.hs b/Duckling/Rules/DA.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/DA.hs
@@ -0,0 +1,38 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.DA
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Duration.DA.Rules as Duration
+import qualified Duckling.Numeral.DA.Rules as Numeral
+import qualified Duckling.Ordinal.DA.Rules as Ordinal
+import qualified Duckling.Time.DA.Rules as Time
+import qualified Duckling.TimeGrain.DA.Rules as TimeGrain
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = []
+rules (This Duration) = Duration.rules
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = []
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = []
+rules (This Time) = Time.rules
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = []
diff --git a/Duckling/Rules/DE.hs b/Duckling/Rules/DE.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/DE.hs
@@ -0,0 +1,38 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.DE
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Duration.DE.Rules as Duration
+import qualified Duckling.Ordinal.DE.Rules as Ordinal
+import qualified Duckling.Numeral.DE.Rules as Numeral
+import qualified Duckling.Time.DE.Rules as Time
+import qualified Duckling.TimeGrain.DE.Rules as TimeGrain
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = []
+rules (This Duration) = Duration.rules
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = []
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = []
+rules (This Time) = Time.rules
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = []
diff --git a/Duckling/Rules/EN.hs b/Duckling/Rules/EN.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/EN.hs
@@ -0,0 +1,44 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.EN
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.AmountOfMoney.EN.Rules as AmountOfMoney
+import qualified Duckling.Distance.EN.Rules as Distance
+import qualified Duckling.Duration.EN.Rules as Duration
+import qualified Duckling.Email.EN.Rules as Email
+import qualified Duckling.Numeral.EN.Rules as Numeral
+import qualified Duckling.Ordinal.EN.Rules as Ordinal
+import qualified Duckling.Quantity.EN.Rules as Quantity
+import qualified Duckling.Temperature.EN.Rules as Temperature
+import qualified Duckling.Time.EN.Rules as Time
+import qualified Duckling.TimeGrain.EN.Rules as TimeGrain
+import Duckling.Types
+import qualified Duckling.Volume.EN.Rules as Volume
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = Distance.rules
+rules (This Duration) = Duration.rules
+rules (This Numeral) = Numeral.rules
+rules (This Email) = Email.rules
+rules (This AmountOfMoney) = AmountOfMoney.rules
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = Quantity.rules
+rules (This RegexMatch) = []
+rules (This Temperature) = Temperature.rules
+rules (This Time) = Time.rules
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = Volume.rules
diff --git a/Duckling/Rules/ES.hs b/Duckling/Rules/ES.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/ES.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.ES
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.AmountOfMoney.ES.Rules as AmountOfMoney
+import qualified Duckling.Distance.ES.Rules as Distance
+import qualified Duckling.Numeral.ES.Rules as Numeral
+import qualified Duckling.Ordinal.ES.Rules as Ordinal
+import qualified Duckling.Temperature.ES.Rules as Temperature
+import qualified Duckling.Time.ES.Rules as Time
+import qualified Duckling.TimeGrain.ES.Rules as TimeGrain
+import qualified Duckling.Volume.ES.Rules as Volume
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = Distance.rules
+rules (This Duration) = []
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = AmountOfMoney.rules
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = Temperature.rules
+rules (This Time) = Time.rules
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = Volume.rules
diff --git a/Duckling/Rules/ET.hs b/Duckling/Rules/ET.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/ET.hs
@@ -0,0 +1,35 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.ET
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Numeral.ET.Rules as Numeral
+import qualified Duckling.Ordinal.ET.Rules as Ordinal
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = []
+rules (This Duration) = []
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = []
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = []
+rules (This Time) = []
+rules (This TimeGrain) = []
+rules (This Url) = []
+rules (This Volume) = []
diff --git a/Duckling/Rules/FR.hs b/Duckling/Rules/FR.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/FR.hs
@@ -0,0 +1,44 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.FR
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.AmountOfMoney.FR.Rules as AmountOfMoney
+import qualified Duckling.Distance.FR.Rules as Distance
+import qualified Duckling.Duration.FR.Rules as Duration
+import qualified Duckling.Email.FR.Rules as Email
+import qualified Duckling.Numeral.FR.Rules as Numeral
+import qualified Duckling.Ordinal.FR.Rules as Ordinal
+import qualified Duckling.Quantity.FR.Rules as Quantity
+import qualified Duckling.Temperature.FR.Rules as Temperature
+import qualified Duckling.Time.FR.Rules as Time
+import qualified Duckling.TimeGrain.FR.Rules as TimeGrain
+import qualified Duckling.Volume.FR.Rules as Volume
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = Distance.rules
+rules (This Duration) = Duration.rules
+rules (This Numeral) = Numeral.rules
+rules (This Email) = Email.rules
+rules (This AmountOfMoney) = AmountOfMoney.rules
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = Quantity.rules
+rules (This RegexMatch) = []
+rules (This Temperature) = Temperature.rules
+rules (This Time) = Time.rules
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = Volume.rules
diff --git a/Duckling/Rules/GA.hs b/Duckling/Rules/GA.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/GA.hs
@@ -0,0 +1,42 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.GA
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.AmountOfMoney.GA.Rules as AmountOfMoney
+import qualified Duckling.Distance.GA.Rules as Distance
+import qualified Duckling.Duration.GA.Rules as Duration
+import qualified Duckling.Numeral.GA.Rules as Numeral
+import qualified Duckling.Ordinal.GA.Rules as Ordinal
+import qualified Duckling.Temperature.GA.Rules as Temperature
+import qualified Duckling.Time.GA.Rules as Time
+import qualified Duckling.TimeGrain.GA.Rules as TimeGrain
+import qualified Duckling.Volume.GA.Rules as Volume
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = Distance.rules
+rules (This Duration) = Duration.rules
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = AmountOfMoney.rules
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = Temperature.rules
+rules (This Time) = Time.rules
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = Volume.rules
diff --git a/Duckling/Rules/HE.hs b/Duckling/Rules/HE.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/HE.hs
@@ -0,0 +1,38 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.HE
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import Duckling.Types
+import qualified Duckling.Duration.HE.Rules as Duration
+import qualified Duckling.Numeral.HE.Rules as Numeral
+import qualified Duckling.Ordinal.HE.Rules as Ordinal
+import qualified Duckling.TimeGrain.HE.Rules as TimeGrain
+import qualified Duckling.Time.HE.Rules as Time
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = []
+rules (This Duration) = Duration.rules
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = []
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = []
+rules (This Time) = Time.rules
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = []
diff --git a/Duckling/Rules/HR.hs b/Duckling/Rules/HR.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/HR.hs
@@ -0,0 +1,43 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.HR
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import Duckling.Types
+import qualified Duckling.AmountOfMoney.HR.Rules as AmountOfMoney
+import qualified Duckling.Distance.HR.Rules as Distance
+import qualified Duckling.Duration.HR.Rules as Duration
+import qualified Duckling.Numeral.HR.Rules as Numeral
+import qualified Duckling.Ordinal.HR.Rules as Ordinal
+import qualified Duckling.Quantity.HR.Rules as Quantity
+import qualified Duckling.Temperature.HR.Rules as Temperature
+import qualified Duckling.Time.HR.Rules as Time
+import qualified Duckling.TimeGrain.HR.Rules as TimeGrain
+import qualified Duckling.Volume.HR.Rules as Volume
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = Distance.rules
+rules (This Duration) = Duration.rules
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = AmountOfMoney.rules
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = Quantity.rules
+rules (This RegexMatch) = []
+rules (This Temperature) = Temperature.rules
+rules (This Time) = Time.rules
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = Volume.rules
diff --git a/Duckling/Rules/ID.hs b/Duckling/Rules/ID.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/ID.hs
@@ -0,0 +1,36 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.ID
+  ( rules
+  ) where
+
+import qualified Duckling.AmountOfMoney.ID.Rules as AmountOfMoney
+import Duckling.Dimensions.Types
+import qualified Duckling.Numeral.ID.Rules as Numeral
+import qualified Duckling.Ordinal.ID.Rules as Ordinal
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = []
+rules (This Duration) = []
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = AmountOfMoney.rules
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = []
+rules (This Time) = []
+rules (This TimeGrain) = []
+rules (This Url) = []
+rules (This Volume) = []
diff --git a/Duckling/Rules/IT.hs b/Duckling/Rules/IT.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/IT.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.IT
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Duration.IT.Rules as Duration
+import qualified Duckling.Email.IT.Rules as Email
+import qualified Duckling.Numeral.IT.Rules as Numeral
+import qualified Duckling.Ordinal.IT.Rules as Ordinal
+import qualified Duckling.Temperature.IT.Rules as Temperature
+import qualified Duckling.Time.IT.Rules as Time
+import qualified Duckling.TimeGrain.IT.Rules as TimeGrain
+import qualified Duckling.Volume.IT.Rules as Volume
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = []
+rules (This Duration) = Duration.rules
+rules (This Numeral) = Numeral.rules
+rules (This Email) = Email.rules
+rules (This AmountOfMoney) = []
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = Temperature.rules
+rules (This Time) = Time.rules
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = Volume.rules
diff --git a/Duckling/Rules/JA.hs b/Duckling/Rules/JA.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/JA.hs
@@ -0,0 +1,37 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.JA
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Numeral.JA.Rules as Numeral
+import qualified Duckling.Ordinal.JA.Rules as Ordinal
+import qualified Duckling.Temperature.JA.Rules as Temperature
+import qualified Duckling.TimeGrain.JA.Rules as TimeGrain
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = []
+rules (This Duration) = []
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = []
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = Temperature.rules
+rules (This Time) = []
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = []
diff --git a/Duckling/Rules/KO.hs b/Duckling/Rules/KO.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/KO.hs
@@ -0,0 +1,43 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.KO
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.AmountOfMoney.KO.Rules as AmountOfMoney
+import qualified Duckling.Distance.KO.Rules as Distance
+import qualified Duckling.Duration.KO.Rules as Duration
+import qualified Duckling.Numeral.KO.Rules as Numeral
+import qualified Duckling.Ordinal.KO.Rules as Ordinal
+import qualified Duckling.Quantity.KO.Rules as Quantity
+import qualified Duckling.Temperature.KO.Rules as Temperature
+import qualified Duckling.Time.KO.Rules as Time
+import qualified Duckling.TimeGrain.KO.Rules as TimeGrain
+import Duckling.Types
+import qualified Duckling.Volume.KO.Rules as Volume
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = Distance.rules
+rules (This Duration) = Duration.rules
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = AmountOfMoney.rules
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = Quantity.rules
+rules (This RegexMatch) = []
+rules (This Temperature) = Temperature.rules
+rules (This Time) = Time.rules
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = Volume.rules
diff --git a/Duckling/Rules/MY.hs b/Duckling/Rules/MY.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/MY.hs
@@ -0,0 +1,34 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.MY
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Numeral.MY.Rules as Numeral
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = []
+rules (This Duration) = []
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = []
+rules (This Ordinal) = []
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = []
+rules (This Time) = []
+rules (This TimeGrain) = []
+rules (This Url) = []
+rules (This Volume) = []
diff --git a/Duckling/Rules/NB.hs b/Duckling/Rules/NB.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/NB.hs
@@ -0,0 +1,39 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.NB
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.AmountOfMoney.NB.Rules as AmountOfMoney
+import qualified Duckling.Duration.NB.Rules as Duration
+import qualified Duckling.Numeral.NB.Rules as Numeral
+import qualified Duckling.Ordinal.NB.Rules as Ordinal
+import qualified Duckling.Time.NB.Rules as Time
+import qualified Duckling.TimeGrain.NB.Rules as TimeGrain
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = []
+rules (This Duration) = Duration.rules
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = AmountOfMoney.rules
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = []
+rules (This Time) = Time.rules
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = []
diff --git a/Duckling/Rules/NL.hs b/Duckling/Rules/NL.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/NL.hs
@@ -0,0 +1,37 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.NL
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Distance.NL.Rules as Distance
+import qualified Duckling.Numeral.NL.Rules as Numeral
+import qualified Duckling.Ordinal.NL.Rules as Ordinal
+import qualified Duckling.Volume.NL.Rules as Volume
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = Distance.rules
+rules (This Duration) = []
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = []
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = []
+rules (This Time) = []
+rules (This TimeGrain) = []
+rules (This Url) = []
+rules (This Volume) = Volume.rules
diff --git a/Duckling/Rules/PL.hs b/Duckling/Rules/PL.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/PL.hs
@@ -0,0 +1,38 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.PL
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Duration.PL.Rules as Duration
+import qualified Duckling.Numeral.PL.Rules as Numeral
+import qualified Duckling.Ordinal.PL.Rules as Ordinal
+import qualified Duckling.Time.PL.Rules as Time
+import qualified Duckling.TimeGrain.PL.Rules as TimeGrain
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = []
+rules (This Duration) = Duration.rules
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = []
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = []
+rules (This Time) = Time.rules
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = []
diff --git a/Duckling/Rules/PT.hs b/Duckling/Rules/PT.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/PT.hs
@@ -0,0 +1,43 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.PT
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.AmountOfMoney.PT.Rules as AmountOfMoney
+import qualified Duckling.Distance.PT.Rules as Distance
+import qualified Duckling.Numeral.PT.Rules as Numeral
+import qualified Duckling.Ordinal.PT.Rules as Ordinal
+import qualified Duckling.PhoneNumber.PT.Rules as PhoneNumber
+import qualified Duckling.Quantity.PT.Rules as Quantity
+import qualified Duckling.Temperature.PT.Rules as Temperature
+import qualified Duckling.Time.PT.Rules as Time
+import qualified Duckling.TimeGrain.PT.Rules as TimeGrain
+import Duckling.Types
+import qualified Duckling.Volume.PT.Rules as Volume
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = Distance.rules
+rules (This Duration) = []
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = AmountOfMoney.rules
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = PhoneNumber.rules
+rules (This Quantity) = Quantity.rules
+rules (This RegexMatch) = []
+rules (This Temperature) = Temperature.rules
+rules (This Time) = Time.rules
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = Volume.rules
diff --git a/Duckling/Rules/RO.hs b/Duckling/Rules/RO.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/RO.hs
@@ -0,0 +1,43 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.RO
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.AmountOfMoney.RO.Rules as AmountOfMoney
+import qualified Duckling.Distance.RO.Rules as Distance
+import qualified Duckling.Duration.RO.Rules as Duration
+import qualified Duckling.Numeral.RO.Rules as Numeral
+import qualified Duckling.Ordinal.RO.Rules as Ordinal
+import qualified Duckling.Quantity.RO.Rules as Quantity
+import qualified Duckling.Temperature.RO.Rules as Temperature
+import qualified Duckling.Time.RO.Rules as Time
+import qualified Duckling.TimeGrain.RO.Rules as TimeGrain
+import qualified Duckling.Volume.RO.Rules as Volume
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = Distance.rules
+rules (This Duration) = Duration.rules
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = AmountOfMoney.rules
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = Quantity.rules
+rules (This RegexMatch) = []
+rules (This Temperature) = Temperature.rules
+rules (This Time) = Time.rules
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = Volume.rules
diff --git a/Duckling/Rules/RU.hs b/Duckling/Rules/RU.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/RU.hs
@@ -0,0 +1,35 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.RU
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Numeral.RU.Rules as Numeral
+import qualified Duckling.Ordinal.RU.Rules as Ordinal
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = []
+rules (This Duration) = []
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = []
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = []
+rules (This Time) = []
+rules (This TimeGrain) = []
+rules (This Url) = []
+rules (This Volume) = []
diff --git a/Duckling/Rules/SV.hs b/Duckling/Rules/SV.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/SV.hs
@@ -0,0 +1,39 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.SV
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.AmountOfMoney.SV.Rules as AmountOfMoney
+import qualified Duckling.Duration.SV.Rules as Duration
+import qualified Duckling.Ordinal.SV.Rules as Ordinal
+import qualified Duckling.Numeral.SV.Rules as Numeral
+import qualified Duckling.Time.SV.Rules as Time
+import qualified Duckling.TimeGrain.SV.Rules as TimeGrain
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = []
+rules (This Duration) = Duration.rules
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = AmountOfMoney.rules
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = []
+rules (This Time) = Time.rules
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = []
diff --git a/Duckling/Rules/TR.hs b/Duckling/Rules/TR.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/TR.hs
@@ -0,0 +1,35 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.TR
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Numeral.TR.Rules as Numeral
+import qualified Duckling.Ordinal.TR.Rules as Ordinal
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = []
+rules (This Duration) = []
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = []
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = []
+rules (This Time) = []
+rules (This TimeGrain) = []
+rules (This Url) = []
+rules (This Volume) = []
diff --git a/Duckling/Rules/UK.hs b/Duckling/Rules/UK.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/UK.hs
@@ -0,0 +1,35 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.UK
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Numeral.UK.Rules as Numeral
+import qualified Duckling.Ordinal.UK.Rules as Ordinal
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = []
+rules (This Duration) = []
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = []
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = []
+rules (This Time) = []
+rules (This TimeGrain) = []
+rules (This Url) = []
+rules (This Volume) = []
diff --git a/Duckling/Rules/VI.hs b/Duckling/Rules/VI.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/VI.hs
@@ -0,0 +1,38 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.VI
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import Duckling.Types
+import qualified Duckling.AmountOfMoney.VI.Rules as AmountOfMoney
+import qualified Duckling.Numeral.VI.Rules as Numeral
+import qualified Duckling.Ordinal.VI.Rules as Ordinal
+import qualified Duckling.TimeGrain.VI.Rules as TimeGrain
+import qualified Duckling.Time.VI.Rules as Time
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = []
+rules (This Duration) = []
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = AmountOfMoney.rules
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = []
+rules (This Time) = Time.rules
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = []
diff --git a/Duckling/Rules/ZH.hs b/Duckling/Rules/ZH.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Rules/ZH.hs
@@ -0,0 +1,38 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Rules.ZH
+  ( rules
+  ) where
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Numeral.ZH.Rules as Numeral
+import qualified Duckling.Ordinal.ZH.Rules as Ordinal
+import qualified Duckling.Temperature.ZH.Rules as Temperature
+import qualified Duckling.Time.ZH.Rules as Time
+import qualified Duckling.TimeGrain.ZH.Rules as TimeGrain
+import Duckling.Types
+
+rules :: Some Dimension -> [Rule]
+rules (This Distance) = []
+rules (This Duration) = []
+rules (This Numeral) = Numeral.rules
+rules (This Email) = []
+rules (This AmountOfMoney) = []
+rules (This Ordinal) = Ordinal.rules
+rules (This PhoneNumber) = []
+rules (This Quantity) = []
+rules (This RegexMatch) = []
+rules (This Temperature) = Temperature.rules
+rules (This Time) = Time.rules
+rules (This TimeGrain) = TimeGrain.rules
+rules (This Url) = []
+rules (This Volume) = []
diff --git a/Duckling/Temperature/EN/Corpus.hs b/Duckling/Temperature/EN/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/EN/Corpus.hs
@@ -0,0 +1,50 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.EN.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Temperature.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (TemperatureValue Celsius 37)
+             [ "37°C"
+             , "37 ° celsius"
+             , "37 degrees Celsius"
+             , "thirty seven celsius"
+             , "37 degrees Celsius"
+             , "thirty seven celsius"
+             ]
+  , examples (TemperatureValue Fahrenheit 70)
+             [ "70°F"
+             , "70 ° Fahrenheit"
+             , "70 degrees F"
+             , "seventy Fahrenheit"
+             ]
+  , examples (TemperatureValue Degree 45)
+             [ "45°"
+             , "45 degrees"
+             , "45 deg."
+             ]
+  , examples (TemperatureValue Degree (-2))
+             [ "-2°"
+             , "- 2 degrees"
+             , "2 degrees below zero"
+             , "2 below zero"
+             ]
+  ]
diff --git a/Duckling/Temperature/EN/Rules.hs b/Duckling/Temperature/EN/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/EN/Rules.hs
@@ -0,0 +1,86 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.EN.Rules
+  ( rules ) where
+
+import Data.Maybe
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.Helpers
+import qualified Duckling.Temperature.Types as TTemperature
+import Duckling.Temperature.Types (TemperatureData(..))
+import Duckling.Types
+
+ruleTemperatureDegrees :: Rule
+ruleTemperatureDegrees = Rule
+  { name = "<latent temp> degrees"
+  , pattern =
+    [ dimension Temperature
+    , regex "(deg(ree?)?s?\\.?)|°"
+    ]
+  , prod = \tokens -> case tokens of
+    (Token Temperature td:_) -> Just . Token Temperature $
+      withUnit TTemperature.Degree td
+    _ -> Nothing
+  }
+
+ruleTemperatureCelsius :: Rule
+ruleTemperatureCelsius = Rule
+  { name = "<temp> Celsius"
+  , pattern =
+    [ dimension Temperature
+    , regex "c(el[cs]?(ius)?)?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+    (Token Temperature td:_) -> Just . Token Temperature $
+      withUnit TTemperature.Celsius td
+    _ -> Nothing
+  }
+
+ruleTemperatureFahrenheit :: Rule
+ruleTemperatureFahrenheit = Rule
+  { name = "<temp> Fahrenheit"
+  , pattern =
+    [ dimension Temperature
+    , regex "f(ah?rh?eh?n(h?eit)?)?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+    (Token Temperature td:_) -> Just . Token Temperature $
+      withUnit TTemperature.Fahrenheit td
+    _ -> Nothing
+  }
+
+ruleTemperatureBelowZero :: Rule
+ruleTemperatureBelowZero = Rule
+  { name = "<temp> below zero"
+  , pattern =
+    [ dimension Temperature
+    , regex "below zero"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td@(TemperatureData {TTemperature.value = v}):_) ->
+        case TTemperature.unit td of
+          Nothing -> Just . Token Temperature . withUnit TTemperature.Degree $
+            td {TTemperature.value = - v}
+          _ -> Just . Token Temperature $ td {TTemperature.value = - v}
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleTemperatureDegrees
+  , ruleTemperatureCelsius
+  , ruleTemperatureFahrenheit
+  , ruleTemperatureBelowZero
+  ]
diff --git a/Duckling/Temperature/ES/Corpus.hs b/Duckling/Temperature/ES/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/ES/Corpus.hs
@@ -0,0 +1,51 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.ES.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Temperature.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = ES}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (TemperatureValue Celsius 37)
+             [ "37°C"
+             , "37 ° celsius"
+             , "37 grados Celsius"
+             , "37 grados C"
+             , "treinta y siete celsius"
+             , "37 centígrados"
+             , "37 grados centígrados"
+             ]
+  , examples (TemperatureValue Fahrenheit 70)
+             [ "70°F"
+             , "70 ° Fahrenheit"
+             , "70 grados F"
+             , "setenta Fahrenheit"
+             ]
+  , examples (TemperatureValue Degree 45)
+             [ "45°"
+             , "45 grados"
+             ]
+  , examples (TemperatureValue Degree (-10))
+             [ "-10°"
+             , "- diez grados"
+             , "10 bajo cero"
+             ]
+  ]
diff --git a/Duckling/Temperature/ES/Rules.hs b/Duckling/Temperature/ES/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/ES/Rules.hs
@@ -0,0 +1,85 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.ES.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.Helpers
+import Duckling.Temperature.Types (TemperatureData (..))
+import qualified Duckling.Temperature.Types as TTemperature
+import Duckling.Types
+
+ruleLatentTempTemp :: Rule
+ruleLatentTempTemp = Rule
+  { name = "<latent temp> temp"
+  , pattern =
+    [ dimension Temperature
+    , regex "(grados?)|\x00b0"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Degree td
+      _ -> Nothing
+  }
+
+ruleTempCelsius :: Rule
+ruleTempCelsius = Rule
+  { name = "<temp> Celsius"
+  , pattern =
+    [ dimension Temperature
+    , regex "(cent(i|\x00ed)grados?|c(el[cs]?(ius)?)?\\.?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Celsius td
+      _ -> Nothing
+  }
+
+ruleTempFahrenheit :: Rule
+ruleTempFahrenheit = Rule
+  { name = "<temp> Fahrenheit"
+  , pattern =
+    [ dimension Temperature
+    , regex "f(ah?reh?n(h?eit)?)?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Fahrenheit td
+      _ -> Nothing
+  }
+
+ruleLatentTempTempBajoCero :: Rule
+ruleLatentTempTempBajoCero = Rule
+  { name = "<latent temp> temp bajo cero"
+  , pattern =
+    [ dimension Temperature
+    , regex "bajo cero"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td@(TemperatureData {TTemperature.value = v}):_) ->
+        case TTemperature.unit td of
+          Nothing -> Just . Token Temperature . withUnit TTemperature.Degree $
+            td {TTemperature.value = - v}
+          _ -> Just . Token Temperature $ td {TTemperature.value = - v}
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleLatentTempTemp
+  , ruleLatentTempTempBajoCero
+  , ruleTempCelsius
+  , ruleTempFahrenheit
+  ]
diff --git a/Duckling/Temperature/FR/Corpus.hs b/Duckling/Temperature/FR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/FR/Corpus.hs
@@ -0,0 +1,53 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.FR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Temperature.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = FR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (TemperatureValue Celsius 37)
+             [ "37°C"
+             , "37 ° celsius"
+             , "37 degres Celsius"
+             , "37 degré C"
+             , "trente sept celsius"
+             , "37 degré C"
+             , "trente sept celsius"
+             ]
+  , examples (TemperatureValue Fahrenheit 70)
+             [ "70°F"
+             , "70 ° Fahrenheit"
+             , "70 degrès F"
+             , "soixante-dix Fahrenheit"
+             ]
+  , examples (TemperatureValue Degree 45)
+             [ "45°"
+             , "45 degrés"
+             , "45 deg."
+             ]
+  , examples (TemperatureValue Degree (-10))
+             [ "-10°"
+             , "- 10 degres"
+             , "10 degres en dessous de zero"
+             , "10 en dessous de zero"
+             ]
+  ]
diff --git a/Duckling/Temperature/FR/Rules.hs b/Duckling/Temperature/FR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/FR/Rules.hs
@@ -0,0 +1,85 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.FR.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.Helpers
+import Duckling.Temperature.Types (TemperatureData (..))
+import qualified Duckling.Temperature.Types as TTemperature
+import Duckling.Types
+
+ruleLatentTempDegrees :: Rule
+ruleLatentTempDegrees = Rule
+  { name = "<latent temp> degrees"
+  , pattern =
+    [ dimension Temperature
+    , regex "(deg(r(\x00e9|e|\x00e8))?s?\\.?)|\x00b0"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Degree td
+      _ -> Nothing
+  }
+
+ruleTempCelsius :: Rule
+ruleTempCelsius = Rule
+  { name = "<temp> Celsius"
+  , pattern =
+    [ dimension Temperature
+    , regex "c(el[cs]?(ius)?)?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Celsius td
+      _ -> Nothing
+  }
+
+ruleTempFahrenheit :: Rule
+ruleTempFahrenheit = Rule
+  { name = "<temp> Fahrenheit"
+  , pattern =
+    [ dimension Temperature
+    , regex "f(ah?reh?n(h?eit)?)?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Fahrenheit td
+      _ -> Nothing
+  }
+
+ruleLatentTempEnDessousDeZero :: Rule
+ruleLatentTempEnDessousDeZero = Rule
+  { name = "<latent temp> en dessous de zero"
+  , pattern =
+    [ dimension Temperature
+    , regex "en dessous de (0|z(\x00e9|e)ro)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td@(TemperatureData {TTemperature.value = v}):_) ->
+        case TTemperature.unit td of
+          Nothing -> Just . Token Temperature . withUnit TTemperature.Degree $
+            td {TTemperature.value = - v}
+          _ -> Just . Token Temperature $ td {TTemperature.value = - v}
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleLatentTempDegrees
+  , ruleLatentTempEnDessousDeZero
+  , ruleTempCelsius
+  , ruleTempFahrenheit
+  ]
diff --git a/Duckling/Temperature/GA/Corpus.hs b/Duckling/Temperature/GA/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/GA/Corpus.hs
@@ -0,0 +1,49 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.GA.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Temperature.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = GA}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (TemperatureValue Celsius 37)
+             [ "37°C"
+             , "37 ° celsius"
+             , "37 céimeanna Celsius"
+             , "37 céimeanna C"
+             , "37 céimeanna ceinteagrád"
+             ]
+  , examples (TemperatureValue Fahrenheit 70)
+             [ "70°F"
+             , "70 ° Fahrenheit"
+             , "70 céimeanna F"
+             ]
+  , examples (TemperatureValue Degree 45)
+             [ "45°"
+             , "45 céimeanna"
+             ]
+  , examples (TemperatureValue Degree (-10))
+             [ "-10°"
+             , "- 10 céimeanna"
+             , "10 céimeanna faoi bhun náid"
+             , "10 faoi bhun náid"
+             ]
+  ]
diff --git a/Duckling/Temperature/GA/Rules.hs b/Duckling/Temperature/GA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/GA/Rules.hs
@@ -0,0 +1,85 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.GA.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.Helpers
+import Duckling.Temperature.Types (TemperatureData (..))
+import qualified Duckling.Temperature.Types as TTemperature
+import Duckling.Types
+
+ruleLatentTempCim :: Rule
+ruleLatentTempCim = Rule
+  { name = "<latent temp> céim"
+  , pattern =
+    [ dimension Temperature
+    , regex "g?ch?(\x00e9|e)im(e(anna)?)?|\x00b0"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Degree td
+      _ -> Nothing
+  }
+
+ruleTempCelsius :: Rule
+ruleTempCelsius = Rule
+  { name = "<temp> Celsius"
+  , pattern =
+    [ dimension Temperature
+    , regex "ceinteagr(\x00e1|a)d|c(el[cs]?(ius)?)?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Celsius td
+      _ -> Nothing
+  }
+
+ruleTempFahrenheit :: Rule
+ruleTempFahrenheit = Rule
+  { name = "<temp> Fahrenheit"
+  , pattern =
+    [ dimension Temperature
+    , regex "f(ah?reh?n(h?eit)?)?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Fahrenheit td
+      _ -> Nothing
+  }
+
+ruleLatentTempFaoiBhunNid :: Rule
+ruleLatentTempFaoiBhunNid = Rule
+  { name = "<latent temp> faoi bhun náid"
+  , pattern =
+    [ dimension Temperature
+    , regex "faoi bhun (0|n(a|\x00e1)id)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td@(TemperatureData {TTemperature.value = v}):_) ->
+        case TTemperature.unit td of
+          Nothing -> Just . Token Temperature . withUnit TTemperature.Degree $
+            td {TTemperature.value = - v}
+          _ -> Just . Token Temperature $ td {TTemperature.value = - v}
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleLatentTempCim
+  , ruleLatentTempFaoiBhunNid
+  , ruleTempCelsius
+  , ruleTempFahrenheit
+  ]
diff --git a/Duckling/Temperature/HR/Corpus.hs b/Duckling/Temperature/HR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/HR/Corpus.hs
@@ -0,0 +1,45 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.HR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Temperature.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = HR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (TemperatureValue Celsius 37)
+             [ "37°C"
+             , "37 ° celzija"
+             , "37 stupnjeva Celzija"
+             , "trideset i sedam celzija"
+             , "37 stupnjeva Celzija"
+             , "trideset sedam celzija"
+             ]
+  , examples (TemperatureValue Fahrenheit 70)
+             [ "70°F"
+             , "70 ° Fahrenheit"
+             , "70 stupnjeva F"
+             , "sedamdeset Fahrenheit"
+             ]
+  , examples (TemperatureValue Degree 45)
+             [ "45°"
+             , "45 stupnjeva"
+             , "45 deg."
+             ]
+  ]
diff --git a/Duckling/Temperature/HR/Rules.hs b/Duckling/Temperature/HR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/HR/Rules.hs
@@ -0,0 +1,67 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.HR.Rules
+  ( rules ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.Helpers
+import Duckling.Temperature.Types (TemperatureData (..))
+import Duckling.Types
+import qualified Duckling.Temperature.Types as TTemperature
+
+ruleLatentTempStupnjevi :: Rule
+ruleLatentTempStupnjevi = Rule
+  { name = "<latent temp> stupnjevi"
+  , pattern =
+    [ dimension Temperature
+    , regex "deg\\.?|stupa?nj((ev)?a)?|\x00b0"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Degree td
+      _ -> Nothing
+  }
+
+ruleTempCelzij :: Rule
+ruleTempCelzij = Rule
+  { name = "<temp> Celzij"
+  , pattern =
+    [ dimension Temperature
+    , regex "c(elz?(ija?)?)?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Celsius td
+      _ -> Nothing
+  }
+
+ruleTempFahrenheit :: Rule
+ruleTempFahrenheit = Rule
+  { name = "<temp> Fahrenheit"
+  , pattern =
+    [ dimension Temperature
+    , regex "f(ah?rh?eh?n(h?eit)?)?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Fahrenheit td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleLatentTempStupnjevi
+  , ruleTempCelzij
+  , ruleTempFahrenheit
+  ]
diff --git a/Duckling/Temperature/Helpers.hs b/Duckling/Temperature/Helpers.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/Helpers.hs
@@ -0,0 +1,37 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.Helpers
+  ( isLatent
+  , withUnit
+  ) where
+
+import Data.Maybe
+import Prelude
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Temperature.Types as TTemperature
+import Duckling.Temperature.Types (TemperatureData(..))
+import Duckling.Types
+
+-- -----------------------------------------------------------------
+-- Patterns
+
+isLatent :: Predicate
+isLatent (Token Temperature (TemperatureData {TTemperature.unit = Nothing})) =
+  True
+isLatent _ = False
+
+-- -----------------------------------------------------------------
+-- Production
+
+withUnit :: TTemperature.TemperatureUnit -> TemperatureData -> TemperatureData
+withUnit u td = td {TTemperature.unit = Just u}
diff --git a/Duckling/Temperature/IT/Corpus.hs b/Duckling/Temperature/IT/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/IT/Corpus.hs
@@ -0,0 +1,52 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.IT.Corpus
+  ( corpus ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Temperature.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = IT}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (TemperatureValue Celsius 37)
+             [ "37°C"
+             , "37 ° celsius"
+             , "37 ° centigradi"
+             , "37 gradi Celsius"
+             , "37 gradi Centigradi"
+             , "trentasette celsius"
+             , "trentasette gradi centigradi"
+             ]
+  , examples (TemperatureValue Celsius 1)
+             [ "1 grado centigrado"
+             ]
+  , examples (TemperatureValue Fahrenheit 70)
+             [ "70°F"
+             , "70 ° Fahrenheit"
+             , "70 gradi F"
+             , "70 gradi Fahreneit"
+             , "settanta Fahrenheit"
+             ]
+  , examples (TemperatureValue Degree 45)
+             [ "45°"
+             , "45 gradi"
+             ]
+  , examples (TemperatureValue Degree 1)
+             [ "1 grado"
+             ]
+  ]
diff --git a/Duckling/Temperature/IT/Rules.hs b/Duckling/Temperature/IT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/IT/Rules.hs
@@ -0,0 +1,67 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.IT.Rules
+  ( rules ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.Helpers
+import Duckling.Temperature.Types (TemperatureData (..))
+import Duckling.Types
+import qualified Duckling.Temperature.Types as TTemperature
+
+ruleLatentTempDegrees :: Rule
+ruleLatentTempDegrees = Rule
+  { name = "<latent temp> degrees"
+  , pattern =
+    [ dimension Temperature
+    , regex "(grad[io]?\\.?)|\x00b0"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Degree td
+      _ -> Nothing
+  }
+
+ruleTempCelcius :: Rule
+ruleTempCelcius = Rule
+  { name = "<temp> Celcius"
+  , pattern =
+    [ dimension Temperature
+    , regex "(c((el[cs]?(ius)?)|(entigrad[io]))?\\.?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Celsius td
+      _ -> Nothing
+  }
+
+ruleTempFahrenheit :: Rule
+ruleTempFahrenheit = Rule
+  { name = "<temp> Fahrenheit"
+  , pattern =
+    [ dimension Temperature
+    , regex "f(ah?rh?eh?n(h?eit)?)?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Fahrenheit td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleLatentTempDegrees
+  , ruleTempCelcius
+  , ruleTempFahrenheit
+  ]
diff --git a/Duckling/Temperature/JA/Corpus.hs b/Duckling/Temperature/JA/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/JA/Corpus.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.JA.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Temperature.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = JA}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (TemperatureValue Celsius 37)
+             [ "37°C"
+             , "摂氏37°"
+             , "摂氏37度"
+             ]
+  , examples (TemperatureValue Fahrenheit 70)
+             [ "70°F"
+             , "華氏70°"
+             , "華氏70度"
+             ]
+  , examples (TemperatureValue Degree 45)
+             [ "45°"
+             , "45度"
+             ]
+  ]
diff --git a/Duckling/Temperature/JA/Rules.hs b/Duckling/Temperature/JA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/JA/Rules.hs
@@ -0,0 +1,97 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.JA.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.Helpers
+import qualified Duckling.Temperature.Types as TTemperature
+import Duckling.Types
+
+ruleLatentTempDegrees :: Rule
+ruleLatentTempDegrees = Rule
+  { name = "<latent temp> degrees"
+  , pattern =
+    [ dimension Temperature
+    , regex "\x5ea6|\x00b0"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Degree td
+      _ -> Nothing
+  }
+
+ruleTempCelcius :: Rule
+ruleTempCelcius = Rule
+  { name = "<temp> Celcius"
+  , pattern =
+    [ dimension Temperature
+    , regex "\x6442\x6c0f(\x00b0|\x5ea6)|(\x00b0)C"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Celsius td
+      _ -> Nothing
+  }
+
+ruleCelciusTemp :: Rule
+ruleCelciusTemp = Rule
+  { name = "Celcius <temp>"
+  , pattern =
+    [ regex "\x6442\x6c0f"
+    , dimension Temperature
+    , regex "\x5ea6|\x00b0"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Celsius td
+      _ -> Nothing
+  }
+
+ruleTempFahrenheit :: Rule
+ruleTempFahrenheit = Rule
+  { name = "<temp> Fahrenheit"
+  , pattern =
+    [ dimension Temperature
+    , regex "\x83ef\x6c0f(\x00b0|\x5ea6)|(\x00b0)F"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Fahrenheit td
+      _ -> Nothing
+  }
+
+ruleFahrenheitTemp :: Rule
+ruleFahrenheitTemp = Rule
+  { name = "Fahrenheit <temp>"
+  , pattern =
+    [ regex "\x83ef\x6c0f"
+    , dimension Temperature
+    , regex "\x5ea6|\x00b0"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Fahrenheit td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleCelciusTemp
+  , ruleFahrenheitTemp
+  , ruleLatentTempDegrees
+  , ruleTempCelcius
+  , ruleTempFahrenheit
+  ]
diff --git a/Duckling/Temperature/KO/Corpus.hs b/Duckling/Temperature/KO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/KO/Corpus.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.KO.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Temperature.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = KO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (TemperatureValue Celsius 37)
+             [ "37°C"
+             , "섭씨37°"
+             , "섭씨37도"
+             ]
+  , examples (TemperatureValue Fahrenheit 70)
+             [ "70°F"
+             , "화씨70°"
+             , "화씨70도"
+             ]
+  , examples (TemperatureValue Degree 45)
+             [ "45°"
+             , "45도"
+             ]
+  ]
diff --git a/Duckling/Temperature/KO/Rules.hs b/Duckling/Temperature/KO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/KO/Rules.hs
@@ -0,0 +1,95 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.KO.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.Helpers
+import qualified Duckling.Temperature.Types as TTemperature
+import Duckling.Types
+
+ruleLatentTempDegrees :: Rule
+ruleLatentTempDegrees = Rule
+  { name = "<latent temp> degrees"
+  , pattern =
+    [ Predicate isLatent
+    , regex "\xb3c4|\x00b0"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Degree td
+      _ -> Nothing
+  }
+
+ruleTemp :: Rule
+ruleTemp = Rule
+  { name = "섭씨 <temp>"
+  , pattern =
+    [ regex "\xc12d\xc528"
+    , dimension Temperature
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Celsius td
+      _ -> Nothing
+  }
+
+ruleTempC :: Rule
+ruleTempC = Rule
+  { name = "<temp> °C"
+  , pattern =
+    [ dimension Temperature
+    , regex "c"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Celsius td
+      _ -> Nothing
+  }
+
+ruleTemp2 :: Rule
+ruleTemp2 = Rule
+  { name = "화씨 <temp>"
+  , pattern =
+    [ regex "\xd654\xc528"
+    , dimension Temperature
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Fahrenheit td
+      _ -> Nothing
+  }
+
+ruleTempF :: Rule
+ruleTempF = Rule
+  { name = "<temp> °F"
+  , pattern =
+    [ dimension Temperature
+    , regex "f"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Fahrenheit td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleLatentTempDegrees
+  , ruleTemp
+  , ruleTemp2
+  , ruleTempC
+  , ruleTempF
+  ]
diff --git a/Duckling/Temperature/PT/Corpus.hs b/Duckling/Temperature/PT/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/PT/Corpus.hs
@@ -0,0 +1,51 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.PT.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Temperature.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = PT}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (TemperatureValue Celsius 37)
+             [ "37°C"
+             , "37 ° celsius"
+             , "37 graus Celsius"
+             , "37 graus C"
+             , "trinta e sete celsius"
+             , "37 centígrados"
+             , "37 graus centigrados"
+             ]
+  , examples (TemperatureValue Fahrenheit 70)
+             [ "70°F"
+             , "70 ° Fahrenheit"
+             , "70 graus F"
+             , "setenta Fahrenheit"
+             ]
+  , examples (TemperatureValue Degree 45)
+             [ "45°"
+             , "45 graus"
+             ]
+  , examples (TemperatureValue Degree (-10))
+             [ "-10°"
+             , "- dez graus"
+             , "10 abaixo de zero"
+             ]
+  ]
diff --git a/Duckling/Temperature/PT/Rules.hs b/Duckling/Temperature/PT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/PT/Rules.hs
@@ -0,0 +1,85 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.PT.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.Helpers
+import Duckling.Temperature.Types (TemperatureData (..))
+import qualified Duckling.Temperature.Types as TTemperature
+import Duckling.Types
+
+ruleLatentTempTemp :: Rule
+ruleLatentTempTemp = Rule
+  { name = "<latent temp> temp"
+  , pattern =
+    [ dimension Temperature
+    , regex "(graus?)|\x00b0"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Degree td
+      _ -> Nothing
+  }
+
+ruleTempCelsius :: Rule
+ruleTempCelsius = Rule
+  { name = "<temp> Celsius"
+  , pattern =
+    [ dimension Temperature
+    , regex "(cent(i|\x00ed)grados?|c(el[cs]?(ius)?)?\\.?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Celsius td
+      _ -> Nothing
+  }
+
+ruleTempFahrenheit :: Rule
+ruleTempFahrenheit = Rule
+  { name = "<temp> Fahrenheit"
+  , pattern =
+    [ dimension Temperature
+    , regex "f(ah?reh?n(h?eit)?)?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Fahrenheit td
+      _ -> Nothing
+  }
+
+ruleLatentTempTempAbaixoDeZero :: Rule
+ruleLatentTempTempAbaixoDeZero = Rule
+  { name = "<latent temp> temp abaixo de zero"
+  , pattern =
+    [ dimension Temperature
+    , regex "((graus?)|\x00b0)?( abaixo (de)? zero)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td@(TemperatureData {TTemperature.value = v}):_) ->
+        case TTemperature.unit td of
+          Nothing -> Just . Token Temperature . withUnit TTemperature.Degree $
+            td {TTemperature.value = - v}
+          _ -> Just . Token Temperature $ td {TTemperature.value = - v}
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleLatentTempTemp
+  , ruleLatentTempTempAbaixoDeZero
+  , ruleTempCelsius
+  , ruleTempFahrenheit
+  ]
diff --git a/Duckling/Temperature/RO/Corpus.hs b/Duckling/Temperature/RO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/RO/Corpus.hs
@@ -0,0 +1,45 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.RO.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Temperature.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = RO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (TemperatureValue Celsius 37)
+             [ "37°C"
+             , "37 ° celsius"
+             , "37 grade Celsius"
+             , "treizeci si sapte celsius"
+             , "37 grade Celsius"
+             , "treizeci si sapte celsius"
+             ]
+  , examples (TemperatureValue Fahrenheit 70)
+             [ "70°F"
+             , "70 ° Fahrenheit"
+             , "70 grade F"
+             , "saptezeci Fahrenheit"
+             ]
+  , examples (TemperatureValue Degree 45)
+             [ "45°"
+             , "45 grade"
+             ]
+  ]
diff --git a/Duckling/Temperature/RO/Rules.hs b/Duckling/Temperature/RO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/RO/Rules.hs
@@ -0,0 +1,67 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.RO.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.Helpers
+import qualified Duckling.Temperature.Types as TTemperature
+import Duckling.Types
+
+ruleLatentTempGrade :: Rule
+ruleLatentTempGrade = Rule
+  { name = "<latent temp> grade"
+  , pattern =
+    [ dimension Temperature
+    , regex "(grade)|\x00b0"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Degree td
+      _ -> Nothing
+  }
+
+ruleTempCelcius :: Rule
+ruleTempCelcius = Rule
+  { name = "<temp> Celcius"
+  , pattern =
+    [ dimension Temperature
+    , regex "c(el[cs]?(ius)?)?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Celsius td
+      _ -> Nothing
+  }
+
+ruleTempFahrenheit :: Rule
+ruleTempFahrenheit = Rule
+  { name = "<temp> Fahrenheit"
+  , pattern =
+    [ dimension Temperature
+    , regex "f(ah?rh?eh?n(h?eit)?)?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Fahrenheit td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleLatentTempGrade
+  , ruleTempCelcius
+  , ruleTempFahrenheit
+  ]
diff --git a/Duckling/Temperature/Rules.hs b/Duckling/Temperature/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/Rules.hs
@@ -0,0 +1,43 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.Rules
+  ( rules
+  ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Dimensions.Types
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Temperature.Types (TemperatureData (..))
+import qualified Duckling.Temperature.Types as TTemperature
+import Duckling.Types
+
+ruleNumeralAsTemp :: Rule
+ruleNumeralAsTemp = Rule
+  { name = "number as temp"
+  , pattern =
+    [ dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral nd:_) ->
+        Just . Token Temperature $ TemperatureData
+          { TTemperature.unit = Nothing
+          , TTemperature.value = floor $ TNumeral.value nd
+          }
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleNumeralAsTemp
+  ]
diff --git a/Duckling/Temperature/Types.hs b/Duckling/Temperature/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/Types.hs
@@ -0,0 +1,56 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Duckling.Temperature.Types where
+
+import Control.DeepSeq
+import Data.Aeson
+import Data.Hashable
+import Data.Text (Text)
+import qualified Data.Text as Text
+import GHC.Generics
+import Prelude
+
+import Duckling.Resolve (Resolve(..))
+
+data TemperatureUnit =
+  Degree | Celsius | Fahrenheit
+  deriving (Eq, Generic, Hashable, Show, Ord, NFData)
+
+instance ToJSON TemperatureUnit where
+  toJSON = String . Text.toLower . Text.pack . show
+
+data TemperatureData = TemperatureData
+  { unit :: Maybe TemperatureUnit
+  , value :: Int
+  } deriving (Eq, Generic, Hashable, Show, Ord, NFData)
+
+instance Resolve TemperatureData where
+  type ResolvedValue TemperatureData = TemperatureValue
+  resolve _ TemperatureData {unit = Nothing} = Nothing
+  resolve _ TemperatureData {unit = Just unit, value} =
+    Just TemperatureValue {vUnit = unit, vValue = value}
+
+data TemperatureValue = TemperatureValue
+  { vUnit :: TemperatureUnit
+  , vValue :: Int
+  } deriving (Eq, Show)
+
+instance ToJSON TemperatureValue where
+  toJSON (TemperatureValue unit value) = object
+    [ "type" .= ("value" :: Text)
+    , "value" .= value
+    , "unit" .= unit
+    ]
diff --git a/Duckling/Temperature/ZH/Corpus.hs b/Duckling/Temperature/ZH/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/ZH/Corpus.hs
@@ -0,0 +1,53 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.ZH.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Temperature.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = ZH}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (TemperatureValue Celsius 37)
+             [ "37°C"
+             , "摄氏37°"
+             , "攝氏37°"
+             , "摄氏37度"
+             , "攝氏37度"
+             , "37摄氏°"
+             , "37攝氏°"
+             , "37摄氏度"
+             , "37攝氏度"
+             ]
+  , examples (TemperatureValue Fahrenheit 70)
+             [ "70°F"
+             , "华氏70°"
+             , "華氏70°"
+             , "华氏70度"
+             , "華氏70度"
+             , "70华氏°"
+             , "70華氏°"
+             , "70华氏度"
+             , "70華氏度"
+             ]
+  , examples (TemperatureValue Degree 45)
+             [ "45°"
+             , "45度"
+             ]
+  ]
diff --git a/Duckling/Temperature/ZH/Rules.hs b/Duckling/Temperature/ZH/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Temperature/ZH/Rules.hs
@@ -0,0 +1,97 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Temperature.ZH.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.Helpers
+import qualified Duckling.Temperature.Types as TTemperature
+import Duckling.Types
+
+ruleLatentTempDegrees :: Rule
+ruleLatentTempDegrees = Rule
+  { name = "<latent temp> degrees"
+  , pattern =
+    [ dimension Temperature
+    , regex "\x5ea6|\x00b0"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Degree td
+      _ -> Nothing
+  }
+
+ruleTempCelcius :: Rule
+ruleTempCelcius = Rule
+  { name = "<temp> Celcius"
+  , pattern =
+    [ dimension Temperature
+    , regex "(\x6444|\x651d)\x6c0f(\x00b0|\x5ea6)|(\x00b0)C"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Celsius td
+      _ -> Nothing
+  }
+
+ruleCelciusTemp :: Rule
+ruleCelciusTemp = Rule
+  { name = "Celcius <temp>"
+  , pattern =
+    [ regex "(\x6444|\x651d)\x6c0f"
+    , dimension Temperature
+    , regex "\x5ea6|\x00b0"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Celsius td
+      _ -> Nothing
+  }
+
+ruleTempFahrenheit :: Rule
+ruleTempFahrenheit = Rule
+  { name = "<temp> Fahrenheit"
+  , pattern =
+    [ dimension Temperature
+    , regex "(\x534e|\x83ef)\x6c0f(\x00b0|\x5ea6)|(\x00b0)F"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Fahrenheit td
+      _ -> Nothing
+  }
+
+ruleFahrenheitTemp :: Rule
+ruleFahrenheitTemp = Rule
+  { name = "Fahrenheit <temp>"
+  , pattern =
+    [ regex "(\x534e|\x83ef)\x6c0f"
+    , dimension Temperature
+    , regex "\x5ea6|\x00b0"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Temperature td:_) -> Just . Token Temperature $
+        withUnit TTemperature.Fahrenheit td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleCelciusTemp
+  , ruleFahrenheitTemp
+  , ruleLatentTempDegrees
+  , ruleTempCelcius
+  , ruleTempFahrenheit
+  ]
diff --git a/Duckling/Testing/Types.hs b/Duckling/Testing/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Testing/Types.hs
@@ -0,0 +1,72 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE TupleSections #-}
+
+module Duckling.Testing.Types
+  ( Corpus
+  , Datetime
+  , Example
+  , NegativeCorpus
+  , TestPredicate
+  , dt
+  , examples
+  , examplesCustom
+  , parserCheck
+  , refTime
+  , testContext
+  , zTime
+  ) where
+
+import Data.Aeson
+import Data.Fixed (Pico)
+import qualified Data.Time as Time
+import Data.Text (Text)
+import Prelude
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Types
+
+type TestPredicate = Context -> ResolvedToken -> Bool
+type Example = (Text, TestPredicate)
+type Corpus = (Context, [Example])
+type NegativeCorpus = (Context, [Text])
+
+examplesCustom :: TestPredicate -> [Text] -> [Example]
+examplesCustom check = map (, check)
+
+simpleCheck :: ToJSON a => a -> TestPredicate
+simpleCheck json _ (Resolved {jsonValue = v}) = toJSON json == v
+
+parserCheck :: Eq a => a -> (Value -> Maybe a) -> TestPredicate
+parserCheck expected parse _ (Resolved {jsonValue = v}) =
+  maybe False (expected ==) $ parse v
+
+examples :: ToJSON a => a -> [Text] -> [Example]
+examples output = examplesCustom (simpleCheck output)
+
+type Datetime = (Integer, Int, Int, Int, Int, Pico)
+
+dt :: Datetime -> Time.UTCTime
+dt (year, month, days, hours, minutes, seconds) = Time.UTCTime day diffTime
+  where
+    day = Time.fromGregorian year month days
+    diffTime = Time.timeOfDayToTime $ Time.TimeOfDay hours minutes seconds
+
+zTime :: Datetime -> Int -> Time.ZonedTime
+zTime datetime offset = fromUTC (dt datetime) $ Time.hoursToTimeZone offset
+
+refTime :: Datetime -> Int -> DucklingTime
+refTime datetime offset = fromZonedTime $ zTime datetime offset
+
+-- Tuesday Feb 12, 2013 at 4:30am is the "now" for the tests
+testContext :: Context
+testContext = Context
+  {lang = EN, referenceTime = refTime (2013, 2, 12, 4, 30, 0) (-2)}
diff --git a/Duckling/Time/Corpus.hs b/Duckling/Time/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/Corpus.hs
@@ -0,0 +1,57 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.Corpus
+  ( datetime
+  , datetimeInterval
+  , datetimeOpenInterval
+  , examples
+  ) where
+
+import Data.Aeson
+import qualified Data.HashMap.Strict as H
+import Data.Text (Text)
+import qualified Data.Time.LocalTime.TimeZone.Series as Series
+import Prelude
+import Data.String
+
+import Duckling.Resolve
+import Duckling.Testing.Types hiding (examples)
+import Duckling.Time.Types hiding (Month)
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Types hiding (Entity(..))
+
+datetime :: Datetime -> Grain -> Context -> SingleTimeValue
+datetime d g ctx =
+  timeValue tzSeries TimeObject {start = dt d, end = Nothing, grain = g}
+  where
+    DucklingTime (Series.ZoneSeriesTime _ tzSeries) = referenceTime ctx
+
+datetimeInterval :: (Datetime, Datetime) -> Grain -> Context -> SingleTimeValue
+datetimeInterval (d1, d2) g ctx = timeValue tzSeries TimeObject
+  {start = dt d1, end = Just $ dt d2, grain = g}
+  where
+    DucklingTime (Series.ZoneSeriesTime _ tzSeries) = referenceTime ctx
+
+datetimeOpenInterval
+  :: IntervalDirection -> Datetime -> Grain -> Context -> SingleTimeValue
+datetimeOpenInterval dir d g ctx = openInterval tzSeries dir TimeObject
+  {start = dt d, end = Nothing, grain = g}
+  where
+    DucklingTime (Series.ZoneSeriesTime _ tzSeries) = referenceTime ctx
+
+check :: ToJSON a => (Context -> a) -> TestPredicate
+check f context (Resolved {jsonValue}) = case jsonValue of
+  Object o -> toJSON (f context) == (Object $ H.delete "values" o)
+  _ -> False
+
+examples :: ToJSON a => (Context -> a) -> [Text] -> [Example]
+examples f = examplesCustom (check f)
diff --git a/Duckling/Time/DA/Corpus.hs b/Duckling/Time/DA/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/DA/Corpus.hs
@@ -0,0 +1,633 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.DA.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Time.Corpus
+import Duckling.Time.Types hiding (Month)
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Testing.Types hiding (examples)
+
+corpus :: Corpus
+corpus = (testContext {lang = DA}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (datetime (2013, 2, 12, 4, 30, 0) Second)
+             [ "nu"
+             , "lige nu"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "i dag"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "i går"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "i morgen"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "mandag"
+             , "man."
+             , "på mandag"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "Mandag den 18. februar"
+             , "Man, 18 februar"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "tirsdag"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "torsdag"
+             , "tors"
+             , "tors."
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "fredag"
+             , "fre"
+             , "fre."
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "lørdag"
+             , "lør"
+             , "lør."
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "søndag"
+             , "søn"
+             , "søn."
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "Den første marts"
+             , "1. marts"
+             , "Den 1. marts"
+             ]
+  , examples (datetime (2013, 3, 3, 0, 0, 0) Day)
+             [ "3 marts"
+             , "den tredje marts"
+             , "den 3. marts"
+             ]
+  , examples (datetime (2015, 3, 3, 0, 0, 0) Day)
+             [ "3 marts 2015"
+             , "tredje marts 2015"
+             , "3. marts 2015"
+             , "3-3-2015"
+             , "03-03-2015"
+             , "3/3/2015"
+             , "3/3/15"
+             , "2015-3-3"
+             , "2015-03-03"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "På den 15."
+             , "På den 15"
+             , "Den 15."
+             , "Den 15"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "den 15. februar"
+             , "15. februar"
+             , "februar 15"
+             , "15-02"
+             , "15/02"
+             ]
+  , examples (datetime (2013, 8, 8, 0, 0, 0) Day)
+             [ "8 Aug"
+             ]
+  , examples (datetime (2014, 10, 0, 0, 0, 0) Month)
+             [ "Oktober 2014"
+             ]
+  , examples (datetime (1974, 10, 31, 0, 0, 0) Day)
+             [ "31/10/1974"
+             , "31/10/74"
+             , "31-10-74"
+             ]
+  , examples (datetime (2015, 4, 14, 0, 0, 0) Day)
+             [ "14april 2015"
+             , "April 14, 2015"
+             , "fjortende April 15"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "næste tirsdag"
+             ]
+  , examples (datetime (2013, 2, 22, 0, 0, 0) Day)
+             [ "næste fredag igen"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "næste marts"
+             ]
+  , examples (datetime (2014, 3, 0, 0, 0, 0) Month)
+             [ "næste marts igen"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "Søndag, 10 feb"
+             , "Søndag 10 Feb"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "Ons, Feb13"
+             , "Ons feb13"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "Mandag, Feb 18"
+             , "Man, februar 18"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Week)
+             [ "denne uge"
+             ]
+  , examples (datetime (2013, 2, 4, 0, 0, 0) Week)
+             [ "sidste uge"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Week)
+             [ "næste uge"
+             ]
+  , examples (datetime (2013, 1, 0, 0, 0, 0) Month)
+             [ "sidste måned"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "næste måned"
+             ]
+  , examples (datetime (2013, 1, 1, 0, 0, 0) Quarter)
+             [ "dette kvartal"
+             ]
+  , examples (datetime (2013, 4, 1, 0, 0, 0) Quarter)
+             [ "næste kvartal"
+             ]
+  , examples (datetime (2013, 7, 1, 0, 0, 0) Quarter)
+             [ "tredje kvartal"
+             , "3. kvartal"
+             ]
+  , examples (datetime (2018, 10, 1, 0, 0, 0) Quarter)
+             [ "4. kvartal 2018"
+             , "fjerde kvartal 2018"
+             ]
+  , examples (datetime (2012, 0, 0, 0, 0, 0) Year)
+             [ "sidste år"
+             ]
+  , examples (datetime (2013, 0, 0, 0, 0, 0) Year)
+             [ "i år"
+             , "dette år"
+             ]
+  , examples (datetime (2014, 0, 0, 0, 0, 0) Year)
+             [ "næste år"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "sidste søndag"
+             , "søndag i sidste uge"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "sidste tirsdag"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "næste tirsdag"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "næste onsdag"
+             ]
+  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
+             [ "onsdag i næste uge"
+             , "onsdag næste uge"
+             , "næste onsdag igen"
+             ]
+  , examples (datetime (2013, 2, 22, 0, 0, 0) Day)
+             [ "næste fredag igen"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "mandag i denne uge"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "tirsdag i denne uge"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "onsdag i denne uge"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "i overmorgen"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "i forgårs"
+             ]
+  , examples (datetime (2013, 3, 25, 0, 0, 0) Day)
+             [ "sidste mandag i marts"
+             , "sidste mandag i Marts"
+             ]
+  , examples (datetime (2014, 3, 30, 0, 0, 0) Day)
+             [ "sidste søndag i marts 2014"
+             , "sidste søndag i Marts 2014"
+             ]
+  , examples (datetime (2013, 10, 3, 0, 0, 0) Day)
+             [ "tredje dag i oktober"
+             , "tredje dag i Oktober"
+             ]
+  , examples (datetime (2014, 10, 6, 0, 0, 0) Week)
+             [ "første uge i oktober 2014"
+             , "første uge i Oktober 2014"
+             ]
+  , examples (datetime (2015, 10, 31, 0, 0, 0) Day)
+             [ "sidste dag i oktober 2015"
+             , "sidste dag i Oktober 2015"
+             ]
+  , examples (datetime (2014, 9, 22, 0, 0, 0) Week)
+             [ "sidste uge i september 2014"
+             , "sidste uge i September 2014"
+             ]
+  , examples (datetime (2013, 10, 1, 0, 0, 0) Day)
+             [ "første tirsdag i oktober"
+             , "første tirsdag i Oktober"
+             ]
+  , examples (datetime (2014, 9, 16, 0, 0, 0) Day)
+             [ "tredje tirsdag i september 2014"
+             , "tredje tirsdag i September 2014"
+             ]
+  , examples (datetime (2014, 10, 1, 0, 0, 0) Day)
+             [ "første onsdag i oktober 2014"
+             , "første onsdag i Oktober 2014"
+             ]
+  , examples (datetime (2014, 10, 8, 0, 0, 0) Day)
+             [ "anden onsdag i oktober 2014"
+             , "anden onsdag i Oktober 2014"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 0, 0) Hour)
+             [ "klokken 3"
+             , "kl. 3"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 18, 0) Minute)
+             [ "3:18"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "klokken 15"
+             , "kl. 15"
+             , "15h"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "ca. kl. 15"
+             , "cirka kl. 15"
+             , "omkring klokken 15"
+             ]
+  , examples (datetime (2013, 2, 13, 17, 0, 0) Hour)
+             [ "imorgen klokken 17 sharp"
+             , "imorgen kl. 17 præcis"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 15, 0) Minute)
+             [ "kvarter over 15"
+             , "kvart over 15"
+             , "15:15"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 20, 0) Minute)
+             [ "kl. 20 over 15"
+             , "klokken 20 over 15"
+             , "kl. 15:20"
+             , "15:20"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 30, 0) Minute)
+             [ "15:30"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 23, 24) Second)
+             [ "15:23:24"
+             ]
+  , examples (datetime (2013, 2, 12, 11, 45, 0) Minute)
+             [ "kvarter i 12"
+             , "kvart i 12"
+             , "11:45"
+             ]
+  , examples (datetime (2013, 2, 16, 9, 0, 0) Hour)
+             [ "klokken 9 på lørdag"
+             ]
+  , examples (datetime (2014, 7, 18, 19, 0, 0) Minute)
+             [ "Fre, Jul 18, 2014 19:00"
+             ]
+  , examples (datetime (2014, 7, 18, 0, 0, 0) Day)
+             [ "Fre, Jul 18"
+             , "Jul 18, Fre"
+             ]
+  , examples (datetime (2014, 9, 20, 19, 30, 0) Minute)
+             [ "kl. 19:30, Lør, 20 sep"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 30, 1) Second)
+             [ "om 1 sekund"
+             , "om ét sekund"
+             , "om et sekund"
+             , "ét sekund fra nu"
+             , "et sekund fra nu"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 31, 0) Second)
+             [ "om 1 minut"
+             , "om ét minut"
+             , "om et minut"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 32, 0) Second)
+             [ "om 2 minutter"
+             , "om to minutter"
+             , "om 2 minutter mere"
+             , "om to minutter mere"
+             , "2 minutter fra nu"
+             , "to minutter fra nu"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Second)
+             [ "om 60 minutter"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 0, 0) Second)
+             [ "om en halv time"
+             ]
+  , examples (datetime (2013, 2, 12, 7, 0, 0) Second)
+             [ "om 2,5 time"
+             , "om 2 og en halv time"
+             , "om to og en halv time"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Minute)
+             [ "om én time"
+             , "om 1 time"
+             , "om 1t"
+             ]
+  , examples (datetime (2013, 2, 12, 6, 30, 0) Minute)
+             [ "om et par timer"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 30, 0) Minute)
+             [ "om 24 timer"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 0, 0) Hour)
+             [ "om en dag"
+             ]
+  , examples (datetime (2016, 2, 0, 0, 0, 0) Month)
+             [ "3 år fra i dag"
+             ]
+  , examples (datetime (2013, 2, 19, 4, 0, 0) Hour)
+             [ "om 7 dage"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "om en uge"
+             , "om én uge"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 0, 0) Second)
+             [ "om ca. en halv time"
+             , "om cirka en halv time"
+             ]
+  , examples (datetime (2013, 2, 5, 4, 0, 0) Hour)
+             [ "7 dage siden"
+             , "syv dage siden"
+             ]
+  , examples (datetime (2013, 1, 29, 4, 0, 0) Hour)
+             [ "14 dage siden"
+             , "fjorten dage siden"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "en uge siden"
+             , "én uge siden"
+             , "1 uge siden"
+             ]
+  , examples (datetime (2013, 1, 22, 0, 0, 0) Day)
+             [ "3 uger siden"
+             , "tre uger siden"
+             ]
+  , examples (datetime (2012, 11, 12, 0, 0, 0) Day)
+             [ "3 måneder siden"
+             , "tre måneder siden"
+             ]
+  , examples (datetime (2011, 2, 0, 0, 0, 0) Month)
+             [ "to år siden"
+             , "2 år siden"
+             ]
+  , examples (datetime (1954, 0, 0, 0, 0, 0) Year)
+             [ "1954"
+             ]
+  , examples (datetime (2013, 12, 0, 0, 0, 0) Month)
+             [ "et år efter juleaften"
+             , "ét år efter juleaften"
+             ]
+  , examples (datetimeInterval ((2013, 6, 21, 0, 0, 0), (2013, 9, 24, 0, 0, 0)) Day)
+             [ "denne sommer"
+             , "den her sommer"
+             ]
+  , examples (datetimeInterval ((2012, 12, 21, 0, 0, 0), (2013, 3, 21, 0, 0, 0)) Day)
+             [ "denne vinter"
+             , "den her vinter"
+             ]
+  , examples (datetime (2013, 12, 25, 0, 0, 0) Day)
+             [ "1 juledag"
+             , "1. juledag"
+             , "første juledag"
+             ]
+  , examples (datetime (2013, 12, 31, 0, 0, 0) Day)
+             [ "nytårsaftensdag"
+             , "nytårsaften"
+             ]
+  , examples (datetime (2014, 1, 1, 0, 0, 0) Day)
+             [ "nytårsdag"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 18, 0, 0), (2013, 2, 13, 0, 0, 0)) Hour)
+             [ "i aften"
+             ]
+  , examples (datetimeInterval ((2013, 2, 8, 18, 0, 0), (2013, 2, 11, 0, 0, 0)) Hour)
+             [ "sidste weekend"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 18, 0, 0), (2013, 2, 14, 0, 0, 0)) Hour)
+             [ "i morgen aften"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 12, 0, 0), (2013, 2, 13, 14, 0, 0)) Hour)
+             [ "i morgen middag"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 18, 0, 0), (2013, 2, 12, 0, 0, 0)) Hour)
+             [ "i går aftes"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 18, 0, 0), (2013, 2, 18, 0, 0, 0)) Hour)
+             [ "denne weekend"
+             , "i weekenden"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 4, 0, 0), (2013, 2, 18, 12, 0, 0)) Hour)
+             [ "mandag morgen"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 29, 58), (2013, 2, 12, 4, 30, 0)) Second)
+             [ "sidste 2 sekunder"
+             , "sidste to sekunder"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 1), (2013, 2, 12, 4, 30, 4)) Second)
+             [ "næste 3 sekunder"
+             , "næste tre sekunder"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 28, 0), (2013, 2, 12, 4, 30, 0)) Minute)
+             [ "sidste 2 minutter"
+             , "sidste to minutter"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 31, 0), (2013, 2, 12, 4, 34, 0)) Minute)
+             [ "næste 3 minutter"
+             , "næste tre minutter"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 3, 0, 0), (2013, 2, 12, 4, 0, 0)) Hour)
+             [ "sidste 1 time"
+             , "seneste 1 time"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 5, 0, 0), (2013, 2, 12, 8, 0, 0)) Hour)
+             [ "næste 3 timer"
+             , "næste tre timer"
+             ]
+  , examples (datetimeInterval ((2013, 2, 10, 0, 0, 0), (2013, 2, 12, 0, 0, 0)) Day)
+             [ "sidste 2 dage"
+             , "sidste to dage"
+             , "seneste 2 dage"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
+             [ "næste 3 dage"
+             , "næste tre dage"
+             ]
+  , examples (datetimeInterval ((2013, 1, 28, 0, 0, 0), (2013, 2, 11, 0, 0, 0)) Week)
+             [ "sidste 2 uger"
+             , "sidste to uger"
+             , "seneste to uger"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 0, 0, 0), (2013, 3, 11, 0, 0, 0)) Week)
+             [ "næste 3 uger"
+             , "næste tre uger"
+             ]
+  , examples (datetimeInterval ((2012, 12, 0, 0, 0, 0), (2013, 2, 0, 0, 0, 0)) Month)
+             [ "sidste 2 måneder"
+             , "sidste to måneder"
+             , "seneste to måneder"
+             ]
+  , examples (datetimeInterval ((2013, 3, 0, 0, 0, 0), (2013, 6, 0, 0, 0, 0)) Month)
+             [ "næste 3 måneder"
+             , "næste tre måneder"
+             ]
+  , examples (datetimeInterval ((2011, 0, 0, 0, 0, 0), (2013, 0, 0, 0, 0, 0)) Year)
+             [ "sidste 2 år"
+             , "sidste to år"
+             , "seneste 2 år"
+             ]
+  , examples (datetimeInterval ((2014, 0, 0, 0, 0, 0), (2017, 0, 0, 0, 0, 0)) Year)
+             [ "næste 3 år"
+             , "næste tre år"
+             ]
+  , examples (datetimeInterval ((2013, 7, 13, 0, 0, 0), (2013, 7, 16, 0, 0, 0)) Day)
+             [ "13-15 juli"
+             , "13-15 Juli"
+             , "13 til 15 Juli"
+             , "13 juli til 15 juli"
+             ]
+  , examples (datetimeInterval ((2013, 8, 8, 0, 0, 0), (2013, 8, 13, 0, 0, 0)) Day)
+             [ "8 Aug - 12 Aug"
+             , "8 Aug - 12 aug"
+             , "8 aug - 12 aug"
+             , "8 august - 12 august"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 9, 30, 0), (2013, 2, 12, 11, 1, 0)) Minute)
+             [ "9:30 - 11:00"
+             , "9:30 til 11:00"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 30, 0), (2013, 2, 14, 11, 1, 0)) Minute)
+             [ "fra 9:30 - 11:00 på torsdag"
+             , "fra 9:30 til 11:00 på torsdag"
+             , "mellem 9:30 og 11:00 på torsdag"
+             , "9:30 - 11:00 på torsdag"
+             , "9:30 til 11:00 på torsdag"
+             , "efter 9:30 men før 11:00 på torsdag"
+             , "torsdag fra 9:30 til 11:00"
+             , "torsdag mellem 9:30 og 11:00"
+             , "fra 9:30 til 11:00 på torsdag"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 0, 0), (2013, 2, 14, 12, 0, 0)) Hour)
+             [ "torsdag fra 9 til 11"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 11, 30, 0), (2013, 2, 12, 13, 31, 0)) Minute)
+             [ "11:30-13:30"
+             , "11:30-13:30"
+             , "11:30-13:30"
+             , "11:30-13:30"
+             , "11:30-13:30"
+             , "11:30-13:30"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 26, 0, 0, 0)) Second)
+             [ "indenfor 2 uger"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 14, 0, 0) Hour)
+             [ "inden kl. 14"
+             , "indtil kl. 14"
+             , "inden klokken 14"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 0, 0) Minute)
+             [ "@ 16 CET"
+             , "kl. 16 CET"
+             , "klokken 16 CET"
+             ]
+  , examples (datetime (2013, 2, 14, 6, 0, 0) Minute)
+             [ "torsdag kl. 8:00 GMT"
+             , "torsdag klokken 8:00 GMT"
+             , "torsdag 08:00 GMT"
+             ]
+  , examples (datetime (2013, 2, 12, 14, 0, 0) Hour)
+             [ "idag kl. 14"
+             , "idag klokken 14"
+             , "kl. 14"
+             , "klokken 14"
+             ]
+  , examples (datetime (2013, 4, 25, 16, 0, 0) Minute)
+             [ "25/4 kl. 16:00"
+             , "25/4 klokken 16:00"
+             , "25-04 klokken 16:00"
+             , "25-4 kl. 16:00"
+             ]
+  , examples (datetime (2013, 2, 13, 15, 0, 0) Minute)
+             [ "15:00 i morgen"
+             , "kl. 15:00 i morgen"
+             , "klokken 15:00 i morgen"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 12, 14, 0, 0) Hour)
+             [ "efter kl. 14"
+             , "efter klokken 14"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 17, 4, 0, 0) Hour)
+             [ "efter 5 dage"
+             , "efter fem dage"
+             ]
+  , examples (datetime (2013, 2, 17, 4, 0, 0) Hour)
+             [ "om 5 dage"
+             , "om fem dage"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 13, 14, 0, 0) Hour)
+             [ "efter i morgen kl. 14"
+             , "efter i morgen klokken 14"
+             , "i morgen efter kl. 14"
+             , "i morgen efter klokken 14"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 11, 0, 0) Hour)
+             [ "før kl. 11"
+             , "før klokken 11"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 13, 11, 0, 0) Hour)
+             [ "i morgen før kl. 11"
+             , "i morgen før klokken 11"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 12, 0, 0), (2013, 2, 12, 19, 0, 0)) Hour)
+             [ "om eftermiddagen"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 30, 0) Minute)
+             [ "kl. 13:30"
+             , "klokken 13:30"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 45, 0) Second)
+             [ "om 15 minutter"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 13, 0, 0), (2013, 2, 12, 17, 0, 0)) Hour)
+             [ "efter frokost"
+             ]
+  , examples (datetime (2013, 2, 12, 10, 30, 0) Minute)
+             [ "10:30"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 0, 0), (2013, 2, 12, 12, 0, 0)) Hour)
+             [ "denne morgen"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "næste mandag"
+             ]
+  ]
diff --git a/Duckling/Time/DA/Rules.hs b/Duckling/Time/DA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/DA/Rules.hs
@@ -0,0 +1,2007 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.DA.Rules
+  ( rules ) where
+
+import Control.Monad (liftM2)
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Types (OrdinalData (..))
+import qualified Duckling.Ordinal.Types as TOrdinal
+import Duckling.Time.Helpers
+import Duckling.Time.Types (TimeData (..))
+import qualified Duckling.Time.Types as TTime
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Regex.Types
+import Duckling.Types
+
+ruleNamedday :: Rule
+ruleNamedday = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "mandag|man\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 1
+  }
+
+ruleTheDayAfterTomorrow :: Rule
+ruleTheDayAfterTomorrow = Rule
+  { name = "the day after tomorrow"
+  , pattern =
+    [ regex "i overmorgen"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 2
+  }
+
+ruleNamedmonth12 :: Rule
+ruleNamedmonth12 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "december|dec\\.?"
+    ]
+  , prod = \_ -> tt $ month 12
+  }
+
+ruleRelativeMinutesTotillbeforeIntegerHourofday :: Rule
+ruleRelativeMinutesTotillbeforeIntegerHourofday = Rule
+  { name = "relative minutes to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "i"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleQuarterTotillbeforeIntegerHourofday :: Rule
+ruleQuarterTotillbeforeIntegerHourofday = Rule
+  { name = "quarter to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ regex "(et|\x00e9t)? ?kvart(er)? i"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> do
+        t <- minutesBefore 15 td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleHalfTotillbeforeIntegerHourofday :: Rule
+ruleHalfTotillbeforeIntegerHourofday = Rule
+  { name = "half to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ regex "halv time i"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> do
+        t <- minutesBefore 30 td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleConstitutionDay :: Rule
+ruleConstitutionDay = Rule
+  { name = "constitution day"
+  , pattern =
+    [ regex "grundlov'?s?'? dag"
+    ]
+  , prod = \_ -> tt $ monthDay 6 5
+  }
+
+ruleNamedday2 :: Rule
+ruleNamedday2 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "tirsdag|tirs?\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 2
+  }
+
+ruleValentinesDay :: Rule
+ruleValentinesDay = Rule
+  { name = "valentine's day"
+  , pattern =
+    [ regex "valentine'?s?( dag)?"
+    ]
+  , prod = \_ -> tt $ monthDay 2 14
+  }
+
+ruleTheOrdinalCycleOfTime :: Rule
+ruleTheOrdinalCycleOfTime = Rule
+  { name = "the <ordinal> <cycle> of <time>"
+  , pattern =
+    [ regex "den"
+    , dimension Ordinal
+    , dimension TimeGrain
+    , regex "af|i|fra"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleNthTimeOfTime2 :: Rule
+ruleNthTimeOfTime2 = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ regex "den"
+    , dimension Ordinal
+    , dimension Time
+    , regex "af|i"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       Token Time td1:
+       _:
+       Token Time td2:
+       _) -> Token Time . predNth (v - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNewYearsDay :: Rule
+ruleNewYearsDay = Rule
+  { name = "new year's day"
+  , pattern =
+    [ regex "nyt\x00e5rsdag"
+    ]
+  , prod = \_ -> tt $ monthDay 1 1
+  }
+
+ruleLastTime :: Rule
+ruleLastTime = Rule
+  { name = "last <time>"
+  , pattern =
+    [ regex "(sidste|seneste)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleNamedday6 :: Rule
+ruleNamedday6 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "l\x00f8rdag|l\x00f8r\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 6
+  }
+
+ruleDatetimeDatetimeInterval :: Rule
+ruleDatetimeDatetimeInterval = Rule
+  { name = "<datetime> - <datetime> (interval)"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "\\-|til|indtil"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth7 :: Rule
+ruleNamedmonth7 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "juli|jul\\.?"
+    ]
+  , prod = \_ -> tt $ month 7
+  }
+
+ruleEvening :: Rule
+ruleEvening = Rule
+  { name = "evening"
+  , pattern =
+    [ regex "afte(s|n(en)?)"
+    ]
+  , prod = \_ ->
+      let from = hour False 18
+          to = hour False 0
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleTheDayofmonthNonOrdinal :: Rule
+ruleTheDayofmonthNonOrdinal = Rule
+  { name = "the <day-of-month> (non ordinal)"
+  , pattern =
+    [ regex "den"
+    , Predicate $ isIntegerBetween 1 31
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        n <- getIntValue token
+        tt $ dayOfMonth n
+      _ -> Nothing
+  }
+
+ruleCycleAfterTime :: Rule
+ruleCycleAfterTime = Rule
+  { name = "<cycle> after <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "efter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain 1 td
+      _ -> Nothing
+  }
+
+ruleInDuration :: Rule
+ruleInDuration = Rule
+  { name = "in <duration>"
+  , pattern =
+    [ regex "om"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleNow :: Rule
+ruleNow = Rule
+  { name = "now"
+  , pattern =
+    [ regex "lige nu|nu|(i )?dette \x00f8jeblik"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Second 0
+  }
+
+ruleLastCycleOfTime :: Rule
+ruleLastCycleOfTime = Rule
+  { name = "last <cycle> of <time>"
+  , pattern =
+    [ regex "sidste"
+    , dimension TimeGrain
+    , regex "af|i"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleLastOf grain td
+      _ -> Nothing
+  }
+
+ruleFromDatetimeDatetimeInterval :: Rule
+ruleFromDatetimeDatetimeInterval = Rule
+  { name = "from <datetime> - <datetime> (interval)"
+  , pattern =
+    [ regex "fra"
+    , dimension Time
+    , regex "\\-|til|indtil"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleRelativeMinutesAfterpastIntegerHourofday :: Rule
+ruleRelativeMinutesAfterpastIntegerHourofday = Rule
+  { name = "relative minutes after|past <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "over"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:
+       _:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleQuarterAfterpastIntegerHourofday :: Rule
+ruleQuarterAfterpastIntegerHourofday = Rule
+  { name = "quarter after|past <integer> (hour-of-day)"
+  , pattern =
+    [ regex "(et|\x00e9t)? ?kvart(er)? over"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 15
+      _ -> Nothing
+  }
+
+ruleHalfAfterpastIntegerHourofday :: Rule
+ruleHalfAfterpastIntegerHourofday = Rule
+  { name = "half after|past <integer> (hour-of-day)"
+  , pattern =
+    [ regex "halv time over"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+
+ruleMonthDdddInterval :: Rule
+ruleMonthDdddInterval = Rule
+  { name = "<month> dd-dd (interval)"
+  , pattern =
+    [ regex "([012]?\\d|30|31)(ter|\\.)?"
+    , regex "\\-|til"
+    , regex "([012]?\\d|30|31)(ter|\\.)?"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:_)):
+       _:
+       Token RegexMatch (GroupMatch (m2:_)):
+       Token Time td:
+       _) -> do
+        v1 <- parseInt m1
+        v2 <- parseInt m2
+        from <- intersect td (dayOfMonth v1)
+        to <- intersect td (dayOfMonth v2)
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleNamedday4 :: Rule
+ruleNamedday4 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "torsdag|tors?\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 4
+  }
+
+ruleTheCycleAfterTime :: Rule
+ruleTheCycleAfterTime = Rule
+  { name = "the <cycle> after <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "(en|tet|et)? efter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain 1 td
+      _ -> Nothing
+  }
+
+ruleTheCycleBeforeTime :: Rule
+ruleTheCycleBeforeTime = Rule
+  { name = "the <cycle> before <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "(en|tet|et)? f\x00f8r"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain (-1) td
+      _ -> Nothing
+  }
+
+ruleSeason4 :: Rule
+ruleSeason4 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "for\x00e5r"
+    ]
+  , prod = \_ -> tt $
+      interval TTime.Open (monthDay 3 20, monthDay 6 21)
+  }
+
+ruleYearLatent2 :: Rule
+ruleYearLatent2 = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2101 10000
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ year v
+      _ -> Nothing
+  }
+
+ruleTimeAfterNext :: Rule
+ruleTimeAfterNext = Rule
+  { name = "<time> after next"
+  , pattern =
+    [ regex "n(\x00e6)ste"
+    , dimension Time
+    , regex "igen"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 1 True td
+      _ -> Nothing
+  }
+
+ruleTheIdesOfNamedmonth :: Rule
+ruleTheIdesOfNamedmonth = Rule
+  { name = "the ides of <named-month>"
+  , pattern =
+    [ regex "midten af"
+    , 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
+  }
+
+ruleNoon :: Rule
+ruleNoon = Rule
+  { name = "noon"
+  , pattern =
+    [ regex "middag|(kl(\\.|okken)?)? tolv"
+    ]
+  , prod = \_ -> tt $ hour False 12
+  }
+
+ruleToday :: Rule
+ruleToday = Rule
+  { name = "today"
+  , pattern =
+    [ regex "i dag|idag"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 0
+  }
+
+ruleThisnextDayofweek :: Rule
+ruleThisnextDayofweek = Rule
+  { name = "this|next <day-of-week>"
+  , pattern =
+    [ regex "(kommende|n(\x00e6)ste)"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleTheDayBeforeYesterday :: Rule
+ruleTheDayBeforeYesterday = Rule
+  { name = "the day before yesterday"
+  , pattern =
+    [ regex "i forg\x00e5rs"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 2
+  }
+
+ruleBetweenTimeofdayAndTimeofdayInterval :: Rule
+ruleBetweenTimeofdayAndTimeofdayInterval = Rule
+  { name = "between <time-of-day> and <time-of-day> (interval)"
+  , pattern =
+    [ regex "mellem"
+    , Predicate isATimeOfDay
+    , regex "og"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNextCycle :: Rule
+ruleNextCycle = Rule
+  { name = "next <cycle>"
+  , pattern =
+    [ regex "n(\x00e6)ste|kommende"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleNamedmonth :: Rule
+ruleNamedmonth = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "januar|jan\\.?"
+    ]
+  , prod = \_ -> tt $ month 1
+  }
+
+ruleTheCycleOfTime :: Rule
+ruleTheCycleOfTime = Rule
+  { name = "the <cycle> of <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "(en|tet|et)? af"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain 0 td
+      _ -> Nothing
+  }
+
+ruleTimeofdayApproximately :: Rule
+ruleTimeofdayApproximately = Rule
+  { name = "<time-of-day> approximately"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(cirka|ca\\.|-?ish)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleOnDate :: Rule
+ruleOnDate = Rule
+  { name = "on <date>"
+  , pattern =
+    [ regex "den|p\x00e5"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleNamedmonth3 :: Rule
+ruleNamedmonth3 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "marts|mar\\.?"
+    ]
+  , prod = \_ -> tt $ month 3
+  }
+
+ruleDurationFromNow :: Rule
+ruleDurationFromNow = Rule
+  { name = "<duration> from now"
+  , pattern =
+    [ dimension Duration
+    , regex "fra (i dag|nu)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) -> tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleLunch :: Rule
+ruleLunch = Rule
+  { name = "lunch"
+  , pattern =
+    [ regex "(til )?middag"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 14
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleLastCycle :: Rule
+ruleLastCycle = Rule
+  { name = "last <cycle>"
+  , pattern =
+    [ regex "sidste|seneste"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) -> tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleDdmm :: Rule
+ruleDdmm = Rule
+  { name = "dd/mm"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[\\/-](0?[1-9]|1[0-2])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        m <- parseInt m2
+        d <- parseInt m1
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleAfternoon :: Rule
+ruleAfternoon = Rule
+  { name = "afternoon"
+  , pattern =
+    [ regex "eftermiddag(en)?"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 19
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleNamedmonth4 :: Rule
+ruleNamedmonth4 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "april|apr\\.?"
+    ]
+  , prod = \_ -> tt $ month 4
+  }
+
+ruleTimeBeforeLast :: Rule
+ruleTimeBeforeLast = Rule
+  { name = "<time> before last"
+  , pattern =
+    [ regex "sidste"
+    , dimension Time
+    , regex "igen"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth (-2) False td
+      _ -> Nothing
+  }
+
+ruleNamedmonthDayofmonthOrdinal :: Rule
+ruleNamedmonthDayofmonthOrdinal = Rule
+  { name = "<named-month> <day-of-month> (ordinal)"
+  , pattern =
+    [ Predicate isAMonth
+    , Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleChristmasEve :: Rule
+ruleChristmasEve = Rule
+  { name = "christmas eve"
+  , pattern =
+    [ regex "juleaften(sdag)?"
+    ]
+  , prod = \_ -> tt $ monthDay 12 24
+  }
+
+ruleInduringThePartofday :: Rule
+ruleInduringThePartofday = Rule
+  { name = "in|during the <part-of-day>"
+  , pattern =
+    [ regex "om|i"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleHourofdayIntegerAsRelativeMinutes :: Rule
+ruleHourofdayIntegerAsRelativeMinutes = Rule
+  { name = "<hour-of-day> <integer> (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleHourofdayQuart :: Rule
+ruleHourofdayQuart = Rule
+  { name = "<hour-of-day> quarter (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "(et|\x00e9t)? ?kvart(er)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 15
+      _ -> Nothing
+  }
+
+ruleHourofdayHalf :: Rule
+ruleHourofdayHalf = Rule
+  { name = "<hour-of-day> half (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "halv time"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+
+ruleNamedday5 :: Rule
+ruleNamedday5 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "fredag|fre\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 5
+  }
+
+ruleDayofmonthordinalNamedmonth :: Rule
+ruleDayofmonthordinalNamedmonth = Rule
+  { name = "<day-of-month>(ordinal) <named-month>"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleIntersectBy :: Rule
+ruleIntersectBy = Rule
+  { name = "intersect by \",\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex ","
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNthTimeAfterTime :: Rule
+ruleNthTimeAfterTime = Rule
+  { name = "nth <time> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "efter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       Token Time td1:
+       _:
+       Token Time td2:
+       _) -> tt $ predNthAfter (v - 1) td1 td2
+      _ -> Nothing
+  }
+
+ruleAfterDuration :: Rule
+ruleAfterDuration = Rule
+  { name = "after <duration>"
+  , pattern =
+    [ regex "efter"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt . withDirection TTime.After $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleTimeofdayLatent :: Rule
+ruleTimeofdayLatent = Rule
+  { name = "time-of-day (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 0 23
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ hour False v
+      _ -> Nothing
+  }
+
+ruleDayofmonthOrdinalOfNamedmonth :: Rule
+ruleDayofmonthOrdinalOfNamedmonth = Rule
+  { name = "<day-of-month> (ordinal) of <named-month>"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    , regex "af|i"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleFromTimeofdayTimeofdayInterval :: Rule
+ruleFromTimeofdayTimeofdayInterval = Rule
+  { name = "from <time-of-day> - <time-of-day> (interval)"
+  , pattern =
+    [ regex "(efter|fra)"
+    , Predicate isATimeOfDay
+    , regex "((men )?f\x00f8r)|\\-|til|indtil"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth2 :: Rule
+ruleNamedmonth2 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "februar|feb\\.?"
+    ]
+  , prod = \_ -> tt $ month 2
+  }
+
+ruleExactlyTimeofday :: Rule
+ruleExactlyTimeofday = Rule
+  { name = "exactly <time-of-day>"
+  , pattern =
+    [ regex "pr(\x00e6)cis( kl\\.| klokken)?"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleInduringThePartofday2 :: Rule
+ruleInduringThePartofday2 = Rule
+  { name = "in|during the <part-of-day>"
+  , pattern =
+    [ regex "om|i"
+    , Predicate isAPartOfDay
+    , regex "en|ten"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleSeason3 :: Rule
+ruleSeason3 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "vinter"
+    ]
+  , prod = \_ ->
+      let from = monthDay 12 21
+          to = monthDay 3 20
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleSeason :: Rule
+ruleSeason = Rule
+  { name = "season"
+  , pattern =
+    [ regex "sommer"
+    ]
+  , prod = \_ ->
+      let from = monthDay 6 21
+          to = monthDay 9 23
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleBetweenDatetimeAndDatetimeInterval :: Rule
+ruleBetweenDatetimeAndDatetimeInterval = Rule
+  { name = "between <datetime> and <datetime> (interval)"
+  , pattern =
+    [ regex "mellem"
+    , dimension Time
+    , regex "og"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNewYearsEve :: Rule
+ruleNewYearsEve = Rule
+  { name = "new year's eve"
+  , pattern =
+    [ regex "nyt\x00e5rsaften(sdag)?"
+    ]
+  , prod = \_ -> tt $ monthDay 12 31
+  }
+
+ruleDurationAgo :: Rule
+ruleDurationAgo = Rule
+  { name = "<duration> ago"
+  , pattern =
+    [ dimension Duration
+    , regex "siden"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt $ durationAgo dd
+      _ -> Nothing
+  }
+
+ruleByTheEndOfTime :: Rule
+ruleByTheEndOfTime = Rule
+  { name = "by the end of <time>"
+  , pattern =
+    [ regex "i slutningen af"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $
+        interval TTime.Closed (cycleNth TG.Second 0, td)
+      _ -> Nothing
+  }
+
+ruleAfterWork :: Rule
+ruleAfterWork = Rule
+  { name = "after work"
+  , pattern =
+    [ regex "efter arbejde"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 17, hour False 21)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleLastNCycle :: Rule
+ruleLastNCycle = Rule
+  { name = "last n <cycle>"
+  , pattern =
+    [ regex "sidste|seneste"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        n <- getIntValue token
+        tt $ cycleN True grain (- n)
+      _ -> Nothing
+  }
+
+ruleTimeofdaySharp :: Rule
+ruleTimeofdaySharp = Rule
+  { name = "<time-of-day> sharp"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(sharp|pr(\x00e6)cis)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleWithinDuration :: Rule
+ruleWithinDuration = Rule
+  { name = "within <duration>"
+  , pattern =
+    [ regex "indenfor"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) -> tt $
+        interval TTime.Open (cycleNth TG.Second 0, inDuration dd)
+      _ -> Nothing
+  }
+
+ruleMidnighteodendOfDay :: Rule
+ruleMidnighteodendOfDay = Rule
+  { name = "midnight|EOD|end of day"
+  , pattern =
+    [ regex "midnat|EOD"
+    ]
+  , prod = \_ -> tt $ hour False 0
+  }
+
+ruleDayofmonthNonOrdinalNamedmonth :: Rule
+ruleDayofmonthNonOrdinalNamedmonth = Rule
+  { name = "<day-of-month> (non ordinal) <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ Predicate isNotLatent
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleAboutTimeofday :: Rule
+ruleAboutTimeofday = Rule
+  { name = "about <time-of-day>"
+  , pattern =
+    [ regex "(omkring|cirka|ca\\.)( kl\\.| klokken)?"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleUntilTimeofday :: Rule
+ruleUntilTimeofday = Rule
+  { name = "until <time-of-day>"
+  , pattern =
+    [ regex "(engang )?inden|indtil|f\x00f8r|op til"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ withDirection TTime.Before td
+      _ -> Nothing
+  }
+
+ruleAtTimeofday :: Rule
+ruleAtTimeofday = Rule
+  { name = "at <time-of-day>"
+  , pattern =
+    [ regex "klokken|kl\\.|@"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth6 :: Rule
+ruleNamedmonth6 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "juni|jun\\.?"
+    ]
+  , prod = \_ -> tt $ month 6
+  }
+
+ruleNthTimeOfTime :: Rule
+ruleNthTimeOfTime = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "af|i"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       Token Time td1:
+       _:
+       Token Time td2:
+       _) -> Token Time . predNth (v - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNamedmonth8 :: Rule
+ruleNamedmonth8 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "august|aug\\.?"
+    ]
+  , prod = \_ -> tt $ month 8
+  }
+
+ruleTimePartofday :: Rule
+ruleTimePartofday = Rule
+  { name = "<time> <part-of-day>"
+  , pattern =
+    [ dimension Time
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleWeekend :: Rule
+ruleWeekend = Rule
+  { name = "week-end"
+  , pattern =
+    [ regex "(week(\\s|-)?end)(en)?"
+    ]
+  , prod = \_ -> do
+      from <- intersect (dayOfWeek 5) (hour False 18)
+      to <- intersect (dayOfWeek 1) (hour False 0)
+      tt $ interval TTime.Open (from, to)
+  }
+
+ruleAboutDuration :: Rule
+ruleAboutDuration = Rule
+  { name = "about <duration>"
+  , pattern =
+    [ regex "(omkring|cirka|ca.)"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) -> Just . Token Duration $ dd
+      _ -> Nothing
+  }
+
+ruleEomendOfMonth :: Rule
+ruleEomendOfMonth = Rule
+  { name = "EOM|End of month"
+  , pattern =
+    [ regex "EOM"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Month 1
+  }
+
+ruleNthTimeAfterTime2 :: Rule
+ruleNthTimeAfterTime2 = Rule
+  { name = "nth <time> after <time>"
+  , pattern =
+    [ regex "den"
+    , dimension Ordinal
+    , dimension Time
+    , regex "efter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       Token Time td1:
+       _:
+       Token Time td2:
+       _) -> tt $ predNthAfter (v - 1) td1 td2
+      _ -> Nothing
+  }
+
+ruleNextTime :: Rule
+ruleNextTime = Rule
+  { name = "next <time>"
+  , pattern =
+    [ regex "n(\x00e6)ste|kommende"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleOrdinalQuarterYear :: Rule
+ruleOrdinalQuarterYear = Rule
+  { name = "<ordinal> quarter <year>"
+  , pattern =
+    [ dimension Ordinal
+    , Predicate $ isGrain TG.Quarter
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:_:Token Time td:_) ->
+        tt $ cycleNthAfter False TG.Quarter (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleYyyymmdd :: Rule
+ruleYyyymmdd = Rule
+  { name = "yyyy-mm-dd"
+  , pattern =
+    [ regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
+        y <- parseInt m1
+        m <- parseInt m2
+        d <- parseInt m3
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleTheOrdinalCycleAfterTime :: Rule
+ruleTheOrdinalCycleAfterTime = Rule
+  { name = "the <ordinal> <cycle> after <time>"
+  , pattern =
+    [ regex "den"
+    , dimension Ordinal
+    , dimension TimeGrain
+    , regex "efter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleIntersectByOfFromS :: Rule
+ruleIntersectByOfFromS = Rule
+  { name = "intersect by \"of\", \"from\", \"'s\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "i"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNextNCycle :: Rule
+ruleNextNCycle = Rule
+  { name = "next n <cycle>"
+  , pattern =
+    [ regex "n(\x00e6)ste"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        n <- getIntValue token
+        tt $ cycleN True grain n
+      _ -> Nothing
+  }
+
+ruleADuration :: Rule
+ruleADuration = Rule
+  { name = "a <duration>"
+  , pattern =
+    [ regex "(om )?en|et|\x00e9n|\x00e9t"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleMorning :: Rule
+ruleMorning = Rule
+  { name = "morning"
+  , pattern =
+    [ regex "morgen(en)?"
+    ]
+  , prod = \_ ->
+      let from = hour False 4
+          to = hour False 12
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleThisPartofday :: Rule
+ruleThisPartofday = Rule
+  { name = "this <part-of-day>"
+  , pattern =
+    [ regex "i|denne"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        Token Time . partOfDay <$> intersect (cycleNth TG.Day 0) td
+      _ -> Nothing
+  }
+
+ruleThisCycle :: Rule
+ruleThisCycle = Rule
+  { name = "this <cycle>"
+  , pattern =
+    [ regex "denne|dette|i|nuv(\x00e6)rende|indev(\x00e6)rende"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 0
+      _ -> Nothing
+  }
+
+ruleThisTime :: Rule
+ruleThisTime = Rule
+  { name = "this <time>"
+  , pattern =
+    [ regex "(denne|dette|i|den her)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleDurationHence :: Rule
+ruleDurationHence = Rule
+  { name = "<duration> hence"
+  , pattern =
+    [ dimension Duration
+    , regex "hence"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleDayofmonthNonOrdinalOfNamedmonth :: Rule
+ruleDayofmonthNonOrdinalOfNamedmonth = Rule
+  { name = "<day-of-month> (non ordinal) of <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , regex "af|i"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleAfterLunch :: Rule
+ruleAfterLunch = Rule
+  { name = "after lunch"
+  , pattern =
+    [ regex "efter (frokost|middag)"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 13, hour False 17)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleOnANamedday :: Rule
+ruleOnANamedday = Rule
+  { name = "on a named-day"
+  , pattern =
+    [ regex "p\x00e5 en"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleYearLatent :: Rule
+ruleYearLatent = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween (- 10000) 999
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        y <- getIntValue token
+        tt . mkLatent $ year y
+      _ -> Nothing
+  }
+
+ruleYesterday :: Rule
+ruleYesterday = Rule
+  { name = "yesterday"
+  , pattern =
+    [ regex "i g\x00e5r|ig\x00e5r"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 1
+  }
+
+ruleSeason2 :: Rule
+ruleSeason2 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "efter\x00e5r"
+    ]
+  , prod = \_ ->
+      let from = monthDay 9 23
+          to = monthDay 12 21
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleAfterTimeofday :: Rule
+ruleAfterTimeofday = Rule
+  { name = "after <time-of-day>"
+  , pattern =
+    [ regex "(engang )?efter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ withDirection TTime.After td
+      _ -> Nothing
+  }
+
+ruleChristmas :: Rule
+ruleChristmas = Rule
+  { name = "christmas"
+  , pattern =
+    [ regex "((1\\.?)|f\x00f8rste)? ?juledag"
+    ]
+  , prod = \_ -> tt $ monthDay 12 25
+  }
+
+ruleNight :: Rule
+ruleNight = Rule
+  { name = "night"
+  , pattern =
+    [ regex "nat(ten)?"
+    ]
+  , prod = \_ ->
+      let from = hour False 0
+          to = hour False 4
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleDayofmonthOrdinal :: Rule
+ruleDayofmonthOrdinal = Rule
+  { name = "<day-of-month> (ordinal)"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):_) ->
+        tt . mkLatent $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleOrdinalCycleAfterTime :: Rule
+ruleOrdinalCycleAfterTime = Rule
+  { name = "<ordinal> <cycle> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "efter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleOrdinalCycleOfTime :: Rule
+ruleOrdinalCycleOfTime = Rule
+  { name = "<ordinal> <cycle> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "af|i|fra"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleNamedmonth5 :: Rule
+ruleNamedmonth5 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "maj"
+    ]
+  , prod = \_ -> tt $ month 5
+  }
+
+ruleNamedday7 :: Rule
+ruleNamedday7 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "s\x00f8ndag|s\x00f8n\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 7
+  }
+
+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 False h m
+      _ -> Nothing
+  }
+
+ruleTonight :: Rule
+ruleTonight = Rule
+  { name = "tonight"
+  , pattern =
+    [ regex "i aften"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 18, hour False 0)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleYear :: Rule
+ruleYear = Rule
+  { name = "year"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1000 2100
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        y <- getIntValue token
+        tt $ year y
+      _ -> Nothing
+  }
+
+ruleNamedmonth10 :: Rule
+ruleNamedmonth10 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "oktober|okt\\.?"
+    ]
+  , prod = \_ -> tt $ month 10
+  }
+
+ruleHalloweenDay :: Rule
+ruleHalloweenDay = Rule
+  { name = "halloween day"
+  , pattern =
+    [ regex "hall?owe?en"
+    ]
+  , prod = \_ -> tt $ monthDay 10 31
+  }
+
+ruleNamedmonthDayofmonthNonOrdinal :: Rule
+ruleNamedmonthDayofmonthNonOrdinal = 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
+  }
+
+ruleAbsorptionOfAfterNamedDay :: Rule
+ruleAbsorptionOfAfterNamedDay = Rule
+  { name = "absorption of , after named day"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex ","
+    ]
+  , prod = \tokens -> case tokens of
+      (x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleLastDayofweekOfTime :: Rule
+ruleLastDayofweekOfTime = Rule
+  { name = "last <day-of-week> of <time>"
+  , pattern =
+    [ regex "sidste"
+    , Predicate isADayOfWeek
+    , regex "af|i"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ predLastOf td1 td2
+      _ -> Nothing
+  }
+
+ruleFathersDay :: Rule
+ruleFathersDay = Rule
+  { name = "Father's Day"
+  , pattern =
+    [ regex "far'?s?'? dag"
+    ]
+  , prod = \_ -> tt $ monthDay 6 5
+  }
+
+ruleCycleBeforeTime :: Rule
+ruleCycleBeforeTime = Rule
+  { name = "<cycle> before <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "f\x00f8r"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain (-1) td
+      _ -> Nothing
+  }
+
+ruleDdmmyyyy :: Rule
+ruleDdmmyyyy = Rule
+  { name = "dd/mm/yyyy"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[\\/-](0?[1-9]|1[0-2])[\\/-](\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
+        y <- parseInt m3
+        m <- parseInt m2
+        d <- parseInt m1
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleTimeofdayTimeofdayInterval :: Rule
+ruleTimeofdayTimeofdayInterval = Rule
+  { name = "<time-of-day> - <time-of-day> (interval)"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\-|til|indtil"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth11 :: Rule
+ruleNamedmonth11 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "november|nov\\.?"
+    ]
+  , prod = \_ -> tt $ month 11
+  }
+
+ruleDurationAfterTime :: Rule
+ruleDurationAfterTime = Rule
+  { name = "<duration> after <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "efter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_:Token Time td:_) ->
+        tt $ durationAfter dd td
+      _ -> Nothing
+  }
+
+ruleOrdinalQuarter :: Rule
+ruleOrdinalQuarter = Rule
+  { name = "<ordinal> quarter"
+  , pattern =
+    [ dimension Ordinal
+    , Predicate $ isGrain TG.Quarter
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . cycleNthAfter False TG.Quarter (n - 1) $
+          cycleNth TG.Year 0
+      _ -> Nothing
+  }
+
+ruleNamedday3 :: Rule
+ruleNamedday3 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "onsdag|ons\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 3
+  }
+
+ruleTheDayofmonthOrdinal :: Rule
+ruleTheDayofmonthOrdinal = Rule
+  { name = "the <day-of-month> (ordinal)"
+  , pattern =
+    [ regex "den"
+    , Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal (OrdinalData {TOrdinal.value = v}):_) ->
+        tt $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleDurationBeforeTime :: Rule
+ruleDurationBeforeTime = Rule
+  { name = "<duration> before <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "f\x00f8r"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_:Token Time td:_) ->
+        tt $ durationBefore dd td
+      _ -> Nothing
+  }
+
+rulePartofdayOfTime :: Rule
+rulePartofdayOfTime = Rule
+  { name = "<part-of-day> of <time>"
+  , pattern =
+    [ Predicate isAPartOfDay
+    , regex "(en |ten )?den"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleEoyendOfYear :: Rule
+ruleEoyendOfYear = Rule
+  { name = "EOY|End of year"
+  , pattern =
+    [ regex "EOY"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Year 1
+  }
+
+ruleTomorrow :: Rule
+ruleTomorrow = Rule
+  { name = "tomorrow"
+  , pattern =
+    [ regex "i morgen|imorgen"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 1
+  }
+
+ruleMothersDay :: Rule
+ruleMothersDay = Rule
+  { name = "Mother's Day"
+  , pattern =
+    [ regex "mor'?s? dag"
+    ]
+  , prod = \_ -> tt $ nthDOWOfMonth 1 7 5
+  }
+
+ruleTimeofdayOclock :: Rule
+ruleTimeofdayOclock = Rule
+  { name = "<time-of-day> o'clock"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "h"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth9 :: Rule
+ruleNamedmonth9 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "september|sept?\\.?"
+    ]
+  , prod = \_ -> tt $ month 9
+  }
+
+ruleDayofmonthordinalNamedmonthYear :: Rule
+ruleDayofmonthordinalNamedmonthYear = 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
+         n <- parseInt match
+         td2 <- intersectDOM td token
+         Token Time <$> intersect td2 (year n)
+      _ -> 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 (m1:m2:m3:_)):_) -> do
+        h <- parseInt m1
+        m <- parseInt m2
+        s <- parseInt m3
+        tt $ hourMinuteSecond False h m s
+      _ -> Nothing
+  }
+
+ruleTimezone :: Rule
+ruleTimezone = Rule
+  { name = "<time> timezone"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\b(YEKT|YEKST|YAKT|YAKST|WITA|WIT|WIB|WGT|WGST|WFT|WET|WEST|WAT|WAST|VUT|VLAT|VLAST|VET|UZT|UYT|UYST|UTC|ULAT|TVT|TMT|TLT|TKT|TJT|TFT|TAHT|SST|SRT|SGT|SCT|SBT|SAST|SAMT|RET|PYT|PYST|PWT|PST|PONT|PMST|PMDT|PKT|PHT|PHOT|PGT|PETT|PETST|PET|PDT|OMST|OMSST|NZST|NZDT|NUT|NST|NPT|NOVT|NOVST|NFT|NDT|NCT|MYT|MVT|MUT|MST|MSK|MSD|MMT|MHT|MDT|MAWT|MART|MAGT|MAGST|LINT|LHST|LHDT|KUYT|KST|KRAT|KRAST|KGT|JST|IST|IRST|IRKT|IRKST|IRDT|IOT|IDT|ICT|HOVT|HKT|GYT|GST|GMT|GILT|GFT|GET|GAMT|GALT|FNT|FKT|FKST|FJT|FJST|EST|EGT|EGST|EET|EEST|EDT|ECT|EAT|EAST|EASST|DAVT|ChST|CXT|CVT|CST|COT|CLT|CLST|CKT|CHAST|CHADT|CET|CEST|CDT|CCT|CAT|CAST|BTT|BST|BRT|BRST|BOT|BNT|AZT|AZST|AZOT|AZOST|AWST|AWDT|AST|ART|AQTT|ANAT|ANAST|AMT|AMST|ALMT|AKST|AKDT|AFT|AEST|AEDT|ADT|ACST|ACDT)\\b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       Token RegexMatch (GroupMatch (tz:_)):
+       _) -> Token Time <$> inTimezone tz td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleADuration
+  , ruleAboutDuration
+  , ruleAboutTimeofday
+  , ruleAbsorptionOfAfterNamedDay
+  , ruleAfterDuration
+  , ruleAfterLunch
+  , ruleAfterTimeofday
+  , ruleAfterWork
+  , ruleAfternoon
+  , ruleAtTimeofday
+  , ruleBetweenDatetimeAndDatetimeInterval
+  , ruleBetweenTimeofdayAndTimeofdayInterval
+  , ruleByTheEndOfTime
+  , ruleChristmas
+  , ruleChristmasEve
+  , ruleConstitutionDay
+  , ruleCycleAfterTime
+  , ruleCycleBeforeTime
+  , ruleDatetimeDatetimeInterval
+  , ruleDayofmonthNonOrdinalNamedmonth
+  , ruleDayofmonthNonOrdinalOfNamedmonth
+  , ruleDayofmonthOrdinal
+  , ruleDayofmonthOrdinalOfNamedmonth
+  , ruleDayofmonthordinalNamedmonth
+  , ruleDayofmonthordinalNamedmonthYear
+  , ruleDdmm
+  , ruleDdmmyyyy
+  , ruleDurationAfterTime
+  , ruleDurationAgo
+  , ruleDurationBeforeTime
+  , ruleDurationFromNow
+  , ruleDurationHence
+  , ruleEomendOfMonth
+  , ruleEoyendOfYear
+  , ruleEvening
+  , ruleExactlyTimeofday
+  , ruleFathersDay
+  , ruleFromDatetimeDatetimeInterval
+  , ruleFromTimeofdayTimeofdayInterval
+  , ruleHalloweenDay
+  , ruleHhmm
+  , ruleHhmmss
+  , ruleHourofdayIntegerAsRelativeMinutes
+  , ruleInDuration
+  , ruleInduringThePartofday
+  , ruleInduringThePartofday2
+  , ruleIntersect
+  , ruleIntersectBy
+  , ruleIntersectByOfFromS
+  , ruleLastCycle
+  , ruleLastCycleOfTime
+  , ruleLastDayofweekOfTime
+  , ruleLastNCycle
+  , ruleLastTime
+  , ruleLunch
+  , ruleMidnighteodendOfDay
+  , ruleMonthDdddInterval
+  , ruleMorning
+  , ruleMothersDay
+  , ruleNamedday
+  , ruleNamedday2
+  , ruleNamedday3
+  , ruleNamedday4
+  , ruleNamedday5
+  , ruleNamedday6
+  , ruleNamedday7
+  , ruleNamedmonth
+  , ruleNamedmonth10
+  , ruleNamedmonth11
+  , ruleNamedmonth12
+  , ruleNamedmonth2
+  , ruleNamedmonth3
+  , ruleNamedmonth4
+  , ruleNamedmonth5
+  , ruleNamedmonth6
+  , ruleNamedmonth7
+  , ruleNamedmonth8
+  , ruleNamedmonth9
+  , ruleNamedmonthDayofmonthNonOrdinal
+  , ruleNamedmonthDayofmonthOrdinal
+  , ruleNewYearsDay
+  , ruleNewYearsEve
+  , ruleNextCycle
+  , ruleNextNCycle
+  , ruleNextTime
+  , ruleNight
+  , ruleNoon
+  , ruleNow
+  , ruleNthTimeAfterTime
+  , ruleNthTimeAfterTime2
+  , ruleNthTimeOfTime
+  , ruleNthTimeOfTime2
+  , ruleOnANamedday
+  , ruleOnDate
+  , ruleOrdinalCycleAfterTime
+  , ruleOrdinalCycleOfTime
+  , ruleOrdinalQuarter
+  , ruleOrdinalQuarterYear
+  , rulePartofdayOfTime
+  , ruleRelativeMinutesAfterpastIntegerHourofday
+  , ruleRelativeMinutesTotillbeforeIntegerHourofday
+  , ruleSeason
+  , ruleSeason2
+  , ruleSeason3
+  , ruleSeason4
+  , ruleTheCycleAfterTime
+  , ruleTheCycleBeforeTime
+  , ruleTheCycleOfTime
+  , ruleTheDayAfterTomorrow
+  , ruleTheDayBeforeYesterday
+  , ruleTheDayofmonthNonOrdinal
+  , ruleTheDayofmonthOrdinal
+  , ruleTheIdesOfNamedmonth
+  , ruleTheOrdinalCycleAfterTime
+  , ruleTheOrdinalCycleOfTime
+  , ruleThisCycle
+  , ruleThisPartofday
+  , ruleThisTime
+  , ruleThisnextDayofweek
+  , ruleTimeAfterNext
+  , ruleTimeBeforeLast
+  , ruleTimePartofday
+  , ruleTimeofdayApproximately
+  , ruleTimeofdayLatent
+  , ruleTimeofdayOclock
+  , ruleTimeofdaySharp
+  , ruleTimeofdayTimeofdayInterval
+  , ruleToday
+  , ruleTomorrow
+  , ruleTonight
+  , ruleUntilTimeofday
+  , ruleValentinesDay
+  , ruleWeekend
+  , ruleWithinDuration
+  , ruleYear
+  , ruleYearLatent
+  , ruleYearLatent2
+  , ruleYesterday
+  , ruleYyyymmdd
+  , ruleQuarterTotillbeforeIntegerHourofday
+  , ruleHalfTotillbeforeIntegerHourofday
+  , ruleQuarterAfterpastIntegerHourofday
+  , ruleHalfAfterpastIntegerHourofday
+  , ruleHourofdayQuart
+  , ruleHourofdayHalf
+  , ruleTimezone
+  ]
diff --git a/Duckling/Time/DE/Corpus.hs b/Duckling/Time/DE/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/DE/Corpus.hs
@@ -0,0 +1,618 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.DE.Corpus
+  ( corpus
+  , negativeCorpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Time.Corpus
+import Duckling.Time.Types hiding (Month)
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Testing.Types hiding (examples)
+
+corpus :: Corpus
+corpus = (testContext {lang = DE}, allExamples)
+
+negativeCorpus :: NegativeCorpus
+negativeCorpus = (testContext {lang = DE}, examples)
+  where
+    examples =
+      [ "ein Hotel"
+      , "ein Angebot"
+      ]
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (datetime (2013, 2, 12, 4, 30, 0) Second)
+             [ "jetzt"
+             , "genau jetzt"
+             , "gerade eben"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "heute"
+             , "zu dieser zeit"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "gestern"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "morgen"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "montag"
+             , "mo."
+             , "diesen montag"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "Montag, Feb 18"
+             , "Montag, Februar 18"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "dienstag"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "donnerstag"
+             , "do"
+             , "do."
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "freitag"
+             , "fr."
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "samstag"
+             , "sa."
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "sonntag"
+             , "so."
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "1 märz"
+             , "erster märz"
+             ]
+  , examples (datetime (2013, 3, 3, 0, 0, 0) Day)
+             [ "märz 3"
+             ]
+  , examples (datetime (2015, 3, 3, 0, 0, 0) Day)
+             [ "märz 3 2015"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "am 15ten"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "15. februar"
+             , "februar 15"
+             , "15te februar"
+             , "15.2."
+             , "am 15.2."
+             , "februar 15"
+             ]
+  , examples (datetime (2013, 8, 8, 0, 0, 0) Day)
+             [ "Aug 8"
+             ]
+  , examples (datetime (2014, 10, 0, 0, 0, 0) Month)
+             [ "Oktober 2014"
+             ]
+  , examples (datetime (1974, 10, 31, 0, 0, 0) Day)
+             [ "31.10.1974"
+             , "31.10.74"
+             ]
+  , examples (datetime (2015, 4, 14, 0, 0, 0) Day)
+             [ "14 april 2015"
+             , "April 14, 2015"
+             , "14te April 15"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "nächsten dienstag"
+             ]
+  , examples (datetime (2013, 2, 22, 0, 0, 0) Day)
+             [ "übernächsten freitag"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "nächsten märz"
+             ]
+  , examples (datetime (2014, 3, 0, 0, 0, 0) Month)
+             [ "übernächsten märz"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "Sonntag, Feb 10"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "Mittwoch, Feb 13"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "Montag, Feb 18"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Week)
+             [ "diese woche"
+             , "kommende woche"
+             ]
+  , examples (datetime (2013, 2, 4, 0, 0, 0) Week)
+             [ "letzte woche"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Week)
+             [ "nächste woche"
+             ]
+  , examples (datetime (2013, 1, 0, 0, 0, 0) Month)
+             [ "letzten monat"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "nächsten monat"
+             ]
+  , examples (datetime (2013, 1, 1, 0, 0, 0) Quarter)
+             [ "dieses quartal"
+             ]
+  , examples (datetime (2013, 4, 1, 0, 0, 0) Quarter)
+             [ "nächstes quartal"
+             ]
+  , examples (datetime (2013, 7, 1, 0, 0, 0) Quarter)
+             [ "drittes quartal"
+             ]
+  , examples (datetime (2018, 10, 1, 0, 0, 0) Quarter)
+             [ "4tes quartal 2018"
+             ]
+  , examples (datetime (2012, 0, 0, 0, 0, 0) Year)
+             [ "letztes jahr"
+             ]
+  , examples (datetime (2013, 0, 0, 0, 0, 0) Year)
+             [ "dieses jahr"
+             ]
+  , examples (datetime (2014, 0, 0, 0, 0, 0) Year)
+             [ "nächstes jahr"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "letzten sonntag"
+             , "sonntag der letzten woche"
+             , "sonntag letzte woche"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "letzten dienstag"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "nächsten dienstag"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "nächsten mittwoch"
+             ]
+  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
+             [ "mittwoch der nächsten woche"
+             , "mittwoch nächste woche"
+             , "mittwoch nach dem nächsten"
+             ]
+  , examples (datetime (2013, 2, 22, 0, 0, 0) Day)
+             [ "freitag nach dem nächsten"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "montag dieser woche"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "dienstag dieser woche"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "mittwoch dieser woche"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "übermorgen"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "vorgestern"
+             ]
+  , examples (datetime (2013, 3, 25, 0, 0, 0) Day)
+             [ "letzter montag im märz"
+             ]
+  , examples (datetime (2014, 3, 30, 0, 0, 0) Day)
+             [ "letzter sonntag im märz 2014"
+             ]
+  , examples (datetime (2013, 10, 3, 0, 0, 0) Day)
+             [ "dritter tag im oktober"
+             ]
+  , examples (datetime (2014, 10, 6, 0, 0, 0) Week)
+             [ "erste woche im oktober 2014"
+             ]
+  , examples (datetime (2015, 10, 31, 0, 0, 0) Day)
+             [ "letzter tag im oktober 2015"
+             ]
+  , examples (datetime (2014, 9, 22, 0, 0, 0) Week)
+             [ "letzte woche im september 2014"
+             ]
+  , examples (datetime (2013, 10, 1, 0, 0, 0) Day)
+             [ "erster dienstag im oktober"
+             ]
+  , examples (datetime (2014, 9, 16, 0, 0, 0) Day)
+             [ "dritter dienstag im september 2014"
+             ]
+  , examples (datetime (2014, 10, 1, 0, 0, 0) Day)
+             [ "erster mittwoch im oktober 2014"
+             ]
+  , examples (datetime (2014, 10, 8, 0, 0, 0) Day)
+             [ "zweiter mittwoch im oktober 2014"
+             ]
+  , examples (datetime (2015, 1, 13, 0, 0, 0) Day)
+             [ "dritter dienstag nach weihnachten 2014"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 0, 0) Hour)
+             [ "um 4 in der früh"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "um 3"
+             , "3 uhr"
+             , "um drei"
+             ]
+  , examples (datetime (2013, 2, 12, 3, 18, 0) Minute)
+             [ "3:18 früh"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 18, 0) Minute)
+             [ "3:18"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "um 3 am nachmittag"
+             , "um 15"
+             , "um 15 uhr"
+             , "15 uhr"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "zirka 15 uhr"
+             , "zirka 3 uhr am nachmittag"
+             , "um ungefähr 15 uhr"
+             ]
+  , examples (datetime (2013, 2, 13, 17, 0, 0) Hour)
+             [ "pünktlich um 17 uhr morgen"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 15, 0) Minute)
+             [ "um viertel nach 3"
+             , "viertel nach drei Uhr"
+             , "3 uhr 15 am nachmittag"
+             , "15:15"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 20, 0) Minute)
+             [ "um 20 nach 3"
+             , "15:20 am nachmittag"
+             , "15 uhr 20 nachmittags"
+             , "zwanzig nach 3"
+             , "15:20"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 30, 0) Minute)
+             [ "um halb 4"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 30, 0) Minute)
+             [ "halb vier uhr nachmittags"
+             , "halb vier am nachmittag"
+             , "15:30"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 30, 0) Minute)
+             [ "3:30"
+             ]
+  , examples (datetime (2013, 2, 12, 11, 45, 0) Minute)
+             [ "viertel vor 12"
+             , "11:45"
+             ]
+  , examples (datetime (2013, 2, 12, 11, 45, 0) Second)
+             [ "15 minuten vor 12"
+             ]
+  , examples (datetime (2013, 2, 12, 20, 0, 0) Hour)
+             [ "8 uhr am abend"
+             , "heute abend um 20 Uhr"
+             ]
+  , examples (datetime (2013, 2, 12, 20, 0, 0) Minute)
+             [ "heute um 20:00"
+             ]
+  , examples (datetime (2013, 9, 20, 19, 30, 0) Minute)
+             [ "um 19:30 am fr, 20. Sept."
+             ]
+  , examples (datetime (2013, 2, 16, 9, 0, 0) Hour)
+             [ "am samstag um 9 Uhr"
+             ]
+  , examples (datetime (2014, 7, 18, 19, 0, 0) Hour)
+             [ "Fr, 18. Juli 2014 7 uhr abends"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 30, 1) Second)
+             [ "in einer sekunde"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 31, 0) Second)
+             [ "in einer minute"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 32, 0) Second)
+             [ "in 2 minuten"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Second)
+             [ "in 60 minuten"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 0, 0) Second)
+             [ "in einer halben stunde"
+             , "in 30 minuten"
+             ]
+  , examples (datetime (2013, 2, 12, 7, 0, 0) Second)
+             [ "in 2.5 stunden"
+             , "in zwei ein halb stunden"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Minute)
+             [ "in einer stunde"
+             ]
+  , examples (datetime (2013, 2, 12, 6, 30, 0) Minute)
+             [ "in zwei stunden"
+             ]
+  , examples (datetime (2013, 2, 12, 6, 30, 0) Minute)
+             [ "in ein paar stunden"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 30, 0) Minute)
+             [ "in 24 stunden"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "morgen"
+             ]
+  , examples (datetime (2016, 2, 0, 0, 0, 0) Month)
+             [ "in 3 Jahren"
+             ]
+  , examples (datetime (2013, 2, 19, 4, 0, 0) Hour)
+             [ "in 7 tagen"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "in 1 woche"
+             , "in einer woche"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 0, 0) Second)
+             [ "in zirka einer halben stunde"
+             ]
+  , examples (datetime (2013, 2, 5, 4, 0, 0) Hour)
+             [ "vor 7 tagen"
+             ]
+  , examples (datetime (2013, 1, 29, 4, 0, 0) Hour)
+             [ "vor 14 tagen"
+             ]
+  , examples (datetime (2013, 1, 29, 0, 0, 0) Day)
+             [ "vor zwei wochen"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "vor einer woche"
+             ]
+  , examples (datetime (2013, 1, 22, 0, 0, 0) Day)
+             [ "vor drei wochen"
+             ]
+  , examples (datetime (2012, 11, 12, 0, 0, 0) Day)
+             [ "vor drei monaten"
+             ]
+  , examples (datetime (2011, 2, 0, 0, 0, 0) Month)
+             [ "vor zwei jahren"
+             ]
+  , examples (datetime (2013, 2, 19, 4, 0, 0) Hour)
+             [ "in 7 tagen"
+             ]
+  , examples (datetime (2013, 12, 0, 0, 0, 0) Month)
+             [ "ein jahr nach weihnachten"
+             ]
+  , examples (datetimeInterval ((2013, 6, 21, 0, 0, 0), (2013, 9, 24, 0, 0, 0)) Day)
+             [ "diesen sommer"
+             ]
+  , examples (datetimeInterval ((2012, 12, 21, 0, 0, 0), (2013, 3, 21, 0, 0, 0)) Day)
+             [ "diesen winter"
+             ]
+  , examples (datetime (2013, 12, 25, 0, 0, 0) Day)
+             [ "Weihnachten"
+             , "Weihnachtstag"
+             ]
+  , examples (datetime (2013, 12, 31, 0, 0, 0) Day)
+             [ "Silvester"
+             ]
+  , examples (datetime (2014, 1, 1, 0, 0, 0) Day)
+             [ "Neujahrstag"
+             , "Neujahr"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "Valentinstag"
+             ]
+  , examples (datetime (2013, 5, 12, 0, 0, 0) Day)
+             [ "Muttertag"
+             ]
+  , examples (datetime (2013, 6, 16, 0, 0, 0) Day)
+             [ "Vatertag"
+             ]
+  , examples (datetime (2013, 10, 3, 0, 0, 0) Day)
+             [ "Tag der Deutschen Einheit"
+             , "3. Oktober"
+             ]
+  , examples (datetime (2013, 10, 31, 0, 0, 0) Day)
+             [ "Halloween"
+             ]
+  , examples (datetime (2013, 11, 1, 0, 0, 0) Day)
+             [ "Allerheiligen"
+             ]
+  , examples (datetime (2013, 12, 6, 0, 0, 0) Day)
+             [ "Nikolaus"
+             , "Nikolaustag"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 18, 0, 0), (2013, 2, 13, 0, 0, 0)) Hour)
+             [ "heute abend"
+             , "am abend"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 18, 0, 0), (2013, 2, 14, 0, 0, 0)) Hour)
+             [ "morgen abend"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 12, 0, 0), (2013, 2, 13, 14, 0, 0)) Hour)
+             [ "morgen mittag"
+             , "morgen zu mittag"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 18, 0, 0), (2013, 2, 12, 0, 0, 0)) Hour)
+             [ "gestern abend"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 18, 0, 0), (2013, 2, 18, 0, 0, 0)) Hour)
+             [ "dieses wochenende"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 3, 0, 0), (2013, 2, 18, 12, 0, 0)) Hour)
+             [ "montag morgens"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 3, 0, 0), (2013, 2, 15, 12, 0, 0)) Hour)
+             [ "morgens am 15. februar"
+             , "15. februar morgens"
+             , "am morgen des 15. februar"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 29, 58), (2013, 2, 12, 4, 30, 0)) Second)
+             [ "letzte 2 sekunden"
+             , "letzten zwei sekunden"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 1), (2013, 2, 12, 4, 30, 4)) Second)
+             [ "nächste 3 sekunden"
+             , "nächsten drei sekunden"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 28, 0), (2013, 2, 12, 4, 30, 0)) Minute)
+             [ "letzte 2 minuten"
+             , "letzten zwei minuten"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 31, 0), (2013, 2, 12, 4, 34, 0)) Minute)
+             [ "nächste 3 minuten"
+             , "nächsten drei minuten"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 5, 0, 0), (2013, 2, 12, 8, 0, 0)) Hour)
+             [ "nächste 3 stunden"
+             , "nächsten drei stunden"
+             ]
+  , examples (datetimeInterval ((2013, 2, 10, 0, 0, 0), (2013, 2, 12, 0, 0, 0)) Day)
+             [ "letzte 2 tage"
+             , "letzten zwei tage"
+             , "vergangenen zwei tage"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
+             [ "nächsten 3 tagen"
+             , "nächsten drei tage"
+             , "kommenden drei tagen"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 15, 0, 0, 0)) Day)
+             [ "nächsten paar tagen"
+             , "kommenden paar tagen"
+             ]
+  , examples (datetimeInterval ((2013, 1, 28, 0, 0, 0), (2013, 2, 11, 0, 0, 0)) Week)
+             [ "letzten 2 wochen"
+             , "letzte zwei wochen"
+             , "vergangenen 2 wochen"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 0, 0, 0), (2013, 3, 11, 0, 0, 0)) Week)
+             [ "nächsten 3 wochen"
+             , "nächste drei wochen"
+             , "kommenden drei wochen"
+             ]
+  , examples (datetimeInterval ((2012, 12, 0, 0, 0, 0), (2013, 2, 0, 0, 0, 0)) Month)
+             [ "letzten 2 monaten"
+             , "letzte zwei monate"
+             , "vergangenen zwei monaten"
+             ]
+  , examples (datetimeInterval ((2013, 3, 0, 0, 0, 0), (2013, 6, 0, 0, 0, 0)) Month)
+             [ "nächsten 3 monaten"
+             , "nächste drei monate"
+             , "kommenden drei monaten"
+             ]
+  , examples (datetimeInterval ((2011, 0, 0, 0, 0, 0), (2013, 0, 0, 0, 0, 0)) Year)
+             [ "letzten 2 jahren"
+             , "letzten zwei jahre"
+             , "vergangenen zwei jahren"
+             ]
+  , examples (datetimeInterval ((2014, 0, 0, 0, 0, 0), (2017, 0, 0, 0, 0, 0)) Year)
+             [ "nächsten 3 jahren"
+             , "kommenden drei jahren"
+             , "nächste drei jahre"
+             ]
+  , examples (datetimeInterval ((2013, 7, 13, 0, 0, 0), (2013, 7, 16, 0, 0, 0)) Day)
+             [ "13. - 15. Juli"
+             , "13ter bis 15ter Juli"
+             , "13 bis 15 Juli"
+             , "13 - 15 Juli"
+             , "Juli 13 - Juli 15"
+             ]
+  , examples (datetimeInterval ((2013, 8, 8, 0, 0, 0), (2013, 8, 13, 0, 0, 0)) Day)
+             [ "Aug 8 - Aug 12"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 9, 30, 0), (2013, 2, 12, 11, 1, 0)) Minute)
+             [ "9:30 - 11:00"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 30, 0), (2013, 2, 14, 11, 1, 0)) Minute)
+             [ "am Donnerstag von 9:30 - 11:00"
+             , "am Donnerstag zwischen 9:30 und 11:00"
+             , "Donnerstag 9:30 - 11:00"
+             , "am Donnerstag nach 9:30 aber vor 11:00"
+             , "Donnerstag von 9:30 bis 11:00"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 0, 0), (2013, 2, 14, 12, 0, 0)) Hour)
+             [ "Donnerstag Vormittag von 9 bis 11"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 11, 30, 0), (2013, 2, 12, 13, 31, 0)) Minute)
+             [ "11:30-13:30"
+             ]
+  , examples (datetime (2013, 9, 21, 1, 30, 0) Minute)
+             [ "1:30 am Sa, 21. Sept"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 26, 0, 0, 0)) Second)
+             [ "binnen 2 wochen"
+             , "innerhalb von 2 wochen"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 14, 0, 0) Hour)
+             [ "bis 2 Uhr nachmittag"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 13, 0, 0, 0) Hour)
+             [ "bis zum ende des tages"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 3, 1, 0, 0, 0) Month)
+             [ "bis zum ende des monats"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 0, 0) Minute)
+             [ "16 Uhr CET"
+             ]
+  , examples (datetime (2013, 2, 14, 6, 0, 0) Minute)
+             [ "donnerstag 8:00 GMT"
+             ]
+  , examples (datetime (2013, 2, 12, 14, 0, 0) Hour)
+             [ "heute um 14 Uhr"
+             , "um 2"
+             ]
+  , examples (datetime (2013, 2, 13, 15, 0, 0) Hour)
+             [ "morgen um 15 Uhr"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 12, 14, 0, 0) Hour)
+             [ "nach 14 Uhr"
+             , "nach 14h"
+             , "nach 2 Uhr"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 11, 0, 0) Hour)
+             [ "bis 11 uhr"
+             , "bis 11h vormittags"
+             , "bis 11 am vormittag"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 12, 0, 0), (2013, 2, 12, 19, 0, 0)) Hour)
+             [ "am nachmittag"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 30, 0) Minute)
+             [ "um 13:30 am nachmittag"
+             , "nachmittags um 1 uhr 30"
+             , "13:30"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 45, 0) Second)
+             [ "in 15 minuten"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 13, 0, 0), (2013, 2, 12, 17, 0, 0)) Hour)
+             [ "nach dem mittagessen"
+             ]
+  , examples (datetime (2013, 2, 12, 10, 30, 0) Minute)
+             [ "10:30"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "nächsten montag"
+             , "kommenden montag"
+             ]
+  , examples (datetime (2013, 12, 10, 0, 0, 0) Day)
+             [ "10.12."
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 18, 30, 0), (2013, 2, 12, 19, 1, 0)) Minute)
+             [ "18:30h - 19:00h"
+             ]
+  ]
diff --git a/Duckling/Time/DE/Rules.hs b/Duckling/Time/DE/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/DE/Rules.hs
@@ -0,0 +1,2024 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.DE.Rules
+  ( rules ) where
+
+import Control.Monad (liftM2)
+import qualified Data.Text as Text
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Types (OrdinalData (..))
+import qualified Duckling.Ordinal.Types as TOrdinal
+import Duckling.Regex.Types
+import Duckling.Time.Helpers
+import Duckling.Time.Types (TimeData (..))
+import qualified Duckling.Time.Types as TTime
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleNamedday :: Rule
+ruleNamedday = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "montags?|mo\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 1
+  }
+
+ruleNamedmonth12 :: Rule
+ruleNamedmonth12 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "dezember|dez\\.?"
+    ]
+  , prod = \_ -> tt $ month 12
+  }
+
+ruleRelativeMinutesTotillbeforeIntegerHourofday :: Rule
+ruleRelativeMinutesTotillbeforeIntegerHourofday = Rule
+  { name = "relative minutes to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "vor"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleQuarterTotillbeforeIntegerHourofday :: Rule
+ruleQuarterTotillbeforeIntegerHourofday = Rule
+  { name = "quarter to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [regex "vie?rtel vor"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> do
+        t <- minutesBefore 15 td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleHalfTotillbeforeIntegerHourofday :: Rule
+ruleHalfTotillbeforeIntegerHourofday = Rule
+  { name = "half to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ regex "halbe? vor"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> do
+        t <- minutesBefore 30 td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleNamedday2 :: Rule
+ruleNamedday2 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "die?nstags?|di\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 2
+  }
+
+ruleValentinesDay :: Rule
+ruleValentinesDay = Rule
+  { name = "valentine's day"
+  , pattern =
+    [ regex "valentin'?stag"
+    ]
+  , prod = \_ -> tt $ monthDay 2 14
+  }
+
+ruleTheOrdinalCycleOfTime :: Rule
+ruleTheOrdinalCycleOfTime = Rule
+  { name = "the <ordinal> <cycle> of <time>"
+  , pattern =
+    [ regex "der|die|das"
+    , dimension Ordinal
+    , dimension TimeGrain
+    , regex "im|in|von"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleNthTimeOfTime2 :: Rule
+ruleNthTimeOfTime2 = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ regex "der|die|das"
+    , dimension Ordinal
+    , dimension Time
+    , regex "im"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       Token Time td1:
+       _:
+       Token Time td2:
+       _) -> Token Time . predNth (v - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNewYearsDay :: Rule
+ruleNewYearsDay = Rule
+  { name = "new year's day"
+  , pattern =
+    [ regex "neujahr(s?tag)?"
+    ]
+  , prod = \_ -> tt $ monthDay 1 1
+  }
+
+ruleLastTime :: Rule
+ruleLastTime = Rule
+  { name = "last <time>"
+  , pattern =
+    [ regex "letzten?|letztes"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleNamedday6 :: Rule
+ruleNamedday6 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "samstags?|sa\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 6
+  }
+
+ruleDatetimeDatetimeInterval :: Rule
+ruleDatetimeDatetimeInterval = Rule
+  { name = "<datetime> - <datetime> (interval)"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "\\-|bis"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth7 :: Rule
+ruleNamedmonth7 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "juli|jul\\.?"
+    ]
+  , prod = \_ -> tt $ month 7
+  }
+
+ruleEvening :: Rule
+ruleEvening = Rule
+  { name = "evening"
+  , pattern =
+    [ regex "abends?"
+    ]
+  , prod = \_ ->
+      let from = hour False 18
+          to = hour False 0
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleTheDayofmonthNonOrdinal :: Rule
+ruleTheDayofmonthNonOrdinal = Rule
+  { name = "the <day-of-month> (non ordinal)"
+  , pattern =
+    [ regex "der"
+    , Predicate $ isIntegerBetween 1 31
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        v <- getIntValue token
+        tt $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleInDuration :: Rule
+ruleInDuration = Rule
+  { name = "in <duration>"
+  , pattern =
+    [ regex "in"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleNow :: Rule
+ruleNow = Rule
+  { name = "now"
+  , pattern =
+    [ regex "(genau)? ?jetzt|diesen moment|in diesem moment|gerade eben"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Second 0
+  }
+
+ruleLastCycleOfTime :: Rule
+ruleLastCycleOfTime = Rule
+  { name = "last <cycle> of <time>"
+  , pattern =
+    [ regex "letzte(r|n|s)?"
+    , dimension TimeGrain
+    , regex "um|im"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleLastOf grain td
+      _ -> Nothing
+  }
+
+ruleFromDatetimeDatetimeInterval :: Rule
+ruleFromDatetimeDatetimeInterval = Rule
+  { name = "from <datetime> - <datetime> (interval)"
+  , pattern =
+    [ regex "vo[nm]"
+    , dimension Time
+    , regex "\\-|bis"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleRelativeMinutesAfterpastIntegerHourofday :: Rule
+ruleRelativeMinutesAfterpastIntegerHourofday = Rule
+  { name = "relative minutes after|past <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "nach"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:
+       _:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleQuarterAfterpastIntegerHourofday :: Rule
+ruleQuarterAfterpastIntegerHourofday = Rule
+  { name = "quarter after|past <integer> (hour-of-day)"
+  , pattern =
+    [ regex "vie?rtel nach"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 15
+      _ -> Nothing
+  }
+
+ruleHalfAfterpastIntegerHourofday :: Rule
+ruleHalfAfterpastIntegerHourofday = Rule
+  { name = "half after|past <integer> (hour-of-day)"
+  , pattern =
+    [ regex "halbe? nach"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+
+ruleMonthDdddInterval :: Rule
+ruleMonthDdddInterval = Rule
+  { name = "<month> dd-dd (interval)"
+  , pattern =
+    [ regex "([012]?\\d|30|31)(ter|\\.)?"
+    , regex "\\-|bis"
+    , regex "([012]?\\d|30|31)(ter|\\.)?"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:_)):
+       _:
+       Token RegexMatch (GroupMatch (m2:_)):
+       Token Time td:
+       _) -> do
+        v1 <- parseInt m1
+        v2 <- parseInt m2
+        from <- intersect (dayOfMonth v1) td
+        to <- intersect (dayOfMonth v2) td
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleNamedday4 :: Rule
+ruleNamedday4 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "donn?erstags?|do\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 4
+  }
+
+ruleAfterTomorrow :: Rule
+ruleAfterTomorrow = Rule
+  { name = "after tomorrow"
+  , pattern =
+    [ regex "(\x00fc)bermorgen"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 2
+  }
+
+ruleTheCycleAfterTime :: Rule
+ruleTheCycleAfterTime = Rule
+  { name = "the <cycle> after <time>"
+  , pattern =
+    [ regex "der"
+    , dimension TimeGrain
+    , regex "nach"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain 1 td
+      _ -> Nothing
+  }
+
+ruleTheCycleBeforeTime :: Rule
+ruleTheCycleBeforeTime = Rule
+  { name = "the <cycle> before <time>"
+  , pattern =
+    [ regex "der"
+    , dimension TimeGrain
+    , regex "vor"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain (-1) td
+      _ -> Nothing
+  }
+
+ruleSeason4 :: Rule
+ruleSeason4 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "fr(\x00fc)hling|fr(\x00fc)hjahr"
+    ]
+  , prod = \_ ->
+      let from = monthDay 3 20
+          to = monthDay 6 21
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleYearLatent2 :: Rule
+ruleYearLatent2 = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2101 10000
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ year v
+      _ -> Nothing
+  }
+
+ruleTimeAfterNext :: Rule
+ruleTimeAfterNext = Rule
+  { name = "<time> after next"
+  , pattern =
+    [ dimension Time
+    , regex "nach dem n(\x00e4)chsten"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 1 True td
+      _ -> Nothing
+  }
+
+ruleTheIdesOfNamedmonth :: Rule
+ruleTheIdesOfNamedmonth = Rule
+  { name = "the ides of <named-month>"
+  , pattern =
+    [ regex "die iden (des?)"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td@TimeData {TTime.form = Just (TTime.Month m)}:_) ->
+        Token Time <$>
+          intersect (dayOfMonth $ if elem m [3, 5, 7, 10] then 15 else 13) td
+      _ -> Nothing
+  }
+
+ruleNoon :: Rule
+ruleNoon = Rule
+  { name = "noon"
+  , pattern =
+    [ regex "mittags?|zw(\x00f6)lf (uhr)?"
+    ]
+  , prod = \_ -> tt $ hour False 12
+  }
+
+ruleToday :: Rule
+ruleToday = Rule
+  { name = "today"
+  , pattern =
+    [ regex "heute|(um diese zeit|zu dieser zeit|um diesen zeitpunkt|zu diesem zeitpunkt)"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 0
+  }
+
+ruleThisnextDayofweek :: Rule
+ruleThisnextDayofweek = Rule
+  { name = "this|next <day-of-week>"
+  , pattern =
+    [ regex "diese(n|r)|kommenden|n(\x00e4)chsten"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleBetweenTimeofdayAndTimeofdayInterval :: Rule
+ruleBetweenTimeofdayAndTimeofdayInterval = Rule
+  { name = "between <time-of-day> and <time-of-day> (interval)"
+  , pattern =
+    [ regex "zwischen"
+    , Predicate isATimeOfDay
+    , regex "und"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNextCycle :: Rule
+ruleNextCycle = Rule
+  { name = "next <cycle>"
+  , pattern =
+    [ regex "n(\x00e4)chste(r|n|s)?|kommende(r|n|s)?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleNamedmonth :: Rule
+ruleNamedmonth = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "januar|jan\\.?"
+    ]
+  , prod = \_ -> tt $ month 1
+  }
+
+ruleTimeofdayApproximately :: Rule
+ruleTimeofdayApproximately = Rule
+  { name = "<time-of-day> approximately"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(um )?zirka|ungef(\x00e4)hr|etwa"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleOnDate :: Rule
+ruleOnDate = Rule
+  { name = "on <date>"
+  , pattern =
+    [ regex "am"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleNamedmonth3 :: Rule
+ruleNamedmonth3 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "m(\x00e4)rz|m(\x00e4)r\\.?"
+    ]
+  , prod = \_ -> tt $ month 3
+  }
+
+ruleDurationFromNow :: Rule
+ruleDurationFromNow = Rule
+  { name = "<duration> from now"
+  , pattern =
+    [ dimension Duration
+    , regex "ab (heute|jetzt)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleLunch :: Rule
+ruleLunch = Rule
+  { name = "lunch"
+  , pattern =
+    [ regex "(am |zu )?mittags?"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 14
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleLastCycle :: Rule
+ruleLastCycle = Rule
+  { name = "last <cycle>"
+  , pattern =
+    [ regex "letzte(r|n|s)?|vergangene(r|n|s)?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleAfternoon :: Rule
+ruleAfternoon = Rule
+  { name = "afternoon"
+  , pattern =
+    [ regex "nach ?mittags?"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 19
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleNamedmonth4 :: Rule
+ruleNamedmonth4 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "april|apr\\.?"
+    ]
+  , prod = \_ -> tt $ month 4
+  }
+
+ruleTimeBeforeLast :: Rule
+ruleTimeBeforeLast = Rule
+  { name = "<time> before last"
+  , pattern =
+    [ regex "vorletzten?|vor ?letztes?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth (-2) False td
+      _ -> Nothing
+  }
+
+ruleNamedmonthDayofmonthOrdinal :: Rule
+ruleNamedmonthDayofmonthOrdinal = Rule
+  { name = "<named-month> <day-of-month> (ordinal)"
+  , pattern =
+    [ Predicate isAMonth
+    , Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleChristmasEve :: Rule
+ruleChristmasEve = Rule
+  { name = "christmas eve"
+  , pattern =
+    [ regex "heilig(er)? abend"
+    ]
+  , prod = \_ -> tt $ monthDay 12 24
+  }
+
+ruleInduringThePartofday :: Rule
+ruleInduringThePartofday = Rule
+  { name = "in|during the <part-of-day>"
+  , pattern =
+    [ regex "(in|an|am|w(\x00e4)h?rend)( der| dem| des)?"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleHourofdayIntegerAsRelativeMinutes :: Rule
+ruleHourofdayIntegerAsRelativeMinutes = Rule
+  { name = "<hour-of-day> <integer> (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleHourofdayQuarter :: Rule
+ruleHourofdayQuarter = Rule
+  { name = "<hour-of-day> <quarter> (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "vie?rtel"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
+        tt $ hourMinute is12H hours 15
+      _ -> Nothing
+  }
+
+ruleHourofdayHalf :: Rule
+ruleHourofdayHalf = Rule
+  { name = "<hour-of-day> <half> (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "halbe?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
+        tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+
+ruleNamedday5 :: Rule
+ruleNamedday5 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "freitags?|fr\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 5
+  }
+
+ruleDayofmonthordinalNamedmonth :: Rule
+ruleDayofmonthordinalNamedmonth = Rule
+  { name = "<day-of-month>(ordinal) <named-month>"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleIntersectBy :: Rule
+ruleIntersectBy = Rule
+  { name = "intersect by ','"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex ","
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNthTimeAfterTime :: Rule
+ruleNthTimeAfterTime = Rule
+  { name = "nth <time> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "nach"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       Token Time td1:
+       _:
+       Token Time td2:
+       _) -> tt $ predNthAfter (v - 1) td1 td2
+      _ -> Nothing
+  }
+
+ruleMmdd :: Rule
+ruleMmdd = Rule
+  { name = "mm/dd"
+  , pattern =
+    [ regex "([012]?[1-9]|10|20|30|31)\\.(0?[1-9]|10|11|12)\\."
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        d <- parseInt m1
+        m <- parseInt m2
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleAfterDuration :: Rule
+ruleAfterDuration = Rule
+  { name = "after <duration>"
+  , pattern =
+    [ regex "nach"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleTimeofdayLatent :: Rule
+ruleTimeofdayLatent = 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 < 12) n
+      _ -> Nothing
+  }
+
+ruleFromTimeofdayTimeofdayInterval :: Rule
+ruleFromTimeofdayTimeofdayInterval = Rule
+  { name = "from <time-of-day> - <time-of-day> (interval)"
+  , pattern =
+    [ regex "(von|nach|ab|fr(\x00fc)hestens (um)?)"
+    , Predicate isATimeOfDay
+    , regex "((noch|aber|jedoch)? vor)|\\-|bis"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth2 :: Rule
+ruleNamedmonth2 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "februar|feb\\.?"
+    ]
+  , prod = \_ -> tt $ month 2
+  }
+
+ruleExactlyTimeofday :: Rule
+ruleExactlyTimeofday = Rule
+  { name = "exactly <time-of-day>"
+  , pattern =
+    [ regex "genau|exakt|p(\x00fc)nktlich|punkt( um)?"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleSeason3 :: Rule
+ruleSeason3 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "winter"
+    ]
+  , prod = \_ ->
+      let from = monthDay 12 21
+          to = monthDay 3 20
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleSeason :: Rule
+ruleSeason = Rule
+  { name = "season"
+  , pattern =
+    [ regex "sommer"
+    ]
+  , prod = \_ ->
+      let from = monthDay 6 21
+          to = monthDay 9 23
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleSchweizerBundesfeiertag :: Rule
+ruleSchweizerBundesfeiertag = Rule
+  { name = "Schweizer Bundesfeiertag"
+  , pattern =
+    [ regex "schweiz(er)? (bundes)?feiertag|bundes feiertag"
+    ]
+  , prod = \_ -> tt $ monthDay 8 1
+  }
+
+ruleBetweenDatetimeAndDatetimeInterval :: Rule
+ruleBetweenDatetimeAndDatetimeInterval = Rule
+  { name = "between <datetime> and <datetime> (interval)"
+  , pattern =
+    [ regex "zwischen"
+    , dimension Time
+    , regex "und"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNewYearsEve :: Rule
+ruleNewYearsEve = Rule
+  { name = "new year's eve"
+  , pattern =
+    [ regex "silvester"
+    ]
+  , prod = \_ -> tt $ monthDay 12 31
+  }
+
+ruleDurationAgo :: Rule
+ruleDurationAgo = Rule
+  { name = "<duration> ago"
+  , pattern =
+    [ regex "vor"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ durationAgo dd
+      _ -> Nothing
+  }
+
+ruleByTheEndOfTime :: Rule
+ruleByTheEndOfTime = Rule
+  { name = "by the end of <time>"
+  , pattern =
+    [ regex "bis (zum)? ende (von)?|(noch)? vor"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $
+        interval TTime.Closed (td, cycleNth TG.Second 0)
+      _ -> Nothing
+  }
+
+ruleAfterWork :: Rule
+ruleAfterWork = Rule
+  { name = "after work"
+  , pattern =
+    [ regex "nach (der)? arbeit|(am)? feier ?abend"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 17, hour False 21)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleBeforeYesterday :: Rule
+ruleBeforeYesterday = Rule
+  { name = "before yesterday"
+  , pattern =
+    [ regex "vorgestern"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 2
+  }
+
+ruleLastNCycle :: Rule
+ruleLastNCycle = Rule
+  { name = "last n <cycle>"
+  , pattern =
+    [ regex "letzten?|vergangenen?"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        n <- getIntValue token
+        tt $ cycleN True grain (- n)
+      _ -> Nothing
+  }
+
+ruleTimeofdaySharp :: Rule
+ruleTimeofdaySharp = Rule
+  { name = "<time-of-day> sharp"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "genau|exakt|p(\x00fc)nktlich|punkt( um)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleAllerheiligen :: Rule
+ruleAllerheiligen = Rule
+  { name = "Allerheiligen"
+  , pattern =
+    [ regex "allerheiligen?|aller heiligen?"
+    ]
+  , prod = \_ -> tt $ monthDay 11 1
+  }
+
+ruleWithinDuration :: Rule
+ruleWithinDuration = Rule
+  { name = "within <duration>"
+  , pattern =
+    [ regex "binnen|innerhalb( von)?"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) -> tt $
+        interval TTime.Open (cycleNth TG.Second 0, inDuration dd)
+      _ -> Nothing
+  }
+
+ruleMidnighteodendOfDay :: Rule
+ruleMidnighteodendOfDay = Rule
+  { name = "midnight|EOD|end of day"
+  , pattern =
+    [ regex "mitternacht|EOD|tagesende|ende (des)? tag(es)?"
+    ]
+  , prod = \_ -> tt $ hour False 0
+  }
+
+ruleDayofmonthNonOrdinalNamedmonth :: Rule
+ruleDayofmonthNonOrdinalNamedmonth = Rule
+  { name = "<day-of-month> (non ordinal) <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ Predicate isNotLatent
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleAboutTimeofday :: Rule
+ruleAboutTimeofday = Rule
+  { name = "about <time-of-day>"
+  , pattern =
+    [ regex "(um )?zirka|ungef(\x00e4)hr|etwa"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleUntilTimeofday :: Rule
+ruleUntilTimeofday = Rule
+  { name = "until <time-of-day>"
+  , pattern =
+    [ regex "vor|bis( zu[rm]?)?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ withDirection TTime.Before td
+      _ -> Nothing
+  }
+
+ruleSterreichischerNationalfeiertag :: Rule
+ruleSterreichischerNationalfeiertag = Rule
+  { name = "Österreichischer Nationalfeiertag"
+  , pattern =
+    [ regex "((\x00f6)sterreichischer?)? nationalfeiertag|national feiertag"
+    ]
+  , prod = \_ -> tt $ monthDay 10 26
+  }
+
+ruleAtTimeofday :: Rule
+ruleAtTimeofday = Rule
+  { name = "at <time-of-day>"
+  , pattern =
+    [ regex "um|@"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth6 :: Rule
+ruleNamedmonth6 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "juni|jun\\.?"
+    ]
+  , prod = \_ -> tt $ month 6
+  }
+
+ruleNthTimeOfTime :: Rule
+ruleNthTimeOfTime = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "im"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       Token Time td1:
+       _:
+       Token Time td2:
+       _) -> Token Time . predNth (v - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNamedmonth8 :: Rule
+ruleNamedmonth8 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "august|aug\\.?"
+    ]
+  , prod = \_ -> tt $ month 8
+  }
+
+ruleTimePartofday :: Rule
+ruleTimePartofday = Rule
+  { name = "<time> <part-of-day>"
+  , pattern =
+    [ dimension Time
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleWeekend :: Rule
+ruleWeekend = Rule
+  { name = "week-end"
+  , pattern =
+    [ regex "wochen ?ende?"
+    ]
+  , prod = \_ -> do
+      from <- intersect (dayOfWeek 5) (hour False 18)
+      to <- intersect (dayOfWeek 1) (hour False 0)
+      tt $ interval TTime.Open (from, to)
+  }
+
+ruleTagDerDeutschenEinheit :: Rule
+ruleTagDerDeutschenEinheit = Rule
+  { name = "Tag der Deutschen Einheit"
+  , pattern =
+    [ regex "tag (der)? deutsc?hen? einheit"
+    ]
+  , prod = \_ -> tt $ monthDay 10 3
+  }
+
+ruleEomendOfMonth :: Rule
+ruleEomendOfMonth = Rule
+  { name = "EOM|End of month"
+  , pattern =
+    [ regex "(das )?ende des monats?"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Month 1
+  }
+
+ruleNthTimeAfterTime2 :: Rule
+ruleNthTimeAfterTime2 = Rule
+  { name = "nth <time> after <time>"
+  , pattern =
+    [ regex "der|das"
+    , dimension Ordinal
+    , dimension Time
+    , regex "nach"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       Token Time td1:
+       _:
+       Token Time td2:
+       _) -> tt $ predNthAfter (v - 1) td1 td2
+      _ -> Nothing
+  }
+
+ruleNextTime :: Rule
+ruleNextTime = Rule
+  { name = "next <time>"
+  , pattern =
+    [ regex "(n(\x00e4)chste|kommende)[ns]?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleOrdinalQuarterYear :: Rule
+ruleOrdinalQuarterYear = Rule
+  { name = "<ordinal> quarter <year>"
+  , pattern =
+    [ dimension Ordinal
+    , Predicate $ isGrain TG.Quarter
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:_:Token Time td:_) ->
+        tt $ cycleNthAfter False TG.Quarter (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleYyyymmdd :: Rule
+ruleYyyymmdd = Rule
+  { name = "yyyy-mm-dd"
+  , pattern =
+    [ regex "(\\d{2,4})-(0?[1-9]|10|11|12)-([012]?[1-9]|10|20|30|31)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
+        y <- parseInt m1
+        m <- parseInt m2
+        d <- parseInt m3
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleTheOrdinalCycleAfterTime :: Rule
+ruleTheOrdinalCycleAfterTime = Rule
+  { name = "the <ordinal> <cycle> after <time>"
+  , pattern =
+    [ regex "the"
+    , dimension Ordinal
+    , dimension TimeGrain
+    , regex "nach"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleIntersectByOfFromS :: Rule
+ruleIntersectByOfFromS = Rule
+  { name = "intersect by 'of', 'from', 's"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "von|der|im"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNextNCycle :: Rule
+ruleNextNCycle = Rule
+  { name = "next n <cycle>"
+  , pattern =
+    [ regex "n(\x00e4)chsten?|kommenden?"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleADuration :: Rule
+ruleADuration = Rule
+  { name = "a <duration>"
+  , pattern =
+    [ regex "(in )?eine?(r|n)?"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleMorning :: Rule
+ruleMorning = Rule
+  { name = "morning"
+  , pattern =
+    [ regex "morgens|(in der )?fr(\x00fc)h|vor ?mittags?|am morgen"
+    ]
+  , prod = \_ ->
+      let from = hour False 3
+          to = hour False 12
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleThisPartofday :: Rule
+ruleThisPartofday = Rule
+  { name = "this <part-of-day>"
+  , pattern =
+    [ regex "diesen?|dieses|heute"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time . partOfDay <$>
+        intersect (cycleNth TG.Day 0) td
+      _ -> Nothing
+  }
+
+ruleThisCycle :: Rule
+ruleThisCycle = Rule
+  { name = "this <cycle>"
+  , pattern =
+    [ regex "diese(r|n|s)?|kommende(r|n|s)?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 0
+      _ -> Nothing
+  }
+
+ruleThisTime :: Rule
+ruleThisTime = Rule
+  { name = "this <time>"
+  , pattern =
+    [ regex "diese(n|r|s)?|(im )?laufenden"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleDurationHence :: Rule
+ruleDurationHence = Rule
+  { name = "<duration> hence"
+  , pattern =
+    [ dimension Duration
+    , regex "hence"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleDayofmonthNonOrdinalOfNamedmonth :: Rule
+ruleDayofmonthNonOrdinalOfNamedmonth = Rule
+  { name = "<day-of-month> (non ordinal) of <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , regex "vom|von"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleNikolaus :: Rule
+ruleNikolaus = Rule
+  { name = "Nikolaus"
+  , pattern =
+    [ regex "nikolaus(tag)?|nikolaus tag|nikolo"
+    ]
+  , prod = \_ -> tt $ monthDay 12 6
+  }
+
+ruleAfterLunch :: Rule
+ruleAfterLunch = Rule
+  { name = "after lunch"
+  , pattern =
+    [ regex "nach dem mittagessen|nachmittags?"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 13, hour False 17)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleOnANamedday :: Rule
+ruleOnANamedday = Rule
+  { name = "on a named-day"
+  , pattern =
+    [ regex "an einem"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleYearLatent :: Rule
+ruleYearLatent = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $
+        liftM2 (||) (isIntegerBetween (- 10000) 0) (isIntegerBetween 25 999)
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        y <- getIntValue token
+        tt . mkLatent $ year y
+      _ -> Nothing
+  }
+
+ruleYesterday :: Rule
+ruleYesterday = Rule
+  { name = "yesterday"
+  , pattern =
+    [ regex "gestern"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 1
+  }
+
+ruleSeason2 :: Rule
+ruleSeason2 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "herbst"
+    ]
+  , prod = \_ ->
+      let from = monthDay 9 23
+          to = monthDay 12 21
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleAfterTimeofday :: Rule
+ruleAfterTimeofday = Rule
+  { name = "after <time-of-day>"
+  , pattern =
+    [ regex "nach"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ withDirection TTime.After td
+      _ -> Nothing
+  }
+
+ruleChristmas :: Rule
+ruleChristmas = Rule
+  { name = "christmas"
+  , pattern =
+    [ regex "weih?nacht(en|stag)?"
+    ]
+  , prod = \_ -> tt $ monthDay 12 25
+  }
+
+ruleNight :: Rule
+ruleNight = Rule
+  { name = "night"
+  , pattern =
+    [ regex "nachts?"
+    ]
+  , prod = \_ ->
+      let from = hour False 0
+          to = hour False 4
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleDayofmonthOrdinal :: Rule
+ruleDayofmonthOrdinal = Rule
+  { name = "<day-of-month> (ordinal)"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):_) ->
+        tt $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleTimeofdayAmpm :: Rule
+ruleTimeofdayAmpm = Rule
+  { name = "<time-of-day> am|pm"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "([ap])\\.?m\\.?(?:[\\s'\"-_{}\\[\\]()]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:Token RegexMatch (GroupMatch (ap:_)):_) ->
+        tt . timeOfDayAMPM td $ Text.toLower ap == "a"
+      _ -> Nothing
+  }
+
+ruleHalfIntegerGermanStyleHourofday :: Rule
+ruleHalfIntegerGermanStyleHourofday = Rule
+  { name = "half <integer> (german style hour-of-day)"
+  , pattern =
+    [ regex "halb"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> do
+        t <- minutesBefore 30 td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleOrdinalCycleAfterTime :: Rule
+ruleOrdinalCycleAfterTime = Rule
+  { name = "<ordinal> <cycle> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "nach"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleOrdinalCycleOfTime :: Rule
+ruleOrdinalCycleOfTime = Rule
+  { name = "<ordinal> <cycle> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "im|in|von"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleAfterNextTime :: Rule
+ruleAfterNextTime = Rule
+  { name = "after next <time>"
+  , pattern =
+    [ regex "(\x00fc)ber ?n(\x00e4)chste[ns]?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 1 True td
+      _ -> Nothing
+  }
+
+ruleNamedmonth5 :: Rule
+ruleNamedmonth5 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "mai"
+    ]
+  , prod = \_ -> tt $ month 5
+  }
+
+ruleNamedday7 :: Rule
+ruleNamedday7 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "sonntags?|so\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 7
+  }
+
+ruleHhmm :: Rule
+ruleHhmm = Rule
+  { name = "hh:mm"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)(?:uhr|h)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        h <- parseInt m1
+        m <- parseInt m2
+        tt $ hourMinute False h m
+      _ -> Nothing
+  }
+
+ruleTonight :: Rule
+ruleTonight = Rule
+  { name = "tonight"
+  , pattern =
+    [ regex "heute? (am)? abends?"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 18, hour False 0)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleYear :: Rule
+ruleYear = Rule
+  { name = "year"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1000 2100
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        y <- getIntValue token
+        tt $ year y
+      _ -> Nothing
+  }
+
+ruleNamedmonth10 :: Rule
+ruleNamedmonth10 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "oktober|okt\\.?"
+    ]
+  , prod = \_ -> tt $ month 10
+  }
+
+ruleHalloweenDay :: Rule
+ruleHalloweenDay = Rule
+  { name = "halloween day"
+  , pattern =
+    [ regex "hall?owe?en?"
+    ]
+  , prod = \_ -> tt $ monthDay 10 31
+  }
+
+ruleNamedmonthDayofmonthNonOrdinal :: Rule
+ruleNamedmonthDayofmonthNonOrdinal = 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
+  }
+
+ruleHhmmMilitary :: Rule
+ruleHhmmMilitary = Rule
+  { name = "hhmm (military)"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (h:m:_)):_) -> do
+        hh <- parseInt h
+        mm <- parseInt m
+        tt . mkLatent $ hourMinute False hh mm
+      _ -> Nothing
+  }
+
+ruleAbsorptionOfAfterNamedDay :: Rule
+ruleAbsorptionOfAfterNamedDay = Rule
+  { name = "absorption of , after named day"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex ","
+    ]
+  , prod = \tokens -> case tokens of
+      (x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleLastDayofweekOfTime :: Rule
+ruleLastDayofweekOfTime = Rule
+  { name = "last <day-of-week> of <time>"
+  , pattern =
+    [ regex "letzte(r|n|s)?"
+    , Predicate isADayOfWeek
+    , regex "um|im"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ predLastOf td1 td2
+      _ -> Nothing
+  }
+
+ruleFathersDay :: Rule
+ruleFathersDay = Rule
+  { name = "Father's Day"
+  , pattern =
+    [ regex "vatt?ertag|vatt?er (tag)?"
+    ]
+  , prod = \_ -> tt $ nthDOWOfMonth 2 7 6
+  }
+
+ruleHhmmMilitaryAmpm :: Rule
+ruleHhmmMilitaryAmpm = Rule
+  { name = "hhmm (military) am|pm"
+  , pattern =
+    [ regex "((?:1[012]|0?\\d))([0-5]\\d)"
+    , regex "([ap])\\.?m\\.?(?:[\\s'\"-_{}\\[\\]()]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):Token RegexMatch (GroupMatch (ap:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt . timeOfDayAMPM (hourMinute True h m) $
+          Text.toLower ap == "a"
+      _ -> Nothing
+  }
+
+ruleTimeofdayTimeofdayInterval :: Rule
+ruleTimeofdayTimeofdayInterval = Rule
+  { name = "<time-of-day> - <time-of-day> (interval)"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\-|bis"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth11 :: Rule
+ruleNamedmonth11 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "november|nov\\.?"
+    ]
+  , prod = \_ -> tt $ month 11
+  }
+
+ruleDurationAfterTime :: Rule
+ruleDurationAfterTime = Rule
+  { name = "<duration> after <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "nach"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_:Token Time td:_) ->
+        tt $ durationAfter dd td
+      _ -> Nothing
+  }
+
+ruleOrdinalQuarter :: Rule
+ruleOrdinalQuarter = Rule
+  { name = "<ordinal> quarter"
+  , pattern =
+    [ dimension Ordinal
+    , Predicate $ isGrain TG.Quarter
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):_) -> tt .
+        cycleNthAfter False TG.Quarter (v - 1) $ cycleNth TG.Year 0
+      _ -> Nothing
+  }
+
+ruleNamedday3 :: Rule
+ruleNamedday3 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "mittwochs?|mi\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 3
+  }
+
+ruleTheDayofmonthOrdinal :: Rule
+ruleTheDayofmonthOrdinal = Rule
+  { name = "the <day-of-month> (ordinal)"
+  , pattern =
+    [ regex "der"
+    , Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal (OrdinalData {TOrdinal.value = v}):_) ->
+        tt $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleDurationBeforeTime :: Rule
+ruleDurationBeforeTime = Rule
+  { name = "<duration> before <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "vor"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_:Token Time td:_) ->
+        tt $ durationBefore dd td
+      _ -> Nothing
+  }
+
+rulePartofdayOfTime :: Rule
+rulePartofdayOfTime = Rule
+  { name = "<part-of-day> of <time>"
+  , pattern =
+    [ Predicate isAPartOfDay
+    , regex "des|von|vom|am"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleMmddyyyy :: Rule
+ruleMmddyyyy = Rule
+  { name = "mm/dd/yyyy"
+  , pattern =
+    [ regex "([012]?[1-9]|10|20|30|31)\\.(0?[1-9]|10|11|12)\\.(\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
+        y <- parseInt m3
+        m <- parseInt m2
+        d <- parseInt m1
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleEoyendOfYear :: Rule
+ruleEoyendOfYear = Rule
+  { name = "EOY|End of year"
+  , pattern =
+    [ regex "(das )?(EOY|jahr(es)? ?ende|ende (des )?jahr(es)?)"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Year 1
+  }
+
+ruleTomorrow :: Rule
+ruleTomorrow = Rule
+  { name = "tomorrow"
+  , pattern =
+    [ regex "morgen"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 1
+  }
+
+ruleMothersDay :: Rule
+ruleMothersDay = Rule
+  { name = "Mother's Day"
+  , pattern =
+    [ regex "mutt?ertag|mutt?er (tag)?"
+    ]
+  , prod = \_ -> tt $ nthDOWOfMonth 1 7 5
+  }
+
+ruleTimeofdayOclock :: Rule
+ruleTimeofdayOclock = Rule
+  { name = "<time-of-day>  o'clock"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "uhr|h(?:[\\s'\"-_{}\\[\\]()]|$)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth9 :: Rule
+ruleNamedmonth9 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "september|sept?\\.?"
+    ]
+  , prod = \_ -> tt $ month 9
+  }
+
+ruleDayofmonthordinalNamedmonthYear :: Rule
+ruleDayofmonthordinalNamedmonthYear = 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
+        n <- parseInt match
+        dom <- intersectDOM td token
+        Token Time <$> intersect dom (year n)
+      _ -> Nothing
+  }
+
+ruleTimezone :: Rule
+ruleTimezone = Rule
+  { name = "<time> timezone"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\b(YEKT|YEKST|YAKT|YAKST|WITA|WIT|WIB|WGT|WGST|WFT|WET|WEST|WAT|WAST|VUT|VLAT|VLAST|VET|UZT|UYT|UYST|UTC|ULAT|TVT|TMT|TLT|TKT|TJT|TFT|TAHT|SST|SRT|SGT|SCT|SBT|SAST|SAMT|RET|PYT|PYST|PWT|PST|PONT|PMST|PMDT|PKT|PHT|PHOT|PGT|PETT|PETST|PET|PDT|OMST|OMSST|NZST|NZDT|NUT|NST|NPT|NOVT|NOVST|NFT|NDT|NCT|MYT|MVT|MUT|MST|MSK|MSD|MMT|MHT|MDT|MAWT|MART|MAGT|MAGST|LINT|LHST|LHDT|KUYT|KST|KRAT|KRAST|KGT|JST|IST|IRST|IRKT|IRKST|IRDT|IOT|IDT|ICT|HOVT|HKT|GYT|GST|GMT|GILT|GFT|GET|GAMT|GALT|FNT|FKT|FKST|FJT|FJST|EST|EGT|EGST|EET|EEST|EDT|ECT|EAT|EAST|EASST|DAVT|ChST|CXT|CVT|CST|COT|CLT|CLST|CKT|CHAST|CHADT|CET|CEST|CDT|CCT|CAT|CAST|BTT|BST|BRT|BRST|BOT|BNT|AZT|AZST|AZOT|AZOST|AWST|AWDT|AST|ART|AQTT|ANAT|ANAST|AMT|AMST|ALMT|AKST|AKDT|AFT|AEST|AEDT|ADT|ACST|ACDT)\\b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       Token RegexMatch (GroupMatch (tz:_)):
+       _) -> Token Time <$> inTimezone tz td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleADuration
+  , ruleAboutTimeofday
+  , ruleAbsorptionOfAfterNamedDay
+  , ruleAfterDuration
+  , ruleAfterLunch
+  , ruleAfterNextTime
+  , ruleAfterTimeofday
+  , ruleAfterTomorrow
+  , ruleAfterWork
+  , ruleAfternoon
+  , ruleAllerheiligen
+  , ruleAtTimeofday
+  , ruleBeforeYesterday
+  , ruleBetweenDatetimeAndDatetimeInterval
+  , ruleBetweenTimeofdayAndTimeofdayInterval
+  , ruleByTheEndOfTime
+  , ruleChristmas
+  , ruleChristmasEve
+  , ruleDatetimeDatetimeInterval
+  , ruleDayofmonthNonOrdinalNamedmonth
+  , ruleDayofmonthNonOrdinalOfNamedmonth
+  , ruleDayofmonthOrdinal
+  , ruleDayofmonthordinalNamedmonth
+  , ruleDayofmonthordinalNamedmonthYear
+  , ruleDurationAfterTime
+  , ruleDurationAgo
+  , ruleDurationBeforeTime
+  , ruleDurationFromNow
+  , ruleDurationHence
+  , ruleEomendOfMonth
+  , ruleEoyendOfYear
+  , ruleEvening
+  , ruleExactlyTimeofday
+  , ruleFathersDay
+  , ruleFromDatetimeDatetimeInterval
+  , ruleFromTimeofdayTimeofdayInterval
+  , ruleHalfIntegerGermanStyleHourofday
+  , ruleHalloweenDay
+  , ruleHhmm
+  , ruleHhmmMilitary
+  , ruleHhmmMilitaryAmpm
+  , ruleHourofdayIntegerAsRelativeMinutes
+  , ruleInDuration
+  , ruleInduringThePartofday
+  , ruleIntersect
+  , ruleIntersectBy
+  , ruleIntersectByOfFromS
+  , ruleLastCycle
+  , ruleLastCycleOfTime
+  , ruleLastDayofweekOfTime
+  , ruleLastNCycle
+  , ruleLastTime
+  , ruleLunch
+  , ruleMidnighteodendOfDay
+  , ruleMmdd
+  , ruleMmddyyyy
+  , ruleMonthDdddInterval
+  , ruleMorning
+  , ruleMothersDay
+  , ruleNamedday
+  , ruleNamedday2
+  , ruleNamedday3
+  , ruleNamedday4
+  , ruleNamedday5
+  , ruleNamedday6
+  , ruleNamedday7
+  , ruleNamedmonth
+  , ruleNamedmonth10
+  , ruleNamedmonth11
+  , ruleNamedmonth12
+  , ruleNamedmonth2
+  , ruleNamedmonth3
+  , ruleNamedmonth4
+  , ruleNamedmonth5
+  , ruleNamedmonth6
+  , ruleNamedmonth7
+  , ruleNamedmonth8
+  , ruleNamedmonth9
+  , ruleNamedmonthDayofmonthNonOrdinal
+  , ruleNamedmonthDayofmonthOrdinal
+  , ruleNewYearsDay
+  , ruleNewYearsEve
+  , ruleNextCycle
+  , ruleNextNCycle
+  , ruleNextTime
+  , ruleNight
+  , ruleNikolaus
+  , ruleNoon
+  , ruleNow
+  , ruleNthTimeAfterTime
+  , ruleNthTimeAfterTime2
+  , ruleNthTimeOfTime
+  , ruleNthTimeOfTime2
+  , ruleOnANamedday
+  , ruleOnDate
+  , ruleOrdinalCycleAfterTime
+  , ruleOrdinalCycleOfTime
+  , ruleOrdinalQuarter
+  , ruleOrdinalQuarterYear
+  , rulePartofdayOfTime
+  , ruleRelativeMinutesAfterpastIntegerHourofday
+  , ruleRelativeMinutesTotillbeforeIntegerHourofday
+  , ruleSchweizerBundesfeiertag
+  , ruleSeason
+  , ruleSeason2
+  , ruleSeason3
+  , ruleSeason4
+  , ruleSterreichischerNationalfeiertag
+  , ruleTagDerDeutschenEinheit
+  , ruleTheCycleAfterTime
+  , ruleTheCycleBeforeTime
+  , ruleTheDayofmonthNonOrdinal
+  , ruleTheDayofmonthOrdinal
+  , ruleTheIdesOfNamedmonth
+  , ruleTheOrdinalCycleAfterTime
+  , ruleTheOrdinalCycleOfTime
+  , ruleThisCycle
+  , ruleThisPartofday
+  , ruleThisTime
+  , ruleThisnextDayofweek
+  , ruleTimeAfterNext
+  , ruleTimeBeforeLast
+  , ruleTimePartofday
+  , ruleTimeofdayAmpm
+  , ruleTimeofdayApproximately
+  , ruleTimeofdayLatent
+  , ruleTimeofdayOclock
+  , ruleTimeofdaySharp
+  , ruleTimeofdayTimeofdayInterval
+  , ruleToday
+  , ruleTomorrow
+  , ruleTonight
+  , ruleUntilTimeofday
+  , ruleValentinesDay
+  , ruleWeekend
+  , ruleWithinDuration
+  , ruleYear
+  , ruleYearLatent
+  , ruleYearLatent2
+  , ruleYesterday
+  , ruleYyyymmdd
+  , ruleQuarterTotillbeforeIntegerHourofday
+  , ruleHalfTotillbeforeIntegerHourofday
+  , ruleQuarterAfterpastIntegerHourofday
+  , ruleHalfAfterpastIntegerHourofday
+  , ruleHourofdayQuarter
+  , ruleHourofdayHalf
+  , ruleTimezone
+  ]
diff --git a/Duckling/Time/EN/Corpus.hs b/Duckling/Time/EN/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/EN/Corpus.hs
@@ -0,0 +1,783 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.EN.Corpus
+  ( corpus
+  , negativeCorpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Time.Corpus
+import Duckling.Time.Types hiding (Month)
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Testing.Types hiding (examples)
+
+corpus :: Corpus
+corpus = (testContext, allExamples)
+
+negativeCorpus :: NegativeCorpus
+negativeCorpus = (testContext, examples)
+  where
+    examples =
+      [ "laughing out loud"
+      , "1 adult"
+      , "we are separated"
+      , "25"
+      , "this is the one"
+      , "in 61"
+      ]
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (datetime (2013, 2, 12, 4, 30, 0) Second)
+             [ "now"
+             , "right now"
+             , "just now"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "today"
+             , "at this time"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             ["yesterday"]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "tomorrow"
+             , "tomorrows"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "monday"
+             , "mon."
+             , "this monday"
+             , "Monday, Feb 18"
+             , "Mon, February 18"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "tuesday"
+             , "Tuesday the 19th"
+             , "Tuesday 19th"
+             ]
+  , examples (datetime (2013, 8, 15, 0, 0, 0) Day)
+             [ "Thu 15th"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "thursday"
+             , "thu"
+             , "thu."
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "friday"
+             , "fri"
+             , "fri."
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "saturday"
+             , "sat"
+             , "sat."
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "sunday"
+             , "sun"
+             , "sun."
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "the 1st of march"
+             , "first of march"
+             , "march first"
+             ]
+  , examples (datetime (2013, 3, 3, 0, 0, 0) Day)
+             ["march 3"]
+  , examples (datetime (2013, 3, 15, 0, 0, 0) Day)
+             ["the ides of march"]
+  , examples (datetime (2015, 3, 3, 0, 0, 0) Day)
+             [ "march 3 2015"
+             , "march 3rd 2015"
+             , "march third 2015"
+             , "3/3/2015"
+             , "3/3/15"
+             , "2015-3-3"
+             , "2015-03-03"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "on the 15th"
+             , "the 15th of february"
+             , "15 of february"
+             , "february the 15th"
+             , "february 15"
+             , "15th february"
+             , "2/15"
+             , "on 2/15"
+             , "February 15"
+             ]
+  , examples (datetime (2013, 8, 8, 0, 0, 0) Day)
+             ["Aug 8"]
+  , examples (datetime (2014, 7, 18, 0, 0, 0) Day)
+             [ "Fri, Jul 18"
+             , "Jul 18, Fri"
+             ]
+  , examples (datetime (2014, 10, 1, 0, 0, 0) Month)
+             ["October 2014"]
+  , examples (datetime (1974, 10, 31, 0, 0, 0) Day)
+             [ "10/31/1974"
+             , "10/31/74"
+             , "10-31-74"
+             ]
+  , examples (datetime (2015, 4, 14, 0, 0, 0) Day)
+             [ "14april 2015"
+             , "April 14, 2015"
+             , "14th April 15"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "next tuesday"
+             , "around next tuesday"
+             ]
+  , examples (datetime (2013, 2, 22, 0, 0, 0) Day)
+             ["friday after next"]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Month)
+             ["next March"]
+  , examples (datetime (2014, 3, 1, 0, 0, 0) Month)
+             ["March after next"]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             ["Sunday, Feb 10"]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             ["Wed, Feb13"]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Week)
+             [ "this week"
+             , "current week"
+             , "coming week"
+             ]
+  , examples (datetime (2013, 2, 4, 0, 0, 0) Week)
+             [ "last week"
+             , "past week"
+             , "previous week"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Week)
+             [ "next week"
+             , "the following week"
+             , "around next week"
+             ]
+  , examples (datetime (2013, 1, 1, 0, 0, 0) Month)
+             ["last month"]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Month)
+             ["next month"]
+  , examples (datetime (2013, 1, 1, 0, 0, 0) Quarter)
+             [ "this quarter"
+             , "this qtr"
+             ]
+  , examples (datetime (2013, 4, 1, 0, 0, 0) Quarter)
+             [ "next quarter"
+             , "next qtr"
+             ]
+  , examples (datetime (2013, 7, 1, 0, 0, 0) Quarter)
+             [ "third quarter"
+             , "3rd quarter"
+             , "third qtr"
+             , "3rd qtr"
+             , "the 3rd qtr"
+             ]
+  , examples (datetime (2018, 10, 1, 0, 0, 0) Quarter)
+             [ "4th quarter 2018"
+             , "4th qtr 2018"
+             , "the 4th qtr of 2018"
+             ]
+  , examples (datetime (2012, 1, 1, 0, 0, 0) Year)
+             [ "last year"
+             , "last yr"
+             ]
+  , examples (datetime (2013, 1, 1, 0, 0, 0) Year)
+             [ "this year"
+             , "current year"
+             , "this yr"
+             ]
+  , examples (datetime (2014, 1, 1, 0, 0, 0) Year)
+             [ "next year"
+             , "next yr"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "last sunday"
+             , "sunday from last week"
+             , "last week's sunday"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             ["last tuesday"]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             ["next tuesday"]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             ["next wednesday"]
+  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
+             [ "wednesday of next week"
+             , "wednesday next week"
+             , "wednesday after next"
+             ]
+  , examples (datetime (2013, 2, 22, 0, 0, 0) Day)
+             ["friday after next"]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             ["monday of this week"]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             ["tuesday of this week"]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             ["wednesday of this week"]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             ["the day after tomorrow"]
+  , examples (datetime (2013, 2, 14, 17, 0, 0) Hour)
+             [ "day after tomorrow 5pm"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             ["the day before yesterday"]
+  , examples (datetime (2013, 2, 10, 8, 0, 0) Hour)
+             [ "day before yesterday 8am"
+             ]
+  , examples (datetime (2013, 3, 25, 0, 0, 0) Day)
+             ["last Monday of March"]
+  , examples (datetime (2014, 3, 30, 0, 0, 0) Day)
+             ["last Sunday of March 2014"]
+  , examples (datetime (2013, 10, 3, 0, 0, 0) Day)
+             ["third day of october"]
+  , examples (datetime (2014, 10, 6, 0, 0, 0) Week)
+             ["first week of october 2014"]
+  , examples (datetime (2013, 10, 7, 0, 0, 0) Week)
+             ["the week of october 6th"]
+  , examples (datetime (2013, 10, 7, 0, 0, 0) Week)
+             ["the week of october 7th"]
+  , examples (datetime (2015, 10, 31, 0, 0, 0) Day)
+             [ "last day of october 2015"
+             , "last day in october 2015"
+             ]
+  , examples (datetime (2014, 9, 22, 0, 0, 0) Week)
+             ["last week of september 2014"]
+  , examples (datetime (2013, 10, 1, 0, 0, 0) Day)
+             [ "first tuesday of october"
+             , "first tuesday in october"
+             ]
+  , examples (datetime (2014, 9, 16, 0, 0, 0) Day)
+             ["third tuesday of september 2014"]
+  , examples (datetime (2014, 10, 1, 0, 0, 0) Day)
+             ["first wednesday of october 2014"]
+  , examples (datetime (2014, 10, 8, 0, 0, 0) Day)
+             ["second wednesday of october 2014"]
+  , examples (datetime (2015, 1, 13, 0, 0, 0) Day)
+             ["third tuesday after christmas 2014"]
+  , examples (datetime (2013, 2, 13, 3, 0, 0) Hour)
+             [ "at 3am"
+             , "3 in the AM"
+             , "at 3 AM"
+             , "3 oclock am"
+             , "at three am"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 18, 0) Minute)
+             [ "3:18am"
+             , "3:18a"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "at 3pm"
+             , "@ 3pm"
+             , "3PM"
+             , "3pm"
+             , "3 oclock pm"
+             , "3 o'clock in the afternoon"
+             , "3ish pm"
+             , "3pm approximately"
+             , "at about 3pm"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 15, 0) Minute)
+             [ "at 15 past 3pm"
+             , "a quarter past 3pm"
+             , "3:15 in the afternoon"
+             , "15:15"
+             , "3:15pm"
+             , "3:15PM"
+             , "3:15p"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 20, 0) Minute)
+             [ "at 20 past 3pm"
+             , "3:20 in the afternoon"
+             , "3:20 in afternoon"
+             , "twenty after 3pm"
+             , "3:20p"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 30, 0) Minute)
+             [ "at half past three pm"
+             , "half past 3 pm"
+             , "15:30"
+             , "3:30pm"
+             , "3:30PM"
+             , "330 p.m."
+             , "3:30 p m"
+             , "3:30"
+             , "half three"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 23, 24) Second)
+             ["15:23:24"]
+  , examples (datetime (2013, 2, 12, 11, 45, 0) Minute)
+             [ "a quarter to noon"
+             , "11:45am"
+             , "15 to noon"
+             ]
+  , examples (datetime (2013, 2, 12, 20, 0, 0) Hour)
+             [ "8 tonight"
+             , "eight tonight"
+             , "8 this evening"
+             , "at 8 in the evening"
+             , "in the evening at eight"
+             ]
+  , examples (datetime (2013, 9, 20, 19, 30, 0) Minute)
+             ["at 7:30 PM on Fri, Sep 20"]
+  , examples (datetime (2013, 2, 16, 9, 0, 0) Hour)
+             ["at 9am on Saturday"]
+  , examples (datetime (2013, 2, 16, 9, 0, 0) Hour)
+             ["on Saturday for 9am"]
+  , examples (datetime (2014, 7, 18, 19, 0, 0) Minute)
+             ["Fri, Jul 18, 2014 07:00 PM"]
+  , examples (datetime (2013, 2, 12, 4, 30, 1) Second)
+             [ "in a sec"
+             , "one second from now"
+             , "in 1\""
+             ]
+  , examples (datetime (2013, 2, 12, 4, 31, 0) Second)
+             [ "in a minute"
+             , "in one minute"
+             , "in 1'"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 32, 0) Second)
+             [ "in 2 minutes"
+             , "in 2 more minutes"
+             , "2 minutes from now"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Second)
+             ["in 60 minutes"]
+  , examples (datetime (2013, 2, 12, 4, 45, 0) Second)
+             [ "in a quarter of an hour"
+             , "in 1/4h"
+             , "in 1/4 h"
+             , "in 1/4 hour"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 0, 0) Second)
+             [ "in half an hour"
+             , "in 1/2h"
+             , "in 1/2 h"
+             , "in 1/2 hour"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 15, 0) Second)
+             [ "in three-quarters of an hour"
+             , "in 3/4h"
+             , "in 3/4 h"
+             , "in 3/4 hour"
+             ]
+  , examples (datetime (2013, 2, 12, 7, 0, 0) Second)
+             [ "in 2.5 hours"
+             , "in 2 and an half hours"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Minute)
+             [ "in one hour"
+             , "in 1h"
+             ]
+  , examples (datetime (2013, 2, 12, 6, 30, 0) Minute)
+             [ "in a couple hours"
+             , "in a couple of hours"
+             ]
+  , examples (datetime (2013, 2, 12, 7, 30, 0) Minute)
+             [ "in a few hours"
+             , "in few hours"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 30, 0) Minute)
+             ["in 24 hours"]
+  , examples (datetime (2013, 2, 13, 4, 0, 0) Hour)
+             [ "in a day"
+             , "a day from now"
+             ]
+  , examples (datetime (2016, 2, 1, 0, 0, 0) Month)
+             ["3 years from today"]
+  , examples (datetime (2013, 2, 19, 4, 0, 0) Hour)
+             ["in 7 days"]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "in 1 week"
+             , "in a week"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 0, 0) Second)
+             ["in about half an hour"]
+  , examples (datetime (2013, 2, 5, 4, 0, 0) Hour)
+             ["7 days ago"]
+  , examples (datetime (2013, 1, 29, 4, 0, 0) Hour)
+             [ "14 days Ago"
+             , "a fortnight ago"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "a week ago"
+             , "one week ago"
+             , "1 week ago"
+             ]
+  , examples (datetime (2013, 1, 22, 0, 0, 0) Day)
+             ["three weeks ago"]
+  , examples (datetime (2012, 11, 12, 0, 0, 0) Day)
+             ["three months ago"]
+  , examples (datetime (2011, 2, 1, 0, 0, 0) Month)
+             ["two years ago"]
+  , examples (datetime (1954, 1, 1, 0, 0, 0) Year)
+             ["1954"]
+  , examples (datetime (2013, 2, 19, 4, 0, 0) Hour)
+             ["7 days hence"]
+  , examples (datetime (2013, 2, 26, 4, 0, 0) Hour)
+             [ "14 days hence"
+             , "a fortnight hence"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "a week hence"
+             , "one week hence"
+             , "1 week hence"
+             ]
+  , examples (datetime (2013, 3, 5, 0, 0, 0) Day)
+             ["three weeks hence"]
+  , examples (datetime (2013, 5, 12, 0, 0, 0) Day)
+             ["three months hence"]
+  , examples (datetime (2015, 2, 1, 0, 0, 0) Month)
+             ["two years hence"]
+  , examples (datetime (2013, 12, 1, 0, 0, 0) Month)
+             [ "one year After christmas"
+             , "a year from Christmas"
+             ]
+  , examples (datetimeInterval ((2013, 6, 21, 0, 0, 0), (2013, 9, 24, 0, 0, 0)) Day)
+             [ "this Summer"
+             , "current summer"
+             ]
+  , examples (datetimeInterval ((2012, 12, 21, 0, 0, 0), (2013, 3, 21, 0, 0, 0)) Day)
+             ["this winter"]
+  , examples (datetime (2013, 12, 25, 0, 0, 0) Day)
+             [ "xmas"
+             , "christmas"
+             , "christmas day"
+             ]
+  , examples (datetime (2013, 12, 31, 0, 0, 0) Day)
+             [ "new year's eve"
+             , "new years eve"
+             ]
+  , examples (datetime (2014, 1, 1, 0, 0, 0) Day)
+             [ "new year's day"
+             , "new years day"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "valentine's day"
+             , "valentine day"
+             ]
+  , examples (datetime (2013, 5, 12, 0, 0, 0) Day)
+             [ "Mother's Day"
+             , "next mothers day"
+             ]
+  , examples (datetime (2012, 5, 13, 0, 0, 0) Day)
+             ["last mothers day"]
+  , examples (datetime (2014, 5, 11, 0, 0, 0) Day)
+             ["mothers day 2014"]
+  , examples (datetime (2013, 6, 16, 0, 0, 0) Day)
+             ["Father's Day"]
+  , examples (datetime (2012, 6, 17, 0, 0, 0) Day)
+             ["last fathers day"]
+  , examples (datetime (1996, 6, 16, 0, 0, 0) Day)
+             ["fathers day 1996"]
+  , examples (datetime (2013, 5, 27, 0, 0, 0) Day)
+             [ "memorial day"
+             , "Next Memorial Day"
+             ]
+  , examples (datetime (2012, 5, 28, 0, 0, 0) Day)
+             [ "last memorial day"
+             , "memorial day of last year"
+             ]
+  , examples (datetimeInterval ((2013, 5, 24, 18, 0, 0), (2013, 5, 28, 0, 0, 0)) Hour)
+             ["memorial day week-end"]
+  , examples (datetime (2013, 7, 4, 0, 0, 0) Day)
+             [ "independence day"
+             , "4th of July"
+             , "4 of july"
+             ]
+  , examples (datetime (2013, 9, 2, 0, 0, 0) Day)
+             ["labor day"]
+  , examples (datetime (2012, 9, 3, 0, 0, 0) Day)
+             [ "labor day of last year"
+             , "Labor Day 2012"
+             ]
+  , examples (datetimeInterval ((2013, 8, 30, 18, 0, 0), (2013, 9, 3, 0, 0, 0)) Hour)
+             ["labor day weekend"]
+  , examples (datetime (2013, 10, 31, 0, 0, 0) Day)
+             [ "halloween"
+             , "next halloween"
+             , "Halloween 2013"
+             ]
+  , examples (datetime (2013, 11, 28, 0, 0, 0) Day)
+             [ "thanksgiving day"
+             , "thanksgiving"
+             , "thanksgiving 2013"
+             , "this thanksgiving"
+             , "next thanksgiving day"
+             ]
+  , examples (datetime (2014, 11, 27, 0, 0, 0) Day)
+             [ "thanksgiving of next year"
+             , "thanksgiving 2014"
+             ]
+  {- FIXME (jodent) Doesn't work in Duckling either. t13908315
+  , examples (datetime (2012, 11, 22, 0, 0, 0) Day)
+             [ "last thanksgiving"
+             , "thanksgiving day 2012"
+             ]
+  , examples (datetime (2016, 11, 24, 0, 0, 0) Day)
+             ["thanksgiving 2016"]-}
+  , examples (datetime (2014, 1, 20, 0, 0, 0) Day)
+             [ "MLK day"
+             , "next Martin Luther King day"
+             , "this MLK day"
+             ]
+  , examples (datetime (2013, 1, 21, 0, 0, 0) Day)
+             [ "last MLK day"
+             , "MLK day 2013"
+             ]
+  , examples (datetime (2012, 1, 16, 0, 0, 0) Day)
+             [ "MLK day of last year"
+             , "MLK day 2012"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 18, 0, 0), (2013, 2, 13, 0, 0, 0)) Hour)
+             [ "this evening"
+             , "today evening"
+             , "tonight"
+             ]
+  , examples (datetimeInterval ((2013, 2, 8, 18, 0, 0), (2013, 2, 11, 0, 0, 0)) Hour)
+             ["this past weekend"]
+  , examples (datetimeInterval ((2013, 2, 13, 18, 0, 0), (2013, 2, 14, 0, 0, 0)) Hour)
+             ["tomorrow evening"]
+  , examples (datetimeInterval ((2013, 2, 13, 12, 0, 0), (2013, 2, 13, 14, 0, 0)) Hour)
+             [ "tomorrow lunch"
+             , "tomorrow at lunch"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 18, 0, 0), (2013, 2, 12, 0, 0, 0)) Hour)
+             ["yesterday evening"]
+  , examples (datetimeInterval ((2013, 2, 15, 18, 0, 0), (2013, 2, 18, 0, 0, 0)) Hour)
+             ["this week-end"]
+  , examples (datetimeInterval ((2013, 2, 18, 4, 0, 0), (2013, 2, 18, 12, 0, 0)) Hour)
+             ["monday mOrnIng"]
+  , examples (datetimeInterval ((2013, 2, 18, 4, 0, 0), (2013, 2, 18, 9, 0, 0)) Hour)
+             [ "monday early in the morning"
+             , "monday early morning"
+             , "monday in the early hours of the morning"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 4, 0, 0), (2013, 2, 15, 12, 0, 0)) Hour)
+             [ "february the 15th in the morning"
+             , "15 of february in the morning"
+             , "morning of the 15th of february"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 29, 58), (2013, 2, 12, 4, 30, 0)) Second)
+             [ "last 2 seconds"
+             , "last two seconds"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 1), (2013, 2, 12, 4, 30, 4)) Second)
+             [ "next 3 seconds"
+             , "next three seconds"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 28, 0), (2013, 2, 12, 4, 30, 0)) Minute)
+             [ "last 2 minutes"
+             , "last two minutes"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 31, 0), (2013, 2, 12, 4, 34, 0)) Minute)
+             [ "next 3 minutes"
+             , "next three minutes"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 3, 0, 0), (2013, 2, 12, 4, 0, 0)) Hour)
+             [ "last 1 hour"
+             , "last one hour"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 5, 0, 0), (2013, 2, 12, 8, 0, 0)) Hour)
+             [ "next 3 hours"
+             , "next three hours"
+             ]
+  , examples (datetimeInterval ((2013, 2, 10, 0, 0, 0), (2013, 2, 12, 0, 0, 0)) Day)
+             [ "last 2 days"
+             , "last two days"
+             , "past 2 days"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
+             [ "next 3 days"
+             , "next three days"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
+             ["next few days"]
+  , examples (datetimeInterval ((2013, 1, 28, 0, 0, 0), (2013, 2, 11, 0, 0, 0)) Week)
+             [ "last 2 weeks"
+             , "last two weeks"
+             , "past 2 weeks"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 0, 0, 0), (2013, 3, 11, 0, 0, 0)) Week)
+             [ "next 3 weeks"
+             , "next three weeks"
+             ]
+  , examples (datetimeInterval ((2012, 12, 1, 0, 0, 0), (2013, 2, 1, 0, 0, 0)) Month)
+             [ "last 2 months"
+             , "last two months"
+             ]
+  , examples (datetimeInterval ((2013, 3, 1, 0, 0, 0), (2013, 6, 1, 0, 0, 0)) Month)
+             [ "next 3 months"
+             , "next three months"
+             ]
+  , examples (datetimeInterval ((2011, 1, 1, 0, 0, 0), (2013, 1, 1, 0, 0, 0)) Year)
+             [ "last 2 years"
+             , "last two years"
+             ]
+  , examples (datetimeInterval ((2014, 1, 1, 0, 0, 0), (2017, 1, 1, 0, 0, 0)) Year)
+             [ "next 3 years"
+             , "next three years"
+             ]
+  , examples (datetimeInterval ((2013, 7, 13, 0, 0, 0), (2013, 7, 16, 0, 0, 0)) Day)
+             [ "July 13-15"
+             , "July 13 to 15"
+             , "July 13 thru 15"
+             , "July 13 through 15"
+             , "July 13 - July 15"
+             ]
+  , examples (datetimeInterval ((2013, 8, 8, 0, 0, 0), (2013, 8, 13, 0, 0, 0)) Day)
+             ["Aug 8 - Aug 12"]
+  , examples (datetimeInterval ((2013, 2, 12, 9, 30, 0), (2013, 2, 12, 11, 1, 0)) Minute)
+             ["9:30 - 11:00"]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 30, 0), (2013, 2, 14, 11, 1, 0)) Minute)
+             [ "from 9:30 - 11:00 on Thursday"
+             , "between 9:30 and 11:00 on thursday"
+             , "between 9:30 and 11:00 on thursday"
+             , "between 9:30 and 11:00 on thursday"
+             , "9:30 - 11:00 on Thursday"
+             , "later than 9:30 but before 11:00 on Thursday"
+             , "Thursday from 9:30 to 11:00"
+             , "from 9:30 untill 11:00 on thursday"
+             , "Thursday from 9:30 untill 11:00"
+             , "9:30 till 11:00 on Thursday"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 1, 0, 0), (2013, 2, 13, 2, 31, 0)) Minute)
+             [ "tomorrow in between 1-2:30 ish"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 15, 0, 0), (2013, 2, 12, 17, 0, 0)) Hour)
+             [ "3-4pm"
+             , "from 3 to 4 in the PM"
+             , "around 3-4pm"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 15, 30, 0), (2013, 2, 12, 19, 0, 0)) Minute)
+             [ "3:30 to 6 PM"
+             , "3:30-6 p.m."
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 8, 0, 0), (2013, 2, 12, 14, 0, 0)) Hour)
+             [ "8am - 1pm"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 0, 0), (2013, 2, 14, 12, 0, 0)) Hour)
+             [ "Thursday from 9a to 11a"
+             , "this Thu 9-11am"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 11, 30, 0), (2013, 2, 12, 13, 31, 0)) Minute)
+             ["11:30-1:30"]
+  , examples (datetime (2013, 9, 21, 13, 30, 0) Minute)
+             ["1:30 PM on Sat, Sep 21"]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 26, 0, 0, 0)) Second)
+             ["Within 2 weeks"]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 12, 14, 0, 0)) Second)
+             ["by 2:00pm"]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 13, 0, 0, 0)) Second)
+             ["by EOD"]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 3, 1, 0, 0, 0)) Second)
+             ["by EOM"]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 4, 1, 0, 0, 0)) Second)
+             ["by the end of next month"]
+  , examples (datetime (2013, 2, 12, 13, 0, 0) Minute)
+             [ "4pm CET"
+             ]
+  , examples (datetime (2013, 2, 14, 6, 0, 0) Minute)
+             [ "Thursday 8:00 GMT"
+             , "Thu at 8 GMT"
+             ]
+  , examples (datetime (2013, 2, 12, 14, 0, 0) Hour)
+             [ "today at 2pm"
+             , "at 2pm"
+             ]
+  , examples (datetime (2013, 4, 25, 16, 0, 0) Minute)
+             ["4/25 at 4:00pm"]
+  , examples (datetime (2013, 2, 13, 15, 0, 0) Hour)
+             [ "3pm tomorrow"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 14, 0, 0) Minute)
+             [ "until 2:00pm"
+             , "through 2:00pm"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 12, 14, 0, 0) Hour)
+             ["after 2 pm"]
+  , examples (datetimeOpenInterval After (2013, 2, 17, 4, 0, 0) Hour)
+             ["after 5 days"]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 11, 0, 0) Hour)
+             ["before 11 am"]
+  , examples (datetimeInterval ((2013, 2, 12, 12, 0, 0), (2013, 2, 12, 19, 0, 0)) Hour)
+             ["in the afternoon"]
+  , examples (datetimeInterval ((2013, 2, 12, 8, 0, 0), (2013, 2, 12, 19, 0, 0)) Hour)
+             [ "8am until 6"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 30, 0) Minute)
+             [ "at 1:30pm"
+             , "1:30pm"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 45, 0) Second)
+             [ "in 15 minutes"
+             , "in 15'"
+             , "in 15"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 13, 0, 0), (2013, 2, 12, 17, 0, 0)) Hour)
+             ["after lunch"]
+  , examples (datetimeInterval ((2013, 2, 12, 15, 0, 0), (2013, 2, 12, 21, 0, 0)) Hour)
+             [ "after school"
+             ]
+  , examples (datetime (2013, 2, 12, 10, 30, 0) Minute)
+             [ "10:30"
+             , "approximately 1030"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 0, 0), (2013, 2, 12, 12, 0, 0)) Hour)
+             ["this morning"]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             ["next monday"]
+  , examples (datetime (2013, 2, 12, 12, 0, 0) Hour)
+             [ "at 12pm"
+             , "at noon"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Hour)
+             [ "at 12am"
+             , "at midnight"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Month)
+             [ "March"
+             , "in March"
+             ]
+  , examples (datetime (2013, 2, 13, 17, 0, 0) Hour)
+             [ "tomorrow afternoon at 5"
+             , "at 5 tomorrow afternoon"
+             , "at 5pm tomorrow"
+             , "tomorrow at 5pm"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 13, 0, 0), (2013, 2, 13, 15, 0, 0)) Hour)
+             [ "1pm-2pm tomorrow"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "on the first"
+             , "the 1st"
+             ]
+  , examples (datetime (2013, 2, 12, 10, 30, 0) Minute)
+             [ "at 1030"
+             , "around 1030"
+             ]
+  , examples (datetime (2013, 2, 12, 19, 30, 0) Minute)
+             [ "at 730 in the evening"
+             ]
+  , examples (datetime (2013, 2, 13, 1, 50, 0) Minute)
+             [ "tomorrow at 150ish"
+             ]
+  , examples (datetime (2013, 2, 12, 23, 0, 0) Hour)
+             [ "tonight at 11"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 23, 0) Minute)
+    -- yes, the result is in the past, we may need to revisit
+             [ "at 4:23"
+             , "4:23am"
+             ]
+  ]
diff --git a/Duckling/Time/EN/Rules.hs b/Duckling/Time/EN/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/EN/Rules.hs
@@ -0,0 +1,1597 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.EN.Rules
+  ( rules ) where
+
+import Control.Monad (liftM2)
+import Data.Maybe
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Helpers (duration)
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Numeral.Types (NumeralData (..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Ordinal.Types (OrdinalData (..))
+import qualified Duckling.Ordinal.Types as TOrdinal
+import Duckling.Regex.Types
+import Duckling.Time.Helpers
+import Duckling.Time.Types (TimeData (..))
+import qualified Duckling.Time.Types as TTime
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ Predicate isNotLatent
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) -> Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleIntersectOf :: Rule
+ruleIntersectOf = Rule
+  { name = "intersect by \",\", \"of\", \"from\", \"'s\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "of|from|for|'s|,"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) -> Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleAbsorbOnTime :: Rule
+ruleAbsorbOnTime = Rule
+  { name = "on <date>"
+  , pattern =
+    [ regex "on"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleAbsorbOnADOW :: Rule
+ruleAbsorbOnADOW = Rule
+  { name = "on a <named-day>"
+  , pattern =
+    [ regex "on a"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleAbsorbInMonth :: Rule
+ruleAbsorbInMonth = Rule
+  { name = "in <named-month>"
+  , pattern =
+    [ regex "in"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> Just token
+      _ -> Nothing
+  }
+
+ruleAbsorbCommaTOD :: Rule
+ruleAbsorbCommaTOD = Rule
+  { name = "absorption of , after named day"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex ","
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> Just token
+      _ -> Nothing
+  }
+
+instants :: [(Text, String, TG.Grain, Int)]
+instants =
+  [ ("now", "((just|right)\\s*)?now|immediately", TG.Second, 0)
+  , ("today", "todays?|(at this time)", TG.Day, 0)
+  , ("tomorrow", "(tmrw?|tomm?or?rows?)", TG.Day, 1)
+  , ("yesterday", "yesterdays?", TG.Day, - 1)
+  , ("end of month", "(the )?(EOM|end of (the )?month)", TG.Month, 1)
+  , ("end of year", "(the )?(EOY|end of (the )?year)", TG.Year, 1)
+  ]
+
+ruleInstants :: [Rule]
+ruleInstants = map go instants
+  where
+    go (name, regexPattern, grain, n) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> tt $ cycleNth grain n
+      }
+
+ruleNextDOW :: Rule
+ruleNextDOW = Rule
+  { name = "this|next <day-of-week>"
+  , pattern =
+    [ regex "this|next"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleThisTime :: Rule
+ruleThisTime = Rule
+  { name = "this <time>"
+  , pattern =
+    [ regex "this|current|coming"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleNextTime :: Rule
+ruleNextTime = Rule
+  { name = "next <time>"
+  , pattern =
+    [ regex "next"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleLastTime :: Rule
+ruleLastTime = Rule
+  { name = "last <time>"
+  , pattern =
+    [ regex "(this past|last|previous)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ predNth (- 1) False td
+      _ -> Nothing
+  }
+
+ruleTimeBeforeLastAfterNext :: Rule
+ruleTimeBeforeLastAfterNext = Rule
+  { name = "<time> before last|after next"
+  , pattern =
+    [ dimension Time
+    , regex "(before last|after next)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:Token RegexMatch (GroupMatch (match:_)):_) ->
+        tt $ predNth 1 (match == "after next") td
+      _ -> Nothing
+  }
+
+ruleLastDOWOfTime :: Rule
+ruleLastDOWOfTime = Rule
+  { name = "last <day-of-week> of <time>"
+  , pattern =
+    [ regex "last"
+    , Predicate isADayOfWeek
+    , regex "of"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ predLastOf td1 td2
+      _ -> Nothing
+  }
+
+ruleLastCycleOfTime :: Rule
+ruleLastCycleOfTime = Rule
+  { name = "last <cycle> of <time>"
+  , pattern =
+    [ regex "last"
+    , dimension TimeGrain
+    , regex "of|in"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleLastOf grain td
+      _ -> Nothing
+  }
+
+ruleNthTimeOfTime :: Rule
+ruleNthTimeOfTime = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "of|in"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token Time td1:_:Token Time td2:_) -> Token Time .
+        predNth (TOrdinal.value od - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleTheNthTimeOfTime :: Rule
+ruleTheNthTimeOfTime = Rule
+  { name = "the nth <time> of <time>"
+  , pattern =
+    [ regex "the"
+    , dimension Ordinal
+    , dimension Time
+    , regex "of|in"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token Time td1:_:Token Time td2:_) -> Token Time .
+         predNth (TOrdinal.value od - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNthTimeAfterTime :: Rule
+ruleNthTimeAfterTime = Rule
+  { name = "nth <time> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "after"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token Time td1:_:Token Time td2:_) ->
+        tt $ predNthAfter (TOrdinal.value od - 1) td1 td2
+      _ -> Nothing
+  }
+
+ruleTheNthTimeAfterTime :: Rule
+ruleTheNthTimeAfterTime = Rule
+  { name = "the nth <time> after <time>"
+  , pattern =
+    [ regex "the"
+    , dimension Ordinal
+    , dimension Time
+    , regex "after"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token Time td1:_:Token Time td2:_) ->
+        tt $ predNthAfter (TOrdinal.value od - 1) td1 td2
+      _ -> Nothing
+  }
+
+ruleYear :: Rule
+ruleYear = Rule
+  { name = "year"
+  , pattern = [Predicate $ isIntegerBetween 1000 2100]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt $ year n
+      _ -> Nothing
+  }
+
+ruleYearPastLatent :: Rule
+ruleYearPastLatent = Rule
+ { name = "past year (latent)"
+ , pattern =
+   [ Predicate $
+       liftM2 (||) (isIntegerBetween (- 10000) 0) (isIntegerBetween 25 999)
+   ]
+ , prod = \tokens -> case tokens of
+     (token:_) -> do
+       n <- getIntValue token
+       tt . mkLatent $ year n
+     _ -> Nothing
+ }
+
+ruleYearFutureLatent :: Rule
+ruleYearFutureLatent = Rule
+ { name = "future year (latent)"
+ , pattern = [Predicate $ isIntegerBetween 2101 10000]
+ , prod = \tokens -> case tokens of
+     (token:_) -> do
+       n <- getIntValue token
+       tt . mkLatent $ year n
+     _ -> Nothing
+ }
+
+ruleDOMLatent :: Rule
+ruleDOMLatent = Rule
+  { name = "<day-of-month> (ordinal)"
+  , pattern = [Predicate isDOMOrdinal]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ dayOfMonth n
+      _ -> Nothing
+  }
+
+ruleTheDOMNumeral :: Rule
+ruleTheDOMNumeral = Rule
+  { name = "the <day-of-month> (number)"
+  , pattern =
+    [ regex "the"
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ dayOfMonth n
+      _ -> Nothing
+  }
+
+ruleTheDOMOrdinal :: Rule
+ruleTheDOMOrdinal = Rule
+  { name = "the <day-of-month> (ordinal)"
+  , pattern =
+    [ regex "the"
+    , Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       _) -> tt $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleNamedDOMOrdinal :: Rule
+ruleNamedDOMOrdinal = Rule
+  { name = "<named-month>|<named-day> <day-of-month> (ordinal)"
+  , pattern =
+    [ Predicate $ liftM2 (||) isAMonth isADayOfWeek
+    , Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleMonthDOMNumeral :: Rule
+ruleMonthDOMNumeral = Rule
+  { name = "<named-month> <day-of-month> (non ordinal)"
+  , pattern =
+    [ Predicate isAMonth
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleDOMOfMonth :: Rule
+ruleDOMOfMonth = Rule
+  { name = "<day-of-month> (ordinal or number) of <named-month>"
+  , pattern =
+    [ Predicate isDOMValue
+    , regex "of|in"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleDOMMonth :: Rule
+ruleDOMMonth = Rule
+  { name = "<day-of-month> (ordinal or number) <named-month>"
+  , pattern =
+    [ Predicate isDOMValue
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleDOMOrdinalMonthYear :: Rule
+ruleDOMOrdinalMonthYear = Rule
+  { name = "<day-of-month>(ordinal) <named-month> year"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    , Predicate isAMonth
+    , regex "(\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:Token RegexMatch (GroupMatch (match:_)):_) -> do
+        intVal <- parseInt match
+        dom <- intersectDOM td token
+        Token Time <$> intersect dom (year intVal)
+      _ -> Nothing
+  }
+
+ruleIdesOfMonth :: Rule
+ruleIdesOfMonth = Rule
+  { name = "the ides of <named-month>"
+  , pattern =
+    [ regex "the ides? of"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td@TimeData {TTime.form = Just (TTime.Month m)}:_) ->
+        Token Time <$>
+          intersect td (dayOfMonth $ if elem m [3, 5, 7, 10] then 15 else 13)
+      _ -> Nothing
+  }
+
+ruleTODLatent :: Rule
+ruleTODLatent = Rule
+  { name = "time-of-day (latent)"
+  , pattern = [Predicate $ isIntegerBetween 0 23]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ hour True n
+      _ -> Nothing
+  }
+
+ruleAtTOD :: Rule
+ruleAtTOD = Rule
+  { name = "at <time-of-day>"
+  , pattern =
+    [ regex "at|@"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleTODOClock :: Rule
+ruleTODOClock = Rule
+  { name = "<time-of-day> o'clock"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "o.?clock"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleHHMM :: Rule
+ruleHHMM = Rule
+  { name = "hh:mm"
+  , pattern = [regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt $ hourMinute True h m
+      _ -> Nothing
+  }
+
+ruleHHMMLatent :: Rule
+ruleHHMMLatent = Rule
+  { name = "hhmm (latent)"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt . mkLatent $ hourMinute True h m
+      _ -> Nothing
+  }
+
+ruleHHMMSS :: Rule
+ruleHHMMSS = Rule
+  { name = "hh:mm:ss"
+  , pattern = [regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)[:.]([0-5]\\d)"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:ss:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        s <- parseInt ss
+        tt $ hourMinuteSecond True h m s
+      _ -> Nothing
+  }
+
+ruleMilitaryAMPM :: Rule
+ruleMilitaryAMPM = Rule
+  { name = "hhmm (military) am|pm"
+  , pattern =
+    [ regex "((?:1[012]|0?\\d))([0-5]\\d)"
+    , regex "([ap])\\.?m?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):Token RegexMatch (GroupMatch (ap:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt . timeOfDayAMPM (hourMinute True h m) $
+          Text.toLower ap == "a"
+      _ -> Nothing
+  }
+
+ruleTODAMPM :: Rule
+ruleTODAMPM = Rule
+  { name = "<time-of-day> am|pm"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(in the )?([ap])(\\s|\\.)?m?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:Token RegexMatch (GroupMatch (_:ap:_)):_) ->
+        tt . timeOfDayAMPM td $ Text.toLower ap == "a"
+      _ -> Nothing
+  }
+
+ruleHONumeral :: Rule
+ruleHONumeral = Rule
+  { name = "<hour-of-day> <integer>"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isNotLatent isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleHODHalf :: Rule
+ruleHODHalf = Rule
+  { name = "<hour-of-day> half"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "half"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
+        tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+
+ruleHODQuarter :: Rule
+ruleHODQuarter = Rule
+  { name = "<hour-of-day> quarter"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "(a|one)? ?quarter"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
+        tt $ hourMinute is12H hours 15
+      _ -> Nothing
+  }
+
+ruleNumeralToHOD :: Rule
+ruleNumeralToHOD = Rule
+  { name = "<integer> to|till|before <hour-of-day>"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "to|till|before|of"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleHalfToHOD :: Rule
+ruleHalfToHOD = Rule
+  { name = "half to|till|before <hour-of-day>"
+  , pattern =
+    [ regex "half (to|till|before|of)"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesBefore 30 td
+      _ -> Nothing
+  }
+
+ruleQuarterToHOD :: Rule
+ruleQuarterToHOD = Rule
+  { name = "quarter to|till|before <hour-of-day>"
+  , pattern =
+    [ regex "(a|one)? ?quarter (to|till|before|of)"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesBefore 15 td
+      _ -> Nothing
+  }
+
+ruleNumeralAfterHOD :: Rule
+ruleNumeralAfterHOD = Rule
+  { name = "integer after|past <hour-of-day>"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "after|past"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        t <- minutesAfter n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleHalfAfterHOD :: Rule
+ruleHalfAfterHOD = Rule
+  { name = "half after|past <hour-of-day>"
+  , pattern =
+    [ regex "half (after|past)"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesAfter 30 td
+      _ -> Nothing
+  }
+
+ruleQuarterAfterHOD :: Rule
+ruleQuarterAfterHOD = Rule
+  { name = "quarter after|past <hour-of-day>"
+  , pattern =
+    [ regex "(a|one)? ?quarter (after|past)"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesAfter 15 td
+      _ -> Nothing
+  }
+
+ruleHalfHOD :: Rule
+ruleHalfHOD = Rule
+  { name = "half <integer> (UK style hour-of-day)"
+  , pattern =
+    [ regex "half"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesAfter 30 td
+      _ -> Nothing
+  }
+
+ruleMMDDYYYY :: Rule
+ruleMMDDYYYY = Rule
+  { name = "mm/dd/yyyy"
+  , pattern =
+    [regex "(0?[1-9]|1[0-2])[/-](3[01]|[12]\\d|0?[1-9])[-/](\\d{2,4})"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (mm:dd:yy:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleYYYYMMDD :: Rule
+ruleYYYYMMDD = Rule
+  { name = "yyyy-mm-dd"
+  , pattern = [regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (yy:mm:dd:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleMMDD :: Rule
+ruleMMDD = Rule
+  { name = "mm/dd"
+  , pattern = [regex "(0?[1-9]|1[0-2])/(3[01]|[12]\\d|0?[1-9])"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (mm:dd:_)):_) -> do
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleNoonMidnightEOD :: Rule
+ruleNoonMidnightEOD = Rule
+  { name = "noon|midnight|EOD|end of day"
+  , pattern = [regex "(noon|midni(ght|te)|(the )?(EOD|end of (the )?day))"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> tt . hour False $
+        if match == "noon" then 12 else 0
+      _ -> Nothing
+  }
+
+rulePartOfDays :: Rule
+rulePartOfDays = Rule
+  { name = "part of days"
+  , pattern = [regex "(morning|after ?noo?n|evening|night|(at )?lunch)"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) ->
+        let td = interval TTime.Open $ case Text.toLower match of
+              "morning"  -> (hour False 4, hour False 12)
+              "evening"  -> (hour False 18, hour False 0)
+              "night"    -> (hour False 18, hour False 0)
+              "lunch"    -> (hour False 12, hour False 14)
+              "at lunch" -> (hour False 12, hour False 14)
+              _          -> (hour False 12, hour False 19)
+          in tt . partOfDay $ mkLatent td
+      _ -> Nothing
+  }
+
+ruleEarlyMorning :: Rule
+ruleEarlyMorning = Rule
+  { name = "early morning"
+  , pattern =
+    [ regex "early ((in|hours of) the )?morning"
+    ]
+  , prod = \_ -> tt . partOfDay . mkLatent $
+      interval TTime.Open (hour False 4, hour False 9)
+  }
+
+rulePODIn :: Rule
+rulePODIn = Rule
+  { name = "in|during the <part-of-day>"
+  , pattern =
+    [ regex "(in|during)( the)?"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+rulePODThis :: Rule
+rulePODThis = Rule
+  { name = "this <part-of-day>"
+  , pattern =
+    [ regex "this"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time . partOfDay . notLatent <$>
+        intersect (cycleNth TG.Day 0) td
+      _ -> Nothing
+  }
+
+ruleTonight :: Rule
+ruleTonight = Rule
+  { name = "tonight"
+  , pattern = [regex "toni(ght|gth|te)"]
+  , prod = \_ ->
+      let today = cycleNth TG.Day 0
+          evening = interval TTime.Open (hour False 18, hour False 0) in
+        Token Time . partOfDay . notLatent <$> intersect today evening
+  }
+
+ruleAfterPartofday :: Rule
+ruleAfterPartofday = Rule
+  { name = "after lunch/work/school"
+  , pattern =
+    [ regex "after[\\s-]?(lunch|work|school)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        pair <- 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
+        Token Time . partOfDay . notLatent <$>
+          intersect (cycleNth TG.Day 0) (interval TTime.Open pair)
+      _ -> Nothing
+  }
+
+-- Since part of days are latent, general time intersection is blocked
+ruleTimePOD :: Rule
+ruleTimePOD = Rule
+  { name = "<time> <part-of-day>"
+  , pattern =
+    [ dimension Time
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:Token Time pod:_) -> Token Time <$> intersect pod td
+      _ -> Nothing
+  }
+
+rulePODofTime :: Rule
+rulePODofTime = Rule
+  { name = "<part-of-day> of <time>"
+  , pattern =
+    [ Predicate isAPartOfDay
+    , regex "of"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time pod:_:Token Time td:_) -> Token Time <$> intersect pod td
+      _ -> Nothing
+  }
+
+ruleWeekend :: Rule
+ruleWeekend = Rule
+  { name = "week-end"
+  , pattern = [regex "(week(\\s|-)?end|wkend)"]
+  , prod = \_ -> do
+      fri <- intersect (dayOfWeek 5) (hour False 18)
+      mon <- intersect (dayOfWeek 1) (hour False 0)
+      tt $ interval TTime.Open (fri, mon)
+  }
+
+ruleSeasons :: Rule
+ruleSeasons = Rule
+  { name = "seasons"
+  , pattern = [regex "(summer|fall|autumn|winter|spring)"]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> do
+        start <- case Text.toLower match of
+          "summer" -> Just $ monthDay 6 21
+          "fall"   -> Just $ monthDay 9 23
+          "autumn" -> Just $ monthDay 9 23
+          "winter" -> Just $ monthDay 12 21
+          "spring" -> Just $ monthDay 3 20
+          _ -> Nothing
+        end <- case Text.toLower match of
+          "summer" -> Just $ monthDay 9 23
+          "fall"   -> Just $ monthDay 12 21
+          "autumn" -> Just $ monthDay 12 21
+          "winter" -> Just $ monthDay 3 20
+          "spring" -> Just $ monthDay 6 21
+          _ -> Nothing
+        tt $ interval TTime.Open (start, end)
+      _ -> Nothing
+
+  }
+
+ruleTODPrecision :: Rule
+ruleTODPrecision = Rule
+  { name = "<time-of-day> sharp|exactly"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(sharp|exactly|-?ish|approximately)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+rulePrecisionTOD :: Rule
+rulePrecisionTOD = Rule
+  { name = "about|exactly <time-of-day>"
+  , pattern =
+    [ regex "(about|around|approximately|exactly)"
+    , Predicate $ isGrainFinerThan TG.Year
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleIntervalMonthDDDD :: Rule
+ruleIntervalMonthDDDD = Rule
+  { name = "<month> dd-dd (interval)"
+  , pattern =
+    [ Predicate isAMonth
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    , regex "\\-|to|th?ru|through|(un)?til(l)?"
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      ( Token Time td
+       :Token RegexMatch (GroupMatch (d1:_))
+       :_
+       :Token RegexMatch (GroupMatch (d2:_))
+       :_) -> do
+        dd1 <- parseInt d1
+        dd2 <- parseInt d2
+        dom1 <- intersect (dayOfMonth dd1) td
+        dom2 <- intersect (dayOfMonth dd2) td
+        tt $ interval TTime.Closed (dom1, dom2)
+      _ -> Nothing
+  }
+
+-- Blocked for :latent time. May need to accept certain latents only, like hours
+ruleIntervalDash :: Rule
+ruleIntervalDash = Rule
+  { name = "<datetime> - <datetime> (interval)"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "\\-|to|th?ru|through|(un)?til(l)?"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ 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:_) ->
+        tt $ 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:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+-- Specific for time-of-day, to help resolve ambiguities
+ruleIntervalTODDash :: Rule
+ruleIntervalTODDash = Rule
+  { name = "<time-of-day> - <time-of-day> (interval)"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isNotLatent isATimeOfDay
+    , regex "\\-|:|to|th?ru|through|(un)?til(l)?"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ 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:_) ->
+        tt $ 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
+       tt $
+         interval TTime.Closed (timeOfDayAMPM td1 ampm, timeOfDayAMPM td2 ampm)
+     _ -> Nothing
+ }
+
+ruleIntervalTODBetween :: Rule
+ruleIntervalTODBetween = Rule
+  { name = "between <time-of-day> and <time-of-day> (interval)"
+  , pattern =
+    [ regex "between"
+    , Predicate isATimeOfDay
+    , regex "and"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleIntervalBy :: Rule
+ruleIntervalBy = Rule
+  { name = "by <time>"
+  , pattern =
+    [ regex "by"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ interval TTime.Open (cycleNth TG.Second 0, td)
+      _ -> Nothing
+  }
+
+ruleIntervalByTheEndOf :: Rule
+ruleIntervalByTheEndOf = Rule
+  { name = "by the end of <time>"
+  , pattern =
+    [ regex "by (the )?end of"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ interval TTime.Closed (cycleNth TG.Second 0, td)
+      _ -> Nothing
+  }
+
+ruleIntervalUntilTOD :: Rule
+ruleIntervalUntilTOD = Rule
+  { name = "until <time-of-day>"
+  , pattern =
+    [ regex "(anytime |sometimes? )?(before|(un)?til(l)?|through|up to)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ withDirection TTime.Before td
+      _ -> Nothing
+  }
+
+ruleIntervalAfterTOD :: Rule
+ruleIntervalAfterTOD = Rule
+  { name = "after <time-of-day>"
+  , pattern =
+    [ regex "(anytime |sometimes? )?after"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ withDirection TTime.After td
+      _ -> Nothing
+  }
+
+ruleIntervalSinceTOD :: Rule
+ruleIntervalSinceTOD = Rule
+  { name = "since <time-of-day>"
+  , pattern =
+    [ regex "since"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ withDirection TTime.After td
+      _ -> Nothing
+  }
+
+daysOfWeek :: [(Text, String)]
+daysOfWeek =
+  [ ( "Monday"   , "monday|mon\\.?"         )
+  , ( "Tuesday"  , "tuesday|tues?\\.?"      )
+  , ( "Wednesday", "wed?nesday|wed\\.?"     )
+  , ( "Thursday" , "thursday|thu(rs?)?\\.?" )
+  , ( "Friday"   , "friday|fri\\.?"         )
+  , ( "Saturday" , "saturday|sat\\.?"       )
+  , ( "Sunday"   , "sunday|sun\\.?"         )
+  ]
+
+ruleDaysOfWeek :: [Rule]
+ruleDaysOfWeek = zipWith go daysOfWeek [1..7]
+  where
+    go (name, regexPattern) i = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> tt $ dayOfWeek i
+      }
+
+months :: [(Text, String)]
+months =
+  [ ( "January"  , "january|jan\\.?"     )
+  , ( "February" , "february|feb\\.?"    )
+  , ( "March"    , "march|mar\\.?"       )
+  , ( "April"    , "april|apr\\.?"       )
+  , ( "May"      , "may"                 )
+  , ( "June"     , "june|jun\\.?"        )
+  , ( "July"     , "july|jul\\.?"        )
+  , ( "August"   , "august|aug\\.?"      )
+  , ( "September", "september|sept?\\.?" )
+  , ( "October"  , "october|oct\\.?"     )
+  , ( "November" , "november|nov\\.?"    )
+  , ( "December" , "december|dec\\.?"    )
+  ]
+
+ruleMonths :: [Rule]
+ruleMonths = zipWith go months [1..12]
+  where
+    go (name, regexPattern) i = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> tt $ month i
+      }
+
+usHolidays :: [(Text, String, Int, Int)]
+usHolidays =
+  [ ( "Christmas"       , "(xmas|christmas)( day)?"         , 12, 25 )
+  , ( "Christmas Eve"   , "(xmas|christmas)( day)?('s)? eve", 12, 24 )
+  , ( "New Year's Eve"  , "new year'?s? eve"                , 12, 31 )
+  , ( "New Year's Day"  , "new year'?s?( day)?"             , 1 , 1  )
+  , ( "Valentine's Day" , "valentine'?s?( day)?"            , 2 , 14 )
+  , ( "Independence Day", "independence day"                , 7 , 4  )
+  , ( "Halloween"       , "hall?owe?en( day)?"              , 10, 31 )
+  ]
+
+ruleUSHolidays :: [Rule]
+ruleUSHolidays = map go usHolidays
+  where
+    go (name, regexPattern, m, d) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> tt $ monthDay m d
+      }
+
+moreUSHolidays :: [(Text, String, Int, Int, Int)]
+moreUSHolidays =
+  [ ( "Martin Luther King's Day" -- Third Monday of January
+    , "(MLK|Martin Luther King,?)( Jr.?| Junior)? day"
+    , 3, 1, 1
+    )
+  , ( "Father's Day" -- Third Sunday of June
+    , "father'?s?'? day"
+    , 2, 7, 6
+    )
+  , ( "Mother's Day" -- Second Sunday of May
+    , "mother'?s?'? day"
+    , 1, 7, 5
+    )
+  , ( "Thanksgiving Day" -- Fourth Thursday of November
+    , "thanks?giving( day)?"
+    , 4, 4, 11
+    )
+  , ( "Black Friday" -- Fourth Friday of November
+    , "black frid?day"
+    , 4, 5, 11
+    )
+  ,  ( "Labor Day" -- First Monday of September
+     , "labor day"
+     , 1, 1, 9
+     )
+  ]
+
+ruleMoreUSHolidays :: [Rule]
+ruleMoreUSHolidays = map go moreUSHolidays
+  where
+    go (name, regexPattern, n, dow, m) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> tt $ nthDOWOfMonth n dow m
+      }
+
+-- Last Monday of May
+ruleMemorialDay :: Rule
+ruleMemorialDay = Rule
+  { name = "Memorial Day"
+  , pattern = [regex "memorial day"]
+  , prod = \_ -> tt $ predLastOf (dayOfWeek 1) (month 5)
+  }
+
+-- Long weekend before the last Monday of May
+ruleMemorialDayWeekend :: Rule
+ruleMemorialDayWeekend = Rule
+  { name = "Memorial Day Weekend"
+  , pattern = [regex "memorial day week(\\s|-)?end"]
+  , prod = \_ ->
+      tt . longWEBefore $ predLastOf (dayOfWeek 1) (month 5)
+  }
+
+-- Long weekend before the first Monday of September
+ruleLaborDayWeekend :: Rule
+ruleLaborDayWeekend = Rule
+  { name = "Labor Day weekend"
+  , pattern = [regex "labor day week(\\s|-)?end"]
+  , prod = \_ -> tt . longWEBefore $ nthDOWOfMonth 1 1 9
+  }
+
+ruleCycleThisLastNext :: Rule
+ruleCycleThisLastNext = Rule
+  { name = "this|last|next <cycle>"
+  , pattern =
+    [ regex "(this|current|coming|next|the following|last|past|previous)"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):Token TimeGrain grain:_) ->
+        case Text.toLower match of
+          "this"          -> tt $ cycleNth grain 0
+          "coming"        -> tt $ cycleNth grain 0
+          "current"       -> tt $ cycleNth grain 0
+          "last"          -> tt . cycleNth grain $ - 1
+          "past"          -> tt . cycleNth grain $ - 1
+          "previous"      -> tt . cycleNth grain $ - 1
+          "next"          -> tt $ cycleNth grain 1
+          "the following" -> tt $ cycleNth grain 1
+          _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleCycleTheAfterBeforeTime :: Rule
+ruleCycleTheAfterBeforeTime = Rule
+  { name = "the <cycle> after|before <time>"
+  , pattern =
+    [ regex "the"
+    , dimension TimeGrain
+    , regex "(after|before)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (  _
+       : Token TimeGrain grain
+       : Token RegexMatch (GroupMatch (match:_))
+       : Token Time td
+       : _) ->
+        let n = if match == "after" then 1 else - 1 in
+          tt $ cycleNthAfter False grain n td
+      _ -> Nothing
+  }
+
+ruleCycleAfterBeforeTime :: Rule
+ruleCycleAfterBeforeTime = Rule
+  { name = "<cycle> after|before <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "(after|before)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:Token RegexMatch (GroupMatch (match:_)):Token Time td:_) ->
+        let n = if match == "after" then 1 else - 1 in
+          tt $ cycleNthAfter False grain n td
+      _ -> Nothing
+  }
+
+ruleCycleLastNextN :: Rule
+ruleCycleLastNextN = Rule
+  { name = "last|next n <cycle>"
+  , pattern =
+    [ regex "((last|past)|(next))"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):token:Token TimeGrain grain:_) -> do
+        n <- getIntValue token
+        tt . cycleN True grain $ if match == "next" then n else - n
+      _ -> Nothing
+  }
+
+ruleCycleOrdinalOfTime :: Rule
+ruleCycleOrdinalOfTime = Rule
+  { name = "<ordinal> <cycle> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "of|in|from"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token TimeGrain grain:_:Token Time td:_) -> do
+        n <- getIntValue token
+        tt $ cycleNthAfter True grain (n - 1) td
+      _ -> Nothing
+  }
+
+ruleCycleTheOrdinalOfTime :: Rule
+ruleCycleTheOrdinalOfTime = Rule
+  { name = "the <ordinal> <cycle> of <time>"
+  , pattern =
+    [ regex "the"
+    , dimension Ordinal
+    , dimension TimeGrain
+    , regex "of|in|from"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_:Token Time td:_) -> do
+        n <- getIntValue token
+        tt $ cycleNthAfter True grain (n - 1) td
+      _ -> Nothing
+  }
+
+ruleCycleTheOfTime :: Rule
+ruleCycleTheOfTime = Rule
+  { name = "the <cycle> of <time>"
+  , pattern =
+    [ regex "the"
+    , dimension TimeGrain
+    , regex "of"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain 0 td
+      _ -> Nothing
+  }
+
+ruleCycleOrdinalAfterTime :: Rule
+ruleCycleOrdinalAfterTime = Rule
+  { name = "<ordinal> <cycle> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "after"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token TimeGrain grain:_:Token Time td:_) -> do
+        n <- getIntValue token
+        tt $ cycleNthAfter True grain (n - 1) td
+      _ -> Nothing
+  }
+
+ruleCycleTheOrdinalAfterTime :: Rule
+ruleCycleTheOrdinalAfterTime = Rule
+  { name = "<ordinal> <cycle> after <time>"
+  , pattern =
+    [ regex "the"
+    , dimension Ordinal
+    , dimension TimeGrain
+    , regex "after"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_:Token Time td:_) -> do
+        n <- getIntValue token
+        tt $ cycleNthAfter True grain (n - 1) td
+      _ -> Nothing
+  }
+
+ruleCycleOrdinalQuarter :: Rule
+ruleCycleOrdinalQuarter = Rule
+  { name = "<ordinal> quarter"
+  , pattern =
+    [ dimension Ordinal
+    , Predicate $ isGrain TG.Quarter
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . cycleNthAfter True TG.Quarter (n - 1) $
+          cycleNth TG.Year 0
+      _ -> Nothing
+  }
+
+ruleCycleTheOrdinalQuarter :: Rule
+ruleCycleTheOrdinalQuarter = Rule
+  { name = "the <ordinal> quarter"
+  , pattern =
+    [ regex "the"
+    , dimension Ordinal
+    , Predicate $ isGrain TG.Quarter
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        n <- getIntValue token
+        tt . cycleNthAfter True TG.Quarter (n - 1) $
+          cycleNth TG.Year 0
+      _ -> Nothing
+  }
+
+ruleCycleOrdinalQuarterYear :: Rule
+ruleCycleOrdinalQuarterYear = Rule
+  { name = "<ordinal> quarter <year>"
+  , pattern =
+    [ dimension Ordinal
+    , Predicate $ isGrain TG.Quarter
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        tt $ cycleNthAfter False TG.Quarter (n - 1) td
+      _ -> Nothing
+  }
+
+ruleDurationInWithinAfter :: Rule
+ruleDurationInWithinAfter = Rule
+  { name = "in|within|after <duration>"
+  , pattern =
+    [ regex "(in|within|after)"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       Token Duration dd:
+       _) -> case Text.toLower match of
+         "within" -> tt $
+           interval TTime.Open (cycleNth TG.Second 0, inDuration dd)
+         "after" -> tt . withDirection TTime.After $ inDuration dd
+         "in" -> tt $ inDuration dd
+         _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleDurationHenceAgo :: Rule
+ruleDurationHenceAgo = Rule
+  { name = "<duration> hence|ago"
+  , pattern =
+    [ dimension Duration
+    , regex "(hence|ago)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> case Text.toLower match of
+        "ago" -> tt $ durationAgo dd
+        _ -> tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleInNumeral :: Rule
+ruleInNumeral = Rule
+  { name = "in <number> (implicit minutes)"
+  , pattern =
+    [ regex "in"
+    , Predicate $ isIntegerBetween 0 60
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        tt . inDuration . duration TG.Minute $ floor v
+      _ -> Nothing
+  }
+
+ruleDurationAfterBeforeTime :: Rule
+ruleDurationAfterBeforeTime = Rule
+  { name = "<duration> after|before|from <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "(after|before|from)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:
+       Token RegexMatch (GroupMatch (match:_)):
+       Token Time td:
+       _) -> case Text.toLower match of
+         "before" -> tt $ durationBefore dd td
+         _        -> tt $ durationAfter dd td
+      _ -> Nothing
+  }
+
+ruleTimezone :: Rule
+ruleTimezone = Rule
+  { name = "<time> timezone"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\b(YEKT|YEKST|YAKT|YAKST|WITA|WIT|WIB|WGT|WGST|WFT|WET|WEST|WAT|WAST|VUT|VLAT|VLAST|VET|UZT|UYT|UYST|UTC|ULAT|TVT|TMT|TLT|TKT|TJT|TFT|TAHT|SST|SRT|SGT|SCT|SBT|SAST|SAMT|RET|PYT|PYST|PWT|PST|PONT|PMST|PMDT|PKT|PHT|PHOT|PGT|PETT|PETST|PET|PDT|OMST|OMSST|NZST|NZDT|NUT|NST|NPT|NOVT|NOVST|NFT|NDT|NCT|MYT|MVT|MUT|MST|MSK|MSD|MMT|MHT|MDT|MAWT|MART|MAGT|MAGST|LINT|LHST|LHDT|KUYT|KST|KRAT|KRAST|KGT|JST|IST|IRST|IRKT|IRKST|IRDT|IOT|IDT|ICT|HOVT|HKT|GYT|GST|GMT|GILT|GFT|GET|GAMT|GALT|FNT|FKT|FKST|FJT|FJST|EST|EGT|EGST|EET|EEST|EDT|ECT|EAT|EAST|EASST|DAVT|ChST|CXT|CVT|CST|COT|CLT|CLST|CKT|CHAST|CHADT|CET|CEST|CDT|CCT|CAT|CAST|BTT|BST|BRT|BRST|BOT|BNT|AZT|AZST|AZOT|AZOST|AWST|AWDT|AST|ART|AQTT|ANAT|ANAST|AMT|AMST|ALMT|AKST|AKDT|AFT|AEST|AEDT|ADT|ACST|ACDT)\\b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       Token RegexMatch (GroupMatch (tz:_)):
+       _) -> Token Time <$> inTimezone tz td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleIntersect
+  , ruleIntersectOf
+  , ruleAbsorbOnTime
+  , ruleAbsorbOnADOW
+  , ruleAbsorbInMonth
+  , ruleAbsorbCommaTOD
+  , ruleNextDOW
+  , ruleThisTime
+  , ruleNextTime
+  , ruleLastTime
+  , ruleTimeBeforeLastAfterNext
+  , ruleLastDOWOfTime
+  , ruleLastCycleOfTime
+  , ruleNthTimeOfTime
+  , ruleTheNthTimeOfTime
+  , ruleNthTimeAfterTime
+  , ruleTheNthTimeAfterTime
+  , ruleYear
+  , ruleYearPastLatent
+  , ruleYearFutureLatent
+  , ruleTheDOMNumeral
+  , ruleTheDOMOrdinal
+  , ruleDOMLatent
+  , ruleNamedDOMOrdinal
+  , ruleMonthDOMNumeral
+  , ruleDOMMonth
+  , ruleDOMOfMonth
+  , ruleDOMOrdinalMonthYear
+  , ruleIdesOfMonth
+  , ruleTODLatent
+  , ruleAtTOD
+  , ruleTODOClock
+  , ruleHHMM
+  , ruleHHMMLatent
+  , ruleHHMMSS
+  , ruleMilitaryAMPM
+  , ruleTODAMPM
+  , ruleHONumeral
+  , ruleHODHalf
+  , ruleHODQuarter
+  , ruleNumeralToHOD
+  , ruleHalfToHOD
+  , ruleQuarterToHOD
+  , ruleNumeralAfterHOD
+  , ruleHalfAfterHOD
+  , ruleQuarterAfterHOD
+  , ruleHalfHOD
+  , ruleMMDDYYYY
+  , ruleYYYYMMDD
+  , ruleMMDD
+  , ruleNoonMidnightEOD
+  , rulePartOfDays
+  , ruleEarlyMorning
+  , rulePODIn
+  , rulePODThis
+  , ruleTonight
+  , ruleAfterPartofday
+  , ruleTimePOD
+  , rulePODofTime
+  , ruleWeekend
+  , ruleSeasons
+  , ruleTODPrecision
+  , rulePrecisionTOD
+  , ruleIntervalMonthDDDD
+  , ruleIntervalDash
+  , ruleIntervalFrom
+  , ruleIntervalBetween
+  , ruleIntervalTODDash
+  , ruleIntervalTODFrom
+  , ruleIntervalTODAMPM
+  , ruleIntervalTODBetween
+  , ruleIntervalBy
+  , ruleIntervalByTheEndOf
+  , ruleIntervalUntilTOD
+  , ruleIntervalAfterTOD
+  , ruleIntervalSinceTOD
+  , ruleMemorialDay
+  , ruleMemorialDayWeekend
+  , ruleLaborDayWeekend
+  , ruleCycleThisLastNext
+  , ruleCycleTheAfterBeforeTime
+  , ruleCycleAfterBeforeTime
+  , ruleCycleLastNextN
+  , ruleCycleOrdinalOfTime
+  , ruleCycleTheOrdinalOfTime
+  , ruleCycleTheOfTime
+  , ruleCycleOrdinalAfterTime
+  , ruleCycleTheOrdinalAfterTime
+  , ruleCycleOrdinalQuarter
+  , ruleCycleTheOrdinalQuarter
+  , ruleCycleOrdinalQuarterYear
+  , ruleDurationInWithinAfter
+  , ruleDurationHenceAgo
+  , ruleDurationAfterBeforeTime
+  , ruleInNumeral
+  , ruleTimezone
+  ]
+  ++ ruleInstants
+  ++ ruleDaysOfWeek
+  ++ ruleMonths
+  ++ ruleUSHolidays
+  ++ ruleMoreUSHolidays
diff --git a/Duckling/Time/ES/Corpus.hs b/Duckling/Time/ES/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/ES/Corpus.hs
@@ -0,0 +1,416 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.ES.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Time.Corpus
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Testing.Types hiding (examples)
+
+corpus :: Corpus
+corpus = (testContext {lang = ES}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (datetime (2013, 2, 12, 4, 30, 0) Second)
+             [ "ahora"
+             , "ya"
+             , "ahorita"
+             , "cuanto antes"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "hoy"
+             , "en este momento"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "ayer"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "anteayer"
+             , "antier"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "mañana"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "pasado mañana"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "lunes"
+             , "lu"
+             , "lun."
+             , "este lunes"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "lunes, 18 de febrero"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "martes"
+             , "ma"
+             , "ma."
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "miercoles"
+             , "miércoles"
+             , "mx"
+             , "mié."
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "jueves"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "viernes"
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "sabado"
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "domingo"
+             ]
+  , examples (datetime (2013, 5, 5, 0, 0, 0) Day)
+             [ "el 5 de mayo"
+             , "el cinco de mayo"
+             ]
+  , examples (datetime (2013, 5, 5, 0, 0, 0) Day)
+             [ "el cinco de mayo de 2013"
+             , "mayo 5 del 2013"
+             , "5-5-2013"
+             ]
+  , examples (datetime (2013, 7, 4, 0, 0, 0) Day)
+             [ "el 4 de julio"
+             , "el 4/7"
+             ]
+  , examples (datetime (2013, 3, 3, 0, 0, 0) Day)
+             [ "el 3 de marzo"
+             , "3 de marzo"
+             , "el 3-3"
+             ]
+  , examples (datetime (2013, 4, 5, 0, 0, 0) Day)
+             [ "el 5 de abril"
+             , "5 de abril"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "el 1 de marzo"
+             , "1 de marzo"
+             , "el primero de marzo"
+             , "el uno de marzo"
+             , "primero de marzo"
+             , "uno de marzo"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "1-3-2013"
+             , "1.3.2013"
+             , "1/3/2013"
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "el 16"
+             , "16 de febrero"
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "el 17"
+             , "17 de febrero"
+             , "17-2"
+             , "el 17/2"
+             ]
+  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
+             [ "el 20"
+             , "20 de febrero"
+             , "20/2"
+             ]
+  , examples (datetime (1974, 10, 31, 0, 0, 0) Day)
+             [ "31/10/1974"
+             , "31/10/74"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "el martes que viene"
+             ]
+  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
+             [ "miércoles que viene"
+             , "el miércoles de la semana que viene"
+             , "miercoles de la próxima semana"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "el lunes de esta semana"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "martes de esta semana"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "el miércoles de esta semana"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Week)
+             [ "esta semana"
+             ]
+  , examples (datetime (2013, 2, 4, 0, 0, 0) Week)
+             [ "la semana pasada"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Week)
+             [ "la semana que viene"
+             , "la proxima semana"
+             ]
+  , examples (datetime (2013, 1, 1, 0, 0, 0) Month)
+             [ "el pasado mes"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "el mes que viene"
+             , "el proximo mes"
+             ]
+  , examples (datetime (2012, 0, 0, 0, 0, 0) Year)
+             [ "el año pasado"
+             ]
+  , examples (datetime (2013, 0, 0, 0, 0, 0) Year)
+             [ "este ano"
+             ]
+  , examples (datetime (2014, 0, 0, 0, 0, 0) Year)
+             [ "el año que viene"
+             , "el proximo ano"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "el domingo pasado"
+             , "el domingo de la semana pasada"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "el martes pasado"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "a las tres de la tarde"
+             , "a las tres"
+             , "a las 3 pm"
+             , "a las 15 horas"
+             ]
+  , examples (datetime (2013, 2, 12, 20, 0, 0) Hour)
+             [ "a las ocho de la tarde"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Minute)
+             [ "15:00"
+             , "15.00"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Hour)
+             [ "medianoche"
+             ]
+  , examples (datetime (2013, 2, 12, 12, 0, 0) Hour)
+             [ "mediodía"
+             , "las doce"
+             ]
+  , examples (datetime (2013, 2, 12, 12, 15, 0) Minute)
+             [ "las doce y cuarto"
+             ]
+  , examples (datetime (2013, 2, 12, 11, 55, 0) Minute)
+             [ "las doce menos cinco"
+             ]
+  , examples (datetime (2013, 2, 12, 12, 30, 0) Minute)
+             [ "las doce y media"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 0, 0) Hour)
+             [ "las tres de la manana"
+             , "las tres en la manana"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 15, 0) Minute)
+             [ "a las tres y quince"
+             , "a las 3 y cuarto"
+             , "a las tres y cuarto de la tarde"
+             , "a las tres y cuarto en la tarde"
+             , "15:15"
+             , "15.15"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 30, 0) Minute)
+             [ "a las tres y media"
+             , "a las 3 y treinta"
+             , "a las tres y media de la tarde"
+             , "a las 3 y treinta del mediodía"
+             , "15:30"
+             , "15.30"
+             ]
+  , examples (datetime (2013, 2, 12, 11, 45, 0) Minute)
+             [ "las doce menos cuarto"
+             , "11:45"
+             , "las once y cuarenta y cinco"
+             , "hoy a las doce menos cuarto"
+             , "hoy a las once y cuarenta y cinco"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 15, 0) Minute)
+             [ "5 y cuarto"
+             ]
+  , examples (datetime (2013, 2, 12, 6, 0, 0) Hour)
+             [ "6 de la mañana"
+             ]
+  , examples (datetime (2013, 2, 13, 11, 0, 0) Hour)
+             [ "miércoles a las once de la mañana"
+             ]
+  , examples (datetime (2013, 2, 13, 11, 0, 0) Hour)
+             [ "mañana a las once"
+             , "mañana a 11"
+             ]
+  , examples (datetime (2014, 9, 12, 0, 0, 0) Day)
+             [ "viernes, el 12 de septiembre de 2014"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 30, 1) Second)
+             [ "en un segundo"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 31, 0) Second)
+             [ "en un minuto"
+             , "en 1 min"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 32, 0) Second)
+             [ "en 2 minutos"
+             , "en dos minutos"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Second)
+             [ "en 60 minutos"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Minute)
+             [ "en una hora"
+             ]
+  , examples (datetime (2013, 2, 12, 2, 30, 0) Minute)
+             [ "hace dos horas"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 30, 0) Minute)
+             [ "en 24 horas"
+             , "en veinticuatro horas"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 0, 0) Hour)
+             [ "en un dia"
+             ]
+  , examples (datetime (2013, 2, 19, 4, 0, 0) Hour)
+             [ "en 7 dias"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "en una semana"
+             ]
+  , examples (datetime (2013, 1, 22, 0, 0, 0) Day)
+             [ "hace tres semanas"
+             ]
+  , examples (datetime (2013, 4, 12, 0, 0, 0) Day)
+             [ "en dos meses"
+             ]
+  , examples (datetime (2012, 11, 12, 0, 0, 0) Day)
+             [ "hace tres meses"
+             ]
+  , examples (datetime (2014, 2, 0, 0, 0, 0) Month)
+             [ "en un ano"
+             , "en 1 año"
+             ]
+  , examples (datetime (2011, 2, 0, 0, 0, 0) Month)
+             [ "hace dos años"
+             ]
+  , examples (datetimeInterval ((2013, 6, 21, 0, 0, 0), (2013, 9, 24, 0, 0, 0)) Day)
+             [ "este verano"
+             ]
+  , examples (datetimeInterval ((2012, 12, 21, 0, 0, 0), (2013, 3, 21, 0, 0, 0)) Day)
+             [ "este invierno"
+             ]
+  , examples (datetime (2013, 12, 25, 0, 0, 0) Day)
+             [ "Navidad"
+             , "la Navidad"
+             ]
+  , examples (datetime (2013, 12, 31, 0, 0, 0) Day)
+             [ "Nochevieja"
+             ]
+  , examples (datetime (2014, 1, 1, 0, 0, 0) Day)
+             [ "ano nuevo"
+             , "año nuevo"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 18, 0, 0), (2013, 2, 13, 0, 0, 0)) Hour)
+             [ "esta noche"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 18, 0, 0), (2013, 2, 14, 0, 0, 0)) Hour)
+             [ "mañana por la noche"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 18, 0, 0), (2013, 2, 12, 0, 0, 0)) Hour)
+             [ "ayer por la noche"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 18, 0, 0), (2013, 2, 18, 0, 0, 0)) Hour)
+             [ "este weekend"
+             , "este fin de semana"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 4, 0, 0), (2013, 2, 18, 12, 0, 0)) Hour)
+             [ "lunes por la mañana"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 4, 0, 0), (2013, 2, 15, 12, 0, 0)) Hour)
+             [ "el 15 de febrero por la mañana"
+             ]
+  , examples (datetime (2013, 2, 12, 20, 0, 0) Hour)
+             [ "a las 8 de la tarde"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 29, 58), (2013, 2, 12, 4, 30, 0)) Second)
+             [ "pasados 2 segundos"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 1), (2013, 2, 12, 4, 30, 4)) Second)
+             [ "proximos 3 segundos"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 28, 0), (2013, 2, 12, 4, 30, 0)) Minute)
+             [ "pasados 2 minutos"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 31, 0), (2013, 2, 12, 4, 34, 0)) Minute)
+             [ "proximos 3 minutos"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 5, 0, 0), (2013, 2, 12, 8, 0, 0)) Hour)
+             [ "proximas 3 horas"
+             ]
+  , examples (datetimeInterval ((2013, 2, 10, 0, 0, 0), (2013, 2, 12, 0, 0, 0)) Day)
+             [ "pasados 2 dias"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
+             [ "proximos 3 dias"
+             ]
+  , examples (datetimeInterval ((2013, 1, 28, 0, 0, 0), (2013, 2, 11, 0, 0, 0)) Week)
+             [ "pasadas dos semanas"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 0, 0, 0), (2013, 3, 11, 0, 0, 0)) Week)
+             [ "3 proximas semanas"
+             , "3 semanas que vienen"
+             ]
+  , examples (datetimeInterval ((2012, 12, 0, 0, 0, 0), (2013, 2, 0, 0, 0, 0)) Month)
+             [ "pasados 2 meses"
+             , "dos pasados meses"
+             ]
+  , examples (datetimeInterval ((2013, 3, 0, 0, 0, 0), (2013, 6, 0, 0, 0, 0)) Month)
+             [ "3 próximos meses"
+             , "proximos tres meses"
+             , "tres meses que vienen"
+             ]
+  , examples (datetimeInterval ((2011, 0, 0, 0, 0, 0), (2013, 0, 0, 0, 0, 0)) Year)
+             [ "pasados 2 anos"
+             , "dos pasados años"
+             ]
+  , examples (datetimeInterval ((2014, 0, 0, 0, 0, 0), (2017, 0, 0, 0, 0, 0)) Year)
+             [ "3 próximos años"
+             , "proximo tres años"
+             , "3 años que vienen"
+             ]
+  , examples (datetimeInterval ((2013, 7, 13, 0, 0, 0), (2013, 7, 16, 0, 0, 0)) Day)
+             [ "13 a 15 de julio"
+             , "13 - 15 de julio de 2013"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 9, 30, 0), (2013, 2, 12, 11, 0, 0)) Minute)
+             [ "9:30 - 11:00"
+             ]
+  , examples (datetimeInterval ((2013, 12, 21, 0, 0, 0), (2014, 1, 7, 0, 0, 0)) Day)
+             [ "21 de Dic. a 6 de Ene"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 12, 7, 30, 0)) Second)
+             [ "dentro de tres horas"
+             ]
+  , examples (datetime (2013, 2, 12, 16, 0, 0) Hour)
+             [ "a las cuatro de la tarde"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 0, 0) Minute)
+             [ "a las cuatro CET"
+             ]
+  , examples (datetime (2013, 8, 15, 0, 0, 0) Day)
+             [ "jue 15"
+             ]
+  ]
diff --git a/Duckling/Time/ES/Rules.hs b/Duckling/Time/ES/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/ES/Rules.hs
@@ -0,0 +1,1593 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.ES.Rules
+  ( rules ) where
+
+import Control.Monad (liftM2)
+import qualified Data.Text as Text
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Types (OrdinalData (..))
+import qualified Duckling.Ordinal.Types as TOrdinal
+import Duckling.Regex.Types
+import Duckling.Time.Helpers
+import Duckling.Time.Types (TimeData (..))
+import qualified Duckling.Time.Types as TTime
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleNamedday :: Rule
+ruleNamedday = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "lunes|lun?\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 1
+  }
+
+ruleTheDayAfterTomorrow :: Rule
+ruleTheDayAfterTomorrow = Rule
+  { name = "the day after tomorrow"
+  , pattern =
+    [ regex "pasado\\s?ma(n|\x00f1)ana"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 2
+  }
+
+ruleHaceDuration :: Rule
+ruleHaceDuration = Rule
+  { name = "hace <duration>"
+  , pattern =
+    [ regex "hace"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ durationAgo dd
+      _ -> Nothing
+  }
+
+ruleNamedmonth12 :: Rule
+ruleNamedmonth12 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "diciembre|dic\\.?"
+    ]
+  , prod = \_ -> tt $ month 12
+  }
+
+ruleCeTime :: Rule
+ruleCeTime = Rule
+  { name = "ce <time>"
+  , pattern =
+    [ regex "este"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleNamedday2 :: Rule
+ruleNamedday2 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "martes|mar?\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 2
+  }
+
+ruleThisDayofweek :: Rule
+ruleThisDayofweek = Rule
+  { name = "this <day-of-week>"
+  , pattern =
+    [ regex "este"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleNamedday6 :: Rule
+ruleNamedday6 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "s(\x00e1|a)bado|s(\x00e1|a)b\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 6
+  }
+
+ruleDatetimeDatetimeInterval :: Rule
+ruleDatetimeDatetimeInterval = Rule
+  { name = "<datetime> - <datetime> (interval)"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "\\-|al?"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Open (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth7 :: Rule
+ruleNamedmonth7 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "julio|jul\\.?"
+    ]
+  , prod = \_ -> tt $ month 7
+  }
+
+ruleEvening :: Rule
+ruleEvening = Rule
+  { name = "evening"
+  , pattern =
+    [ regex "noche"
+    ]
+  , prod = \_ ->
+      let from = hour False 18
+          to = hour False 0
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleDayOfMonthSt :: Rule
+ruleDayOfMonthSt = Rule
+  { name = "day of month (1st)"
+  , pattern =
+    [ regex "primero|uno|prem\\.?|1o"
+    ]
+  , prod = \_ -> tt $ dayOfMonth 1
+  }
+
+ruleEnDuration :: Rule
+ruleEnDuration = Rule
+  { name = "en <duration>"
+  , pattern =
+    [ regex "en"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleNow :: Rule
+ruleNow = Rule
+  { name = "now"
+  , pattern =
+    [ regex "(hoy)|(en este momento)"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 0
+  }
+
+ruleUltimoDayofweekDeTime :: Rule
+ruleUltimoDayofweekDeTime = Rule
+  { name = "ultimo <day-of-week> de <time>"
+  , pattern =
+    [ regex "(\x00fa|u)ltimo"
+    , Predicate isADayOfWeek
+    , regex "de|en"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ predLastOf td1 td2
+      _ -> Nothing
+  }
+
+ruleEntreDatetimeEtDatetimeInterval :: Rule
+ruleEntreDatetimeEtDatetimeInterval = Rule
+  { name = "entre <datetime> et <datetime> (interval)"
+  , pattern =
+    [ regex "entre"
+    , dimension Time
+    , regex "y"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Open (td1, td2)
+      _ -> Nothing
+  }
+
+ruleHhhmmTimeofday :: Rule
+ruleHhhmmTimeofday = Rule
+  { name = "hh(:|.|h)mm (time-of-day)"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))[:h\\.]([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        h <- parseInt m1
+        m <- parseInt m2
+        tt $ hourMinute True h m
+      _ -> Nothing
+  }
+
+ruleNamedday4 :: Rule
+ruleNamedday4 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "jueves|jue|jue\\."
+    ]
+  , prod = \_ -> tt $ dayOfWeek 4
+  }
+
+ruleElDayofmonthDeNamedmonth :: Rule
+ruleElDayofmonthDeNamedmonth = Rule
+  { name = "el <day-of-month> de <named-month>"
+  , pattern =
+    [ regex "el"
+    , Predicate isDOMInteger
+    , regex "de"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleNPasadosCycle :: Rule
+ruleNPasadosCycle = Rule
+  { name = "n pasados <cycle>"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2 9999
+    , regex "pasad(a|o)s?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain (- v)
+      _ -> Nothing
+  }
+
+ruleElProximoCycle :: Rule
+ruleElProximoCycle = Rule
+  { name = "el proximo <cycle> "
+  , pattern =
+    [ regex "(el|los|la|las) ?"
+    , regex "pr(\x00f3|o)xim(o|a)s?|siguientes?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+rulePasadosNCycle :: Rule
+rulePasadosNCycle = Rule
+  { name = "pasados n <cycle>"
+  , pattern =
+    [ regex "pasad(a|o)s?"
+    , Predicate $ isIntegerBetween 2 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain (- v)
+      _ -> Nothing
+  }
+
+ruleElDayofmonthNonOrdinal :: Rule
+ruleElDayofmonthNonOrdinal = Rule
+  { name = "el <day-of-month> (non ordinal)"
+  , pattern =
+    [ regex "el"
+    , Predicate $ isIntegerBetween 1 31
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        v <- getIntValue token
+        tt $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleSeason4 :: Rule
+ruleSeason4 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "primavera"
+    ]
+  , prod = \_ ->
+      let from = monthDay 3 20
+          to = monthDay 6 21
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleYearLatent2 :: Rule
+ruleYearLatent2 = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2101 10000
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ year v
+      _ -> Nothing
+  }
+
+ruleNoon :: Rule
+ruleNoon = Rule
+  { name = "noon"
+  , pattern =
+    [ regex "mediod(\x00ed|i)a"
+    ]
+  , prod = \_ -> tt $ hour False 12
+  }
+
+ruleProximasNCycle :: Rule
+ruleProximasNCycle = Rule
+  { name = "proximas n <cycle>"
+  , pattern =
+    [ regex "pr(\x00f3|o)xim(o|a)s?"
+    , Predicate $ isIntegerBetween 2 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleNochevieja :: Rule
+ruleNochevieja = Rule
+  { name = "Nochevieja"
+  , pattern =
+    [ regex "nochevieja"
+    ]
+  , prod = \_ -> tt $ monthDay 12 31
+  }
+
+ruleTheDayBeforeYesterday :: Rule
+ruleTheDayBeforeYesterday = Rule
+  { name = "the day before yesterday"
+  , pattern =
+    [ regex "anteayer|antes de (ayer|anoche)|antier"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 2
+  }
+
+ruleHourofdayMinusIntegerAsRelativeMinutes :: Rule
+ruleHourofdayMinusIntegerAsRelativeMinutes = Rule
+  { name = "<hour-of-day> minus <integer> (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "menos\\s?"
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_:token:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleHourofdayMinusIntegerAsRelativeMinutes2 :: Rule
+ruleHourofdayMinusIntegerAsRelativeMinutes2 = Rule
+  { name = "<hour-of-day> minus <integer> (as relative minutes) minutes"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "menos\\s?"
+    , Predicate $ isIntegerBetween 1 59
+    , regex "min\\.?(uto)?s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_:token:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleHourofdayMinusQuarter :: Rule
+ruleHourofdayMinusQuarter = Rule
+  { name = "<hour-of-day> minus quarter (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "menos\\s? cuarto"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> do
+        t <- minutesBefore 15 td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleHourofdayMinusHalf :: Rule
+ruleHourofdayMinusHalf = Rule
+  { name = "<hour-of-day> minus half (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "menos\\s? media"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> do
+        t <- minutesBefore 30 td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleHourofdayMinusThreeQuarter :: Rule
+ruleHourofdayMinusThreeQuarter = Rule
+  { name = "<hour-of-day> minus three quarter (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "menos\\s? (3|tres) cuartos?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> do
+        t <- minutesBefore 45 td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleHourofdayIntegerAsRelativeMinutes :: Rule
+ruleHourofdayIntegerAsRelativeMinutes = Rule
+  { name = "<hour-of-day> <integer> (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleHourofdayIntegerAsRelativeMinutes2 :: Rule
+ruleHourofdayIntegerAsRelativeMinutes2 = Rule
+  { name = "<hour-of-day> <integer> (as relative minutes) minutes"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    , regex "min\\.?(uto)?s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleHourofdayQuarter :: Rule
+ruleHourofdayQuarter = Rule
+  { name = "<hour-of-day> quarter (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "cuarto"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
+        tt $ hourMinute is12H hours 15
+      _ -> Nothing
+  }
+
+ruleHourofdayHalf :: Rule
+ruleHourofdayHalf = Rule
+  { name = "<hour-of-day> half (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "media"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
+        tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+
+ruleHourofdayThreeQuarter :: Rule
+ruleHourofdayThreeQuarter = Rule
+  { name = "<hour-of-day> three quarters (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "(3|tres) cuartos?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
+        tt $ hourMinute is12H hours 45
+      _ -> Nothing
+  }
+
+ruleHourofdayAndRelativeMinutes :: Rule
+ruleHourofdayAndRelativeMinutes = Rule
+  { name = "<hour-of-day> and <relative minutes>"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "y"
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleHourofdayAndRelativeMinutes2 :: Rule
+ruleHourofdayAndRelativeMinutes2 = Rule
+  { name = "<hour-of-day> and <relative minutes> minutes"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "y"
+    , Predicate $ isIntegerBetween 1 59
+    , regex "min\\.?(uto)?s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleHourofdayAndQuarter :: Rule
+ruleHourofdayAndQuarter = Rule
+  { name = "<hour-of-day> and quarter"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "y cuarto"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
+        tt $ hourMinute is12H hours 15
+      _ -> Nothing
+  }
+
+ruleHourofdayAndHalf :: Rule
+ruleHourofdayAndHalf = Rule
+  { name = "<hour-of-day> and half"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "y media"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
+        tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+
+ruleHourofdayAndThreeQuarter :: Rule
+ruleHourofdayAndThreeQuarter = Rule
+  { name = "<hour-of-day> and 3 quarters"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "y (3|tres) cuartos?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
+        tt $ hourMinute is12H hours 45
+      _ -> Nothing
+  }
+
+ruleNamedmonth :: Rule
+ruleNamedmonth = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "enero|ene\\.?"
+    ]
+  , prod = \_ -> tt $ month 1
+  }
+
+ruleInThePartofday :: Rule
+ruleInThePartofday = Rule
+  { name = "in the <part-of-day>"
+  , pattern =
+    [ regex "(a|en|de|por) la"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleDelYear :: Rule
+ruleDelYear = Rule
+  { name = "del <year>"
+  , pattern =
+    [ regex "del( a(\x00f1|n)o)?"
+    , Predicate $ isIntegerBetween 1000 2100
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        v <- getIntValue token
+        tt $ year v
+      _ -> Nothing
+  }
+
+ruleNamedmonth3 :: Rule
+ruleNamedmonth3 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "marzo|mar\\.?"
+    ]
+  , prod = \_ -> tt $ month 3
+  }
+
+ruleDdmm :: Rule
+ruleDdmm = Rule
+  { name = "dd[/-]mm"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[/-](0?[1-9]|1[0-2])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        d <- parseInt m1
+        m <- parseInt m2
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleAfternoon :: Rule
+ruleAfternoon = Rule
+  { name = "afternoon"
+  , pattern =
+    [ regex "tarde"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 19
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleNamedmonth4 :: Rule
+ruleNamedmonth4 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "abril|abr\\.?"
+    ]
+  , prod = \_ -> tt $ month 4
+  }
+
+ruleMidnight :: Rule
+ruleMidnight = Rule
+  { name = "midnight"
+  , pattern =
+    [ regex "medianoche"
+    ]
+  , prod = \_ -> tt $ hour False 0
+  }
+
+ruleAnoNuevo :: Rule
+ruleAnoNuevo = Rule
+  { name = "ano nuevo"
+  , pattern =
+    [ regex "a(n|\x00f1)o nuevo"
+    ]
+  , prod = \_ -> tt $ monthDay 1 1
+  }
+
+ruleNamedday5 :: Rule
+ruleNamedday5 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "viernes|vie|vie\\."
+    ]
+  , prod = \_ -> tt $ dayOfWeek 5
+  }
+
+ruleDdddMonthinterval :: Rule
+ruleDdddMonthinterval = Rule
+  { name = "dd-dd <month>(interval)"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])"
+    , regex "\\-|al?"
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    , regex "de"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:_)):
+       _:
+       Token RegexMatch (GroupMatch (m2:_)):
+       _:
+       Token Time td:
+       _) -> do
+        d1 <- parseInt m1
+        d2 <- parseInt m2
+        from <- intersect (dayOfMonth d1) td
+        to <- intersect (dayOfMonth d2) td
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleTimeofdayLatent :: Rule
+ruleTimeofdayLatent = Rule
+  { name = "time-of-day (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 0 23
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ hour True v
+      _ -> Nothing
+  }
+
+ruleNamedmonth2 :: Rule
+ruleNamedmonth2 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "febrero|feb\\.?"
+    ]
+  , prod = \_ -> tt $ month 2
+  }
+
+ruleNamedmonthnameddayPast :: Rule
+ruleNamedmonthnameddayPast = Rule
+  { name = "<named-month|named-day> past"
+  , pattern =
+    [ dimension Time
+    , regex "pasad(o|a)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleSeason3 :: Rule
+ruleSeason3 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "invierno"
+    ]
+  , prod = \_ ->
+      let from = monthDay 12 21
+          to = monthDay 3 20
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleSeason :: Rule
+ruleSeason = Rule
+  { name = "season"
+  , pattern =
+    [ regex "verano"
+    ]
+  , prod = \_ ->
+      let from = monthDay 6 21
+          to = monthDay 9 23
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleRightNow :: Rule
+ruleRightNow = Rule
+  { name = "right now"
+  , pattern =
+    [ regex "ahor(it)?a|ya|en\\s?seguida|cuanto antes"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Second 0
+  }
+
+ruleDimTimeDeLaTarde :: Rule
+ruleDimTimeDeLaTarde = Rule
+  { name = "<dim time> de la tarde"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(a|en|de) la tarde"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        let tarde = interval TTime.Open (hour False 12, hour False 21)
+        in Token Time <$> intersect td (mkLatent $ partOfDay tarde)
+      _ -> Nothing
+  }
+
+ruleIntegerInThePartofday :: Rule
+ruleIntegerInThePartofday = Rule
+  { name = "<integer> in the <part-of-day>"
+  , pattern =
+    [ Predicate isAPartOfDay
+    , regex "(a|en|de|por) la"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNCycleProximoqueViene :: Rule
+ruleNCycleProximoqueViene = Rule
+  { name = "n <cycle> (proximo|que viene)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2 9999
+    , dimension TimeGrain
+    , regex "(pr(\x00f3|o)xim(o|a)s?|que vienen?|siguientes?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleNamedmonthnameddayNext :: Rule
+ruleNamedmonthnameddayNext = Rule
+  { name = "<named-month|named-day> next"
+  , pattern =
+    [ dimension Time
+    , regex "que vienen?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 1 False td
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ Predicate isNotLatent
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleTimeofdayPartofday :: Rule
+ruleTimeofdayPartofday = Rule
+  { name = "<time-of-day> <part-of-day>"
+  , pattern =
+    [ dimension Time
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleDimTimeDeLaManana :: Rule
+ruleDimTimeDeLaManana = Rule
+  { name = "<dim time> de la manana"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(a|en|de) la ma(\x00f1|n)ana"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        let manana = interval TTime.Open (hour False 0, hour False 12)
+        in Token Time <$> intersect td (mkLatent $ partOfDay manana)
+      _ -> Nothing
+  }
+
+ruleDeDatetimeDatetimeInterval :: Rule
+ruleDeDatetimeDatetimeInterval = Rule
+  { name = "de <datetime> - <datetime> (interval)"
+  , pattern =
+    [ regex "del?"
+    , dimension Time
+    , regex "\\-|al?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Open (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNthTimeDeTime2 :: Rule
+ruleNthTimeDeTime2 = Rule
+  { name = "nth <time> de <time>"
+  , pattern =
+    [ regex "the"
+    , dimension Ordinal
+    , dimension Time
+    , regex "de|en"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       Token Time td1:
+       _:
+       Token Time td2:
+       _) -> Token Time . predNth (v - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNamedmonth6 :: Rule
+ruleNamedmonth6 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "junio|jun\\.?"
+    ]
+  , prod = \_ -> tt $ month 6
+  }
+
+ruleDentroDeDuration :: Rule
+ruleDentroDeDuration = Rule
+  { name = "dentro de <duration>"
+  , pattern =
+    [ regex "dentro de"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $
+          interval TTime.Open (cycleNth TG.Second 0, inDuration dd)
+      _ -> Nothing
+  }
+
+ruleNamedmonth8 :: Rule
+ruleNamedmonth8 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "agosto|ago\\.?"
+    ]
+  , prod = \_ -> tt $ month 8
+  }
+
+ruleWeekend :: Rule
+ruleWeekend = Rule
+  { name = "week-end"
+  , pattern =
+    [ regex "week[ -]?end|fin de semana"
+    ]
+  , prod = \_ -> do
+      from <- intersect (dayOfWeek 5) (hour False 18)
+      to <- intersect (dayOfWeek 1) (hour False 0)
+      tt $ interval TTime.Open (from, to)
+  }
+
+ruleOrdinalQuarterYear :: Rule
+ruleOrdinalQuarterYear = Rule
+  { name = "<ordinal> quarter <year>"
+  , pattern =
+    [ dimension Ordinal
+    , Predicate $ isGrain TG.Quarter
+    , regex "del? ?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):_:Token Time td:_) ->
+        tt $ cycleNthAfter False TG.Quarter (v - 1) td
+      _ -> Nothing
+  }
+
+ruleYyyymmdd :: Rule
+ruleYyyymmdd = Rule
+  { name = "yyyy-mm-dd"
+  , pattern =
+    [ regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
+        y <- parseInt m1
+        m <- parseInt m2
+        d <- parseInt m3
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleTimeofdayHoras :: Rule
+ruleTimeofdayHoras = Rule
+  { name = "<time-of-day> horas"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "h\\.?(ora)?s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNavidad :: Rule
+ruleNavidad = Rule
+  { name = "Navidad"
+  , pattern =
+    [ regex "(la )?navidad"
+    ]
+  , prod = \_ -> tt $ monthDay 12 25
+  }
+
+ruleElCycleAntesTime :: Rule
+ruleElCycleAntesTime = Rule
+  { name = "el <cycle> antes <time>"
+  , pattern =
+    [ regex "l[ea']? ?"
+    , dimension TimeGrain
+    , regex "antes de"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain (-1) td
+      _ -> Nothing
+  }
+
+ruleTwoTimeTokensSeparatedBy :: Rule
+ruleTwoTimeTokensSeparatedBy = Rule
+  { name = "two time tokens separated by \",\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex ","
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleMorning :: Rule
+ruleMorning = Rule
+  { name = "morning"
+  , pattern =
+    [ regex "ma(\x00f1|n)ana"
+    ]
+  , prod = \_ ->
+      let from = hour False 4
+          to = hour False 12
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleALasHourmintimeofday :: Rule
+ruleALasHourmintimeofday = Rule
+  { name = "a las <hour-min>(time-of-day)"
+  , pattern =
+    [ regex "((al?)( las?)?|las?)"
+    , Predicate isATimeOfDay
+    , regex "horas?"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleThisPartofday :: Rule
+ruleThisPartofday = Rule
+  { name = "this <part-of-day>"
+  , pattern =
+    [ regex "est(e|a)"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time . partOfDay <$>
+        intersect (cycleNth TG.Day 0) td
+      _ -> Nothing
+  }
+
+ruleLaCyclePasado :: Rule
+ruleLaCyclePasado = Rule
+  { name = "la <cycle> pasado"
+  , pattern =
+    [ regex "(el|los|la|las) ?"
+    , dimension TimeGrain
+    , regex "pasad(a|o)s?|(u|\x00fa)ltim[ao]s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleYearLatent :: Rule
+ruleYearLatent = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween (- 10000) 999
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ year n
+      _ -> Nothing
+  }
+
+ruleYesterday :: Rule
+ruleYesterday = Rule
+  { name = "yesterday"
+  , pattern =
+    [ regex "ayer"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 1
+  }
+
+ruleSeason2 :: Rule
+ruleSeason2 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "oto(\x00f1|n)o"
+    ]
+  , prod = \_ ->
+      let from = monthDay 9 23
+          to = monthDay 12 21
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleDayofweekDayofmonth :: Rule
+ruleDayofweekDayofmonth = Rule
+  { name = "<day-of-week> <day-of-month>"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleTimeofdayAmpm :: Rule
+ruleTimeofdayAmpm = Rule
+  { name = "<time-of-day> am|pm"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "([ap])\\.?m?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:Token RegexMatch (GroupMatch (ap:_)):_) ->
+        tt . timeOfDayAMPM td $ Text.toLower ap == "a"
+      _ -> Nothing
+  }
+
+ruleDayofmonthDeNamedmonth :: Rule
+ruleDayofmonthDeNamedmonth = Rule
+  { name = "<day-of-month> de <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , regex "de"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleEntreDdEtDdMonthinterval :: Rule
+ruleEntreDdEtDdMonthinterval = Rule
+  { name = "entre dd et dd <month>(interval)"
+  , pattern =
+    [ regex "entre( el)?"
+    , regex "(0?[1-9]|[12]\\d|3[01])"
+    , regex "y( el)?"
+    , regex "(0?[1-9]|[12]\\d|3[01])"
+    , regex "de"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token RegexMatch (GroupMatch (m1:_)):
+       _:
+       Token RegexMatch (GroupMatch (m2:_)):
+       _:
+       Token Time td:
+       _) -> do
+        v1 <- parseInt m1
+        v2 <- parseInt m2
+        from <- intersect (dayOfMonth v1) td
+        to <- intersect (dayOfMonth v2) td
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleNamedmonthDayofmonth :: Rule
+ruleNamedmonthDayofmonth = Rule
+  { name = "<named-month> <day-of-month>"
+  , pattern =
+    [ Predicate isAMonth
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleNamedmonth5 :: Rule
+ruleNamedmonth5 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "mayo?\\.?"
+    ]
+  , prod = \_ -> tt $ month 5
+  }
+
+ruleNamedday7 :: Rule
+ruleNamedday7 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "domingo|dom\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 7
+  }
+
+ruleElTime :: Rule
+ruleElTime = Rule
+  { name = "el <time>"
+  , pattern =
+    [ regex "d?el"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleYear :: Rule
+ruleYear = Rule
+  { name = "year"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1000 2100
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt $ year v
+      _ -> Nothing
+  }
+
+ruleNamedmonth10 :: Rule
+ruleNamedmonth10 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "octubre|oct\\.?"
+    ]
+  , prod = \_ -> tt $ month 10
+  }
+
+ruleEsteenUnCycle :: Rule
+ruleEsteenUnCycle = Rule
+  { name = "este|en un <cycle>"
+  , pattern =
+    [ regex "(est(e|a|os)|en (el|los|la|las) ?)"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 0
+      _ -> Nothing
+  }
+
+ruleNProximasCycle :: Rule
+ruleNProximasCycle = Rule
+  { name = "n proximas <cycle>"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2 9999
+    , regex "pr(\x00f3|o)xim(o|a)s?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleLaPasadoCycle :: Rule
+ruleLaPasadoCycle = Rule
+  { name = "la pasado <cycle>"
+  , pattern =
+    [ regex "(el|los|la|las) ?"
+    , regex "pasad(a|o)s?|(u|\x00fa)ltim[ao]s?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:_:Token TimeGrain grain:_) ->
+        tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleALasTimeofday :: Rule
+ruleALasTimeofday = Rule
+  { name = "a las <time-of-day>"
+  , pattern =
+    [ regex "(al?)( las?)?|las?"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleDdmmyyyy :: Rule
+ruleDdmmyyyy = Rule
+  { name = "dd[/-.]mm[/-.]yyyy"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[\\./-](0?[1-9]|1[0-2])[\\./-](\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
+        d <- parseInt m1
+        m <- parseInt m2
+        y <- parseInt m3
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleNamedmonth11 :: Rule
+ruleNamedmonth11 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "noviembre|nov\\.?"
+    ]
+  , prod = \_ -> tt $ month 11
+  }
+
+ruleOrdinalQuarter :: Rule
+ruleOrdinalQuarter = Rule
+  { name = "<ordinal> quarter"
+  , pattern =
+    [ dimension Ordinal
+    , Predicate $ isGrain TG.Quarter
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):_) ->
+        tt . cycleNthAfter False TG.Quarter (v - 1)
+          $ cycleNth TG.Year 0
+      _ -> Nothing
+  }
+
+ruleElCycleProximoqueViene :: Rule
+ruleElCycleProximoqueViene = Rule
+  { name = "el <cycle> (proximo|que viene)"
+  , pattern =
+    [ regex "(el|los|la|las) ?"
+    , dimension TimeGrain
+    , regex "(pr(\x00f3|o)xim(o|a)s?|que vienen?|siguientes?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleElCycleProximoqueVieneTime :: Rule
+ruleElCycleProximoqueVieneTime = Rule
+  { name = "el <cycle> proximo|que viene <time>"
+  , pattern =
+    [ regex "(el|los|la|las)"
+    , dimension TimeGrain
+    , regex "(pr(\x00f3|o)xim(o|a)s?|que vienen?|siguientes?)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain 1 td
+      _ -> Nothing
+  }
+
+ruleDelMedioda :: Rule
+ruleDelMedioda = Rule
+  { name = "del mediodía"
+  , pattern =
+    [ regex "del mediod(i|\x00ed)a"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 17
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleNamedday3 :: Rule
+ruleNamedday3 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "mi(e|\x00e9)\\.?(rcoles)?|mx|mier?\\."
+    ]
+  , prod = \_ -> tt $ dayOfWeek 3
+  }
+
+ruleIntersectByDe :: Rule
+ruleIntersectByDe = Rule
+  { name = "intersect by `de`"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "de"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleTomorrow :: Rule
+ruleTomorrow = Rule
+  { name = "tomorrow"
+  , pattern =
+    [ regex "ma(n|\x00f1)ana"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 1
+  }
+
+ruleNthTimeDeTime :: Rule
+ruleNthTimeDeTime = Rule
+  { name = "nth <time> de <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "de|en"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       Token Time td1:
+       _:
+       Token Time td2:
+       _) -> Token Time . predNth (v - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNamedmonth9 :: Rule
+ruleNamedmonth9 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "septiembre|sept?\\.?"
+    ]
+  , prod = \_ -> tt $ month 9
+  }
+
+ruleTimezone :: Rule
+ruleTimezone = Rule
+  { name = "<time> timezone"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\b(YEKT|YEKST|YAKT|YAKST|WITA|WIT|WIB|WGT|WGST|WFT|WET|WEST|WAT|WAST|VUT|VLAT|VLAST|VET|UZT|UYT|UYST|UTC|ULAT|TVT|TMT|TLT|TKT|TJT|TFT|TAHT|SST|SRT|SGT|SCT|SBT|SAST|SAMT|RET|PYT|PYST|PWT|PST|PONT|PMST|PMDT|PKT|PHT|PHOT|PGT|PETT|PETST|PET|PDT|OMST|OMSST|NZST|NZDT|NUT|NST|NPT|NOVT|NOVST|NFT|NDT|NCT|MYT|MVT|MUT|MST|MSK|MSD|MMT|MHT|MDT|MAWT|MART|MAGT|MAGST|LINT|LHST|LHDT|KUYT|KST|KRAT|KRAST|KGT|JST|IST|IRST|IRKT|IRKST|IRDT|IOT|IDT|ICT|HOVT|HKT|GYT|GST|GMT|GILT|GFT|GET|GAMT|GALT|FNT|FKT|FKST|FJT|FJST|EST|EGT|EGST|EET|EEST|EDT|ECT|EAT|EAST|EASST|DAVT|ChST|CXT|CVT|CST|COT|CLT|CLST|CKT|CHAST|CHADT|CET|CEST|CDT|CCT|CAT|CAST|BTT|BST|BRT|BRST|BOT|BNT|AZT|AZST|AZOT|AZOST|AWST|AWDT|AST|ART|AQTT|ANAT|ANAST|AMT|AMST|ALMT|AKST|AKDT|AFT|AEST|AEDT|ADT|ACST|ACDT)\\b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       Token RegexMatch (GroupMatch (tz:_)):
+       _) -> Token Time <$> inTimezone tz td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleALasHourmintimeofday
+  , ruleALasTimeofday
+  , ruleAfternoon
+  , ruleAnoNuevo
+  , ruleCeTime
+  , ruleDatetimeDatetimeInterval
+  , ruleDayOfMonthSt
+  , ruleDayofmonthDeNamedmonth
+  , ruleDayofweekDayofmonth
+  , ruleDdddMonthinterval
+  , ruleDdmm
+  , ruleDdmmyyyy
+  , ruleDeDatetimeDatetimeInterval
+  , ruleDelMedioda
+  , ruleDelYear
+  , ruleDentroDeDuration
+  , ruleDimTimeDeLaManana
+  , ruleDimTimeDeLaTarde
+  , ruleElCycleAntesTime
+  , ruleElCycleProximoqueViene
+  , ruleElCycleProximoqueVieneTime
+  , ruleElDayofmonthDeNamedmonth
+  , ruleElDayofmonthNonOrdinal
+  , ruleElProximoCycle
+  , ruleElTime
+  , ruleEnDuration
+  , ruleEntreDatetimeEtDatetimeInterval
+  , ruleEntreDdEtDdMonthinterval
+  , ruleEsteenUnCycle
+  , ruleEvening
+  , ruleHaceDuration
+  , ruleHhhmmTimeofday
+  , ruleHourofdayAndRelativeMinutes
+  , ruleHourofdayIntegerAsRelativeMinutes
+  , ruleHourofdayMinusIntegerAsRelativeMinutes
+  , ruleInThePartofday
+  , ruleIntegerInThePartofday
+  , ruleIntersect
+  , ruleIntersectByDe
+  , ruleLaCyclePasado
+  , ruleLaPasadoCycle
+  , ruleMidnight
+  , ruleMorning
+  , ruleNCycleProximoqueViene
+  , ruleNPasadosCycle
+  , ruleNProximasCycle
+  , ruleNamedday
+  , ruleNamedday2
+  , ruleNamedday3
+  , ruleNamedday4
+  , ruleNamedday5
+  , ruleNamedday6
+  , ruleNamedday7
+  , ruleNamedmonth
+  , ruleNamedmonth10
+  , ruleNamedmonth11
+  , ruleNamedmonth12
+  , ruleNamedmonth2
+  , ruleNamedmonth3
+  , ruleNamedmonth4
+  , ruleNamedmonth5
+  , ruleNamedmonth6
+  , ruleNamedmonth7
+  , ruleNamedmonth8
+  , ruleNamedmonth9
+  , ruleNamedmonthDayofmonth
+  , ruleNamedmonthnameddayNext
+  , ruleNamedmonthnameddayPast
+  , ruleNavidad
+  , ruleNochevieja
+  , ruleNoon
+  , ruleNow
+  , ruleNthTimeDeTime
+  , ruleNthTimeDeTime2
+  , ruleOrdinalQuarter
+  , ruleOrdinalQuarterYear
+  , rulePasadosNCycle
+  , ruleProximasNCycle
+  , ruleRightNow
+  , ruleSeason
+  , ruleSeason2
+  , ruleSeason3
+  , ruleSeason4
+  , ruleTheDayAfterTomorrow
+  , ruleTheDayBeforeYesterday
+  , ruleThisDayofweek
+  , ruleThisPartofday
+  , ruleTimeofdayAmpm
+  , ruleTimeofdayHoras
+  , ruleTimeofdayLatent
+  , ruleTimeofdayPartofday
+  , ruleTomorrow
+  , ruleTwoTimeTokensSeparatedBy
+  , ruleUltimoDayofweekDeTime
+  , ruleWeekend
+  , ruleYear
+  , ruleYearLatent
+  , ruleYearLatent2
+  , ruleYesterday
+  , ruleYyyymmdd
+  , ruleHourofdayAndThreeQuarter
+  , ruleHourofdayAndHalf
+  , ruleHourofdayAndQuarter
+  , ruleHourofdayAndRelativeMinutes2
+  , ruleHourofdayThreeQuarter
+  , ruleHourofdayHalf
+  , ruleHourofdayQuarter
+  , ruleHourofdayIntegerAsRelativeMinutes2
+  , ruleHourofdayMinusThreeQuarter
+  , ruleHourofdayMinusHalf
+  , ruleHourofdayMinusQuarter
+  , ruleHourofdayMinusIntegerAsRelativeMinutes2
+  , ruleTimezone
+  ]
diff --git a/Duckling/Time/FR/Corpus.hs b/Duckling/Time/FR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/FR/Corpus.hs
@@ -0,0 +1,774 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.FR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Time.Corpus
+import Duckling.Time.Types hiding (Month)
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Testing.Types hiding (examples)
+
+corpus :: Corpus
+corpus = (testContext {lang = FR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (datetime (2013, 2, 12, 4, 30, 0) Second)
+             [ "maintenant"
+             , "tout de suite"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "aujourd'hui"
+             , "ce jour"
+             , "dans la journée"
+             , "en ce moment"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "hier"
+             , "le jour d'avant"
+             , "le jour précédent"
+             , "la veille"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "avant-hier"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "demain"
+             , "jour suivant"
+             , "le jour d'après"
+             , "le lendemain"
+             , "un jour après"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "après-demain"
+             , "le lendemain du 13 février"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "lundi"
+             , "lun."
+             , "ce lundi"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "lundi 18 février"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "mardi"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "mercredi 13 février"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "jeudi"
+             , "deux jours plus tard"
+             , "deux jours après"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "vendredi"
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "samedi"
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "dimanche"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "le 1er mars"
+             , "premier mars"
+             , "le 1 mars"
+             , "vendredi 1er mars"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "le premier mars 2013"
+             , "1/3/2013"
+             , "2013-03-01"
+             ]
+  , examples (datetime (2013, 3, 2, 0, 0, 0) Day)
+             [ "le 2 mars"
+             , "2 mars"
+             , "le 2/3"
+             ]
+  , examples (datetime (2013, 3, 2, 5, 0, 0) Hour)
+             [ "le 2 mars à 5h"
+             , "2 mars à 5h"
+             , "le 2/3 à 5h"
+             , "le 2 mars à 5h du matin"
+             , "le 2 mars vers 5h"
+             , "2 mars vers 5h"
+             , "2 mars à environ 5h"
+             , "2 mars aux alentours de 5h"
+             , "2 mars autour de 5h"
+             , "le 2/3 vers 5h"
+             ]
+  , examples (datetime (2013, 3, 2, 0, 0, 0) Day)
+             [ "le 2"
+             ]
+  , examples (datetime (2013, 3, 2, 5, 0, 0) Hour)
+             [ "le 2 à 5h"
+             , "le 2 vers 5h"
+             , "le 2 à 5h du mat"
+             ]
+  , examples (datetime (2013, 3, 3, 0, 0, 0) Day)
+             [ "le 3 mars"
+             , "3 mars"
+             , "le 3/3"
+             ]
+  , examples (datetime (2013, 4, 5, 0, 0, 0) Day)
+             [ "le 5 avril"
+             , "5 avril"
+             ]
+  , examples (datetime (2015, 3, 3, 0, 0, 0) Day)
+             [ "le 3 mars 2015"
+             , "3 mars 2015"
+             , "3/3/2015"
+             , "2015-3-3"
+             , "2015-03-03"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "le 15 février"
+             , "15 février"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "15/02/2013"
+             , "15 fev 2013"
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "le 16"
+             ]
+  , examples (datetime (2013, 2, 16, 18, 0, 0) Hour)
+             [ "le 16 à 18h"
+             , "le 16 vers 18h"
+             , "le 16 plutôt vers 18h"
+             , "le 16 à 6h du soir"
+             , "le 16 vers 6h du soir"
+             , "le 16 vers 6h dans la soirée"
+             , "samedi 16 à 18h"
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "17 février"
+             , "le 17 février"
+             , "17/2"
+             , "17/02"
+             , "le 17/02"
+             , "17 02"
+             , "17 2"
+             , "le 17 02"
+             , "le 17 2"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "mercredi 13"
+             ]
+  , examples (datetime (2014, 2, 20, 0, 0, 0) Day)
+             [ "20/02/2014"
+             , "20/2/2014"
+             , "20/02/14"
+             , "le 20/02/14"
+             , "le 20/2/14"
+             , "20 02 2014"
+             , "20 02 14"
+             , "20 2 2014"
+             , "20 2 14"
+             , "le 20 02 2014"
+             , "le 20 02 14"
+             , "le 20 2 2014"
+             , "le 20 2 14"
+             ]
+  , examples (datetime (2013, 10, 31, 0, 0, 0) Day)
+             [ "31 octobre"
+             , "le 31 octobre"
+             , "31/10"
+             , "le 31/10"
+             , "31 10"
+             , "le 31 10"
+             ]
+  , examples (datetime (2014, 12, 24, 0, 0, 0) Day)
+             [ "24/12/2014"
+             , "24/12/14"
+             , "le 24/12/14"
+             , "24 12 2014"
+             , "24 12 14"
+             , "le 24 12 2014"
+             , "le 24 12 14"
+             ]
+  , examples (datetime (1974, 10, 31, 0, 0, 0) Day)
+             [ "31/10/1974"
+             , "31/10/74"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "lundi prochain"
+             , "lundi la semaine prochaine"
+             , "lundi de la semaine prochaine"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "mardi prochain"
+             , "mardi suivant"
+             , "mardi d'après"
+             , "mardi la semaine prochaine"
+             , "mardi de la semaine prochaine"
+             , "mardi la semaine suivante"
+             , "mardi de la semaine suivante"
+             , "mardi la semaine d'après"
+             , "mardi de la semaine d'après"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "mercredi prochain"
+             ]
+  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
+             [ "mercredi suivant"
+             , "mercredi d'après"
+             , "mercredi la semaine prochaine"
+             , "mercredi de la semaine prochaine"
+             , "mercredi la semaine suivante"
+             , "mercredi de la semaine suivante"
+             , "mercredi la semaine d'après"
+             , "mercredi de la semaine d'après"
+             ]
+  , examples (datetime (2013, 2, 25, 0, 0, 0) Day)
+             [ "lundi en huit"
+             , "lundi en 8"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "mardi en huit"
+             , "mardi en 8"
+             ]
+  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
+             [ "mercredi en huit"
+             , "mercredi en 8"
+             ]
+  , examples (datetime (2013, 3, 4, 0, 0, 0) Day)
+             [ "lundi en quinze"
+             , "lundi en 15"
+             ]
+  , examples (datetime (2013, 2, 26, 0, 0, 0) Day)
+             [ "mardi en quinze"
+             , "mardi en 15"
+             ]
+  , examples (datetime (2013, 2, 27, 0, 0, 0) Day)
+             [ "mercredi en quinze"
+             , "mercredi en 15"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "lundi cette semaine"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "mardi cette semaine"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "mercredi cette semaine"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Week)
+             [ "cette semaine"
+             , "dans la semaine"
+             ]
+  , examples (datetime (2013, 2, 4, 0, 0, 0) Week)
+             [ "la semaine dernière"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Week)
+             [ "la semaine prochaine"
+             , "la semaine suivante"
+             , "la semaine qui suit"
+             ]
+  , examples (datetime (2013, 1, 0, 0, 0, 0) Month)
+             [ "le mois dernier"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "le mois prochain"
+             , "le mois suivant"
+             ]
+  , examples (datetime (2012, 0, 0, 0, 0, 0) Year)
+             [ "l'année dernière"
+             ]
+  , examples (datetime (2013, 0, 0, 0, 0, 0) Year)
+             [ "cette année"
+             ]
+  , examples (datetime (2014, 0, 0, 0, 0, 0) Year)
+             [ "l'année prochaine"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "dimanche dernier"
+             , "dimanche de la semaine dernière"
+             ]
+  , examples (datetime (2013, 10, 3, 0, 0, 0) Day)
+             [ "3eme jour d'octobre"
+             , "le 3eme jour d'octobre"
+             ]
+  , examples (datetime (2014, 10, 6, 0, 0, 0) Week)
+             [ "premiere semaine d'octobre 2014"
+             , "la premiere semaine d'octobre 2014"
+             ]
+  , examples (datetime (2013, 10, 7, 0, 0, 0) Week)
+             [ "la semaine du 6 octobre"
+             , "la semaine du 7 octobre"
+             ]
+  , examples (datetime (2015, 10, 31, 0, 0, 0) Day)
+             [ "dernier jour d'octobre 2015"
+             , "le dernier jour d'octobre 2015"
+             ]
+  , examples (datetime (2014, 9, 22, 0, 0, 0) Week)
+             [ "dernière semaine de septembre 2014"
+             , "la dernière semaine de septembre 2014"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "à quinze heures"
+             , "à 15 heures"
+             , "à 3 heures cet après-midi"
+             , "15h"
+             , "15H"
+             , "vers 15 heures"
+             , "à environ 15 heures"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Minute)
+             [ "15:00"
+             , "15h00"
+             , "15H00"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Hour)
+             [ "minuit"
+             ]
+  , examples (datetime (2013, 2, 12, 12, 0, 0) Hour)
+             [ "midi"
+             , "aujourd'hui à midi"
+             ]
+  , examples (datetime (2013, 2, 12, 12, 15, 0) Minute)
+             [ "midi et quart"
+             , "midi quinze"
+             ]
+  , examples (datetime (2013, 2, 12, 11, 55, 0) Minute)
+             [ "midi moins cinq"
+             ]
+  , examples (datetime (2013, 2, 12, 12, 30, 0) Minute)
+             [ "midi et demi"
+             , "midi trente"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 3, 0) Minute)
+             [ "minuit trois"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 3, 0) Minute)
+             [ "aujourd'hui à minuit trois"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 15, 0) Minute)
+             [ "à quinze heures quinze"
+             , "à quinze heures et quinze minutes"
+             , "15h passé de 15 minutes"
+             , "à trois heures et quart cet après-midi"
+             , "15:15"
+             , "15h15"
+             ]
+  , examples (datetime (2013, 2, 13, 15, 15, 0) Minute)
+             [ "à trois heures et quart demain après-midi"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 30, 0) Minute)
+             [ "à quinze heures trente"
+             , "à quinze heures passé de trente minutes"
+             , "à trois heures et demi cet après-midi"
+             , "15:30"
+             , "15h30"
+             ]
+  , examples (datetime (2013, 2, 12, 11, 45, 0) Minute)
+             [ "midi moins le quart"
+             , "11h45"
+             , "onze heures trois quarts"
+             , "aujourd'hui à 11h45"
+             ]
+  , examples (datetime (2013, 2, 13, 11, 0, 0) Hour)
+             [ "mercredi à 11h"
+             ]
+  , examples (datetime (2013, 2, 13, 11, 0, 0) Hour)
+             [ "demain à 11 heures"
+             , "demain à 11H"
+             ]
+  , examples (datetime (2013, 2, 14, 11, 0, 0) Hour)
+             [ "jeudi à 11h"
+             , "après-demain à 11 heures"
+             , "après-demain à 11H"
+             ]
+  , examples (datetime (2013, 2, 15, 12, 0, 0) Hour)
+             [ "vendredi à midi"
+             , "vendredi à 12h"
+             ]
+  , examples (datetime (2013, 2, 15, 16, 0, 0) Hour)
+             [ "vendredi quinze à seize heures"
+             , "vendredi 15 à 16h"
+             , "vendredi quinze à 16h"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 30, 1) Second)
+             [ "dans une seconde"
+             , "dans 1\""
+             ]
+  , examples (datetime (2013, 2, 12, 4, 31, 0) Second)
+             [ "dans une minute"
+             , "dans 1 min"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 32, 0) Second)
+             [ "dans 2 minutes"
+             , "dans deux min"
+             , "dans 2'"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Second)
+             [ "dans 60 minutes"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Minute)
+             [ "dans une heure"
+             ]
+  , examples (datetime (2013, 2, 12, 2, 30, 0) Minute)
+             [ "il y a deux heures"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 30, 0) Minute)
+             [ "dans 24 heures"
+             , "dans vingt quatre heures"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 0, 0) Hour)
+             [ "dans un jour"
+             ]
+  , examples (datetime (2013, 2, 19, 4, 0, 0) Hour)
+             [ "dans 7 jours"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "dans 1 semaine"
+             , "dans une semaine"
+             ]
+  , examples (datetime (2013, 1, 22, 0, 0, 0) Day)
+             [ "il y a trois semaines"
+             ]
+  , examples (datetime (2013, 4, 12, 0, 0, 0) Day)
+             [ "dans deux mois"
+             ]
+  , examples (datetime (2012, 11, 12, 0, 0, 0) Day)
+             [ "il y a trois mois"
+             ]
+  , examples (datetime (2014, 2, 0, 0, 0, 0) Month)
+             [ "dans une année"
+             , "dans 1 an"
+             ]
+  , examples (datetime (2011, 2, 0, 0, 0, 0) Month)
+             [ "il y a deux ans"
+             ]
+  , examples (datetimeInterval ((2013, 6, 21, 0, 0, 0), (2013, 9, 24, 0, 0, 0)) Day)
+             [ "cet été"
+             ]
+  , examples (datetimeInterval ((2012, 12, 21, 0, 0, 0), (2013, 3, 21, 0, 0, 0)) Day)
+             [ "cet hiver"
+             ]
+  , examples (datetime (2013, 12, 25, 0, 0, 0) Day)
+             [ "Noel"
+             , "noël"
+             , "jour de noel"
+             ]
+  , examples (datetimeInterval ((2013, 12, 24, 18, 0, 0), (2013, 12, 25, 0, 0, 0)) Hour)
+             [ "le soir de noël"
+             ]
+  , examples (datetime (2014, 1, 1, 0, 0, 0) Day)
+             [ "jour de l'an"
+             , "nouvel an"
+             , "premier janvier"
+             ]
+  , examples (datetime (2013, 11, 1, 0, 0, 0) Day)
+             [ "la toussaint"
+             , "le jour de la toussaint"
+             , "la journée de la toussaint"
+             , "toussaint"
+             , "le jour des morts"
+             ]
+  , examples (datetime (2013, 5, 1, 0, 0, 0) Day)
+             [ "fête du travail"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 12, 0, 0), (2013, 2, 12, 19, 0, 0)) Hour)
+             [ "cet après-midi"
+             , "l'après-midi"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 7, 0, 0), (2013, 2, 12, 9, 0, 0)) Hour)
+             [ "en début de matinée"
+             , "aujourd'hui très tôt le matin"
+             , "aujourd'hui tôt le matin"
+             , "aujourd'hui le matin tôt"
+             , "aujourd'hui le matin très tôt"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 10, 0, 0), (2013, 2, 12, 12, 0, 0)) Hour)
+             [ "en fin de matinée"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 13, 0, 0), (2013, 2, 12, 17, 0, 0)) Hour)
+             [ "après déjeuner"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 10, 0, 0), (2013, 2, 12, 12, 0, 0)) Hour)
+             [ "avant déjeuner"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 12, 0, 0), (2013, 2, 12, 14, 0, 0)) Hour)
+             [ "aujourd'hui pendant le déjeuner"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 17, 0, 0), (2013, 2, 12, 21, 0, 0)) Hour)
+             [ "après le travail"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 0, 0), (2013, 2, 12, 12, 0, 0)) Hour)
+             [ "dès le matin"
+             , "dès la matinée"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 12, 0, 0), (2013, 2, 12, 14, 0, 0)) Hour)
+             [ "en début d'après-midi"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 17, 0, 0), (2013, 2, 12, 19, 0, 0)) Hour)
+             [ "en fin d'après-midi"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 6, 0, 0), (2013, 2, 12, 10, 0, 0)) Hour)
+             [ "en début de journée"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 17, 0, 0), (2013, 2, 12, 21, 0, 0)) Hour)
+             [ "en fin de journée"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 18, 0, 0), (2013, 2, 13, 0, 0, 0)) Hour)
+             [ "ce soir"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 18, 0, 0), (2013, 2, 12, 21, 0, 0)) Hour)
+             [ "en début de soirée"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 21, 0, 0), (2013, 2, 13, 0, 0, 0)) Hour)
+             [ "en fin de soirée"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 18, 0, 0), (2013, 2, 14, 0, 0, 0)) Hour)
+             [ "demain soir"
+             , "mercredi soir"
+             , "mercredi en soirée"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 18, 0, 0), (2013, 2, 12, 0, 0, 0)) Hour)
+             [ "hier soir"
+             , "la veille au soir"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 18, 0, 0), (2013, 2, 18, 0, 0, 0)) Hour)
+             [ "ce week-end"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 0, 0, 0), (2013, 2, 13, 0, 0, 0)) Day)
+             [ "en début de semaine"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 15, 0, 0, 0)) Day)
+             [ "en milieu de semaine"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 0, 0, 0), (2013, 2, 18, 0, 0, 0)) Day)
+             [ "en fin de semaine"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
+             [ "en semaine"
+             ]
+  , examples (datetimeInterval ((2013, 9, 6, 18, 0, 0), (2013, 9, 9, 0, 0, 0)) Hour)
+             [ "le premier week-end de septembre"
+             ]
+  , examples (datetimeInterval ((2013, 9, 13, 18, 0, 0), (2013, 9, 16, 0, 0, 0)) Hour)
+             [ "le deuxième week-end de septembre"
+             ]
+  , examples (datetimeInterval ((2013, 9, 27, 18, 0, 0), (2013, 9, 30, 0, 0, 0)) Hour)
+             [ "le dernier week-end de septembre"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 4, 0, 0), (2013, 2, 18, 12, 0, 0)) Hour)
+             [ "lundi matin"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 12, 0, 0), (2013, 2, 18, 19, 0, 0)) Hour)
+             [ "lundi après-midi"
+             , "lundi dans l'après-midi"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 17, 0, 0), (2013, 2, 18, 19, 0, 0)) Hour)
+             [ "lundi fin d'après-midi"
+             , "lundi en fin d'après-midi"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 4, 0, 0), (2013, 2, 15, 12, 0, 0)) Hour)
+             [ "le 15 février dans la matinée"
+             , "matinée du 15 février"
+             , "le 15 février le matin"
+             ]
+  , examples (datetime (2013, 2, 12, 20, 0, 0) Hour)
+             [ "8 heures ce soir"
+             , "8h du soir"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 0, 0) Hour)
+             [ "3 heures du matin"
+             , "3h du mat"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 29, 58), (2013, 2, 12, 4, 30, 0)) Second)
+             [ "2 dernières secondes"
+             , "deux dernieres secondes"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 1), (2013, 2, 12, 4, 30, 4)) Second)
+             [ "3 prochaines secondes"
+             , "trois prochaines secondes"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 28, 0), (2013, 2, 12, 4, 30, 0)) Minute)
+             [ "2 dernieres minutes"
+             , "deux dernières minutes"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 31, 0), (2013, 2, 12, 4, 34, 0)) Minute)
+             [ "3 prochaines minutes"
+             , "trois prochaines minutes"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 5, 0, 0), (2013, 2, 12, 8, 0, 0)) Hour)
+             [ "3 prochaines heures"
+             , "3 heures suivantes"
+             ]
+  , examples (datetimeInterval ((2013, 2, 10, 0, 0, 0), (2013, 2, 12, 0, 0, 0)) Day)
+             [ "2 dernier jours"
+             , "deux derniers jour"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
+             [ "3 prochains jours"
+             ]
+  , examples (datetimeInterval ((2013, 1, 28, 0, 0, 0), (2013, 2, 11, 0, 0, 0)) Week)
+             [ "2 dernieres semaines"
+             , "2 semaines passées"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 0, 0, 0), (2013, 3, 11, 0, 0, 0)) Week)
+             [ "3 prochaines semaines"
+             ]
+  , examples (datetimeInterval ((2012, 12, 0, 0, 0, 0), (2013, 2, 0, 0, 0, 0)) Month)
+             [ "2 derniers mois"
+             ]
+  , examples (datetimeInterval ((2013, 3, 0, 0, 0, 0), (2013, 6, 0, 0, 0, 0)) Month)
+             [ "3 prochains mois"
+             , "3 mois suivant"
+             ]
+  , examples (datetimeInterval ((2011, 0, 0, 0, 0, 0), (2013, 0, 0, 0, 0, 0)) Year)
+             [ "2 dernieres annees"
+             , "2 années passées"
+             ]
+  , examples (datetimeInterval ((2014, 0, 0, 0, 0, 0), (2017, 0, 0, 0, 0, 0)) Year)
+             [ "3 prochaines années"
+             ]
+  , examples (datetimeInterval ((2013, 7, 13, 0, 0, 0), (2013, 7, 16, 0, 0, 0)) Day)
+             [ "13-15 juillet"
+             , "13 au 15 juillet"
+             , "13 jusqu'au 15 juillet"
+             , "13 juillet au 15 juillet"
+             , "13 juillet - 15 juillet"
+             , "entre le 13 et le 15 juillet"
+             , "samedi 13 au dimanche 15 juillet"
+             , "du samedi 13 au dimanche 15 juillet"
+             , "du 13 au dimanche 15 juillet"
+             ]
+  , examples (datetimeInterval ((2013, 7, 1, 0, 0, 0), (2013, 7, 11, 0, 0, 0)) Day)
+             [ "1er au 10 juillet"
+             , "lundi 1er au mercredi 10 juillet"
+             , "lundi 1 au mercredi 10 juillet"
+             , "du lundi 1er au mercredi 10 juillet"
+             , "du 1er au mercredi 10 juillet"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 19, 0, 0, 0)) Day)
+             [ "du 13 au 18"
+             , "entre le 13 et le 18"
+             ]
+  , examples (datetimeInterval ((2013, 6, 10, 0, 0, 0), (2013, 7, 2, 0, 0, 0)) Day)
+             [ "10 juin au 1er juillet"
+             , "entre le 10 juin et le 1er juillet"
+             , "du 10 juin au 1er juillet"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 30, 0), (2013, 2, 14, 12, 0, 0)) Minute)
+             [ "de 9h30 jusqu'à 11h jeudi"
+             , "de 9 heures 30 à 11h jeudi"
+             , "de 9 heures 30 a 11h jeudi"
+             , "entre 9h30 et 11h jeudi"
+             , "jeudi mais entre 9h30 et 11h"
+             , "jeudi par exemple entre 9h30 et 11h"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 30, 0), (2013, 2, 14, 11, 1, 0)) Minute)
+             [ "9h30 - 11h00 Jeudi"
+             ]
+  , examples (datetimeOpenInterval After (2013, 3, 8, 0, 0, 0) Day)
+             [ "à partir du 8"
+             , "à partir du 8 mars"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 14, 9, 30, 0) Minute)
+             [ "à partir de 9h30 jeudi"
+             , "jeudi après 9h30"
+             , "jeudi matin à partir de 9 heures 30"
+             ]
+  , examples (datetimeOpenInterval After (2013, 11, 1, 16, 0, 0) Hour)
+             [ "après 16h le 1er novembre"
+             ]
+  , examples (datetimeOpenInterval After (2013, 11, 1, 0, 0, 0) Day)
+             [ "après le 1er novembre"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 16, 0, 0) Hour)
+             [ "avant 16h"
+             , "n'importe quand avant 16h"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 13, 17, 0, 0)) Hour)
+             [ "demain jusqu'à 16h"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 20, 10, 0, 0) Hour)
+             [ "le 20 à partir de 10h"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 15, 12, 0, 0) Hour)
+             [ "vendredi à partir de midi"
+             ]
+  , examples (datetimeInterval ((2013, 2, 20, 0, 0, 0), (2013, 2, 20, 19, 0, 0)) Hour)
+             [ "le 20 jusqu'à 18h"
+             ]
+  , examples (datetimeInterval ((2014, 9, 14, 0, 0, 0), (2014, 9, 21, 0, 0, 0)) Day)
+             [ "14 - 20 sept. 2014"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 26, 0, 0, 0)) Second)
+             [ "d'ici 2 semaines"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 27, 4, 0, 0)) Second)
+             [ "dans les 15 jours"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 5, 0, 0), (2013, 2, 12, 8, 0, 0)) Hour)
+             [ "de 5 à 7"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 0, 0), (2013, 2, 14, 12, 0, 0)) Hour)
+             [ "jeudi de 9h à 11h"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 12, 0, 0), (2013, 2, 12, 15, 0, 0)) Hour)
+             [ "entre midi et 2"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 11, 30, 0), (2013, 2, 12, 13, 31, 0)) Minute)
+             [ "11h30-1h30"
+             , "de 11h30 à 1h30"
+             , "de 11h30 jusqu'à 1h30"
+             ]
+  , examples (datetime (2013, 9, 21, 13, 30, 0) Minute)
+             [ "13h30 samedi 21 septembre"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 0, 0) Minute)
+             [ "à seize heures CET"
+             ]
+  , examples (datetimeInterval ((2013, 3, 25, 0, 0, 0), (2013, 4, 1, 0, 0, 0)) Day)
+             [ "fin mars"
+             , "fin du mois de mars"
+             ]
+  , examples (datetimeInterval ((2013, 4, 1, 0, 0, 0), (2013, 4, 6, 0, 0, 0)) Day)
+             [ "début avril"
+             , "début du mois d'avril"
+             ]
+  , examples (datetimeInterval ((2013, 4, 1, 0, 0, 0), (2013, 4, 15, 0, 0, 0)) Day)
+             [ "la première quinzaine d'avril"
+             ]
+  , examples (datetimeInterval ((2013, 4, 15, 0, 0, 0), (2013, 5, 1, 0, 0, 0)) Day)
+             [ "la deuxième quinzaine d'avril"
+             ]
+  , examples (datetimeInterval ((2013, 4, 1, 0, 0, 0), (2013, 4, 6, 0, 0, 0)) Day)
+             [ "début avril"
+             , "début du mois d'avril"
+             ]
+  , examples (datetimeInterval ((2013, 12, 10, 0, 0, 0), (2013, 12, 20, 0, 0, 0)) Day)
+             [ "mi-décembre"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "mars"
+             , "en mars"
+             , "au mois de mars"
+             , "le mois de mars"
+             ]
+  , examples (datetime (2013, 8, 15, 0, 0, 0) Day)
+             [ "jeudi 15"
+             ]
+  , examples (datetime (2013, 8, 15, 8, 0, 0) Hour)
+             [ "jeudi 15 à 8h"
+             ]
+  ]
diff --git a/Duckling/Time/FR/Rules.hs b/Duckling/Time/FR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/FR/Rules.hs
@@ -0,0 +1,2179 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.FR.Rules
+  ( rules ) where
+
+import Control.Monad (liftM2)
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Regex.Types
+import Duckling.Time.Helpers
+import Duckling.Time.Types (TimeData (..))
+import qualified Duckling.Time.Types as TTime
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleAujourdhui :: Rule
+ruleAujourdhui = Rule
+  { name = "aujourd'hui"
+  , pattern =
+    [ regex "(aujourd'? ?hui)|(ce jour)|(dans la journ(\x00e9|e)e?)|(en ce moment)"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 0
+  }
+
+ruleNamedday :: Rule
+ruleNamedday = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "lun\\.?(di)?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 1
+  }
+
+ruleDayofmonthNamedmonth :: Rule
+ruleDayofmonthNamedmonth = Rule
+  { name = "<day-of-month> <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleHier :: Rule
+ruleHier = Rule
+  { name = "hier"
+  , pattern =
+    [ regex "hier|la veille"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 1
+  }
+
+ruleDbutDeJourne :: Rule
+ruleDbutDeJourne = Rule
+  { name = "début de journée"
+  , pattern =
+    [ regex "d(\x00e9|e)but de journ(\x00e9|e)e"
+    ]
+  , prod = \_ -> tt . partOfDay . mkLatent $
+        interval TTime.Open (hour False 6, hour False 10)
+  }
+
+ruleLeLendemainDuTime :: Rule
+ruleLeLendemainDuTime = Rule
+  { name = "le lendemain du <time>"
+  , pattern =
+    [ regex "(le|au)? ?lendemain du"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ cycleNthAfter True TG.Day 1 td
+      _ -> Nothing
+  }
+
+ruleNamedmonth12 :: Rule
+ruleNamedmonth12 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "d(\x00e9|e)cembre|d(\x00e9|e)c\\.?"
+    ]
+  , prod = \_ -> tt $ month 12
+  }
+
+ruleCePartofday :: Rule
+ruleCePartofday = Rule
+  { name = "ce <part-of-day>"
+  , pattern =
+    [ regex "cet?t?e?"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time . partOfDay . notLatent <$>
+        intersect (cycleNth TG.Day 0) td
+      _ -> Nothing
+  }
+
+ruleCeTime :: Rule
+ruleCeTime = Rule
+  { name = "ce <time>"
+  , pattern =
+    [ regex "ce"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleNamedday2 :: Rule
+ruleNamedday2 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "mar\\.?(di)?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 2
+  }
+
+ruleDurationAvantTime :: Rule
+ruleDurationAvantTime = Rule
+  { name = "<duration> avant <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "avant"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_:Token Time td:_) ->
+        tt $ durationBefore dd td
+      _ -> Nothing
+  }
+
+ruleMidi :: Rule
+ruleMidi = Rule
+  { name = "midi"
+  , pattern =
+    [ regex "midi"
+    ]
+  , prod = \_ -> tt $ hour False 12
+  }
+
+ruleNamedday6 :: Rule
+ruleNamedday6 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "sam\\.?(edi)?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 6
+  }
+
+ruleDatetimeDatetimeInterval :: Rule
+ruleDatetimeDatetimeInterval = Rule
+  { name = "<datetime> - <datetime> (interval)"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "\\-|au|jusqu'(au?|\x00e0)"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth7 :: Rule
+ruleNamedmonth7 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "juillet|juil?\\."
+    ]
+  , prod = \_ -> tt $ month 7
+  }
+
+ruleDatetimeddMonthinterval :: Rule
+ruleDatetimeddMonthinterval = Rule
+  { name = "<datetime>-dd <month>(interval)"
+  , pattern =
+    [ dimension Time
+    , regex "\\-|au|jusqu'au"
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token RegexMatch (GroupMatch (match:_)):Token Time td2:_) -> do
+        n <- parseInt match
+        from <- intersect td2 td1
+        to <- intersect (dayOfMonth n) td2
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleDeTimeofdayTimeofdayInterval :: Rule
+ruleDeTimeofdayTimeofdayInterval = Rule
+  { name = "de <time-of-day> - <time-of-day> (interval)"
+  , pattern =
+    [ regex "(midi )?de"
+    , Predicate isATimeOfDay
+    , regex "\\-|(jusqu')?(\x00e0|au?)"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNProchainsCycle :: Rule
+ruleNProchainsCycle = Rule
+  { name = "n prochains <cycle>"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2 9999
+    , regex "prochaine?s?|suivante?s?|apr(e|\x00e8|\x00e9)s"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token TimeGrain grain:_) -> do
+        n <- getIntValue token
+        tt $ cycleN True grain n
+      _ -> Nothing
+  }
+
+ruleNDerniersCycle :: Rule
+ruleNDerniersCycle = Rule
+  { name = "n derniers <cycle>"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2 9999
+    , regex "derni(e|\x00e8|\x00e9)re?s?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token TimeGrain grain:_) -> do
+        n <- getIntValue token
+        tt . cycleN True grain $ - n
+      _ -> Nothing
+  }
+
+ruleAvantTimeofday :: Rule
+ruleAvantTimeofday = Rule
+  { name = "avant <time-of-day>"
+  , pattern =
+    [ regex "(n[ ']importe quand )?(avant|jusqu'(a|\x00e0))"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ withDirection TTime.Before td
+      _ -> Nothing
+  }
+
+ruleEntreDatetimeEtDatetimeInterval :: Rule
+ruleEntreDatetimeEtDatetimeInterval = Rule
+  { name = "entre <datetime> et <datetime> (interval)"
+  , pattern =
+    [ regex "entre"
+    , dimension Time
+    , regex "et"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleDatetimedayofweekDdMonthinterval :: Rule
+ruleDatetimedayofweekDdMonthinterval = Rule
+  { name = "<datetime>-<day-of-week> dd <month>(interval)"
+  , pattern =
+    [ dimension Time
+    , regex "\\-|(jusqu')?au"
+    , Predicate isADayOfWeek
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:_:Token RegexMatch (GroupMatch (match:_)):Token Time td2:_) -> do
+        n <- parseInt match
+        from <- intersect td2 td1
+        to <- intersect (dayOfMonth n) td2
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleHhhmmTimeofday :: Rule
+ruleHhhmmTimeofday = Rule
+  { name = "hh(:|h)mm (time-of-day)"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))[:h]([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        h <- parseInt m1
+        m <- parseInt m2
+        tt $ hourMinute (h < 12) h m
+      _ -> Nothing
+  }
+
+ruleDdMm :: Rule
+ruleDdMm = Rule
+  { name = "dd mm"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9]) (1[0-2]|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (dd:mm:_)):_) -> do
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleDdMmYyyy :: Rule
+ruleDdMmYyyy = Rule
+  { name = "dd mm yyyy"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9]) (1[0-2]|0?[1-9]) (\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (dd:mm:yy:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleNamedday4 :: Rule
+ruleNamedday4 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "jeu\\.?(di)?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 4
+  }
+
+ruleNamedmonthProchain :: Rule
+ruleNamedmonthProchain = Rule
+  { name = "<named-month> prochain"
+  , pattern =
+    [ dimension Time
+    , regex "prochain"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 1 False td
+      _ -> Nothing
+  }
+
+ruleDiciDuration :: Rule
+ruleDiciDuration = Rule
+  { name = "d'ici <duration>"
+  , pattern =
+    [ regex "d'ici|dans l('|es?)"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $
+          interval TTime.Open (cycleNth TG.Second 0, inDuration dd)
+      _ -> Nothing
+  }
+
+ruleToussaint :: Rule
+ruleToussaint = Rule
+  { name = "toussaint"
+  , pattern =
+    [ regex "((la |la journ(\x00e9|e)e de la |jour de la )?toussaint|jour des morts)"
+    ]
+  , prod = \_ -> tt $ monthDay 11 1
+  }
+
+ruleDernierCycleDeTimeLatent :: Rule
+ruleDernierCycleDeTimeLatent = Rule
+  { name = "dernier <cycle> de <time> (latent)"
+  , pattern =
+    [ regex "derni(e|\x00e9|\x00e8)re?"
+    , dimension TimeGrain
+    , regex "d['e]"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleLastOf grain td
+      _ -> Nothing
+  }
+
+ruleDurationApresTime :: Rule
+ruleDurationApresTime = Rule
+  { name = "<duration> apres <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "apr(e|\x00e8)s"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_:Token Time td:_) ->
+        tt $ durationAfter dd td
+      _ -> Nothing
+  }
+
+ruleNCycleAprs :: Rule
+ruleNCycleAprs = Rule
+  { name = "n <cycle> après"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2 9999
+    , dimension TimeGrain
+    , regex "(d')? ?apr(e|\x00e8|\x00e9)s|qui sui(t|ves?)|plus tard"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token TimeGrain grain:_) -> do
+        n <- getIntValue token
+        tt $ cycleNth grain n
+      _ -> Nothing
+  }
+
+ruleSeason4 :: Rule
+ruleSeason4 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "(ce )?printemps"
+    ]
+  , prod = \_ -> tt $
+      interval TTime.Open (monthDay 3 20, monthDay 6 21)
+  }
+
+ruleYearLatent2 :: Rule
+ruleYearLatent2 = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2101 10000
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ year n
+      _ -> Nothing
+  }
+
+ruleNCycleAvant :: Rule
+ruleNCycleAvant = Rule
+  { name = "n <cycle> avant"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2 9999
+    , dimension TimeGrain
+    , regex "(d')? ?avant|plus t(o|\x00f4)t"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token TimeGrain grain:_) -> do
+        n <- getIntValue token
+        tt . cycleNth grain $ - n
+      _ -> Nothing
+  }
+
+ruleDimTimeDuMatin :: Rule
+ruleDimTimeDuMatin = Rule
+  { name = "<dim time> du matin"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "((du|dans|de) )?((au|le|la) )?mat(in(\x00e9|e)?e?)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        let morning = partOfDay . mkLatent $
+                        interval TTime.Open (hour False 0, hour False 12)
+        in Token Time <$> intersect td morning
+      _ -> Nothing
+  }
+
+ruleOrdinalWeekendDeTime :: Rule
+ruleOrdinalWeekendDeTime = Rule
+  { name = "<ordinal> week-end de <time>"
+  , pattern =
+    [ dimension Ordinal
+    , regex "week(\\s|-)?end (d['eu]|en|du mois de)"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        from <- intersect (dayOfWeek 5) (hour False 18)
+        to <- intersect (dayOfWeek 1) (hour False 0)
+        td2 <- intersect td (interval TTime.Open (from, to))
+        tt $ predNth (n - 1) False td2
+      _ -> Nothing
+  }
+
+ruleNamedmonth :: Rule
+ruleNamedmonth = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "janvier|janv\\.?"
+    ]
+  , prod = \_ -> tt $ month 1
+  }
+
+ruleHourofdayEtQuart :: Rule
+ruleHourofdayEtQuart = Rule
+  { name = "<hour-of-day> et quart"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "(et )?quart"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 15
+      _ -> Nothing
+  }
+
+ruleHourofdayEtDemi :: Rule
+ruleHourofdayEtDemi = Rule
+  { name = "<hour-of-day> et demi"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "et demie?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+
+ruleHourofdayEtTroisQuart :: Rule
+ruleHourofdayEtTroisQuart = Rule
+  { name = "<hour-of-day> et trois quarts"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "(et )?(3|trois) quarts?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 45
+      _ -> Nothing
+  }
+
+ruleHourofdayEtpassDeNumeral :: Rule
+ruleHourofdayEtpassDeNumeral = Rule
+  { name = "<hour-of-day> et|passé de <number>"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "et|(pass(\x00e9|e)e? de)"
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleHourofdayEtpassDeNumeralMinutes :: Rule
+ruleHourofdayEtpassDeNumeralMinutes = Rule
+  { name = "<hour-of-day> et|passé de <number> minutes"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "et|(pass(\x00e9|e)e? de)"
+    , Predicate $ isIntegerBetween 1 59
+    , regex "min\\.?(ute)?s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleHourofdayInteger :: Rule
+ruleHourofdayInteger = Rule
+  { name = "<hour-of-day> <integer> (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleHourofdayIntegerMinutes :: Rule
+ruleHourofdayIntegerMinutes = Rule
+  { name = "<hour-of-day> <integer> minutes"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    , regex "min\\.?(ute)?s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleHourofdayMoinsIntegerAsRelativeMinutes :: Rule
+ruleHourofdayMoinsIntegerAsRelativeMinutes = Rule
+  { name = "<hour-of-day> moins <integer> (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "moins( le)?"
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_:token:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleHourofdayMoinsQuart :: Rule
+ruleHourofdayMoinsQuart = Rule
+  { name = "<hour-of-day> moins quart"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "moins( le)? quart"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> do
+        t <- minutesBefore 15 td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleAprsLeDayofmonth :: Rule
+ruleAprsLeDayofmonth = Rule
+  { name = "après le <day-of-month>"
+  , pattern =
+    [ regex "(apr(e|\x00e8)s le|(a|\x00e0) partir du)"
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        n <- getIntValue token
+        tt . withDirection TTime.After $ dayOfMonth n
+      _ -> Nothing
+  }
+
+ruleNamedmonth3 :: Rule
+ruleNamedmonth3 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "mars|mar\\.?"
+    ]
+  , prod = \_ -> tt $ month 3
+  }
+
+ruleCycleDernier :: Rule
+ruleCycleDernier = Rule
+  { name = "<cycle> dernier"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "derni(\x00e8|e)re?|pass(\x00e9|e)e?|pr(e|\x00e9)c(e|\x00e9)dente?|(d')? ?avant"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_) ->
+        tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleLeOrdinalCycleDeTime :: Rule
+ruleLeOrdinalCycleDeTime = Rule
+  { name = "le <ordinal> <cycle> de <time>"
+  , pattern =
+    [ regex "l[ea]"
+    , dimension Ordinal
+    , dimension TimeGrain
+    , regex "d['eu]|en"
+    , 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
+  }
+
+ruleEnSemaine :: Rule
+ruleEnSemaine = Rule
+  { name = "en semaine"
+  , pattern =
+    [ regex "(pendant la |en )?semaine"
+    ]
+  , prod = \_ -> tt $
+      interval TTime.Open (dayOfWeek 1, dayOfWeek 5)
+  }
+
+ruleDdmm :: Rule
+ruleDdmm = Rule
+  { name = "dd/-mm"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[/-](1[0-2]|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (dd:mm:_)):_) -> do
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleNamedmonthnameddayDernierpass :: Rule
+ruleNamedmonthnameddayDernierpass = Rule
+  { name = "<named-month|named-day> dernier|passé"
+  , pattern =
+    [ dimension Time
+    , regex "derni(e|\x00e9|\x00e8)re?|pass(\x00e9|e)e?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleNamedmonth4 :: Rule
+ruleNamedmonth4 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "avril|avr\\.?"
+    ]
+  , prod = \_ -> tt $ month 4
+  }
+
+ruleLeCycleDernier :: Rule
+ruleLeCycleDernier = Rule
+  { name = "le <cycle> dernier"
+  , pattern =
+    [ regex "l[ae']? ?"
+    , dimension TimeGrain
+    , regex "derni(\x00e8|e)re?|pass(\x00e9|e)e?"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleNCyclePassesprecedents :: Rule
+ruleNCyclePassesprecedents = Rule
+  { name = "n <cycle> passes|precedents"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2 9999
+    , dimension TimeGrain
+    , regex "pass(e|\x00e8|\x00e9)(e|\x00e8|\x00e9)?s?|pr(e|\x00e9)c(e|\x00e9)dente?s?|(d')? ?avant|plus t(o|\x00f4)t"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token TimeGrain grain:_) -> do
+        n <- getIntValue token
+        tt . cycleN True grain $ - n
+      _ -> Nothing
+  }
+
+ruleSoir :: Rule
+ruleSoir = Rule
+  { name = "soir"
+  , pattern =
+    [ regex "soir(\x00e9|e)?e?"
+    ]
+  , prod = \_ -> tt . mkLatent . partOfDay $
+      interval TTime.Open (hour False 18, hour False 0)
+  }
+
+ruleDbutDeSoire :: Rule
+ruleDbutDeSoire = Rule
+  { name = "début de soirée"
+  , pattern =
+    [ regex "d(\x00e9|e)but de soir(\x00e9|e)e?"
+    ]
+  , prod = \_ -> tt . mkLatent . partOfDay $
+      interval TTime.Open (hour False 18, hour False 21)
+  }
+
+ruleFinDeSoire :: Rule
+ruleFinDeSoire = Rule
+  { name = "fin de soirée"
+  , pattern =
+    [ regex "fin de soir(\x00e9|e)e?"
+    ]
+  , prod = \_ -> tt . mkLatent . partOfDay $
+      interval TTime.Open (hour False 21, hour False 0)
+  }
+
+ruleDbutDeMatine :: Rule
+ruleDbutDeMatine = Rule
+  { name = "début de matinée"
+  , pattern =
+    [ regex "le matin (tr(e|\x00e8)s )?t(\x00f4|o)t|(tr(e|\x00e8)s )?t(\x00f4|o)t le matin|d(\x00e9|e)but de matin(\x00e9|e)e"
+    ]
+  , prod = \_ -> tt . mkLatent . partOfDay $
+      interval TTime.Open (hour False 7, hour False 9)
+  }
+
+ruleNamedday5 :: Rule
+ruleNamedday5 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "ven\\.?(dredi)?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 5
+  }
+
+ruleDdddMonthinterval :: Rule
+ruleDdddMonthinterval = Rule
+  { name = "dd-dd <month>(interval)"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])"
+    , regex "\\-|au|jusqu'au"
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:_)):_:Token RegexMatch (GroupMatch (m2:_)):Token Time td:_) -> do
+        d1 <- parseInt m1
+        d2 <- parseInt m2
+        from <- intersect td (dayOfMonth d1)
+        to <- intersect td (dayOfMonth d2)
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleTimeofdayLatent :: Rule
+ruleTimeofdayLatent = 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 < 12) n
+      _ -> Nothing
+  }
+
+ruleDuDddayofweekDdMonthinterval :: Rule
+ruleDuDddayofweekDdMonthinterval = Rule
+  { name = "du dd-<day-of-week> dd <month>(interval)"
+  , pattern =
+    [ regex "du"
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    , regex "\\-|au|jusqu'au"
+    , Predicate isADayOfWeek
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token RegexMatch (GroupMatch (m1:_)):_:_:Token RegexMatch (GroupMatch (m2:_)):Token Time td:_) -> do
+        d1 <- parseInt m1
+        d2 <- parseInt m2
+        from <- intersect (dayOfMonth d1) td
+        to <- intersect (dayOfMonth d2) td
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleDbutNamedmonthinterval :: Rule
+ruleDbutNamedmonthinterval = Rule
+  { name = "début <named-month>(interval)"
+  , pattern =
+    [ regex "d(\x00e9|e)but( du mois d[e'] ?)?"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> do
+        from <- intersect (dayOfMonth 1) td
+        to <- intersect (dayOfMonth 5) td
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleNamedmonth2 :: Rule
+ruleNamedmonth2 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "f(\x00e9|e)vrier|f(\x00e9|e)v\\.?"
+    ]
+  , prod = \_ -> tt $ month 2
+  }
+
+ruleMilieuDeJourne :: Rule
+ruleMilieuDeJourne = Rule
+  { name = "milieu de journée"
+  , pattern =
+    [ regex "milieu de journ(\x00e9|e)e"
+    ]
+  , prod = \_ -> tt . mkLatent . partOfDay $
+      interval TTime.Open (hour False 11, hour False 16)
+  }
+
+ruleFinDeJourne :: Rule
+ruleFinDeJourne = Rule
+  { name = "fin de journée"
+  , pattern =
+    [ regex "fin de journ(\x00e9|e)e"
+    ]
+  , prod = \_ -> tt . mkLatent . partOfDay $
+      interval TTime.Open (hour False 17, hour False 21)
+  }
+
+ruleLeCycleDeTime :: Rule
+ruleLeCycleDeTime = Rule
+  { name = "le <cycle> de <time>"
+  , pattern =
+    [ regex "l[ea]"
+    , dimension TimeGrain
+    , regex "d['eu]|en"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain 0 td
+      _ -> Nothing
+  }
+
+ruleSeason3 :: Rule
+ruleSeason3 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "(cet )?hiver"
+    ]
+  , prod = \_ -> tt $
+      interval TTime.Open (monthDay 12 21, monthDay 3 20)
+  }
+
+ruleSeason :: Rule
+ruleSeason = Rule
+  { name = "season"
+  , pattern =
+    [ regex "(cet )?(\x00e9|e)t(\x00e9|e)"
+    ]
+  , prod = \_ -> tt $
+      interval TTime.Open (monthDay 6 21, monthDay 9 23)
+  }
+
+ruleAprsmidi :: Rule
+ruleAprsmidi = Rule
+  { name = "après-midi"
+  , pattern =
+    [ regex "apr(e|\x00e9|\x00e8)s?[ \\-]?midi"
+    ]
+  , prod = \_ -> tt . mkLatent . partOfDay $
+      interval TTime.Open (hour False 12, hour False 19)
+  }
+
+ruleNoel :: Rule
+ruleNoel = Rule
+  { name = "noel"
+  , pattern =
+    [ regex "(jour de )?no(e|\x00eb)l"
+    ]
+  , prod = \_ -> tt $ monthDay 12 25
+  }
+
+ruleDayofweekProchain :: Rule
+ruleDayofweekProchain = Rule
+  { name = "<day-of-week> prochain"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex "prochain"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleDemain :: Rule
+ruleDemain = Rule
+  { name = "demain"
+  , pattern =
+    [ regex "(demain)|(le lendemain)"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 1
+  }
+
+ruleNamedmonthnameddaySuivantdaprs :: Rule
+ruleNamedmonthnameddaySuivantdaprs = Rule
+  { name = "<named-month|named-day> suivant|d'après"
+  , pattern =
+    [ dimension Time
+    , regex "suivante?s?|d'apr(e|\x00e9|\x00e8)s"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 1 False td
+      _ -> Nothing
+  }
+
+ruleNameddayEnHuit :: Rule
+ruleNameddayEnHuit = Rule
+  { name = "<named-day> en huit"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex "en (huit|8)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 1 False td
+      _ -> Nothing
+  }
+
+ruleHhmmMilitaryTimeofday :: Rule
+ruleHhmmMilitaryTimeofday = Rule
+  { name = "hhmm (military time-of-day)"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        h <- parseInt m1
+        m <- parseInt m2
+        tt . mkLatent $ hourMinute False h m
+      _ -> Nothing
+  }
+
+ruleDimTimeDuSoir :: Rule
+ruleDimTimeDuSoir = Rule
+  { name = "<dim time> du soir"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "((du|dans|de) )?((au|le|la) )?soir(\x00e9|e)?e?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        let td2 = mkLatent . partOfDay $
+                    interval TTime.Open (hour False 16, hour False 0)
+        in Token Time <$> intersect td td2
+      _ -> Nothing
+  }
+
+ruleAprsTimeofday :: Rule
+ruleAprsTimeofday = Rule
+  { name = "après <time-of-day>"
+  , pattern =
+    [ regex "(apr(e|\x00e8)s|(a|\x00e0) partir de)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ withDirection TTime.After td
+      _ -> Nothing
+  }
+
+ruleAprsLeDjeuner :: Rule
+ruleAprsLeDjeuner = Rule
+  { name = "après le déjeuner"
+  , pattern =
+    [ regex "apr(e|\x00e8)s (le )?d(e|\x00e9|\x00e8)jeuner"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 13, hour False 17)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ Predicate isNotLatent
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleErMai :: Rule
+ruleErMai = Rule
+  { name = "1er mai"
+  , pattern =
+    [ regex "f(e|\x00ea)te du travail"
+    ]
+  , prod = \_ -> tt $ monthDay 5 1
+  }
+
+rulePremireQuinzaineDeNamedmonthinterval :: Rule
+rulePremireQuinzaineDeNamedmonthinterval = Rule
+  { name = "première quinzaine de <named-month>(interval)"
+  , pattern =
+    [ regex "(premi(\x00e8|e)re|1 ?(\x00e8|e)re) (quinzaine|15 ?aine) d[e']"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> do
+        from <- intersect (dayOfMonth 1) td
+        to <- intersect (dayOfMonth 14) td
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleDeDatetimeDatetimeInterval :: Rule
+ruleDeDatetimeDatetimeInterval = Rule
+  { name = "de <datetime> - <datetime> (interval)"
+  , pattern =
+    [ regex "de|depuis|du"
+    , dimension Time
+    , regex "\\-|au|jusqu'(au?|\x00e0)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleAvanthier :: Rule
+ruleAvanthier = Rule
+  { name = "avant-hier"
+  , pattern =
+    [ regex "avant[- ]?hier"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 2
+  }
+
+ruleCycleProchainsuivantdaprs :: Rule
+ruleCycleProchainsuivantdaprs = Rule
+  { name = "<cycle> prochain|suivant|d'après"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "prochaine?|suivante?|qui suit|(d')? ?apr(e|\x00e8|\x00e9)s"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleNamedmonth6 :: Rule
+ruleNamedmonth6 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "juin|jun\\.?"
+    ]
+  , prod = \_ -> tt $ month 6
+  }
+
+ruleAprsLeTravail :: Rule
+ruleAprsLeTravail = Rule
+  { name = "après le travail"
+  , pattern =
+    [ regex "apr(e|\x00e8)s (le )?travail"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 17, hour False 21)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleNamedmonth8 :: Rule
+ruleNamedmonth8 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "ao(\x00fb|u)t|aou\\.?"
+    ]
+  , prod = \_ -> tt $ month 8
+  }
+
+ruleLeDayofmonthDatetime :: Rule
+ruleLeDayofmonthDatetime = Rule
+  { name = "le <day-of-month> à <datetime>"
+  , pattern =
+    [ regex "le"
+    , Predicate isDOMInteger
+    , regex "(a|\x00e0)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        Token Time <$> intersect (dayOfMonth n) td
+      _ -> Nothing
+  }
+
+ruleWeekend :: Rule
+ruleWeekend = Rule
+  { name = "week-end"
+  , pattern =
+    [ regex "week(\\s|-)?end"
+    ]
+  , prod = \_ -> do
+      from <- intersect (dayOfWeek 5) (hour False 18)
+      to <- intersect (dayOfWeek 1) (hour False 0)
+      tt $ interval TTime.Open (from, to)
+  }
+
+ruleCedansLeCycle :: Rule
+ruleCedansLeCycle = Rule
+  { name = "ce|dans le <cycle>"
+  , pattern =
+    [ regex "(cet?t?e?s?)|(dans l[ae']? ?)"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 0
+      _ -> Nothing
+  }
+
+ruleDansDuration :: Rule
+ruleDansDuration = Rule
+  { name = "dans <duration>"
+  , pattern =
+    [ regex "dans"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleOrdinalCycleDeTime :: Rule
+ruleOrdinalCycleDeTime = Rule
+  { name = "<ordinal> <cycle> de <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "d['eu]|en"
+    , 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
+  }
+
+ruleAvantLeDjeuner :: Rule
+ruleAvantLeDjeuner = Rule
+  { name = "avant le déjeuner"
+  , pattern =
+    [ regex "avant (le )?d(e|\x00e9|\x00e8)jeuner"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 10, hour False 12)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleDernierWeekendDeTime :: Rule
+ruleDernierWeekendDeTime = Rule
+  { name = "dernier week-end de <time>"
+  , pattern =
+    [ regex "dernier week(\\s|-)?end (d['eu]|en|du mois de)"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> do
+        from <- intersect (dayOfWeek 5) (hour False 18)
+        to <- intersect (dayOfWeek 1) (hour False 0)
+        tt $ predLastOf (interval TTime.Open (from, to)) td
+      _ -> Nothing
+  }
+
+ruleLeCycleProchainsuivantdaprs :: Rule
+ruleLeCycleProchainsuivantdaprs = Rule
+  { name = "le <cycle> prochain|suivant|d'après"
+  , pattern =
+    [ regex "l[ae']? ?|une? ?"
+    , dimension TimeGrain
+    , regex "prochaine?|suivante?|qui suit|(d'? ?)?apr(e|\x00e8|\x00e9)s"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleDimTimePartofday :: Rule
+ruleDimTimePartofday = Rule
+  { name = "<dim time> <part-of-day>"
+  , pattern =
+    [ dimension Time
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleEnNamedmonth :: Rule
+ruleEnNamedmonth = Rule
+  { name = "en <named-month>"
+  , pattern =
+    [ regex "en|au mois de?'?"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleEntreTimeofdayEtTimeofdayInterval :: Rule
+ruleEntreTimeofdayEtTimeofdayInterval = Rule
+  { name = "entre <time-of-day> et <time-of-day> (interval)"
+  , pattern =
+    [ regex "entre"
+    , Predicate isATimeOfDay
+    , regex "et"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleIntersectByDeOr :: Rule
+ruleIntersectByDeOr = Rule
+  { name = "intersect by 'de' or ','"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "de|,"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleDeuximeQuinzaineDeNamedmonthinterval :: Rule
+ruleDeuximeQuinzaineDeNamedmonthinterval = Rule
+  { name = "deuxième quinzaine de <named-month>(interval)"
+  , pattern =
+    [ regex "(deuxi(\x00e8|e)me|2 ?(\x00e8|e)me) (quinzaine|15 ?aine) d[e']"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> do
+        from <- intersect (dayOfMonth 15) td
+        let to = cycleLastOf TG.Day td
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+rulePartofdayDuDimTime :: Rule
+rulePartofdayDuDimTime = Rule
+  { name = "<part-of-day> du <dim time>"
+  , pattern =
+    [ Predicate isAPartOfDay
+    , regex "du"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleYyyymmdd :: Rule
+ruleYyyymmdd = Rule
+  { name = "yyyy-mm-dd"
+  , pattern =
+    [ regex "(\\d{2,4})-(1[0-2]|0?[1-9])-(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
+        y <- parseInt m1
+        m <- parseInt m2
+        d <- parseInt m3
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleMilieuDeSemaine :: Rule
+ruleMilieuDeSemaine = Rule
+  { name = "milieu de semaine"
+  , pattern =
+    [ regex "(en |au )?milieu de (cette |la )?semaine"
+    ]
+  , prod = \_ -> tt $
+      interval TTime.Open (dayOfWeek 3, dayOfWeek 4)
+  }
+
+ruleDbutDeSemaine :: Rule
+ruleDbutDeSemaine = Rule
+  { name = "début de semaine"
+  , pattern =
+    [ regex "(en |au )?d(\x00e9|e)but de (cette |la )?semaine"
+    ]
+  , prod = \_ -> tt $
+      interval TTime.Open (dayOfWeek 1, dayOfWeek 2)
+  }
+
+ruleTimeofdayHeures :: Rule
+ruleTimeofdayHeures = Rule
+  { name = "<time-of-day> heures"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "h\\.?(eure)?s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleDernierDayofweekDeTimeLatent :: Rule
+ruleDernierDayofweekDeTimeLatent = Rule
+  { name = "dernier <day-of-week> de <time> (latent)"
+  , pattern =
+    [ regex "derni(e|\x00e9|\x00e8)re?"
+    , Predicate isADayOfWeek
+    , regex "d['e]"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ predLastOf td1 td2
+      _ -> Nothing
+  }
+
+ruleCeDayofweek :: Rule
+ruleCeDayofweek = Rule
+  { name = "ce <day-of-week>"
+  , pattern =
+    [ regex "ce"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleDuDatetimedayofweekDdMonthinterval :: Rule
+ruleDuDatetimedayofweekDdMonthinterval = Rule
+  { name = "du <datetime>-<day-of-week> dd <month>(interval)"
+  , pattern =
+    [ regex "du"
+    , dimension Time
+    , regex "\\-|au|jusqu'au"
+    , Predicate isADayOfWeek
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:_:Token RegexMatch (GroupMatch (m:_)):Token Time td2:_) -> do
+        n <- parseInt m
+        from <- intersect td2 td1
+        to <- intersect (dayOfMonth n) td2
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleDuDdAuDdinterval :: Rule
+ruleDuDdAuDdinterval = Rule
+  { name = "du dd au dd(interval)"
+  , pattern =
+    [ regex "du"
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    , regex "au|jusqu'au"
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token RegexMatch (GroupMatch (m1:_)):_:Token RegexMatch (GroupMatch (m2:_)):_) -> do
+        n1 <- parseInt m1
+        n2 <- parseInt m2
+        tt $ interval TTime.Closed (dayOfMonth n1, dayOfMonth n2)
+      _ -> Nothing
+  }
+
+ruleMatin :: Rule
+ruleMatin = Rule
+  { name = "matin"
+  , pattern =
+    [ regex "mat(in(\x00e9|e)?e?)?"
+    ]
+  , prod = \_ -> tt . mkLatent . partOfDay $
+      interval TTime.Open (hour False 4, hour False 12)
+  }
+
+ruleYearLatent :: Rule
+ruleYearLatent = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween (- 10000) 999
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ year n
+      _ -> Nothing
+  }
+
+ruleSeason2 :: Rule
+ruleSeason2 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "(cet )?automne"
+    ]
+  , prod = \_ -> tt $
+      interval TTime.Open (monthDay 9 23, monthDay 12 21)
+  }
+
+ruleVersTimeofday :: Rule
+ruleVersTimeofday = Rule
+  { name = "à|vers <time-of-day>"
+  , pattern =
+    [ regex "(vers|autour de|(a|\x00e0) environ|aux alentours de|(a|\x00e0))"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleFinNamedmonthinterval :: Rule
+ruleFinNamedmonthinterval = Rule
+  { name = "fin <named-month>(interval)"
+  , pattern =
+    [ regex "fin( du mois d[e']? ?)?"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> do
+        from <- intersect (dayOfMonth 25) td
+        let to = cycleLastOf TG.Day td
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleDayofweekDayofmonth :: Rule
+ruleDayofweekDayofmonth = Rule
+  { name = "<day-of-week> <day-of-month>"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleNameddayEnQuinze :: Rule
+ruleNameddayEnQuinze = Rule
+  { name = "<named-day> en quinze"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex "en (quinze|15)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 2 False td
+      _ -> Nothing
+  }
+
+ruleLeDayofmonthNonOrdinal :: Rule
+ruleLeDayofmonthNonOrdinal = Rule
+  { name = "le <day-of-month> (non ordinal)"
+  , pattern =
+    [ regex "le"
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        n <- getIntValue token
+        tt $ dayOfMonth n
+      _ -> Nothing
+  }
+
+ruleIntersectByMaisparExempleplutt :: Rule
+ruleIntersectByMaisparExempleplutt = Rule
+  { name = "intersect by 'mais/par exemple/plutôt'"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "mais|par exemple|plut(\x00f4|o)t"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleLeCycleAprssuivantTime :: Rule
+ruleLeCycleAprssuivantTime = Rule
+  { name = "le <cycle> après|suivant <time>"
+  , pattern =
+    [ regex "l[ea']? ?"
+    , dimension TimeGrain
+    , regex "suivante?|apr(e|\x00e8|\x00e9)s"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain 1 td
+      _ -> Nothing
+  }
+
+ruleEntreDdEtDdMonthinterval :: Rule
+ruleEntreDdEtDdMonthinterval = Rule
+  { name = "entre dd et dd <month>(interval)"
+  , pattern =
+    [ regex "entre( le)?"
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    , regex "et( le)?"
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token RegexMatch (GroupMatch (m1:_)):_:Token RegexMatch (GroupMatch (m2:_)):Token Time td:_) -> do
+        n1 <- parseInt m1
+        n2 <- parseInt m2
+        from <- intersect (dayOfMonth n1) td
+        to <- intersect (dayOfMonth n2) td
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleAprsdemain :: Rule
+ruleAprsdemain = Rule
+  { name = "après-demain"
+  , pattern =
+    [ regex "apr(e|\x00e8)s[- ]?demain"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 2
+  }
+
+ruleNamedmonth5 :: Rule
+ruleNamedmonth5 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "mai"
+    ]
+  , prod = \_ -> tt $ month 5
+  }
+
+ruleNamedday7 :: Rule
+ruleNamedday7 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "dim\\.?(anche)?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 7
+  }
+
+ruleFinDeSemaine :: Rule
+ruleFinDeSemaine = Rule
+  { name = "fin de semaine"
+  , pattern =
+    [ regex "(en |(\x00e0|a) la )?fin de (cette |la )?semaine"
+    ]
+  , prod = \_ -> tt $
+      interval TTime.Open (dayOfWeek 4, dayOfWeek 7)
+  }
+
+ruleIlYADuration :: Rule
+ruleIlYADuration = Rule
+  { name = "il y a <duration>"
+  , pattern =
+    [ regex "il y a"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ durationAgo dd
+      _ -> Nothing
+  }
+
+ruleDudansLePartofday :: Rule
+ruleDudansLePartofday = Rule
+  { name = "du|dans le <part-of-day>"
+  , pattern =
+    [ regex "du|dans l[ae']? ?|au|en|l[ae' ]|d(\x00e8|e)s l?[ae']? ?"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleAuDjeuner :: Rule
+ruleAuDjeuner = Rule
+  { name = "au déjeuner"
+  , pattern =
+    [ regex "((\x00e0|a) l(')?heure du|pendant( le)?|au)? d(e|\x00e9|\x00e8)jeuner"
+    ]
+  , prod = \_ -> tt . mkLatent . partOfDay $
+      interval TTime.Open (hour False 12, hour False 14)
+  }
+
+ruleYear :: Rule
+ruleYear = Rule
+  { name = "year"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1000 2100
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt $ year n
+      _ -> Nothing
+  }
+
+ruleNamedmonth10 :: Rule
+ruleNamedmonth10 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "octobre|oct\\.?"
+    ]
+  , prod = \_ -> tt $ month 10
+  }
+
+ruleMaintenant :: Rule
+ruleMaintenant = Rule
+  { name = "maintenant"
+  , pattern =
+    [ regex "maintenant|(tout de suite)"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Second 0
+  }
+
+ruleMilieuDeMatine :: Rule
+ruleMilieuDeMatine = Rule
+  { name = "milieu de matinée"
+  , pattern =
+    [ regex "milieu de matin(\x00e9|e)e"
+    ]
+  , prod = \_ -> tt . mkLatent . partOfDay $
+      interval TTime.Open (hour False 9, hour False 11)
+  }
+
+ruleFinDeMatine :: Rule
+ruleFinDeMatine = Rule
+  { name = "fin de matinée"
+  , pattern =
+    [ regex "fin de matin(\x00e9|e)e"
+    ]
+  , prod = \_ -> tt . mkLatent . partOfDay $
+      interval TTime.Open (hour False 10, hour False 12)
+  }
+
+ruleDdmmyyyy :: Rule
+ruleDdmmyyyy = Rule
+  { name = "dd/-mm/-yyyy"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[/-](1[0-2]|0?[1-9])[-/](\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
+        y <- parseInt m3
+        m <- parseInt m2
+        d <- parseInt m1
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleLeCycleAvantprcdentTime :: Rule
+ruleLeCycleAvantprcdentTime = Rule
+  { name = "le <cycle> avant|précédent <time>"
+  , pattern =
+    [ regex "l[ea']? ?"
+    , dimension TimeGrain
+    , regex "avant|pr(\x00e9|e)c(\x00e9|e)dent"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain (-1) td
+      _ -> Nothing
+  }
+
+ruleDayOfMonthPremier :: Rule
+ruleDayOfMonthPremier = Rule
+  { name = "day of month (premier)"
+  , pattern =
+    [ regex "premier|prem\\.?|1er|1 er"
+    ]
+  , prod = \_ -> tt $ dayOfMonth 1
+  }
+
+ruleTimeofdayTimeofdayInterval :: Rule
+ruleTimeofdayTimeofdayInterval = Rule
+  { name = "<time-of-day> - <time-of-day> (interval)"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "\\-|(jusqu')?(au?|\x00e0)"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleLeVeilleDuTime :: Rule
+ruleLeVeilleDuTime = Rule
+  { name = "le veille du <time>"
+  , pattern =
+    [ regex "(la )?veille du"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ cycleNthAfter True TG.Day (-1) td
+      _ -> Nothing
+  }
+
+ruleNamedmonth11 :: Rule
+ruleNamedmonth11 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "novembre|nov\\.?"
+    ]
+  , prod = \_ -> tt $ month 11
+  }
+
+ruleDbutDaprsmidi :: Rule
+ruleDbutDaprsmidi = Rule
+  { name = "début d'après-midi"
+  , pattern =
+    [ regex "d(\x00e9|e)but d'apr(e|\x00e9|\x00e8)s?[ \\-]?midi"
+    ]
+  , prod = \_ -> tt . mkLatent . partOfDay $
+      interval TTime.Open (hour False 12, hour False 14)
+  }
+
+ruleLeTime :: Rule
+ruleLeTime = Rule
+  { name = "le <time>"
+  , pattern =
+    [ regex "l[ea]"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleSoirDeNol :: Rule
+ruleSoirDeNol = Rule
+  { name = "soir de noël"
+  , pattern =
+    [ regex "soir(\x00e9e)? de no(e|\x00eb)l"
+    ]
+  , prod = \_ -> do
+      from <- intersect (monthDay 12 24) (hour False 18)
+      to <- intersect (monthDay 12 25) (hour False 0)
+      tt $ interval TTime.Open (from, to)
+  }
+
+ruleDayofweekDayofmonthTimeofday :: Rule
+ruleDayofweekDayofmonthTimeofday = Rule
+  { name = "<day-of-week> <day-of-month> à <time-of-day>)"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , Predicate isDOMInteger
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:token:Token Time td2:_) -> do
+        dom <- intersectDOM td1 token
+        Token Time <$> intersect dom td2
+      _ -> Nothing
+  }
+
+ruleMilieuDaprsmidi :: Rule
+ruleMilieuDaprsmidi = Rule
+  { name = "fin d'après-midi"
+  , pattern =
+    [ regex "milieu d'apr(e|\x00e9|\x00e8)s?[ \\-]?midi"
+    ]
+  , prod = \_ -> tt . mkLatent . partOfDay $
+      interval TTime.Open (hour False 15, hour False 17)
+  }
+
+ruleFinDaprsmidi :: Rule
+ruleFinDaprsmidi = Rule
+  { name = "fin d'après-midi"
+  , pattern =
+    [ regex "fin d'apr(e|\x00e9|\x00e8)s?[ \\-]?midi"
+    ]
+  , prod = \_ -> tt . mkLatent . partOfDay $
+      interval TTime.Open (hour False 17, hour False 19)
+  }
+
+ruleJourDeLan :: Rule
+ruleJourDeLan = Rule
+  { name = "jour de l'an"
+  , pattern =
+    [ regex "(jour de l'|nouvel )an"
+    ]
+  , prod = \_ -> tt $ monthDay 1 1
+  }
+
+ruleNamedday3 :: Rule
+ruleNamedday3 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "mer\\.?(credi)?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 3
+  }
+
+ruleMinuit :: Rule
+ruleMinuit = Rule
+  { name = "minuit"
+  , pattern =
+    [ regex "minuit"
+    ]
+  , prod = \_ -> tt $ hour False 0
+  }
+
+ruleNCycleSuivants :: Rule
+ruleNCycleSuivants = Rule
+  { name = "n <cycle> suivants"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2 9999
+    , dimension TimeGrain
+    , regex "prochaine?s?|suivante?s?|apr(e|\x00e8|\x00e9)s|qui sui(t|ves?)|plus tard"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token TimeGrain grain:_) -> do
+        n <- getIntValue token
+        tt $ cycleN True grain n
+      _ -> Nothing
+  }
+
+ruleNamedmonth13 :: Rule
+ruleNamedmonth13 = Rule
+  { name = "<named-month>"
+  , pattern =
+    [ regex "mi[- ]"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> do
+        from <- intersect (dayOfMonth 10) td
+        to <- intersect (dayOfMonth 19) td
+        tt $ interval TTime.Open (from, to)
+      _ -> Nothing
+  }
+
+ruleDayofweekErdayofweekDdMonthinterval :: Rule
+ruleDayofweekErdayofweekDdMonthinterval = Rule
+  { name = "<day-of-week> 1er-<day-of-week> dd <month>(interval)"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex "premier|prem\\.?|1er|1 er"
+    , regex "\\-|au|jusqu'au"
+    , Predicate isADayOfWeek
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:_:_:_:Token RegexMatch (GroupMatch (m:_)):Token Time td:_) -> do
+        n <- parseInt m
+        from <- intersect (dayOfMonth 1) td
+        to <- intersect (dayOfMonth n) td
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleNamedmonth9 :: Rule
+ruleNamedmonth9 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "septembre|sept?\\.?"
+    ]
+  , prod = \_ -> tt $ month 9
+  }
+
+ruleFinDuMois :: Rule
+ruleFinDuMois = Rule
+  { name = "fin du mois"
+  , pattern =
+    [ regex "(((a|\x00e0) )?la )?fin (du|de) mois"
+    ]
+  , prod = \_ -> do
+      let from = cycleNthAfter False TG.Day (- 10) $ cycleNth TG.Month 1
+          to = cycleNth TG.Month 1
+      tt . mkLatent . partOfDay $ interval TTime.Open (from, to)
+  }
+
+ruleTimezone :: Rule
+ruleTimezone = Rule
+  { name = "<time> timezone"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\b(YEKT|YEKST|YAKT|YAKST|WITA|WIT|WIB|WGT|WGST|WFT|WET|WEST|WAT|WAST|VUT|VLAT|VLAST|VET|UZT|UYT|UYST|UTC|ULAT|TVT|TMT|TLT|TKT|TJT|TFT|TAHT|SST|SRT|SGT|SCT|SBT|SAST|SAMT|RET|PYT|PYST|PWT|PST|PONT|PMST|PMDT|PKT|PHT|PHOT|PGT|PETT|PETST|PET|PDT|OMST|OMSST|NZST|NZDT|NUT|NST|NPT|NOVT|NOVST|NFT|NDT|NCT|MYT|MVT|MUT|MST|MSK|MSD|MMT|MHT|MDT|MAWT|MART|MAGT|MAGST|LINT|LHST|LHDT|KUYT|KST|KRAT|KRAST|KGT|JST|IST|IRST|IRKT|IRKST|IRDT|IOT|IDT|ICT|HOVT|HKT|GYT|GST|GMT|GILT|GFT|GET|GAMT|GALT|FNT|FKT|FKST|FJT|FJST|EST|EGT|EGST|EET|EEST|EDT|ECT|EAT|EAST|EASST|DAVT|ChST|CXT|CVT|CST|COT|CLT|CLST|CKT|CHAST|CHADT|CET|CEST|CDT|CCT|CAT|CAST|BTT|BST|BRT|BRST|BOT|BNT|AZT|AZST|AZOT|AZOST|AWST|AWDT|AST|ART|AQTT|ANAT|ANAST|AMT|AMST|ALMT|AKST|AKDT|AFT|AEST|AEDT|ADT|ACST|ACDT)\\b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       Token RegexMatch (GroupMatch (tz:_)):
+       _) -> Token Time <$> inTimezone tz td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAprsLeDayofmonth
+  , ruleAprsLeDjeuner
+  , ruleAprsLeTravail
+  , ruleAprsTimeofday
+  , ruleAprsdemain
+  , ruleAprsmidi
+  , ruleAuDjeuner
+  , ruleAujourdhui
+  , ruleAvantLeDjeuner
+  , ruleAvantTimeofday
+  , ruleAvanthier
+  , ruleCeDayofweek
+  , ruleCePartofday
+  , ruleCeTime
+  , ruleCedansLeCycle
+  , ruleCycleDernier
+  , ruleCycleProchainsuivantdaprs
+  , ruleDansDuration
+  , ruleDatetimeDatetimeInterval
+  , ruleDatetimedayofweekDdMonthinterval
+  , ruleDatetimeddMonthinterval
+  , ruleDayOfMonthPremier
+  , ruleDayofmonthNamedmonth
+  , ruleDayofweekDayofmonth
+  , ruleDayofweekDayofmonthTimeofday
+  , ruleDayofweekErdayofweekDdMonthinterval
+  , ruleDayofweekProchain
+  , ruleDbutDaprsmidi
+  , ruleDbutDeJourne
+  , ruleDbutDeMatine
+  , ruleDbutDeSemaine
+  , ruleDbutNamedmonthinterval
+  , ruleDdMm
+  , ruleDdMmYyyy
+  , ruleDdddMonthinterval
+  , ruleDdmm
+  , ruleDdmmyyyy
+  , ruleDeDatetimeDatetimeInterval
+  , ruleDeTimeofdayTimeofdayInterval
+  , ruleDemain
+  , ruleDernierCycleDeTimeLatent
+  , ruleDernierDayofweekDeTimeLatent
+  , ruleDernierWeekendDeTime
+  , ruleDeuximeQuinzaineDeNamedmonthinterval
+  , ruleDiciDuration
+  , ruleDimTimeDuMatin
+  , ruleDimTimeDuSoir
+  , ruleDimTimePartofday
+  , ruleDuDatetimedayofweekDdMonthinterval
+  , ruleDuDdAuDdinterval
+  , ruleDuDddayofweekDdMonthinterval
+  , ruleDudansLePartofday
+  , ruleDurationApresTime
+  , ruleDurationAvantTime
+  , ruleEnNamedmonth
+  , ruleEnSemaine
+  , ruleEntreDatetimeEtDatetimeInterval
+  , ruleEntreDdEtDdMonthinterval
+  , ruleEntreTimeofdayEtTimeofdayInterval
+  , ruleErMai
+  , ruleMilieuDaprsmidi
+  , ruleFinDaprsmidi
+  , ruleMilieuDeJourne
+  , ruleFinDeJourne
+  , ruleMilieuDeMatine
+  , ruleFinDeMatine
+  , ruleFinDeSemaine
+  , ruleFinNamedmonthinterval
+  , ruleHhhmmTimeofday
+  , ruleHhmmMilitaryTimeofday
+  , ruleHier
+  , ruleIlYADuration
+  , ruleIntersect
+  , ruleIntersectByDeOr
+  , ruleIntersectByMaisparExempleplutt
+  , ruleJourDeLan
+  , ruleLeCycleAprssuivantTime
+  , ruleLeCycleAvantprcdentTime
+  , ruleLeCycleDeTime
+  , ruleLeCycleDernier
+  , ruleLeCycleProchainsuivantdaprs
+  , ruleLeDayofmonthDatetime
+  , ruleLeDayofmonthNonOrdinal
+  , ruleLeLendemainDuTime
+  , ruleLeOrdinalCycleDeTime
+  , ruleLeTime
+  , ruleLeVeilleDuTime
+  , ruleMaintenant
+  , ruleMatin
+  , ruleMidi
+  , ruleMilieuDeSemaine
+  , ruleMinuit
+  , ruleNCycleAprs
+  , ruleNCycleAvant
+  , ruleNCyclePassesprecedents
+  , ruleNCycleSuivants
+  , ruleNDerniersCycle
+  , ruleNProchainsCycle
+  , ruleNamedday
+  , ruleNamedday2
+  , ruleNamedday3
+  , ruleNamedday4
+  , ruleNamedday5
+  , ruleNamedday6
+  , ruleNamedday7
+  , ruleNameddayEnHuit
+  , ruleNameddayEnQuinze
+  , ruleNamedmonth
+  , ruleNamedmonth10
+  , ruleNamedmonth11
+  , ruleNamedmonth12
+  , ruleNamedmonth13
+  , ruleNamedmonth2
+  , ruleNamedmonth3
+  , ruleNamedmonth4
+  , ruleNamedmonth5
+  , ruleNamedmonth6
+  , ruleNamedmonth7
+  , ruleNamedmonth8
+  , ruleNamedmonth9
+  , ruleNamedmonthProchain
+  , ruleNamedmonthnameddayDernierpass
+  , ruleNamedmonthnameddaySuivantdaprs
+  , ruleNoel
+  , ruleOrdinalCycleDeTime
+  , ruleOrdinalWeekendDeTime
+  , rulePartofdayDuDimTime
+  , rulePremireQuinzaineDeNamedmonthinterval
+  , ruleSeason
+  , ruleSeason2
+  , ruleSeason3
+  , ruleSeason4
+  , ruleSoir
+  , ruleDbutDeSoire
+  , ruleFinDeSoire
+  , ruleSoirDeNol
+  , ruleTimeofdayHeures
+  , ruleTimeofdayLatent
+  , ruleTimeofdayTimeofdayInterval
+  , ruleToussaint
+  , ruleVersTimeofday
+  , ruleWeekend
+  , ruleYear
+  , ruleYearLatent
+  , ruleYearLatent2
+  , ruleYyyymmdd
+  , ruleHourofdayMoinsQuart
+  , ruleHourofdayMoinsIntegerAsRelativeMinutes
+  , ruleHourofdayIntegerMinutes
+  , ruleHourofdayInteger
+  , ruleHourofdayEtpassDeNumeral
+  , ruleHourofdayEtpassDeNumeralMinutes
+  , ruleHourofdayEtTroisQuart
+  , ruleHourofdayEtQuart
+  , ruleHourofdayEtDemi
+  , ruleFinDuMois
+  , ruleTimezone
+  ]
diff --git a/Duckling/Time/GA/Corpus.hs b/Duckling/Time/GA/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/GA/Corpus.hs
@@ -0,0 +1,59 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.GA.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Time.Corpus
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Testing.Types hiding (examples)
+
+corpus :: Corpus
+corpus = (testContext {lang = GA}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (datetime (2013, 2, 12, 4, 30, 0) Second)
+             [ "anois"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "inniu"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "inné"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "arú inné"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "amárach"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "arú amárach"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "dé luain"
+             , "an luan"
+             , "an luan seo"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "an luan seo chugainn"
+             , "an luan seo atá ag teacht"
+             , "dé luain seo chugainn"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "18/2/2013"
+             ]
+  ]
diff --git a/Duckling/Time/GA/Rules.hs b/Duckling/Time/GA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/GA/Rules.hs
@@ -0,0 +1,872 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.GA.Rules
+  ( rules ) where
+
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import qualified Duckling.Ordinal.Types as TOrdinal
+import Duckling.Regex.Types
+import Duckling.Time.Helpers
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleArInn :: Rule
+ruleArInn = Rule
+  { name = "arú inné"
+  , pattern =
+    [ regex "ar(\x00fa|u) inn(\x00e9|e)"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 2
+  }
+
+ruleNamedday :: Rule
+ruleNamedday = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "luai?n|lu\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 1
+  }
+
+ruleNollaigNaMban :: Rule
+ruleNollaigNaMban = Rule
+  { name = "Nollaig na mBan"
+  , pattern =
+    [ regex "(l(\x00e1|a) |an )?nollaig (bheag|na mban)"
+    ]
+  , prod = \_ -> tt $ monthDay 1 6
+  }
+
+ruleNamedmonth12 :: Rule
+ruleNamedmonth12 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "(mh?(\x00ed|i) )?(na )?nollai?g|nol\\.?"
+    ]
+  , prod = \_ -> tt $ month 12
+  }
+
+ruleNamedday2 :: Rule
+ruleNamedday2 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "mh?(\x00e1|a)irt|m(\x00e1|a)?\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 2
+  }
+
+ruleNamedday6 :: Rule
+ruleNamedday6 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "sathai?rn|sa\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 6
+  }
+
+ruleNamedmonth7 :: Rule
+ruleNamedmonth7 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "(mh?(\x00ed|i) )?i(\x00fa|u)il|i(\x00fa|u)i\\.?"
+    ]
+  , prod = \_ -> tt $ month 7
+  }
+
+ruleInniu :: Rule
+ruleInniu = Rule
+  { name = "inniu"
+  , pattern =
+    [ regex "inniu"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 0
+  }
+
+ruleAnOrdinalCycleINdiaidhTime :: Rule
+ruleAnOrdinalCycleINdiaidhTime = Rule
+  { name = "an <ordinal> <cycle> i ndiaidh <time>"
+  , pattern =
+    [ regex "an"
+    , dimension Ordinal
+    , dimension TimeGrain
+    , regex "(i ndiaidh|tar (\x00e9|e)is)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleInn :: Rule
+ruleInn = Rule
+  { name = "inné"
+  , pattern =
+    [ regex "inn(\x00e9|e)"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 1
+  }
+
+ruleLFhileBrde :: Rule
+ruleLFhileBrde = Rule
+  { name = "Lá Fhéile Bríde"
+  , pattern =
+    [ regex "(l(\x00e1|a) )?(fh(e|\x00e9)ile|'?le) bh?r(\x00ed|i)de"
+    ]
+  , prod = \_ -> tt $ monthDay 2 1
+  }
+
+ruleLFhileVailintn :: Rule
+ruleLFhileVailintn = Rule
+  { name = "Lá Fhéile Vailintín"
+  , pattern =
+    [ regex "(l(\x00e1|a) )?(fh(e|\x00e9)ile|'?le) vailint(\x00ed|i)n"
+    ]
+  , prod = \_ -> tt $ monthDay 2 14
+  }
+
+ruleTimeSeo :: Rule
+ruleTimeSeo = Rule
+  { name = "<time> seo"
+  , pattern =
+    [ dimension Time
+    , regex "seo"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleTimeSeoChaite :: Rule
+ruleTimeSeoChaite = Rule
+  { name = "<time> seo chaite"
+  , pattern =
+    [ dimension Time
+    , regex "seo ch?aite"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleTimeSeoChugainn :: Rule
+ruleTimeSeoChugainn = Rule
+  { name = "<time> seo chugainn"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "seo (chugainn|at(a|\x00e1) ag teacht)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleNamedday4 :: Rule
+ruleNamedday4 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "d(\x00e9|e)ardaoin|d(\x00e9|e)?\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 4
+  }
+
+ruleAmrach :: Rule
+ruleAmrach = Rule
+  { name = "amárach"
+  , pattern =
+    [ regex "am(\x00e1|a)rach"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 1
+  }
+
+ruleYearLatent2 :: Rule
+ruleYearLatent2 = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2101 10000
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ year n
+      _ -> Nothing
+  }
+
+ruleNamedmonth :: Rule
+ruleNamedmonth = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "(mh?(\x00ed|i) )?(an )?t?ean(\x00e1|a)ir|ean\\.?"
+    ]
+  , prod = \_ -> tt $ month 1
+  }
+
+ruleNamedmonth3 :: Rule
+ruleNamedmonth3 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "(mh?(\x00ed|i) )?(an )?mh?(\x00e1|a)rta|m(\x00e1|a)r\\.?"
+    ]
+  , prod = \_ -> tt $ month 3
+  }
+
+ruleOrdinalRithe :: Rule
+ruleOrdinalRithe = Rule
+  { name = "<ordinal> ráithe"
+  , 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
+  }
+
+ruleAnnaCycleRoimhTime :: Rule
+ruleAnnaCycleRoimhTime = Rule
+  { name = "(an|na) <cycle> roimh <time>"
+  , pattern =
+    [ regex "the"
+    , dimension TimeGrain
+    , regex "roimh"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain (-1) td
+      _ -> Nothing
+  }
+
+ruleCycleShin :: Rule
+ruleCycleShin = Rule
+  { name = "<cycle> ó shin"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "(\x00f3|o) shin"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_) ->
+        tt . cycleNth grain $ 1
+      _ -> Nothing
+  }
+
+ruleDdmm :: Rule
+ruleDdmm = Rule
+  { name = "dd/mm"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])/(0?[1-9]|1[0-2])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        m <- parseInt m2
+        d <- parseInt m1
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleNamedmonth4 :: Rule
+ruleNamedmonth4 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "(mh?(\x00ed|i) )?(an )?t?aibre(\x00e1|a)i?n|abr\\.?"
+    ]
+  , prod = \_ -> tt $ month 4
+  }
+
+ruleIGceannCycle :: Rule
+ruleIGceannCycle = Rule
+  { name = "i gceann <cycle>"
+  , pattern =
+    [ regex "(i|faoi) g?ch?eann"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token TimeGrain grain:_) ->
+        tt $ cycleN True grain (TOrdinal.value od)
+      _ -> Nothing
+  }
+
+ruleAnCycleDeTime :: Rule
+ruleAnCycleDeTime = Rule
+  { name = "an <cycle> de <time>"
+  , pattern =
+    [ regex "an"
+    , dimension TimeGrain
+    , regex "d[e']"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain 0 td
+      _ -> Nothing
+  }
+
+ruleNamedday5 :: Rule
+ruleNamedday5 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "h?aoine|ao\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 5
+  }
+
+ruleDayofmonthordinalNamedmonth :: Rule
+ruleDayofmonthordinalNamedmonth = Rule
+  { name = "<day-of-month>(ordinal) <named-month>"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleIntersectBy :: Rule
+ruleIntersectBy = Rule
+  { name = "intersect by \",\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex ","
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleOrdinalRitheYear :: Rule
+ruleOrdinalRitheYear = Rule
+  { name = "<ordinal> ráithe <year>"
+  , pattern =
+    [ dimension Ordinal
+    , Predicate $ isGrain TG.Quarter
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:_:Token Time td:_) ->
+        tt $ cycleNthAfter False TG.Quarter (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleCycleInniu :: Rule
+ruleCycleInniu = Rule
+  { name = "<cycle> ó inniu"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    , regex "(\x00f3|o)(n l(\x00e1|a) (at(\x00e1|a) )?)?inniu"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleNamedmonth2 :: Rule
+ruleNamedmonth2 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "(mh?(\x00ed|i) )?(na )?feabhra|fea\\.?"
+    ]
+  , prod = \_ -> tt $ month 2
+  }
+
+ruleOrdinalCycleINdiaidhTime :: Rule
+ruleOrdinalCycleINdiaidhTime = Rule
+  { name = "<ordinal> <cycle> i ndiaidh <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "(i ndiaidh|tar (\x00e9|e)is)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleAnDayofmonthOrdinal :: Rule
+ruleAnDayofmonthOrdinal = Rule
+  { name = "an <day-of-month> (ordinal)"
+  , pattern =
+    [ regex "an|na"
+    , Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        n <- getIntValue token
+        tt $ dayOfMonth n
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ Predicate isNotLatent
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNamedmonth6 :: Rule
+ruleNamedmonth6 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "(mh?(\x00ed|i) )?(an )?mh?eith(ea|i)mh|mei\\.?"
+    ]
+  , prod = \_ -> tt $ month 6
+  }
+
+ruleNamedmonth8 :: Rule
+ruleNamedmonth8 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "(mh?(\x00ed|i) )?(na )?l(\x00fa|u)nasa|l(\x00fa|u)n\\.?"
+    ]
+  , prod = \_ -> tt $ month 8
+  }
+
+ruleAnOrdinalCycleDeTime :: Rule
+ruleAnOrdinalCycleDeTime = Rule
+  { name = "an <ordinal> <cycle> de <time>"
+  , pattern =
+    [ regex "an"
+    , dimension Ordinal
+    , dimension TimeGrain
+    , regex "d[e']"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleLNaNaithreacha :: Rule
+ruleLNaNaithreacha = Rule
+  { name = "Lá na nAithreacha"
+  , pattern =
+    [ regex "l(\x00e1|a) na naithreacha"
+    ]
+  , prod = \_ -> tt $ nthDOWOfMonth 2 7 6
+  }
+
+ruleArAmrach :: Rule
+ruleArAmrach = Rule
+  { name = "arú amárach"
+  , pattern =
+    [ regex "ar(\x00fa|u) am(\x00e1|a)rach"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 2
+  }
+
+ruleOrdinalCycleDeTime :: Rule
+ruleOrdinalCycleDeTime = Rule
+  { name = "<ordinal> <cycle> de <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "d[e']"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleYyyymmdd :: Rule
+ruleYyyymmdd = Rule
+  { name = "yyyy-mm-dd"
+  , pattern =
+    [ regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
+        y <- parseInt m1
+        m <- parseInt m2
+        d <- parseInt m3
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleArDate :: Rule
+ruleArDate = Rule
+  { name = "ar <date>"
+  , pattern =
+    [ regex "ar"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleAnNollaig :: Rule
+ruleAnNollaig = Rule
+  { name = "An Nollaig"
+  , pattern =
+    [ regex "(l(\x00e1|a) |an )?(nollai?g)"
+    ]
+  , prod = \_ -> tt $ monthDay 12 25
+  }
+
+ruleOnANamedday :: Rule
+ruleOnANamedday = Rule
+  { name = "on a named-day"
+  , pattern =
+    [ regex "ar an"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleYearLatent :: Rule
+ruleYearLatent = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween (- 10000) 999
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ year n
+      _ -> Nothing
+  }
+
+ruleAnois :: Rule
+ruleAnois = Rule
+  { name = "anois"
+  , pattern =
+    [ regex "anois|(ag an (t-?)?am seo)"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Second 0
+  }
+
+ruleLFhilePdraig :: Rule
+ruleLFhilePdraig = Rule
+  { name = "Lá Fhéile Pádraig"
+  , pattern =
+    [ regex "(l(\x00e1|a) )?(fh(e|\x00e9)ile|'?le) ph?(\x00e1|a)draig"
+    ]
+  , prod = \_ -> tt $ monthDay 3 17
+  }
+
+ruleAnCycleINdiaidhTime :: Rule
+ruleAnCycleINdiaidhTime = Rule
+  { name = "an <cycle> i ndiaidh <time>"
+  , pattern =
+    [ regex "the"
+    , dimension TimeGrain
+    , regex "(i ndiaidh|tar (\x00e9|e)is)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain 1 td
+      _ -> Nothing
+  }
+
+ruleDayofmonthOrdinal :: Rule
+ruleDayofmonthOrdinal = Rule
+  { name = "<day-of-month> (ordinal)"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ dayOfMonth n
+      _ -> Nothing
+  }
+
+ruleAnCycleSeo :: Rule
+ruleAnCycleSeo = Rule
+  { name = "an <cycle> seo"
+  , pattern =
+    [ regex "an"
+    , dimension TimeGrain
+    , regex "seo"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 0
+      _ -> Nothing
+  }
+
+ruleNamedmonth5 :: Rule
+ruleNamedmonth5 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "(mh?(\x00ed|i) )?(na )?bh?ealtaine|bea\\.?"
+    ]
+  , prod = \_ -> tt $ month 5
+  }
+
+ruleNamedday7 :: Rule
+ruleNamedday7 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "domhnai?[cg]h|do\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 7
+  }
+
+ruleAnDayofmonthNonOrdinal :: Rule
+ruleAnDayofmonthNonOrdinal = Rule
+  { name = "an <day-of-month> (non ordinal)"
+  , pattern =
+    [ regex "an|na"
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleDNamedday :: Rule
+ruleDNamedday = Rule
+  { name = "dé named-day"
+  , pattern =
+    [ regex "d(\x00e9|e)"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleYear :: Rule
+ruleYear = Rule
+  { name = "year"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1000 2100
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt $ year v
+      _ -> Nothing
+  }
+
+ruleNamedmonth10 :: Rule
+ruleNamedmonth10 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "(mh?(\x00ed|i) )?dh?eireadh f(\x00f3|o)mhair|def?\\.?"
+    ]
+  , prod = \_ -> tt $ month 10
+  }
+
+ruleCycleRoimhTime :: Rule
+ruleCycleRoimhTime = Rule
+  { name = "<cycle> roimh <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "roimhe?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain (-1) td
+      _ -> Nothing
+  }
+
+ruleAbsorptionOfAfterNamedDay :: Rule
+ruleAbsorptionOfAfterNamedDay = Rule
+  { name = "absorption of , after named day"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex ","
+    ]
+  , prod = \tokens -> case tokens of
+      (x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleDdmmyyyy :: Rule
+ruleDdmmyyyy = Rule
+  { name = "dd/mm/yyyy"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[-/](0?[1-9]|1[0-2])[/-](\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
+        y <- parseInt m3
+        m <- parseInt m2
+        d <- parseInt m1
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleNamedmonth11 :: Rule
+ruleNamedmonth11 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "(mh?(\x00ed|i) )?(na )?samh(ain|na)|sam\\.?"
+    ]
+  , prod = \_ -> tt $ month 11
+  }
+
+ruleAnNamedday :: Rule
+ruleAnNamedday = Rule
+  { name = "an named-day"
+  , pattern =
+    [ regex "an"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleNamedday3 :: Rule
+ruleNamedday3 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "ch?(\x00e9|e)adaoin|c(\x00e9|e)\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 3
+  }
+
+ruleCycleINdiaidhTime :: Rule
+ruleCycleINdiaidhTime = Rule
+  { name = "<cycle> i ndiaidh <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "(i ndiaidh|tar (\x00e9|e)is)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain 1 td
+      _ -> Nothing
+  }
+
+ruleNamedmonth9 :: Rule
+ruleNamedmonth9 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "(mh?(\x00ed|i) )?mh?e(\x00e1|a)n f(\x00f3|o)mhair|mef?\\.?"
+    ]
+  , prod = \_ -> tt $ month 9
+  }
+
+ruleDayofmonthordinalNamedmonthYear :: Rule
+ruleDayofmonthordinalNamedmonthYear = 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
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAbsorptionOfAfterNamedDay
+  , ruleAmrach
+  , ruleAnCycleDeTime
+  , ruleAnCycleINdiaidhTime
+  , ruleAnCycleSeo
+  , ruleAnDayofmonthNonOrdinal
+  , ruleAnDayofmonthOrdinal
+  , ruleAnNamedday
+  , ruleAnNollaig
+  , ruleAnOrdinalCycleDeTime
+  , ruleAnOrdinalCycleINdiaidhTime
+  , ruleAnnaCycleRoimhTime
+  , ruleAnois
+  , ruleArAmrach
+  , ruleArDate
+  , ruleArInn
+  , ruleCycleINdiaidhTime
+  , ruleCycleInniu
+  , ruleCycleRoimhTime
+  , ruleCycleShin
+  , ruleDNamedday
+  , ruleDayofmonthOrdinal
+  , ruleDayofmonthordinalNamedmonth
+  , ruleDayofmonthordinalNamedmonthYear
+  , ruleDdmm
+  , ruleDdmmyyyy
+  , ruleIGceannCycle
+  , ruleInn
+  , ruleInniu
+  , ruleIntersect
+  , ruleIntersectBy
+  , ruleLFhileBrde
+  , ruleLFhilePdraig
+  , ruleLFhileVailintn
+  , ruleLNaNaithreacha
+  , ruleNamedday
+  , ruleNamedday2
+  , ruleNamedday3
+  , ruleNamedday4
+  , ruleNamedday5
+  , ruleNamedday6
+  , ruleNamedday7
+  , ruleNamedmonth
+  , ruleNamedmonth10
+  , ruleNamedmonth11
+  , ruleNamedmonth12
+  , ruleNamedmonth2
+  , ruleNamedmonth3
+  , ruleNamedmonth4
+  , ruleNamedmonth5
+  , ruleNamedmonth6
+  , ruleNamedmonth7
+  , ruleNamedmonth8
+  , ruleNamedmonth9
+  , ruleNollaigNaMban
+  , ruleOnANamedday
+  , ruleOrdinalCycleDeTime
+  , ruleOrdinalCycleINdiaidhTime
+  , ruleOrdinalRithe
+  , ruleOrdinalRitheYear
+  , ruleTimeSeo
+  , ruleTimeSeoChaite
+  , ruleTimeSeoChugainn
+  , ruleYear
+  , ruleYearLatent
+  , ruleYearLatent2
+  , ruleYyyymmdd
+  ]
diff --git a/Duckling/Time/HE/Corpus.hs b/Duckling/Time/HE/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/HE/Corpus.hs
@@ -0,0 +1,292 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.HE.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Time.Corpus
+import Duckling.Time.Types hiding (Month)
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Testing.Types hiding (examples)
+
+corpus :: Corpus
+corpus = (testContext {lang = HE}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (datetime (2013, 2, 12, 4, 30, 0) Second)
+             [ "עכשיו"
+             , "מייד"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "היום"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "אתמול"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "מחר"
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "ראשון"
+             , "יום ראשון"
+             , "בראשון הזה"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "יום שני"
+             , "שני"
+             , "שני הזה"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             -- "שני השמונה עשרה לפברואר"
+             [ "שני 18 לפברואר"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             -- "שלישי ה19"
+             [ "שלישי"
+             , "יום שלישי התשעה עשר"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "חמישי"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "שישי"
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "שבת"
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "ראשון"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             -- "הראשון למרץ"
+             -- "ה1 למרץ"
+             [ "1 למרץ"
+             ]
+  , examples (datetime (2013, 3, 3, 0, 0, 0) Day)
+             [ "במרץ 3"
+             ]
+  , examples (datetime (2013, 3, 15, 0, 0, 0) Day)
+             [ "באמצע מרץ"
+             ]
+  , examples (datetime (2015, 3, 3, 0, 0, 0) Day)
+             -- "השלישי למרץ 2015"
+             [ "3 למרץ 2015"
+             , "שלושה במרץ 2015"
+             , "3/3/2015"
+             , "3/3/15"
+             , "2015-3-3"
+             , "2015-03-03"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             -- "חמש עשרה לחודש"
+             -- "ב15 לחודש"
+             -- "ב15 החודש"
+             [
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "ה15 בפברואר"
+             , "15 לפברואר"
+             , "2/15"
+             , "ב 2/15"
+             , "פברואר 15"
+             ]
+  , examples (datetime (2013, 8, 8, 0, 0, 0) Day)
+             [ "אוגוסט 8"
+             ]
+  , examples (datetime (2014, 10, 0, 0, 0, 0) Month)
+             [ "אוקטובר 2014"
+             ]
+  , examples (datetime (1974, 10, 31, 0, 0, 0) Day)
+             [ "10/31/1974"
+             , "10/31/74"
+             , "10-31-74"
+             ]
+  , examples (datetime (2015, 4, 14, 0, 0, 0) Day)
+             [ "14 לאפריל 2015"
+             , "אפריל 14, 2015"
+             ]
+  , examples (datetime (2013, 2, 22, 0, 0, 0) Day)
+             [ "שישי הבא"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "מרץ הבא"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "ראשון, 10 לפברואר"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             -- "שני, השמונה עשרה לפברואר"
+             -- "יום שני, ה18 לפברואר"
+             [
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Week)
+             [ "בשבוע הזה"
+             ]
+  , examples (datetime (2013, 2, 4, 0, 0, 0) Week)
+             [ "שבוע שעבר"
+             , "שבוע האחרון"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Week)
+             [ "שבוע הבא"
+             ]
+  , examples (datetime (2013, 1, 0, 0, 0, 0) Month)
+             [ "חודש שעבר"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "חודש הבא"
+             ]
+  , examples (datetime (2012, 0, 0, 0, 0, 0) Year)
+             -- "שנה שעברה"
+             [
+             ]
+  , examples (datetime (2014, 0, 0, 0, 0, 0) Year)
+             [ "שנה הבאה"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "ראשון בשבוע שעבר"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "שלישי האחרון"
+             ]
+  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
+             [ "רביעי שבוע הבא"
+             , "רביעי הבא"
+             ]
+  , examples (datetime (2013, 2, 22, 0, 0, 0) Day)
+             [ "שישי הבא"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "רביעי הזה"
+             ]
+  , examples (datetime (2013, 3, 25, 0, 0, 0) Day)
+             [ "שני האחרון של מרץ"
+             ]
+  , examples (datetime (2014, 3, 30, 0, 0, 0) Day)
+             [ "ראשון האחרון של מרץ 2014"
+             ]
+  , examples (datetime (2013, 10, 3, 0, 0, 0) Day)
+             [ "השלישי באוקטובר"
+             ]
+  , examples (datetime (2013, 10, 1, 0, 0, 0) Day)
+             -- "יום שלישי הראשון של אוקטובר"
+             [
+             ]
+  , examples (datetime (2013, 2, 13, 3, 18, 0) Minute)
+             [ "3:18am"
+             , "3:18a"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             -- "@ 3pm"
+             [ "ב 3pm"
+             , "3PM"
+             , "3pm"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             -- "באיזור שלוש בצהריים"
+             [
+             ]
+  , examples (datetime (2013, 2, 12, 15, 15, 0) Minute)
+             -- "3:15 בצהריים"
+             -- "בשלוש ורבע בצהריים"
+             [ "15:15"
+             , "3:15pm"
+             , "3:15PM"
+             , "3:15p"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 20, 0) Minute)
+             -- "3:20 בצהריים"
+             -- "3:20 צהריים"
+             -- "עשרים אחרי שלוש בצהריים"
+             [ "3:20p"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 30, 0) Minute)
+             -- "בשלוש וחצי בערב"
+             -- "שלוש וחצי בצהריים"
+             [ "15:30"
+             , "3:30pm"
+             , "3:30PM"
+             , "330 p.m."
+             , "3:30 p m"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 23, 24) Second)
+             [ "15:23:24"
+             ]
+  , examples (datetime (2013, 2, 12, 11, 45, 0) Minute)
+             [ "רבע ל12"
+             , "11:45am"
+             ]
+  , examples (datetime (2013, 9, 20, 19, 30, 0) Minute)
+             -- "בשבע וחצי בערב ביום שישי העשרים לספטמבר"
+             [
+             ]
+  , examples (datetime (2013, 2, 16, 9, 0, 0) Hour)
+             [ "בתשע בבוקר בשבת"
+             ]
+  , examples (datetime (2014, 7, 18, 19, 0, 0) Minute)
+             [ "שישי, יולי 18, 2014 07:00 PM"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 32, 0) Second)
+             [ "בעוד 2 דקות"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Second)
+             [ "בעוד 60 דקות"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 45, 0) Second)
+             [ "בעוד רבע שעה"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 0, 0) Second)
+             [ "בעוד חצי שעה"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 30, 0) Minute)
+             [ "בעוד 24 שעות"
+             , "בעוד עשרים וארבע שעות"
+             ]
+  , examples (datetime (2013, 2, 19, 4, 0, 0) Hour)
+             [ "בעוד שבעה ימים"
+             ]
+  , examples (datetime (2013, 2, 5, 4, 0, 0) Hour)
+             [ "לפני שבעה ימים"
+             ]
+  , examples (datetime (2012, 11, 12, 0, 0, 0) Day)
+             -- "לפני שלושה חודשים"
+             [
+             ]
+  , examples (datetime (1954, 0, 0, 0, 0, 0) Year)
+             [ "1954"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 18, 0, 0), (2013, 2, 13, 0, 0, 0)) Hour)
+             [ "הערב"
+             , "היום בערב"
+             ]
+  , examples (datetimeInterval ((2013, 2, 8, 18, 0, 0), (2013, 2, 11, 0, 0, 0)) Hour)
+             [ "בסופ״ש האחרון"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 18, 0, 0), (2013, 2, 14, 0, 0, 0)) Hour)
+             [ "מחר בערב"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 12, 0, 0), (2013, 2, 13, 14, 0, 0)) Hour)
+             [ "מחר בצהריים"
+             , "מחר צהריים"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 18, 0, 0), (2013, 2, 12, 0, 0, 0)) Hour)
+             [ "אתמול בערב"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 18, 0, 0), (2013, 2, 18, 0, 0, 0)) Hour)
+             [ "בסופ״ש הזה"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 4, 0, 0), (2013, 2, 18, 12, 0, 0)) Hour)
+             [ "שני בבוקר"
+             ]
+  ]
diff --git a/Duckling/Time/HE/Rules.hs b/Duckling/Time/HE/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/HE/Rules.hs
@@ -0,0 +1,1556 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.HE.Rules
+  ( rules ) where
+
+import Data.Maybe
+import Data.String
+import Data.Text (Text)
+import Prelude
+import qualified Data.Text as Text
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Numeral.Types (NumeralData (..))
+import Duckling.Ordinal.Types (OrdinalData (..))
+import Duckling.Regex.Types
+import Duckling.Time.Helpers
+import Duckling.Time.Types (TimeData (..))
+import Duckling.Types
+import qualified Duckling.Numeral.Types as TNumeral
+import qualified Duckling.Ordinal.Types as TOrdinal
+import qualified Duckling.TimeGrain.Types as TG
+import qualified Duckling.Time.Types as TTime
+
+ruleNextDayofweek :: Rule
+ruleNextDayofweek = Rule
+  { name = "next <day-of-week>"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex "(\x05d4\x05d1\x05d0(\x05d4)?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 1 True td
+      _ -> Nothing
+  }
+
+ruleNamedday :: Rule
+ruleNamedday = Rule
+  { name = "ב <named-day>"
+  , pattern =
+    [ regex "\x05d1"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleAtHourTimeofday :: Rule
+ruleAtHourTimeofday = Rule
+  { name = "at hour <time-of-day>"
+  , pattern =
+    [ regex "\x05d1\x05e9\x05e2\x05d4"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth12 :: Rule
+ruleNamedmonth12 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x05d3\x05e6\x05de\x05d1\x05e8"
+    ]
+  , prod = \_ -> tt $ month 12
+  }
+
+ruleHourofdayAndInteger :: Rule
+ruleHourofdayAndInteger = Rule
+  { name = "<hour-of-day> and <integer>"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "\x05d5"
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleHourofdayAndQuarter :: Rule
+ruleHourofdayAndQuarter = Rule
+  { name = "<hour-of-day> and quarter"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "\x05d5"
+    , regex "\x05e8\x05d1\x05e2(\x05d9)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
+        tt $ hourMinute is12H hours 15
+      _ -> Nothing
+  }
+
+ruleHourofdayAndHalf :: Rule
+ruleHourofdayAndHalf = Rule
+  { name = "<hour-of-day> and half"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "\x05d5"
+    , regex "\x05d7\x05e6\x05d9|\x05de\x05d7\x05e6\x05d9\x05ea"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
+        tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+
+ruleHourofdayInteger :: Rule
+ruleHourofdayInteger = Rule
+  { name = "<hour-of-day> <integer>"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleHourofdayQuarter :: Rule
+ruleHourofdayQuarter = Rule
+  { name = "<hour-of-day> quarter"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "\x05e8\x05d1\x05e2(\x05d9)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
+        tt $ hourMinute is12H hours 15
+      _ -> Nothing
+  }
+
+ruleHourofdayHalf :: Rule
+ruleHourofdayHalf = Rule
+  { name = "<hour-of-day> half"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "\x05d7\x05e6\x05d9|\x05de\x05d7\x05e6\x05d9\x05ea"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
+        tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+
+ruleIntegerTotillbeforeIntegerHourofday :: Rule
+ruleIntegerTotillbeforeIntegerHourofday = Rule
+  { name = "<integer> to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "\x05dc\x05e4\x05e0\x05d9|\x05dc"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        tt t
+      _ -> Nothing
+  }
+
+ruleQuarterTotillbeforeIntegerHourofday :: Rule
+ruleQuarterTotillbeforeIntegerHourofday = Rule
+  { name = "quarter to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ regex "\x05e8\x05d1\x05e2(\x05d9)?"
+    , regex "\x05dc\x05e4\x05e0\x05d9|\x05dc"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:_:Token Time td:_) -> Token Time <$> minutesBefore 15 td
+      _ -> Nothing
+  }
+
+ruleHalfTotillbeforeIntegerHourofday :: Rule
+ruleHalfTotillbeforeIntegerHourofday = Rule
+  { name = "half to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ regex "\x05d7\x05e6\x05d9|\x05de\x05d7\x05e6\x05d9\x05ea"
+    , regex "\x05dc\x05e4\x05e0\x05d9|\x05dc"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:_:Token Time td:_) -> Token Time <$> minutesBefore 30 td
+      _ -> Nothing
+  }
+
+ruleIntegerAfterpastIntegerHourofday :: Rule
+ruleIntegerAfterpastIntegerHourofday = Rule
+  { name = "integer after|past <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "\x05d0\x05d7\x05e8\x05d9"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        t <- minutesAfter n td
+        tt t
+      _ -> Nothing
+  }
+
+ruleQuarterAfterpastIntegerHourofday :: Rule
+ruleQuarterAfterpastIntegerHourofday = Rule
+  { name = "quarter after|past <integer> (hour-of-day)"
+  , pattern =
+    [ regex "\x05e8\x05d1\x05e2(\x05d9)?"
+    , regex "\x05d0\x05d7\x05e8\x05d9"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:_:Token Time td:_) -> Token Time <$> minutesAfter 15 td
+      _ -> Nothing
+  }
+
+ruleHalfAfterpastIntegerHourofday :: Rule
+ruleHalfAfterpastIntegerHourofday = Rule
+  { name = "half after|past <integer> (hour-of-day)"
+  , pattern =
+    [ regex "\x05d7\x05e6\x05d9|\x05de\x05d7\x05e6\x05d9\x05ea"
+    , regex "\x05d0\x05d7\x05e8\x05d9"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesAfter 30 td
+      _ -> Nothing
+  }
+
+ruleNamedday2 :: Rule
+ruleNamedday2 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "(\x05d9\x05d5\x05dd )?\x05e9\x05e0\x05d9"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 1
+  }
+
+ruleSinceTimeofday :: Rule
+ruleSinceTimeofday = Rule
+  { name = "since <time-of-day>"
+  , pattern =
+    [ regex "\x05de"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ withDirection TTime.After td
+      _ -> Nothing
+  }
+
+ruleLastTime :: Rule
+ruleLastTime = Rule
+  { name = "last <time>"
+  , pattern =
+    [ dimension Time
+    , regex "\x05e9\x05e2\x05d1\x05e8|(\x05d4)?\x05e7\x05d5\x05d3\x05dd|(\x05d4)?\x05d0\x05d7\x05e8\x05d5\x05df"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleNamedday6 :: Rule
+ruleNamedday6 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "(\x05d9\x05d5\x05dd )?\x05e9\x05d9\x05e9\x05d9"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 5
+  }
+
+ruleDatetimeDatetimeInterval :: Rule
+ruleDatetimeDatetimeInterval = Rule
+  { name = "<datetime> - <datetime> (interval)"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "\\-|\x05e2(\x05d3)?"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth7 :: Rule
+ruleNamedmonth7 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x05d9\x05d5\x05dc\x05d9"
+    ]
+  , prod = \_ -> tt $ month 7
+  }
+
+ruleTheDayofmonthNonOrdinal :: Rule
+ruleTheDayofmonthNonOrdinal = Rule
+  { name = "the <day-of-month> (non ordinal)"
+  , pattern =
+    [ regex "\x05d4/S"
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ dayOfMonth n
+      _ -> Nothing
+  }
+
+ruleCycleAfterTime :: Rule
+ruleCycleAfterTime = Rule
+  { name = "<cycle> after <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "\x05d0\x05d7\x05e8\x05d9"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain 1 td
+      _ -> Nothing
+  }
+
+ruleInDuration :: Rule
+ruleInDuration = Rule
+  { name = "in <duration>"
+  , pattern =
+    [ regex "\x05d1\x05e2\x05d5\x05d3"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) -> tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleInNamedmonth :: Rule
+ruleInNamedmonth = Rule
+  { name = "in <named-month>"
+  , pattern =
+    [ regex "\x05d1"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleNow :: Rule
+ruleNow = Rule
+  { name = "now"
+  , pattern =
+    [ regex "\x05e2\x05db\x05e9\x05d9\x05d5|\x05de\x05d9\x05d9\x05d3"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Second 0
+  }
+
+ruleCurrentDayofweek :: Rule
+ruleCurrentDayofweek = Rule
+  { name = "current <day-of-week>"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex "(\x05d4\x05d6\x05d4|\x05d4\x05d6\x05d0\x05ea|\x05d4\x05e7\x05e8\x05d5\x05d1(\x05d4)?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleFromDatetimeDatetimeInterval :: Rule
+ruleFromDatetimeDatetimeInterval = Rule
+  { name = "from <datetime> - <datetime> (interval)"
+  , pattern =
+    [ regex "\x05de|\x05de\x05e9\x05e2\x05d4"
+    , dimension Time
+    , regex "\\-|\x05e2\x05d3"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedday4 :: Rule
+ruleNamedday4 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "(\x05d9\x05d5\x05dd )?\x05e8\x05d1\x05d9\x05e2\x05d9"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 3
+  }
+
+ruleTheCycleAfterTime :: Rule
+ruleTheCycleAfterTime = Rule
+  { name = "the <cycle> after <time>"
+  , pattern =
+    [ regex "\x05d4"
+    , dimension TimeGrain
+    , regex "\x05d0\x05d7\x05e8\x05d9"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain 1 td
+      _ -> Nothing
+  }
+
+ruleTheCycleBeforeTime :: Rule
+ruleTheCycleBeforeTime = Rule
+  { name = "the <cycle> before <time>"
+  , pattern =
+    [ regex "\x05d4"
+    , dimension TimeGrain
+    , regex "\x05dc\x05e4\x05e0\x05d9"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain (-1) td
+      _ -> Nothing
+  }
+
+ruleLastDayofweek :: Rule
+ruleLastDayofweek = Rule
+  { name = "last <day-of-week>"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex "(\x05e9\x05e2\x05d1\x05e8(\x05d4)?|\x05d4\x05e7\x05d5\x05d3\x05de\x05ea|\x05d4\x05e7\x05d5\x05d3\x05dd)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleTheIdesOfNamedmonth :: Rule
+ruleTheIdesOfNamedmonth = Rule
+  { name = "the ides of <named-month>"
+  , pattern =
+    [ regex "\x05d1\x05d0\x05de\x05e6\x05e2"
+    , 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
+  }
+
+ruleNoon :: Rule
+ruleNoon = Rule
+  { name = "noon"
+  , pattern =
+    [ regex "(\x05d1)?\x05e6\x05d4\x05e8\x05d9\x05d9\x05dd"
+    ]
+  , prod = \_ -> tt $ hour False 12
+  }
+
+ruleToday :: Rule
+ruleToday = Rule
+  { name = "today"
+  , pattern =
+    [ regex "\x05d4\x05d9\x05d5\x05dd"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 0
+  }
+
+ruleBetweenTimeofdayAndTimeofdayInterval :: Rule
+ruleBetweenTimeofdayAndTimeofdayInterval = Rule
+  { name = "between <time-of-day> and <time-of-day> (interval)"
+  , pattern =
+    [ regex "\x05d1\x05d9\x05df"
+    , Predicate isATimeOfDay
+    , regex "\x05dc"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNextCycle :: Rule
+ruleNextCycle = Rule
+  { name = "next <cycle>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "\x05d4\x05d1\x05d0(\x05d4)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_) -> tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleNamedmonth :: Rule
+ruleNamedmonth = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x05d9\x05e0\x05d5\x05d0\x05e8"
+    ]
+  , prod = \_ -> tt $ month 1
+  }
+
+ruleNamedmonth3 :: Rule
+ruleNamedmonth3 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x05de\x05e8\x05e5"
+    ]
+  , prod = \_ -> tt $ month 3
+  }
+
+ruleForDuration :: Rule
+ruleForDuration = Rule
+  { name = "for <duration>"
+  , pattern =
+    [ regex "\x05ea\x05d5\x05da"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleDurationFromNow :: Rule
+ruleDurationFromNow = Rule
+  { name = "<duration> from now"
+  , pattern =
+    [ dimension Duration
+    , regex "\x05de\x05e2\x05db\x05e9\x05d9\x05d5"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleLunch :: Rule
+ruleLunch = Rule
+  { name = "lunch"
+  , pattern =
+    [ regex "(\x05d1)?\x05e6\x05d4\x05e8\x05d9\x05d9\x05dd"
+    ]
+  , prod = \_ -> tt . partOfDay . mkLatent $
+      interval TTime.Open (hour False 12, hour False 14)
+  }
+
+ruleLastCycle :: Rule
+ruleLastCycle = Rule
+  { name = "last <cycle>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "\x05d4\x05d0\x05d7\x05e8\x05d5\x05df|\x05d4\x05d0\x05d7\x05e8\x05d5\x05e0\x05d4|\x05e9\x05e2\x05d1\x05e8|\x05e9\x05e2\x05d1\x05e8\x05d4"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_) ->
+        tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleAfternoon :: Rule
+ruleAfternoon = Rule
+  { name = "afternoon"
+  , pattern =
+    [ regex "\x05d0\x05d7\x05d4(\x05f4)?\x05e6|\x05d0\x05d7\x05e8 \x05d4\x05e6\x05d4\x05e8\x05d9\x05d9\x05dd"
+    ]
+  , prod = \_ -> tt . partOfDay . mkLatent $
+      interval TTime.Open (hour False 12, hour False 19)
+  }
+
+ruleNamedmonth4 :: Rule
+ruleNamedmonth4 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x05d0\x05e4\x05e8\x05d9\x05dc"
+    ]
+  , prod = \_ -> tt $ month 4
+  }
+
+ruleNamedmonthDayofmonthOrdinal :: Rule
+ruleNamedmonthDayofmonthOrdinal = Rule
+  { name = "<named-month> <day-of-month> (ordinal)"
+  , pattern =
+    [ Predicate isAMonth
+    , Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleNamedday5 :: Rule
+ruleNamedday5 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "(\x05d9\x05d5\x05dd )?\x05d7\x05de\x05d9\x05e9\x05d9"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 4
+  }
+
+ruleDayofmonthordinalNamedmonth :: Rule
+ruleDayofmonthordinalNamedmonth = Rule
+  { name = "<day-of-month>(ordinal) <named-month>"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleIntersectBy :: Rule
+ruleIntersectBy = Rule
+  { name = "intersect by \",\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex ","
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) -> Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNthTimeAfterTime :: Rule
+ruleNthTimeAfterTime = Rule
+  { name = "nth <time> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "\x05d0\x05d7\x05e8\x05d9"
+    , 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
+  }
+
+ruleMmdd :: Rule
+ruleMmdd = Rule
+  { name = "mm/dd"
+  , pattern =
+    [ regex "(0?[1-9]|1[0-2])/(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (mm:dd:_)):_) -> do
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleAfterDuration :: Rule
+ruleAfterDuration = Rule
+  { name = "after <duration>"
+  , pattern =
+    [ regex "\x05d0\x05d7\x05e8\x05d9"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) -> tt . withDirection TTime.After $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleTimeofdayLatent :: Rule
+ruleTimeofdayLatent = Rule
+  { name = "time-of-day (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 0 23
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ hour True n
+      _ -> Nothing
+  }
+
+ruleDayofmonthOrdinalOfNamedmonth :: Rule
+ruleDayofmonthOrdinalOfNamedmonth = Rule
+  { name = "<day-of-month> (ordinal) of <named-month>"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    , regex "\x05e9\x05dc|\x05d1|\x05dc"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleNamedmonth2 :: Rule
+ruleNamedmonth2 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x05e4\x05d1\x05e8\x05d5\x05d0\x05e8"
+    ]
+  , prod = \_ -> tt $ month 2
+  }
+
+ruleThisEvening :: Rule
+ruleThisEvening = Rule
+  { name = "this evening"
+  , pattern =
+    [ regex "\x05d4\x05e2\x05e8\x05d1"
+    ]
+  , prod = \_ ->
+      let td = interval TTime.Open (hour False 18, hour False 0)
+      in Token Time . partOfDay <$> intersect (cycleNth TG.Day 0) td
+  }
+
+ruleBetweenDatetimeAndDatetimeInterval :: Rule
+ruleBetweenDatetimeAndDatetimeInterval = Rule
+  { name = "between <datetime> and <datetime> (interval)"
+  , pattern =
+    [ regex "\x05d1\x05d9\x05df"
+    , dimension Time
+    , regex "\x05dc"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleEndOfYear :: Rule
+ruleEndOfYear = Rule
+  { name = "End of year"
+  , pattern =
+    [ regex "\x05e1\x05d5\x05e3 (\x05d4)?\x05e9\x05e0\x05d4"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Year 1
+  }
+
+ruleDurationAgo :: Rule
+ruleDurationAgo = Rule
+  { name = "<duration> ago"
+  , pattern =
+    [ regex "\x05dc\x05e4\x05e0\x05d9"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ durationAgo dd
+      _ -> Nothing
+  }
+
+ruleLastNCycle :: Rule
+ruleLastNCycle = Rule
+  { name = "last n <cycle>"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    , regex "\x05d0\x05d7\x05e8\x05d5\x05df|\x05d0\x05d7\x05e8\x05d5\x05e0\x05d5\x05ea|\x05d0\x05d7\x05e8\x05d5\x05e0\x05d4|\x05d0\x05d7\x05e8\x05d5\x05e0\x05d9\x05dd"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt . cycleN True grain $ - v
+      _ -> Nothing
+  }
+
+ruleMidnighteodendOfDay :: Rule
+ruleMidnighteodendOfDay = Rule
+  { name = "midnight|EOD|end of day"
+  , pattern =
+    [ regex "(\x05d1)?\x05d7\x05e6\x05d5\x05ea"
+    ]
+  , prod = \_ -> tt $ hour False 0
+  }
+
+ruleDayofmonthNonOrdinalNamedmonth :: Rule
+ruleDayofmonthNonOrdinalNamedmonth = Rule
+  { name = "<day-of-month> (non ordinal) <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ Predicate isNotLatent
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) -> Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleUntilTimeofday :: Rule
+ruleUntilTimeofday = Rule
+  { name = "until <time-of-day>"
+  , pattern =
+    [ regex "\x05e2\x05d3"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ withDirection TTime.Before td
+      _ -> Nothing
+  }
+
+ruleAtTimeofday :: Rule
+ruleAtTimeofday = Rule
+  { name = "at <time-of-day>"
+  , pattern =
+    [ regex "\x05d1"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth6 :: Rule
+ruleNamedmonth6 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x05d9\x05d5\x05e0\x05d9"
+    ]
+  , prod = \_ -> tt $ month 6
+  }
+
+ruleNthTimeOfTime :: Rule
+ruleNthTimeOfTime = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ dimension Time
+    , dimension Ordinal
+    , regex "\x05e9\x05dc|\x05d1"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Ordinal od:_:Token Time td2:_) -> Token Time .
+        predNth (TOrdinal.value od - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNamedmonth8 :: Rule
+ruleNamedmonth8 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x05d0\x05d5\x05d2\x05d5\x05e1\x05d8"
+    ]
+  , prod = \_ -> tt $ month 8
+  }
+
+ruleTimePartofday :: Rule
+ruleTimePartofday = 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
+  }
+
+ruleWeekend :: Rule
+ruleWeekend = Rule
+  { name = "week-end"
+  , pattern =
+    [ regex "(\x05e1\x05d5\x05e4\x05f4\x05e9|\x05e1\x05d5\x05e3 \x05d4\x05e9\x05d1\x05d5\x05e2)"
+    ]
+  , prod = \_ -> do
+      fri <- intersect (dayOfWeek 5) (hour False 18)
+      mon <- intersect (dayOfWeek 1) (hour False 0)
+      tt $ interval TTime.Open (fri, mon)
+  }
+
+ruleNameddayDayofmonthOrdinal :: Rule
+ruleNameddayDayofmonthOrdinal = Rule
+  { name = "<named-day> <day-of-month> (ordinal)"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleDate :: Rule
+ruleDate = Rule
+  { name = "ב <date>"
+  , pattern =
+    [ regex "\x05d1"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleNextTime :: Rule
+ruleNextTime = Rule
+  { name = "next <time>"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "\x05d4\x05d1\x05d0(\x05d4)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleOrdinalQuarterYear :: Rule
+ruleOrdinalQuarterYear = Rule
+  { name = "<ordinal> quarter <year>"
+  , pattern =
+    [ dimension Ordinal
+    , Predicate $ isGrain TG.Quarter
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:_:Token Time td:_) ->
+        tt $ cycleNthAfter False TG.Quarter (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleYyyymmdd :: Rule
+ruleYyyymmdd = Rule
+  { name = "yyyy-mm-dd"
+  , pattern =
+    [ regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (yy:mm:dd:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleTheOrdinalCycleAfterTime :: Rule
+ruleTheOrdinalCycleAfterTime = Rule
+  { name = "the <ordinal> <cycle> after <time>"
+  , pattern =
+    [ regex "\x05d4"
+    , dimension Ordinal
+    , dimension TimeGrain
+    , regex "\x05d0\x05d7\x05e8\x05d9|\x05dc\x05d0\x05d7\x05e8"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleNextNCycle :: Rule
+ruleNextNCycle = Rule
+  { name = "next n <cycle>"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    , regex "\x05d4\x05d1\x05d0|\x05d4\x05d1\x05d0\x05d4|\x05d4\x05d1\x05d0\x05d9\x05dd|\x05d4\x05d1\x05d0\x05d5\x05ea"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleMorning :: Rule
+ruleMorning = Rule
+  { name = "morning"
+  , pattern =
+    [ regex "(\x05d1)?\x05d1\x05d5\x05e7\x05e8"
+    ]
+  , prod = \_ -> tt . partOfDay . mkLatent $
+      interval TTime.Open (hour False 4, hour False 12)
+  }
+
+ruleThisCycle :: Rule
+ruleThisCycle = Rule
+  { name = "this <cycle>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "\x05d4\x05e7\x05e8\x05d5(\x05d1)?\x05d4|\x05d4\x05d6\x05d4|\x05d4\x05d6\x05d0\x05ea"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 0
+      _ -> Nothing
+  }
+
+ruleThisTime :: Rule
+ruleThisTime = Rule
+  { name = "this <time>"
+  , pattern =
+    [ dimension Time
+    , regex "\x05d4\x05e7\x05e8\x05d5\x05d1|\x05d4\x05d6\x05d4"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleDayofmonthNonOrdinalOfNamedmonth :: Rule
+ruleDayofmonthNonOrdinalOfNamedmonth = Rule
+  { name = "<day-of-month> (non ordinal) of <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , regex "\x05e9\x05dc|\x05d1|\x05dc"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleEndOfMonth :: Rule
+ruleEndOfMonth = Rule
+  { name = "End of month"
+  , pattern =
+    [ regex "\x05e1\x05d5\x05e3 (\x05d4)?\x05d7\x05d5\x05d3\x05e9"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Month 1
+  }
+
+ruleYesterday :: Rule
+ruleYesterday = Rule
+  { name = "yesterday"
+  , pattern =
+    [ regex "(\x05d0\x05ea\x05de\x05d5\x05dc|\x05d0\x05de\x05e9)"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 1
+  }
+
+ruleAfterTimeofday :: Rule
+ruleAfterTimeofday = Rule
+  { name = "after <time-of-day>"
+  , pattern =
+    [ regex "\x05d0\x05d7\x05e8\x05d9"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ withDirection TTime.After td
+      _ -> Nothing
+  }
+
+ruleDayofmonthOrdinal :: Rule
+ruleDayofmonthOrdinal = Rule
+  { name = "<day-of-month> (ordinal)"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ dayOfMonth n
+      _ -> Nothing
+  }
+
+ruleTimeofdayAmpm :: Rule
+ruleTimeofdayAmpm = Rule
+  { name = "<time-of-day> am|pm"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(in the )?([ap])(\\s|\\.)?m?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:Token RegexMatch (GroupMatch (_:ap:_)):_) ->
+        tt . timeOfDayAMPM td $ Text.toLower ap == "a"
+      _ -> Nothing
+  }
+
+ruleOrdinalCycleAfterTime :: Rule
+ruleOrdinalCycleAfterTime = Rule
+  { name = "<ordinal> <cycle> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "\x05d0\x05d7\x05e8\x05d9|\x05dc\x05d0\x05d7\x05e8"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleNamedmonth5 :: Rule
+ruleNamedmonth5 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x05de\x05d0\x05d9"
+    ]
+  , prod = \_ -> tt $ month 5
+  }
+
+ruleNamedday7 :: Rule
+ruleNamedday7 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "(\x05d9\x05d5\x05dd )?\x05e9\x05d1\x05ea"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 6
+  }
+
+ruleHhmm :: Rule
+ruleHhmm = Rule
+  { name = "hh:mm"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt $ hourMinute True h m
+      _ -> Nothing
+  }
+
+ruleTimeOfPartofday :: Rule
+ruleTimeOfPartofday = Rule
+  { name = "<time> of <part-of-day>"
+  , pattern =
+    [ dimension Time
+    , regex "\x05d1"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) -> Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleYear :: Rule
+ruleYear = Rule
+  { name = "year"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1000 2100
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt $ year n
+      _ -> Nothing
+  }
+
+ruleNamedmonth10 :: Rule
+ruleNamedmonth10 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x05d0\x05d5\x05e7\x05d8\x05d5\x05d1\x05e8"
+    ]
+  , prod = \_ -> tt $ month 10
+  }
+
+ruleNamedmonthDayofmonthNonOrdinal :: Rule
+ruleNamedmonthDayofmonthNonOrdinal = 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
+  }
+
+ruleNamedday8 :: Rule
+ruleNamedday8 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "(\x05d9\x05d5\x05dd )?\x05e8\x05d0\x05e9\x05d5\x05df"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 7
+  }
+
+ruleAbsorptionOfAfterNamedDay :: Rule
+ruleAbsorptionOfAfterNamedDay = Rule
+  { name = "absorption of , after named day"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex ","
+    ]
+  , prod = \tokens -> case tokens of
+      (x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleLastDayofweekOfTime :: Rule
+ruleLastDayofweekOfTime = Rule
+  { name = "last <day-of-week> of <time>"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex "\x05d4\x05d0\x05d7\x05e8\x05d5\x05df \x05e9\x05dc"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ predLastOf td1 td2
+      _ -> Nothing
+  }
+
+ruleHhmmMilitaryAmpm :: Rule
+ruleHhmmMilitaryAmpm = Rule
+  { name = "hhmm (military) am|pm"
+  , pattern =
+    [ regex "((?:1[012]|0?\\d))([0-5]\\d)"
+    , regex "([ap])\\.?m?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):Token RegexMatch (GroupMatch (ap:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt . timeOfDayAMPM (hourMinute True h m) $
+          Text.toLower ap == "a"
+      _ -> Nothing
+  }
+
+ruleCycleBeforeTime :: Rule
+ruleCycleBeforeTime = Rule
+  { name = "<cycle> before <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "\x05dc\x05e4\x05e0\x05d9"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain (-1) td
+      _ -> Nothing
+  }
+
+ruleNamedmonth11 :: Rule
+ruleNamedmonth11 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x05e0\x05d5\x05d1\x05de\x05d1\x05e8"
+    ]
+  , prod = \_ -> tt $ month 11
+  }
+
+ruleDurationAfterTime :: Rule
+ruleDurationAfterTime = Rule
+  { name = "<duration> after <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "\x05d0\x05d7\x05e8\x05d9|\x05dc\x05d0\x05d7\x05e8"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_:Token Time td:_) ->
+        tt $ durationAfter dd td
+      _ -> Nothing
+  }
+
+ruleEveningnight :: Rule
+ruleEveningnight = Rule
+  { name = "evening|night"
+  , pattern =
+    [ regex "(\x05d1)?\x05e2\x05e8\x05d1"
+    ]
+  , prod = \_ -> tt . partOfDay . mkLatent $
+      interval TTime.Open (hour False 18, hour False 0)
+  }
+
+ruleOrdinalQuarter :: Rule
+ruleOrdinalQuarter = 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
+  }
+
+ruleNamedday3 :: Rule
+ruleNamedday3 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "(\x05d9\x05d5\x05dd )?\x05e9\x05dc\x05d9\x05e9\x05d9"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 2
+  }
+
+ruleTheDayofmonthOrdinal :: Rule
+ruleTheDayofmonthOrdinal = Rule
+  { name = "the <day-of-month> (ordinal)"
+  , pattern =
+    [ regex "\x05d4"
+    , Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       _) -> tt $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleDurationBeforeTime :: Rule
+ruleDurationBeforeTime = Rule
+  { name = "<duration> before <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "\x05dc\x05e4\x05e0\x05d9"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_:Token Time td:_) ->
+        tt $ durationBefore dd td
+      _ -> Nothing
+  }
+
+rulePartofdayOfTime :: Rule
+rulePartofdayOfTime = Rule
+  { name = "<part-of-day> of <time>"
+  , pattern =
+    [ regex "\x05d1"
+    , Predicate isAPartOfDay
+    , regex "\x05e9\x05dc"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) -> Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleMmddyyyy :: Rule
+ruleMmddyyyy = Rule
+  { name = "mm/dd/yyyy"
+  , pattern =
+    [ regex "(0?[1-9]|1[0-2])[/-](3[01]|[12]\\d|0?[1-9])[-/](\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (mm:dd:yy:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleTomorrow :: Rule
+ruleTomorrow = Rule
+  { name = "tomorrow"
+  , pattern =
+    [ regex "(\x05de\x05d7\x05e8|\x05dc\x05de\x05d7\x05e8\x05ea)"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 1
+  }
+
+ruleTimeofdayOclock :: Rule
+ruleTimeofdayOclock = 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
+  }
+
+ruleNamedmonth9 :: Rule
+ruleNamedmonth9 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x05e1\x05e4\x05d8\x05de\x05d1\x05e8"
+    ]
+  , prod = \_ -> tt $ month 9
+  }
+
+ruleDayofmonthordinalNamedmonthYear :: Rule
+ruleDayofmonthordinalNamedmonthYear = 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
+  }
+
+ruleHhmmss :: Rule
+ruleHhmmss = Rule
+  { name = "hh:mm:ss"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)[:.]([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:ss:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        s <- parseInt ss
+        tt $ hourMinuteSecond True h m s
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAbsorptionOfAfterNamedDay
+  , ruleAfterDuration
+  , ruleAfterTimeofday
+  , ruleAfternoon
+  , ruleAtHourTimeofday
+  , ruleAtTimeofday
+  , ruleBetweenDatetimeAndDatetimeInterval
+  , ruleBetweenTimeofdayAndTimeofdayInterval
+  , ruleCurrentDayofweek
+  , ruleCycleAfterTime
+  , ruleCycleBeforeTime
+  , ruleDate
+  , ruleDatetimeDatetimeInterval
+  , ruleDayofmonthNonOrdinalNamedmonth
+  , ruleDayofmonthNonOrdinalOfNamedmonth
+  , ruleDayofmonthOrdinal
+  , ruleDayofmonthOrdinalOfNamedmonth
+  , ruleDayofmonthordinalNamedmonth
+  , ruleDayofmonthordinalNamedmonthYear
+  , ruleDurationAfterTime
+  , ruleDurationAgo
+  , ruleDurationBeforeTime
+  , ruleDurationFromNow
+  , ruleEndOfMonth
+  , ruleEndOfYear
+  , ruleEveningnight
+  , ruleForDuration
+  , ruleFromDatetimeDatetimeInterval
+  , ruleHhmm
+  , ruleHhmmMilitaryAmpm
+  , ruleHhmmss
+  , ruleHourofdayAndInteger
+  , ruleHourofdayAndQuarter
+  , ruleHourofdayAndHalf
+  , ruleHourofdayInteger
+  , ruleHourofdayQuarter
+  , ruleHourofdayHalf
+  , ruleInDuration
+  , ruleInNamedmonth
+  , ruleIntersect
+  , ruleIntersectBy
+  , ruleLastCycle
+  , ruleLastDayofweek
+  , ruleLastDayofweekOfTime
+  , ruleLastNCycle
+  , ruleLastTime
+  , ruleLunch
+  , ruleMidnighteodendOfDay
+  , ruleMmdd
+  , ruleMmddyyyy
+  , ruleMorning
+  , ruleNamedday
+  , ruleNamedday2
+  , ruleNamedday3
+  , ruleNamedday4
+  , ruleNamedday5
+  , ruleNamedday6
+  , ruleNamedday7
+  , ruleNamedday8
+  , ruleNameddayDayofmonthOrdinal
+  , ruleNamedmonth
+  , ruleNamedmonth10
+  , ruleNamedmonth11
+  , ruleNamedmonth12
+  , ruleNamedmonth2
+  , ruleNamedmonth3
+  , ruleNamedmonth4
+  , ruleNamedmonth5
+  , ruleNamedmonth6
+  , ruleNamedmonth7
+  , ruleNamedmonth8
+  , ruleNamedmonth9
+  , ruleNamedmonthDayofmonthNonOrdinal
+  , ruleNamedmonthDayofmonthOrdinal
+  , ruleNextCycle
+  , ruleNextDayofweek
+  , ruleNextNCycle
+  , ruleNextTime
+  , ruleNoon
+  , ruleNow
+  , ruleNthTimeAfterTime
+  , ruleNthTimeOfTime
+  , ruleOrdinalCycleAfterTime
+  , ruleOrdinalQuarter
+  , ruleOrdinalQuarterYear
+  , rulePartofdayOfTime
+  , ruleIntegerAfterpastIntegerHourofday
+  , ruleQuarterAfterpastIntegerHourofday
+  , ruleHalfAfterpastIntegerHourofday
+  , ruleIntegerTotillbeforeIntegerHourofday
+  , ruleQuarterTotillbeforeIntegerHourofday
+  , ruleHalfTotillbeforeIntegerHourofday
+  , ruleSinceTimeofday
+  , ruleTheCycleAfterTime
+  , ruleTheCycleBeforeTime
+  , ruleTheDayofmonthNonOrdinal
+  , ruleTheDayofmonthOrdinal
+  , ruleTheIdesOfNamedmonth
+  , ruleTheOrdinalCycleAfterTime
+  , ruleThisCycle
+  , ruleThisEvening
+  , ruleThisTime
+  , ruleTimeOfPartofday
+  , ruleTimePartofday
+  , ruleTimeofdayAmpm
+  , ruleTimeofdayLatent
+  , ruleTimeofdayOclock
+  , ruleToday
+  , ruleTomorrow
+  , ruleUntilTimeofday
+  , ruleWeekend
+  , ruleYear
+  , ruleYesterday
+  , ruleYyyymmdd
+  ]
diff --git a/Duckling/Time/HR/Corpus.hs b/Duckling/Time/HR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/HR/Corpus.hs
@@ -0,0 +1,671 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.HR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Time.Corpus
+import Duckling.Time.Types hiding (Month)
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Testing.Types hiding (examples)
+
+corpus :: Corpus
+corpus = (testContext {lang = HR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (datetime (2013, 2, 12, 4, 30, 0) Second)
+             [ "sad"
+             , "sada"
+             , "upravo sad"
+             , "ovaj tren"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "danas"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "jucer"
+             , "jučer"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "sutra"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "ponedjeljak"
+             , "pon."
+             , "ovaj ponedjeljak"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "ponedjeljak, 18. veljace"
+             , "ponedjeljak, 18. veljače"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "utorak"
+             , "utorak 19."
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "cetvrtak"
+             , "četvrtak"
+             , "čet"
+             , "cet."
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "petak"
+             , "pet"
+             , "pet."
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "subota"
+             , "sub"
+             , "sub."
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "nedjelja"
+             , "ned"
+             , "ned."
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "1. ozujak"
+             , "1. ožujak"
+             , "prvi ozujka"
+             ]
+  , examples (datetime (2013, 3, 3, 0, 0, 0) Day)
+             [ "treci ozujka"
+             , "treci ožujka"
+             ]
+  , examples (datetime (2015, 3, 3, 0, 0, 0) Day)
+             [ "3. ozujka 2015"
+             , "treci ozujka 2015"
+             , "3/3/2015"
+             , "3/3/15"
+             , "2015-3-3"
+             , "2015-03-03"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "15ti drugi"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "15. veljace"
+             , "15. veljače"
+             , "15/02"
+             ]
+  , examples (datetime (2013, 8, 8, 0, 0, 0) Day)
+             [ "8. kolovoza"
+             , "8. kolovoz"
+             ]
+  , examples (datetime (2014, 10, 0, 0, 0, 0) Month)
+             [ "listopad 2014"
+             ]
+  , examples (datetime (1974, 10, 31, 0, 0, 0) Day)
+             [ "31/10/1974"
+             , "31/10/74"
+             , "74-10-31"
+             ]
+  , examples (datetime (2015, 4, 14, 0, 0, 0) Day)
+             [ "14travanj 2015"
+             , "14. travnja, 2015"
+             , "14. travanj 15"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "sljedeci utorak"
+             , "sljedeceg utorka"
+             ]
+  , examples (datetime (2013, 2, 22, 0, 0, 0) Day)
+             [ "petak nakon sljedeceg"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "sljedeci ozujak"
+             ]
+  , examples (datetime (2014, 3, 0, 0, 0, 0) Month)
+             [ "ozujak nakon sljedeceg"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "nedjelja, 10. veljace"
+             , "nedjelja, 10. veljače"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "Sri, 13. velj"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "ponedjeljak, veljaca 18."
+             , "Pon, 18. veljace"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Week)
+             [ "ovaj tjedan"
+             ]
+  , examples (datetime (2013, 2, 4, 0, 0, 0) Week)
+             [ "prosli tjedan"
+             , "prošli tjedan"
+             , "prethodni tjedan"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Week)
+             [ "sljedeci tjedan"
+             ]
+  , examples (datetime (2013, 1, 0, 0, 0, 0) Month)
+             [ "prethodni mjesec"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "sljedeci mjesec"
+             ]
+  , examples (datetime (2013, 1, 1, 0, 0, 0) Quarter)
+             [ "ovaj kvartal"
+             , "ovo tromjesecje"
+             ]
+  , examples (datetime (2013, 4, 1, 0, 0, 0) Quarter)
+             [ "sljedeci kvartal"
+             ]
+  , examples (datetime (2013, 7, 1, 0, 0, 0) Quarter)
+             [ "treci kvartal"
+             , "3. kvartal"
+             , "trece tromjesecje"
+             , "3. tromjesečje"
+             ]
+  , examples (datetime (2018, 10, 1, 0, 0, 0) Quarter)
+             [ "4. kvartal 2018"
+             , "četvrto tromjesečje 2018"
+             ]
+  , examples (datetime (2012, 0, 0, 0, 0, 0) Year)
+             [ "prošla godina"
+             , "prethodna godina"
+             ]
+  , examples (datetime (2013, 0, 0, 0, 0, 0) Year)
+             [ "ova godina"
+             ]
+  , examples (datetime (2014, 0, 0, 0, 0, 0) Year)
+             [ "sljedece godina"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "prosle nedjelje"
+             , "prosli tjedan u nedjelju"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "prosli utorak"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "sljedeci utorak"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "sljedecu srijedu"
+             ]
+  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
+             [ "sljedeci tjedan u srijedu"
+             , "srijeda sljedeci tjedan"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "sljedeci petak"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "ovaj tjedan u ponedjeljak"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "ovaj utorak"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "ova srijeda"
+             , "ovaj tjedan u srijedu"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "prekosutra"
+             ]
+  , examples (datetime (2013, 2, 14, 17, 0, 0) Hour)
+             [ "prekosutra u 5 popodne"
+             , "prekosutra u 17"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "prekjucer"
+             , "prekjučer"
+             ]
+  , examples (datetime (2013, 2, 10, 8, 0, 0) Hour)
+             [ "prekjučer u 8"
+             , "prekjučer u 8 sati"
+             ]
+  , examples (datetime (2013, 3, 25, 0, 0, 0) Day)
+             [ "zadnji ponedjeljak u ozujku"
+             ]
+  , examples (datetime (2014, 3, 30, 0, 0, 0) Day)
+             [ "zadnja nedjelja u ozujku 2014"
+             ]
+  , examples (datetime (2013, 10, 3, 0, 0, 0) Day)
+             [ "treci dan u listopadu"
+             ]
+  , examples (datetime (2014, 10, 6, 0, 0, 0) Week)
+             [ "prvi tjedan u listopadu 2014"
+             ]
+  , examples (datetime (2015, 10, 31, 0, 0, 0) Day)
+             [ "zadnji dan u listopadu 2015"
+             ]
+  , examples (datetime (2014, 9, 22, 0, 0, 0) Week)
+             [ "zadnji tjedan u rujnu 2014"
+             ]
+  , examples (datetime (2013, 10, 1, 0, 0, 0) Day)
+             [ "prvi utorak u listopadu"
+             ]
+  , examples (datetime (2014, 9, 16, 0, 0, 0) Day)
+             [ "treci utorak u rujnu 2014"
+             ]
+  , examples (datetime (2014, 10, 1, 0, 0, 0) Day)
+             [ "prva srijeda u listopadu 2014"
+             ]
+  , examples (datetime (2014, 10, 8, 0, 0, 0) Day)
+             [ "druga srijeda u listopadu 2014"
+             ]
+  , examples (datetime (2015, 1, 13, 0, 0, 0) Day)
+             [ "treci utorak poslije Bozica 2014"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 0, 0) Hour)
+             [ "3 u noci"
+             , "u 3 ujutro"
+             , "u tri sata u noci"
+             ]
+  , examples (datetime (2013, 2, 12, 3, 18, 0) Minute)
+             [ "3:18 rano"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "u 3 poslijepodne"
+             , "@ 15"
+             , "15 sati poslijepodne"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "oko 3 poslijepodne"
+             , "otprilike u 3 poslijepodne"
+             , "cca 3 poslijepodne"
+             , "cca 15"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 15, 0) Minute)
+             [ "15 i 15"
+             , "3:15 poslijepodne"
+             , "15:15"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 15, 0) Minute)
+             [ "cetvrt nakon 3 poslijepodne"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 20, 0) Minute)
+             [ "3 i 20 popodne"
+             , "3:20 poslijepodne"
+             , "3:20 popodne"
+             , "dvadeset nakon 3 popodne"
+             , "15:20"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 30, 0) Minute)
+             [ "tri i po popodne"
+             , "pola 4 popodne"
+             , "15:30"
+             , "pola cetiri popodne"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 23, 24) Second)
+             [ "15:23:24"
+             ]
+  , examples (datetime (2013, 2, 12, 11, 45, 0) Minute)
+             [ "petnaest do podne"
+             , "11:45"
+             , "četvrt do podneva"
+             ]
+  , examples (datetime (2013, 2, 12, 20, 0, 0) Hour)
+             [ "8 navecer"
+             , "osam sati navecer"
+             , "danas 8 navecer"
+             ]
+  , examples (datetime (2013, 9, 20, 19, 30, 0) Minute)
+             [ "u 7:30 popodne u pet, 20. rujna"
+             ]
+  , examples (datetime (2013, 2, 16, 9, 0, 0) Hour)
+             [ "9 ujutro u subotu"
+             , "u subotu u 9 sati ujutro"
+             ]
+  , examples (datetime (2014, 7, 18, 19, 0, 0) Minute)
+             [ "pet, srp 18., 2014, 19:00"
+             , "pet, srp 18., 2014 u 19:00"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 30, 1) Second)
+             [ "za jednu sekundu"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 31, 0) Second)
+             [ "za jednu minutu"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 32, 0) Second)
+             [ "za 2 minute"
+             , "za jos 2 minute"
+             , "2 minute od sad"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Second)
+             [ "za 60 minuta"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 45, 0) Second)
+             [ "oko cetvrt sata"
+             , "oko 1/4h"
+             , "oko 1/4 h"
+             , "oko 1/4 sata"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 0, 0) Second)
+             [ "za pola sata"
+             , "za pol sata"
+             , "za 1/2h"
+             , "za 1/2 h"
+             , "za 1/2 sata"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 15, 0) Second)
+             [ "za tri-cetvrt sata"
+             , "za 3/4h"
+             , "za 3/4 h"
+             , "za 3/4 sata"
+             ]
+  , examples (datetime (2013, 2, 12, 7, 0, 0) Second)
+             [ "za 2.5 sata"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Minute)
+             [ "za jedan sat"
+             , "za 1h"
+             ]
+  , examples (datetime (2013, 2, 12, 6, 30, 0) Minute)
+             [ "za par sati"
+             ]
+  , examples (datetime (2013, 2, 12, 7, 30, 0) Minute)
+             [ "za nekoliko sati"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 30, 0) Minute)
+             [ "za 24 sata"
+             , "za 24h"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 0, 0) Hour)
+             [ "za 1 dan"
+             , "za jedan dan"
+             ]
+  , examples (datetime (2016, 2, 0, 0, 0, 0) Month)
+             [ "3 godine od danasnjeg dana"
+             ]
+  , examples (datetime (2013, 2, 19, 4, 0, 0) Hour)
+             [ "za 7 dana"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "za 1 tjedan"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 0, 0) Second)
+             [ "za oko pola sata"
+             ]
+  , examples (datetime (2013, 2, 5, 4, 0, 0) Hour)
+             [ "prije 7 dana"
+             ]
+  , examples (datetime (2013, 1, 29, 4, 0, 0) Hour)
+             [ "prije 14 dana"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "prije jedan tjedan"
+             , "prije jednog tjedna"
+             ]
+  , examples (datetime (2013, 1, 22, 0, 0, 0) Day)
+             [ "prije tri tjedna"
+             ]
+  , examples (datetime (2012, 11, 12, 0, 0, 0) Day)
+             [ "prije tri mjeseca"
+             ]
+  , examples (datetime (2011, 2, 0, 0, 0, 0) Month)
+             [ "prije dvije godine"
+             ]
+  , examples (datetime (1954, 0, 0, 0, 0, 0) Year)
+             [ "1954"
+             ]
+  , examples (datetime (2013, 2, 19, 4, 0, 0) Hour)
+             [ "za 7 dana"
+             ]
+  , examples (datetime (2013, 2, 26, 4, 0, 0) Hour)
+             [ "za 14 dana"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "za jedan tjedan"
+             ]
+  , examples (datetime (2013, 3, 5, 0, 0, 0) Day)
+             [ "za tri tjedna"
+             ]
+  , examples (datetime (2013, 5, 12, 0, 0, 0) Day)
+             [ "za tri mjeseca"
+             ]
+  , examples (datetime (2015, 2, 0, 0, 0, 0) Month)
+             [ "za dvije godine"
+             ]
+  , examples (datetime (2013, 12, 0, 0, 0, 0) Month)
+             [ "jednu godinu poslije Bozica"
+             ]
+  , examples (datetimeInterval ((2013, 6, 21, 0, 0, 0), (2013, 9, 24, 0, 0, 0)) Day)
+             [ "ovog ljeta"
+             , "ovo ljeto"
+             , "ljetos"
+             ]
+  , examples (datetimeInterval ((2012, 12, 21, 0, 0, 0), (2013, 3, 21, 0, 0, 0)) Day)
+             [ "ove zime"
+             , "zimus"
+             ]
+  , examples (datetime (2013, 12, 25, 0, 0, 0) Day)
+             [ "Bozic"
+             , "zicbo"
+             ]
+  , examples (datetime (2013, 12, 31, 0, 0, 0) Day)
+             [ "stara godina"
+             ]
+  , examples (datetime (2014, 1, 1, 0, 0, 0) Day)
+             [ "nova godina"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "valentinovo"
+             ]
+  , examples (datetime (2013, 5, 12, 0, 0, 0) Day)
+             [ "majcin dan"
+             ]
+  , examples (datetime (2013, 6, 16, 0, 0, 0) Day)
+             [ "dan oceva"
+             ]
+  , examples (datetime (2013, 10, 31, 0, 0, 0) Day)
+             [ "noc vjestica"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 18, 0, 0), (2013, 2, 13, 0, 0, 0)) Hour)
+             [ "veceras"
+             , "ove veceri"
+             , "danas navecer"
+             ]
+  , examples (datetimeInterval ((2013, 2, 8, 18, 0, 0), (2013, 2, 11, 0, 0, 0)) Hour)
+             [ "prosli vikend"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 18, 0, 0), (2013, 2, 14, 0, 0, 0)) Hour)
+             [ "sutra navecer"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 12, 0, 0), (2013, 2, 13, 14, 0, 0)) Hour)
+             [ "sutra rucak"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 18, 0, 0), (2013, 2, 12, 0, 0, 0)) Hour)
+             [ "jucer navecer"
+             , "prethodne veceri"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 18, 0, 0), (2013, 2, 18, 0, 0, 0)) Hour)
+             [ "ovaj vikend"
+             , "ovog vikenda"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 4, 0, 0), (2013, 2, 18, 12, 0, 0)) Hour)
+             [ "ponedjeljak ujutro"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 3, 0, 0), (2013, 2, 18, 9, 0, 0)) Hour)
+             [ "ponedjeljak rano ujutro"
+             , "ponedjeljak rano"
+             , "ponedjeljak u rane jutarnje sate"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 4, 0, 0), (2013, 2, 15, 12, 0, 0)) Hour)
+             [ "15. veljace ujutro"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 29, 58), (2013, 2, 12, 4, 30, 0)) Second)
+             [ "prosle 2 sekunde"
+             , "prethodne dvije sekunde"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 1), (2013, 2, 12, 4, 30, 4)) Second)
+             [ "sljedece 3 sekunde"
+             , "sljedece tri sekunde"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 28, 0), (2013, 2, 12, 4, 30, 0)) Minute)
+             [ "prosle 2 minute"
+             , "prethodne dvije minute"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 31, 0), (2013, 2, 12, 4, 34, 0)) Minute)
+             [ "sljedece 3 minute"
+             , "sljedece tri minute"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 3, 0, 0), (2013, 2, 12, 4, 0, 0)) Hour)
+             [ "prethodni jedan sat"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 4, 0, 0), (2013, 2, 12, 4, 0, 0)) Hour)
+             [ "prethodna 24 sata"
+             , "prethodna dvadeset i cetiri sata"
+             , "prethodna dvadeset i cetiri sata"
+             , "prethodna 24h"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 5, 0, 0), (2013, 2, 12, 8, 0, 0)) Hour)
+             [ "sljedeca 3 sata"
+             , "sljedeca tri sata"
+             ]
+  , examples (datetimeInterval ((2013, 2, 10, 0, 0, 0), (2013, 2, 12, 0, 0, 0)) Day)
+             [ "prethodna dva dana"
+             , "prethodna 2 dana"
+             , "prosla 2 dana"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
+             [ "sljedeca 3 dana"
+             , "sljedeca tri dana"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
+             [ "sljedecih nekoliko dana"
+             ]
+  , examples (datetimeInterval ((2013, 1, 28, 0, 0, 0), (2013, 2, 11, 0, 0, 0)) Week)
+             [ "prethodna 2 tjedna"
+             , "prethodna dva tjedna"
+             , "prosla 2 tjedna"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 0, 0, 0), (2013, 3, 11, 0, 0, 0)) Week)
+             [ "sljedeca 3 tjedna"
+             , "sljedeca tri tjedna"
+             ]
+  , examples (datetimeInterval ((2012, 12, 0, 0, 0, 0), (2013, 2, 0, 0, 0, 0)) Month)
+             [ "prethodna 2 mjeseca"
+             , "prethodna dva mjeseca"
+             ]
+  , examples (datetimeInterval ((2013, 3, 0, 0, 0, 0), (2013, 6, 0, 0, 0, 0)) Month)
+             [ "sljedeca 3 mjeseca"
+             , "sljedeca tri mjeseca"
+             ]
+  , examples (datetimeInterval ((2011, 0, 0, 0, 0, 0), (2013, 0, 0, 0, 0, 0)) Year)
+             [ "prethodne 2 godine"
+             , "prethodne dvije godine"
+             ]
+  , examples (datetimeInterval ((2014, 0, 0, 0, 0, 0), (2017, 0, 0, 0, 0, 0)) Year)
+             [ "sljedece 3 godine"
+             , "sljedece tri godine"
+             ]
+  , examples (datetimeInterval ((2013, 7, 13, 0, 0, 0), (2013, 7, 16, 0, 0, 0)) Day)
+             [ "srpanj 13-15"
+             , "srpanj 13 do 15"
+             , "srpanj 13 - srpanj 15"
+             ]
+  , examples (datetimeInterval ((2013, 8, 8, 0, 0, 0), (2013, 8, 13, 0, 0, 0)) Day)
+             [ "kol 8 - kol 12"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 9, 30, 0), (2013, 2, 12, 11, 1, 0)) Minute)
+             [ "9:30 - 11:00"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 30, 0), (2013, 2, 14, 11, 1, 0)) Minute)
+             [ "od 9:30 - 11:00 u cetvrtak"
+             , "između 9:30 i 11:00 u cetvrtak"
+             , "9:30 - 11:00 u cetvrtak"
+             , "izmedju 9:30 i 11:00 u cetvrtak"
+             , "cetvrtak od 9:30 do 11:00"
+             , "od 9:30 do 11:00 u cetvrtak"
+             , "cetvrtak od 9:30 do 11:00"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 0, 0), (2013, 2, 14, 12, 0, 0)) Hour)
+             [ "cetvrtak od 9 do 11 ujutro"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 11, 30, 0), (2013, 2, 12, 13, 31, 0)) Minute)
+             [ "11:30-1:30"
+             ]
+  , examples (datetime (2013, 9, 21, 13, 30, 0) Minute)
+             [ "1:30 poslijepodne u sub, ruj 21."
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 0, 0, 0), (2013, 3, 4, 0, 0, 0)) Week)
+             [ "sljedeca 2 tjedna"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 14, 0, 0) Hour)
+             [ "nekad do 2 poslijepodne"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 13, 0, 0, 0)) Second)
+             [ "do kraja ovog dana"
+             , "do kraja dana"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 3, 1, 0, 0, 0)) Second)
+             [ "do kraja ovog mjeseca"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 4, 1, 0, 0, 0)) Second)
+             [ "do kraja sljedeceg mjeseca"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 0, 0) Minute)
+             [ "4 poslijepodne CET"
+             ]
+  , examples (datetime (2013, 2, 14, 6, 0, 0) Minute)
+             [ "cetvrtak 8:00 GMT"
+             ]
+  , examples (datetime (2013, 2, 12, 14, 0, 0) Hour)
+             [ "danas u 14"
+             , "u 2 poslijepodne"
+             ]
+  , examples (datetime (2013, 4, 25, 16, 0, 0) Hour)
+             [ "25/4 U 16 sati"
+             ]
+  , examples (datetime (2013, 2, 13, 15, 0, 0) Hour)
+             [ "15 sati sutra"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 17, 4, 0, 0) Hour)
+             [ "nakon 5 dana"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 11, 0, 0) Hour)
+             [ "prije 11 sat"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 12, 0, 0), (2013, 2, 12, 20, 0, 0)) Hour)
+             [ "ova poslijepodne"
+             , "ovi popodne"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 30, 0) Minute)
+             [ "u 13:30"
+             , "13:30"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 45, 0) Second)
+             [ "za 15 minuta"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 13, 0, 0), (2013, 2, 12, 17, 0, 0)) Hour)
+             [ "poslije rucka"
+             ]
+  , examples (datetime (2013, 2, 12, 10, 30, 0) Minute)
+             [ "10:30"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 0, 0), (2013, 2, 12, 12, 0, 0)) Hour)
+             [ "ove jutro"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "sljedeci ponedjeljak"
+             ]
+  , examples (datetime (2013, 2, 12, 12, 0, 0) Hour)
+             [ "u 12"
+             , "u podne"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Hour)
+             [ "u 12 u noci"
+             , "u ponoc"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "ozujak"
+             , "u ozujku"
+             ]
+  ]
diff --git a/Duckling/Time/HR/Rules.hs b/Duckling/Time/HR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/HR/Rules.hs
@@ -0,0 +1,1898 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.HR.Rules
+  ( rules ) where
+
+import Control.Monad (join, liftM2)
+import Data.Maybe
+import Data.String
+import Data.Text (Text)
+import Prelude
+import qualified Data.Text as Text
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Numeral.Types (NumeralData (..))
+import Duckling.Ordinal.Types (OrdinalData (..))
+import Duckling.Regex.Types
+import Duckling.Time.Helpers
+import Duckling.Time.Types (TimeData (..))
+import Duckling.Types
+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
+
+ruleNamedday :: Rule
+ruleNamedday = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "ponedjelja?ka?|pon\\.?"
+    ]
+  , prod = \_ -> Just . Token Time $ dayOfWeek 1
+  }
+
+ruleNamedmonth12 :: Rule
+ruleNamedmonth12 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "prosina?c(a|u)?|decemba?r(a|u)?|dec\\.?|pros?\\.?|dvanaest(i|a|o(ga?)?)"
+    ]
+  , prod = \_ -> Just . Token Time $ month 12
+  }
+
+ruleHalfIntegerHrStyleHourofday :: Rule
+ruleHalfIntegerHrStyleHourofday = Rule
+  { name = "half <integer> (HR style hour-of-day)"
+  , pattern =
+    [ regex "pol?a?"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesBefore 30 td
+      _ -> Nothing
+  }
+
+ruleNumeralTotillbeforeIntegerHourofday :: Rule
+ruleNumeralTotillbeforeIntegerHourofday = Rule
+  { name = "numeral to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "do"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleQuarterTotillbeforeIntegerHourofday :: Rule
+ruleQuarterTotillbeforeIntegerHourofday = Rule
+  { name = "quarter to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ regex "(kvarata?|(c|\x010d)etvrt|frtalj)\\s+do"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesBefore 15 td
+      _ -> Nothing
+  }
+
+ruleHalfTotillbeforeIntegerHourofday :: Rule
+ruleHalfTotillbeforeIntegerHourofday = Rule
+  { name = "half to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ regex "di pol?a?\\s+do"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesBefore 30 td
+      _ -> Nothing
+  }
+
+ruleNumeralAfterpastHourofday :: Rule
+ruleNumeralAfterpastHourofday = Rule
+  { name = "numeral after|past (hour-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "poslije|nakon"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        t <- minutesAfter n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleQuarterAfterpastHourofday :: Rule
+ruleQuarterAfterpastHourofday = Rule
+  { name = "quarter after|past (hour-of-day)"
+  , pattern =
+    [ regex "(kvarata?|(c|\x010d)etvrt|frtalj)\\s+(poslije|nakon)"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesAfter 15 td
+      _ -> Nothing
+  }
+
+ruleHalfAfterpastHourofday :: Rule
+ruleHalfAfterpastHourofday = Rule
+  { name = "half after|past (hour-of-day)"
+  , pattern =
+    [ regex "(i pol?a?)\\s+(poslije|nakon)"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesAfter 30 td
+      _ -> Nothing
+  }
+
+ruleHourofdayNumeral :: Rule
+ruleHourofdayNumeral = Rule
+  { name = "<hour-of-day> <integer> (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleHourofdayQuarter :: Rule
+ruleHourofdayQuarter = Rule
+  { name = "<hour-of-day> quarter"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "kvarata?|(c|\x010d)etvrt|frtalj"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
+        tt $ hourMinute is12H hours 15
+      _ -> Nothing
+  }
+
+ruleHourofdayHalf :: Rule
+ruleHourofdayHalf = Rule
+  { name = "<hour-of-day> half"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "i pol?a?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
+        tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+
+ruleZaNumeralHourofday :: Rule
+ruleZaNumeralHourofday = Rule
+  { name = "za <integer> (hour-of-day)"
+  , pattern =
+    [ regex "za"
+    , Predicate $ isIntegerBetween 1 59
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       token:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleZaQuarterHourofday :: Rule
+ruleZaQuarterHourofday = Rule
+  { name = "za quarter (hour-of-day)"
+  , pattern =
+    [ regex "za\\s+(kvarata?|(c|\x010d)etvrt|frtalj)"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 15
+      _ -> Nothing
+  }
+
+ruleZaHalfHourofday :: Rule
+ruleZaHalfHourofday = Rule
+  { name = "za half (hour-of-day)"
+  , pattern =
+    [ regex "za\\s+(i pol?a?)"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+
+ruleNamedday2 :: Rule
+ruleNamedday2 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "utora?ka?|uto?\\.?"
+    ]
+  , prod = \_ -> Just . Token Time $ dayOfWeek 2
+  }
+
+ruleValentinesDay :: Rule
+ruleValentinesDay = Rule
+  { name = "valentine's day"
+  , pattern =
+    [ regex "valentinov(og?|a|)"
+    ]
+  , prod = \_ -> Just . Token Time $ monthDay 2 14
+  }
+
+ruleSinceTimeofday :: Rule
+ruleSinceTimeofday = Rule
+  { name = "since <time-of-day>"
+  , pattern =
+    [ regex "od"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ withDirection TTime.After td
+      _ -> Nothing
+  }
+
+ruleNewYearsDay :: Rule
+ruleNewYearsDay = Rule
+  { name = "new year's day"
+  , pattern =
+    [ regex "nov(a|u|e) godin(a|e|u)"
+    ]
+  , prod = \_ -> Just . Token Time $ monthDay 1 1
+  }
+
+ruleLastTime :: Rule
+ruleLastTime = Rule
+  { name = "last <time>"
+  , pattern =
+    [ regex "(prethodn(i|u|a|e|o(ga?)?)|pro(s|\x0161)l(ih?|u|a|e|o(ga?)?))"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        Just . Token Time $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleNamedday6 :: Rule
+ruleNamedday6 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "subot(a|e|u)|sub?\\.?"
+    ]
+  , prod = \_ -> Just . Token Time $ dayOfWeek 6
+  }
+
+ruleDatetimeDatetimeInterval :: Rule
+ruleDatetimeDatetimeInterval = Rule
+  { name = "<datetime> - <datetime> (interval)"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "\\-"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Just . Token Time $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth7 :: Rule
+ruleNamedmonth7 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "srpa?nj(a|u)?|jul(i|u|a)?|jul\\.?|srp\\.?|sedm(i|a|o(ga?)?)"
+    ]
+  , prod = \_ -> Just . Token Time $ month 7
+  }
+
+ruleCycleAfterTime :: Rule
+ruleCycleAfterTime = Rule
+  { name = "<cycle> after <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "nakon"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        Just . Token Time $ cycleNthAfter False grain 1 td
+      _ -> Nothing
+  }
+
+ruleInDuration :: Rule
+ruleInDuration = Rule
+  { name = "in <duration>"
+  , pattern =
+    [ regex "za"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) -> Just . Token Time $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleNow :: Rule
+ruleNow = Rule
+  { name = "now"
+  , pattern =
+    [ regex "((upravo|ov(aj?|og?|e|i))? ?sada?|trenutak|upravo|trenutno)|(ov(aj|og) trena?)"
+    ]
+  , prod = \_ -> Just . Token Time $ cycleNth TG.Second 0
+  }
+
+ruleLastCycleOfTime :: Rule
+ruleLastCycleOfTime = Rule
+  { name = "last <cycle> of <time>"
+  , pattern =
+    [ regex "zadnj(ih?|a|e(ga?)?)"
+    , dimension TimeGrain
+    , regex "u"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        Just . Token Time $ cycleLastOf grain td
+      _ -> Nothing
+  }
+
+ruleFromDatetimeDatetimeInterval :: Rule
+ruleFromDatetimeDatetimeInterval = Rule
+  { name = "from <datetime> - <datetime> (interval)"
+  , pattern =
+    [ regex "od|izme(dj|\x0111)u"
+    , dimension Time
+    , regex "\\-"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        Just . Token Time $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleMonthDdddInterval :: Rule
+ruleMonthDdddInterval = Rule
+  { name = "<month> dd-dd (interval)"
+  , pattern =
+    [ Predicate isAMonth
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    , regex "\\-|do"
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       Token RegexMatch (GroupMatch (m1:_)):
+       _:
+       Token RegexMatch (GroupMatch (m2:_)):
+       _) -> do
+        d1 <- parseInt m1
+        d2 <- parseInt m2
+        from <- intersect (dayOfMonth d1) td
+        to <- intersect (dayOfMonth d2) td
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleNamedday4 :: Rule
+ruleNamedday4 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "(\x010d|c)etvrta?ka?|(\x010d|c)et\\.?"
+    ]
+  , prod = \_ -> Just . Token Time $ dayOfWeek 4
+  }
+
+ruleSeason4 :: Rule
+ruleSeason4 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "prolje(c|\x0107)(e|a)"
+    ]
+  , prod = \_ -> tt $ interval TTime.Open (monthDay 3 20, monthDay 6 21)
+  }
+
+ruleYearLatent2 :: Rule
+ruleYearLatent2 = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2101 10000
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ year n
+      _ -> Nothing
+  }
+
+ruleTimeAfterNext :: Rule
+ruleTimeAfterNext = Rule
+  { name = "<time> after next"
+  , pattern =
+    [ dimension Time
+    , regex "nakon sljede(\x0107|c)(i|e|a)(ga?)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> Just . Token Time $ predNth 1 True td
+      _ -> Nothing
+  }
+
+ruleNoon :: Rule
+ruleNoon = Rule
+  { name = "noon"
+  , pattern =
+    [ regex "(u )?podne(va)?"
+    ]
+  , prod = \_ -> Just . Token Time $ hour False 12
+  }
+
+ruleEarlyMorning :: Rule
+ruleEarlyMorning = Rule
+  { name = "early morning"
+  , pattern =
+    [ regex "(rano( (u )?u?jutros?)?)|(u ran(im|e) jutarnj(im|e) sat(ima|e))"
+    ]
+  , prod = \_ -> tt . partOfDay . mkLatent $
+      interval TTime.Open (hour False 3, hour False 9)
+  }
+
+ruleToday :: Rule
+ruleToday = Rule
+  { name = "today"
+  , pattern =
+    [ regex "danas?|(dana(s|\x0161)nj(i|eg) dana?) "
+    ]
+  , prod = \_ -> Just . Token Time $ cycleNth TG.Day 0
+  }
+
+ruleThisnextDayofweek :: Rule
+ruleThisnextDayofweek = Rule
+  { name = "this|next <day-of-week>"
+  , pattern =
+    [ regex "ov(aj?|og?|e)|sljede(c|\x0107)(i|u|a|e(ga?)?)"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Just . Token Time $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleBetweenTimeofdayAndTimeofdayInterval :: Rule
+ruleBetweenTimeofdayAndTimeofdayInterval = Rule
+  { name = "between <time-of-day> and <time-of-day> (interval)"
+  , pattern =
+    [ regex "od|izme(dj|\x0111)u"
+    , Predicate isATimeOfDay
+    , regex "do|i"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        Just . Token Time $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNextCycle :: Rule
+ruleNextCycle = Rule
+  { name = "next <cycle>"
+  , pattern =
+    [ regex "sljede(c|\x0107)(i|a|u|e(ga?)?)"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) -> Just . Token Time $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleNamedmonth :: Rule
+ruleNamedmonth = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "sije(c|\x010d)a?nj(a|u)?|januar(a|u)?|jan\\.?|sij?\\.?|prv(i|a|o(ga?)?)"
+    ]
+  , prod = \_ -> Just . Token Time $ month 1
+  }
+
+ruleTimeofdayApproximately :: Rule
+ruleTimeofdayApproximately = Rule
+  { name = "<time-of-day> approximately"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(-?ak)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth3 :: Rule
+ruleNamedmonth3 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "o(z|\x017e)uja?k(a|u)?|mart(a|u)?|mar\\.?|o(z|\x017e)u?\\.?|tre(c|\x0107)(i|a|e(ga?)?)"
+    ]
+  , prod = \_ -> Just . Token Time $ month 3
+  }
+
+ruleForDuration :: Rule
+ruleForDuration = Rule
+  { name = "for <duration>"
+  , pattern =
+    [ regex "za( jo(s|\x0161))?|u"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) -> Just . Token Time $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleLastDayofweekTime :: Rule
+ruleLastDayofweekTime = Rule
+  { name = "last <day-of-week> <time>"
+  , pattern =
+    [ regex "zadnj(ih?|a|e(ga?)?)"
+    , Predicate isADayOfWeek
+    , regex "u"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        Just . Token Time $ predLastOf td1 td2
+      _ -> Nothing
+  }
+
+ruleDurationFromNow :: Rule
+ruleDurationFromNow = Rule
+  { name = "<duration> from now"
+  , pattern =
+    [ dimension Duration
+    , regex "od (sada?|ovog trenutka|dana(s|\x0161)nj(i|eg) dana?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) -> Just . Token Time $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleLunch :: Rule
+ruleLunch = Rule
+  { name = "lunch"
+  , pattern =
+    [ regex "(((za )|(u vrijeme )) )?ru(c|\x010d)a?k(a|om)?"
+    ]
+  , prod = \_ -> tt . partOfDay . mkLatent $
+      interval TTime.Open (hour False 12, hour False 14)
+  }
+
+ruleLastCycle :: Rule
+ruleLastCycle = Rule
+  { name = "last <cycle>"
+  , pattern =
+    [ regex "prethodn(i|a|e|u)|pro(s|\x0161)l(i|a|e|u|o(ga?)?)"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) -> Just . Token Time . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleDdmm :: Rule
+ruleDdmm = Rule
+  { name = "dd.mm"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])\\/(0?[1-9]|1[0-2])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (dd:mm:_)):_) -> do
+        d <- parseInt dd
+        m <- parseInt mm
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleAfternoon :: Rule
+ruleAfternoon = Rule
+  { name = "afternoon"
+  , pattern =
+    [ regex "po(slije)?podne"
+    ]
+  , prod = \_ -> tt . partOfDay . mkLatent $
+      interval TTime.Open (hour False 12, hour False 20)
+  }
+
+ruleNamedmonth4 :: Rule
+ruleNamedmonth4 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "trava?nj(a|u)?|april(a|u)?|apr\\.?|tra\\.?|(\x010d|c)etvrt(i|a|o(ga?)?)"
+    ]
+  , prod = \_ -> Just . Token Time $ month 4
+  }
+
+ruleTimeBeforeLast :: Rule
+ruleTimeBeforeLast = Rule
+  { name = "<time> before last"
+  , pattern =
+    [ dimension Time
+    , regex "prije (prethodn(e|o(ga?)?)|pro(s|\x0161)l(ih?|u|a|e|o(ga?)?))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        Just . Token Time $ predNth (-2) False td
+      _ -> Nothing
+  }
+
+ruleLateNight :: Rule
+ruleLateNight = Rule
+  { name = "late night"
+  , pattern =
+    [ regex "(((u|po)\\s)?no(c|\x0107)(i|as|u)?|u?jutros?)"
+    ]
+  , prod = \_ -> tt . partOfDay . mkLatent $
+      interval TTime.Open (hour False 0, hour False 4)
+  }
+
+ruleNamedmonthDayofmonthOrdinal :: Rule
+ruleNamedmonthDayofmonthOrdinal = Rule
+  { name = "<named-month> <day-of-month> (ordinal)"
+  , pattern =
+    [ Predicate isAMonth
+    , Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleChristmasEve :: Rule
+ruleChristmasEve = Rule
+  { name = "christmas eve"
+  , pattern =
+    [ regex "badnjaka?"
+    ]
+  , prod = \_ -> Just . Token Time $ monthDay 12 24
+  }
+
+ruleInduringThePartofday :: Rule
+ruleInduringThePartofday = Rule
+  { name = "in|during the <part-of-day>"
+  , pattern =
+    [ regex "(u|tokom)"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Just . Token Time $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedday5 :: Rule
+ruleNamedday5 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "peta?ka?|pet\\.?"
+    ]
+  , prod = \_ -> Just . Token Time $ dayOfWeek 5
+  }
+
+ruleDayofmonthordinalNamedmonth :: Rule
+ruleDayofmonthordinalNamedmonth = Rule
+  { name = "<day-of-month>(ordinal) <named-month>"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleIntersectBy :: Rule
+ruleIntersectBy = Rule
+  { name = "intersect by \",\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex ","
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) -> Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNthTimeAfterTime :: Rule
+ruleNthTimeAfterTime = Rule
+  { name = "nth <time> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "poslije|nakon|iza"
+    , 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
+  }
+
+ruleAfterDuration :: Rule
+ruleAfterDuration = Rule
+  { name = "after <duration>"
+  , pattern =
+    [ regex "(nakon|poslije)( jo(s|\x0161))?"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) -> tt . withDirection TTime.After $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleTimeofdayLatent :: Rule
+ruleTimeofdayLatent = Rule
+  { name = "time-of-day (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 0 23
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ hour True n
+      _ -> Nothing
+  }
+
+ruleDayofmonthOrdinalOfNamedmonth :: Rule
+ruleDayofmonthOrdinalOfNamedmonth = Rule
+  { name = "<day-of-month> (ordinal) of <named-month>"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    , regex "of|in"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleFromTimeofdayTimeofdayInterval :: Rule
+ruleFromTimeofdayTimeofdayInterval = Rule
+  { name = "from <time-of-day> - <time-of-day> (interval)"
+  , pattern =
+    [ regex "od"
+    , Predicate isATimeOfDay
+    , regex "\\-"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        Just . Token Time $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth2 :: Rule
+ruleNamedmonth2 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "(ve)?lja(c|\x010d)(a|e|i)|februar(a|u)?|feb\\.?|ve(lj)?\\.?|drug(i|a|o(ga?)?)"
+    ]
+  , prod = \_ -> Just . Token Time $ month 2
+  }
+
+ruleExactlyTimeofday :: Rule
+ruleExactlyTimeofday = Rule
+  { name = "exactly <time-of-day>"
+  , pattern =
+    [ regex "to(c|\x010d)no( u)?"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleSeason3 :: Rule
+ruleSeason3 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "zim(a|e|us)?"
+    ]
+  , prod = \_ -> tt $ interval TTime.Open (monthDay 12 21, monthDay 3 20)
+  }
+
+ruleSeason :: Rule
+ruleSeason = Rule
+  { name = "season"
+  , pattern =
+    [ regex "ljet(os?|a)"
+    ]
+  , prod = \_ -> tt $ interval TTime.Open (monthDay 6 21, monthDay 9 23)
+  }
+
+ruleUNamedday :: Rule
+ruleUNamedday = Rule
+  { name = "u <named-day>"
+  , pattern =
+    [ regex "u"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleBetweenDatetimeAndDatetimeInterval :: Rule
+ruleBetweenDatetimeAndDatetimeInterval = Rule
+  { name = "between <datetime> and <datetime> (interval)"
+  , pattern =
+    [ regex "od|izme(dj|\x0111)u"
+    , dimension Time
+    , regex "do|i"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        Just . Token Time $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNewYearsEve :: Rule
+ruleNewYearsEve = Rule
+  { name = "new year's eve"
+  , pattern =
+    [ regex "star(a|u|e) godin(a|e|u)"
+    ]
+  , prod = \_ -> Just . Token Time $ monthDay 12 31
+  }
+
+ruleByTheEndOfTime :: Rule
+ruleByTheEndOfTime = Rule
+  { name = "by the end of <time>"
+  , pattern =
+    [ regex "(do )(kraja|isteka)? "
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ interval TTime.Closed (cycleNth TG.Second 0, td)
+      _ -> Nothing
+  }
+
+ruleAfterWork :: Rule
+ruleAfterWork = Rule
+  { name = "after work"
+  , pattern =
+    [ regex "poslije (posla|kraja radnog vremena)"
+    ]
+  , prod = \_ -> Token Time . partOfDay . notLatent <$>
+      intersect (cycleNth TG.Day 0)
+      (interval TTime.Open (hour False 17, hour False 21))
+  }
+
+ruleLastDayofweekTime2 :: Rule
+ruleLastDayofweekTime2 = Rule
+  { name = "last <day-of-week> <time>"
+  , pattern =
+    [ regex "zadnj(ih?|a|e(ga?)?)"
+    , Predicate isADayOfWeek
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:Token Time td2:_) ->
+        Just . Token Time $ predLastOf td1 td2
+      _ -> Nothing
+  }
+
+ruleLastNCycle :: Rule
+ruleLastNCycle = Rule
+  { name = "last n <cycle>"
+  , pattern =
+    [ regex "prethodn(ih?|a|e)|pro(s|\x0161)l(a|e|ih?)"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        n <- getIntValue token
+        tt . cycleN True grain $ - n
+      _ -> Nothing
+  }
+
+ruleWithinDuration :: Rule
+ruleWithinDuration = Rule
+  { name = "within <duration>"
+  , pattern =
+    [ regex "(u|za )vrijeme"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ interval TTime.Open (cycleNth TG.Second 0, inDuration dd)
+      _ -> Nothing
+  }
+
+ruleMidnighteodendOfDay :: Rule
+ruleMidnighteodendOfDay = Rule
+  { name = "midnight|EOD|end of day"
+  , pattern =
+    [ regex "(u )?pono(c|\x0107)i?|(the )?(EOD|((do )? kraja? dana))"
+    ]
+  , prod = \_ -> Just . Token Time $ hour False 0
+  }
+
+ruleDayofmonthNonOrdinalNamedmonth :: Rule
+ruleDayofmonthNonOrdinalNamedmonth = Rule
+  { name = "<day-of-month> (non ordinal) <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ Predicate isNotLatent
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) -> Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleAboutTimeofday :: Rule
+ruleAboutTimeofday = Rule
+  { name = "about <time-of-day>"
+  , pattern =
+    [ regex "(oko|cca|otprilike)"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleUntilTimeofday :: Rule
+ruleUntilTimeofday = Rule
+  { name = "until <time-of-day>"
+  , pattern =
+    [ regex "((nekad|najkasnije) )?(prije|do)"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Just . Token Time $ withDirection TTime.Before td
+      _ -> Nothing
+  }
+
+ruleAtTimeofday :: Rule
+ruleAtTimeofday = Rule
+  { name = "at <time-of-day>"
+  , pattern =
+    [ regex "u|@"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Just . Token Time $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth6 :: Rule
+ruleNamedmonth6 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "lipa?nj(a|u)?|jun(i|u|a)?|jun\\.?|lip?\\.?|(\x0161|s)est(i|a|o(ga?)?)"
+    ]
+  , prod = \_ -> Just . Token Time $ month 6
+  }
+
+ruleNthTimeOfTime :: Rule
+ruleNthTimeOfTime = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "u"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token Time td1:_:Token Time td2:_) -> Token Time .
+        predNth (TOrdinal.value od - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNamedmonth8 :: Rule
+ruleNamedmonth8 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "kolovoz(a|u)?|august(a|u)?|aug\\.?|kol\\.?|osm(i|a|o(ga?)?)"
+    ]
+  , prod = \_ -> Just . Token Time $ month 8
+  }
+
+ruleTimePartofday :: Rule
+ruleTimePartofday = 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
+  }
+
+ruleWeekend :: Rule
+ruleWeekend = Rule
+  { name = "week-end"
+  , pattern =
+    [ regex "(za )?vikenda?"
+    ]
+  , prod = \_ -> do
+      fri <- intersect (dayOfWeek 5) (hour False 18)
+      mon <- intersect (dayOfWeek 1) (hour False 0)
+      tt $ interval TTime.Open (fri, mon)
+  }
+
+rulePrijeDuration :: Rule
+rulePrijeDuration = Rule
+  { name = "prije <duration>"
+  , pattern =
+    [ regex "prije"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) -> Just . Token Time $ durationAgo dd
+      _ -> Nothing
+  }
+
+ruleAboutDuration :: Rule
+ruleAboutDuration = Rule
+  { name = "about <duration>"
+  , pattern =
+    [ regex "oko"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) -> Just . Token Time $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleEomendOfMonth :: Rule
+ruleEomendOfMonth = Rule
+  { name = "EOM|End of month"
+  , pattern =
+    [ regex "(the )?(EOM|(do )? kraja mjeseca)"
+    ]
+  , prod = \_ -> Just . Token Time $ cycleNth TG.Month 1
+  }
+
+ruleNameddayDayofmonthOrdinal :: Rule
+ruleNameddayDayofmonthOrdinal = Rule
+  { name = "<named-day> <day-of-month> (ordinal)"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleNextTime :: Rule
+ruleNextTime = Rule
+  { name = "next <time>"
+  , pattern =
+    [ regex "sljede(c|\x0107)(i|u|a|e(ga?)?)"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Just . Token Time $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleOrdinalQuarterYear :: Rule
+ruleOrdinalQuarterYear = 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
+  }
+
+ruleYyyymmdd :: Rule
+ruleYyyymmdd = Rule
+  { name = "yyyy-mm-dd"
+  , pattern =
+    [ regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (yy:mm:dd:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleNextNCycle :: Rule
+ruleNextNCycle = Rule
+  { name = "next n <cycle>"
+  , pattern =
+    [ regex "(u )?(sljede(c|\x0107)(ih?|a|eg?))"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        n <- getIntValue token
+        tt $ cycleN True grain n
+      _ -> Nothing
+  }
+
+ruleMorning :: Rule
+ruleMorning = Rule
+  { name = "morning"
+  , pattern =
+    [ regex "(u )?u?jutros?"
+    ]
+  , prod = \_ -> tt . partOfDay . mkLatent $
+      interval TTime.Open (hour False 4, hour False 12)
+  }
+
+ruleThisPartofday :: Rule
+ruleThisPartofday = Rule
+  { name = "this <part-of-day>"
+  , pattern =
+    [ regex "ov(aj?|og?|i|e)"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time . partOfDay . notLatent <$>
+        intersect (cycleNth TG.Day 0) td
+      _ -> Nothing
+  }
+
+ruleThisCycle :: Rule
+ruleThisCycle = Rule
+  { name = "this <cycle>"
+  , pattern =
+    [ regex "ov(aj?|og?|e|i)"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) -> Just . Token Time $ cycleNth grain 0
+      _ -> Nothing
+  }
+
+ruleUNamedmonth :: Rule
+ruleUNamedmonth = Rule
+  { name = "u <named-month>"
+  , pattern =
+    [ regex "u"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleThisTime :: Rule
+ruleThisTime = Rule
+  { name = "this <time>"
+  , pattern =
+    [ regex "ov(aj?|og?|e)(sad)?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Just . Token Time $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleDayofmonthNonOrdinalOfNamedmonth :: Rule
+ruleDayofmonthNonOrdinalOfNamedmonth = Rule
+  { name = "<day-of-month> (non ordinal) of <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , regex "of|in"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleHhmmMilitaryPrefixedWithMToAvoidAmbiguityWithYears :: Rule
+ruleHhmmMilitaryPrefixedWithMToAvoidAmbiguityWithYears = Rule
+  { name = "hhmm (military, prefixed with m to avoid ambiguity with years)"
+  , pattern =
+    [ regex "m((?:[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 $ timeOfDayAMPM (hourMinute False h m) False
+      _ -> Nothing
+  }
+
+ruleDayBeforeYesterday :: Rule
+ruleDayBeforeYesterday = Rule
+  { name = "day before yesterday"
+  , pattern =
+    [ regex "(prekju(c|\x010d)er)"
+    ]
+  , prod = \_ -> Just . Token Time . cycleNth TG.Day $ - 2
+  }
+
+ruleAfterLunch :: Rule
+ruleAfterLunch = Rule
+  { name = "after lunch"
+  , pattern =
+    [ regex "poslije ru(c|\x010d)ka"
+    ]
+  , prod = \_ -> Token Time . partOfDay . notLatent <$>
+      intersect (cycleNth TG.Day 0)
+      (interval TTime.Open (hour False 13, hour False 17))
+  }
+
+ruleYearLatent :: Rule
+ruleYearLatent = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $
+       liftM2 (||) (isIntegerBetween (- 10000) 0) (isIntegerBetween 25 999)
+    ]
+  , prod = \tokens -> case tokens of
+     (token:_) -> do
+       n <- getIntValue token
+       tt . mkLatent $ year n
+     _ -> Nothing
+  }
+
+ruleYesterday :: Rule
+ruleYesterday = Rule
+  { name = "yesterday"
+  , pattern =
+    [ regex "(ju(c|\x010d)er)"
+    ]
+  , prod = \_ -> Just . Token Time . cycleNth TG.Day $ - 1
+  }
+
+ruleSeason2 :: Rule
+ruleSeason2 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "jesen(i|as)?"
+    ]
+  , prod = \_ ->
+      tt $ interval TTime.Open (monthDay 9 23, monthDay 12 21)
+  }
+
+ruleAfterTimeofday :: Rule
+ruleAfterTimeofday = Rule
+  { name = "after <time-of-day>"
+  , pattern =
+    [ regex "(nekad |najranije )?(prije|od)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Just . Token Time $ withDirection TTime.After td
+      _ -> Nothing
+  }
+
+ruleChristmas :: Rule
+ruleChristmas = Rule
+  { name = "christmas"
+  , pattern =
+    [ regex "(zi(c|\x0107)bo|bo(z|\x017e)i(c|\x0107))(a|u|ni|na)?"
+    ]
+  , prod = \_ -> Just . Token Time $ monthDay 12 25
+  }
+
+ruleDayofmonthOrdinal :: Rule
+ruleDayofmonthOrdinal = Rule
+  { name = "<day-of-month>. (ordinal)"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    , regex "\\.|i|og"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleOrdinalCycleAfterTime :: Rule
+ruleOrdinalCycleAfterTime = Rule
+  { name = "<ordinal> <cycle> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "nakon"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        Just . Token Time $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleOrdinalCycleOfTime :: Rule
+ruleOrdinalCycleOfTime = Rule
+  { name = "<ordinal> <cycle> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "od|u"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        Just . Token Time $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleAfterNextTime :: Rule
+ruleAfterNextTime = Rule
+  { name = "after next <time>"
+  , pattern =
+    [ regex "nakon sljede(\x0107|c)(i|e|a)(ga?)?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Just . Token Time $ predNth 1 True td
+      _ -> Nothing
+  }
+
+ruleNamedmonth5 :: Rule
+ruleNamedmonth5 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "sviba?nj(a|u)?|maj|svi\\.?|pet(i|a|o(ga?)?)"
+    ]
+  , prod = \_ -> Just . Token Time $ month 5
+  }
+
+ruleNamedday7 :: Rule
+ruleNamedday7 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "nedjelj(a|e|u)|ned\\.?"
+    ]
+  , prod = \_ -> Just . Token Time $ dayOfWeek 7
+  }
+
+ruleDayAfterTomorrow :: Rule
+ruleDayAfterTomorrow = Rule
+  { name = "day after tomorrow"
+  , pattern =
+    [ regex "(preko?sutra)"
+    ]
+  , prod = \_ -> Just . Token Time $ cycleNth TG.Day 2
+  }
+
+ruleHhmm :: Rule
+ruleHhmm = Rule
+  { name = "hh:mm"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))[(:.\\si]+([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt $ hourMinute True h m
+      _ -> Nothing
+  }
+
+ruleTonight :: Rule
+ruleTonight = Rule
+  { name = "tonight"
+  , pattern =
+    [ regex "(na)?ve(c|\x010d)er(as)?"
+    ]
+  , prod = \_ ->
+      let today = cycleNth TG.Day 0
+          evening = interval TTime.Open (hour False 18, hour False 0) in
+        Token Time . partOfDay . notLatent <$> intersect today evening
+  }
+
+ruleBeforeLasttime :: Rule
+ruleBeforeLasttime = Rule
+  { name = "before last<time>"
+  , pattern =
+    [ regex "prije (prethodn(i|u|a|e|o(ga?)?)|pro(s|\x0161)l(ih?|a|e|o(ga?)?))"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Just . Token Time $ predNth (-2) False td
+      _ -> Nothing
+  }
+
+ruleTimezone :: Rule
+ruleTimezone = Rule
+  { name = "<time> timezone"
+  , pattern =
+    [ Predicate $ liftM2 (&&) (isGrainFinerThan TG.Day) isNotLatent
+    , regex "\\b(YEKT|YEKST|YAKT|YAKST|WITA|WIT|WIB|WGT|WGST|WFT|WET|WEST|WAT|WAST|VUT|VLAT|VLAST|VET|UZT|UYT|UYST|UTC|ULAT|TVT|TMT|TLT|TKT|TJT|TFT|TAHT|SST|SRT|SGT|SCT|SBT|SAST|SAMT|RET|PYT|PYST|PWT|PST|PONT|PMST|PMDT|PKT|PHT|PHOT|PGT|PETT|PETST|PET|PDT|OMST|OMSST|NZST|NZDT|NUT|NST|NPT|NOVT|NOVST|NFT|NDT|NCT|MYT|MVT|MUT|MST|MSK|MSD|MMT|MHT|MDT|MAWT|MART|MAGT|MAGST|LINT|LHST|LHDT|KUYT|KST|KRAT|KRAST|KGT|JST|IST|IRST|IRKT|IRKST|IRDT|IOT|IDT|ICT|HOVT|HKT|GYT|GST|GMT|GILT|GFT|GET|GAMT|GALT|FNT|FKT|FKST|FJT|FJST|EST|EGT|EGST|EET|EEST|EDT|ECT|EAT|EAST|EASST|DAVT|ChST|CXT|CVT|CST|COT|CLT|CLST|CKT|CHAST|CHADT|CET|CEST|CDT|CCT|CAT|CAST|BTT|BST|BRT|BRST|BOT|BNT|AZT|AZST|AZOT|AZOST|AWST|AWDT|AST|ART|AQTT|ANAT|ANAST|AMT|AMST|ALMT|AKST|AKDT|AFT|AEST|AEDT|ADT|ACST|ACDT)\\b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       Token RegexMatch (GroupMatch (tz:_)):
+       _) -> Token Time <$> inTimezone tz td
+      _ -> Nothing
+  }
+
+ruleYear :: Rule
+ruleYear = Rule
+  { name = "year"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1000 2100
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt $ year n
+      _ -> Nothing
+  }
+
+ruleNamedmonth10 :: Rule
+ruleNamedmonth10 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "listopad(a|u)?|oktobar(a|u)?|okt\\.?|lis\\.?|deset(i|a|o(ga?)?)"
+    ]
+  , prod = \_ -> Just . Token Time $ month 10
+  }
+
+ruleHalloweenDay :: Rule
+ruleHalloweenDay = Rule
+  { name = "halloween day"
+  , pattern =
+    [ regex "no(c|\x0107) vje(s|\x0161)tica"
+    ]
+  , prod = \_ -> Just . Token Time $ monthDay 10 31
+  }
+
+ruleNamedmonthDayofmonthNonOrdinal :: Rule
+ruleNamedmonthDayofmonthNonOrdinal = 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
+  }
+
+ruleAbsorptionOfAfterNamedDay :: Rule
+ruleAbsorptionOfAfterNamedDay = Rule
+  { name = "absorption of , after named day"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex ","
+    ]
+  , prod = \tokens -> case tokens of
+      (x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleFathersDay :: Rule
+ruleFathersDay = Rule
+  { name = "Father's Day"
+  , pattern =
+    [ regex "dan (o(c|\x010d)eva|tata)"
+    ]
+  , prod = \_ -> tt $ nthDOWOfMonth 2 7 6
+  }
+
+ruleByTime :: Rule
+ruleByTime = Rule
+  { name = "by <time>"
+  , pattern =
+    [ regex "do"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ interval TTime.Open (cycleNth TG.Second 0, td)
+      _ -> Nothing
+  }
+
+ruleCycleBeforeTime :: Rule
+ruleCycleBeforeTime = Rule
+  { name = "<cycle> before <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "prije"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        Just . Token Time $ cycleNthAfter False grain (-1) td
+      _ -> Nothing
+  }
+
+ruleDdmmyyyy :: Rule
+ruleDdmmyyyy = Rule
+  { name = "dd/mm/yyyy"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[\\/](0?[1-9]|1[0-2])[\\/](\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (dd:mm:yy:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleTimeofdayTimeofdayInterval :: Rule
+ruleTimeofdayTimeofdayInterval = Rule
+  { name = "<time-of-day> - <time-of-day> (interval)"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isNotLatent isATimeOfDay
+    , regex "\\-|:"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Just . Token Time $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth11 :: Rule
+ruleNamedmonth11 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "studen(i|oga?|om)|novemba?r(a|u)?|nov\\.?|stu\\.?|jedanaest(i|a|o(ga?)?)"
+    ]
+  , prod = \_ -> Just . Token Time $ month 11
+  }
+
+ruleDurationAfterTime :: Rule
+ruleDurationAfterTime = Rule
+  { name = "<duration> after <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "nakon|poslije|od"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_:Token Time td:_) ->
+        Just . Token Time $ durationAfter dd td
+      _ -> Nothing
+  }
+
+ruleEveningnight :: Rule
+ruleEveningnight = Rule
+  { name = "evening|night"
+  , pattern =
+    [ regex "(na)?ve(c|\x010d)er(i|as)?"
+    ]
+  , prod = \_ -> tt . partOfDay . mkLatent $
+      interval TTime.Open (hour False 18, hour False 0)
+  }
+
+ruleOrdinalQuarter :: Rule
+ruleOrdinalQuarter = 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
+  }
+
+ruleDayBeforeDayBeforeYesterday :: Rule
+ruleDayBeforeDayBeforeYesterday = Rule
+  { name = "day before day before yesterday"
+  , pattern =
+    [ regex "(prek\\s?prekju(c|\x010d)er)"
+    ]
+  , prod = \_ -> Just . Token Time . cycleNth TG.Day $ - 3
+  }
+
+ruleNamedday3 :: Rule
+ruleNamedday3 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "srijed(a|e|u)|sri\\.?"
+    ]
+  , prod = \_ -> Just . Token Time $ dayOfWeek 3
+  }
+
+ruleDurationBeforeTime :: Rule
+ruleDurationBeforeTime = Rule
+  { name = "<duration> before <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "prije"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_:Token Time td:_) ->
+        Just . Token Time $ durationBefore dd td
+      _ -> Nothing
+  }
+
+ruleEoyendOfYear :: Rule
+ruleEoyendOfYear = Rule
+  { name = "EOY|End of year"
+  , pattern =
+    [ regex "(the )?(EOY|(do )? kraja? godine)"
+    ]
+  , prod = \_ -> Just . Token Time $ cycleNth TG.Year 1
+  }
+
+ruleTomorrow :: Rule
+ruleTomorrow = Rule
+  { name = "tomorrow"
+  , pattern =
+    [ regex "(sutra(dan)?)"
+    ]
+  , prod = \_ -> Just . Token Time $ cycleNth TG.Day 1
+  }
+
+ruleMothersDay :: Rule
+ruleMothersDay = Rule
+  { name = "Mother's Day"
+  , pattern =
+    [ regex "maj(c|\x010d)in dan"
+    ]
+  , prod = \_ -> tt $ nthDOWOfMonth 1 7 5
+  }
+
+ruleTimeofdayOclock :: Rule
+ruleTimeofdayOclock = Rule
+  { name = "<time-of-day> o'clock"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "sat(i|a)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> Just . Token Time $ notLatent td
+      _ -> Nothing
+  }
+
+ruleYear2 :: Rule
+ruleYear2 = Rule
+  { name = "year"
+  , pattern =
+    [ Predicate $ isOrdinalBetween 1000 2100
+    , regex "\\.|e"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt $ year v
+      _ -> Nothing
+  }
+
+ruleNamedmonth9 :: Rule
+ruleNamedmonth9 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "ruja?n(a|u)?|septemba?r(a|u)?|sept?\\.?|ruj\\.?|devet(i|a|o(ga?)?)"
+    ]
+  , prod = \_ -> Just . Token Time $ month 9
+  }
+
+ruleDayofmonthordinalNamedmonthYear :: Rule
+ruleDayofmonthordinalNamedmonthYear = 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
+  }
+
+ruleHhmmss :: Rule
+ruleHhmmss = Rule
+  { name = "hh:mm:ss"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)[:.]([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:ss:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        s <- parseInt ss
+        tt $ hourMinuteSecond True h m s
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAboutDuration
+  , ruleAboutTimeofday
+  , ruleAbsorptionOfAfterNamedDay
+  , ruleAfterDuration
+  , ruleAfterLunch
+  , ruleAfterNextTime
+  , ruleAfterTimeofday
+  , ruleAfterWork
+  , ruleAfternoon
+  , ruleAtTimeofday
+  , ruleBeforeLasttime
+  , ruleBetweenDatetimeAndDatetimeInterval
+  , ruleBetweenTimeofdayAndTimeofdayInterval
+  , ruleByTheEndOfTime
+  , ruleByTime
+  , ruleChristmas
+  , ruleChristmasEve
+  , ruleCycleAfterTime
+  , ruleCycleBeforeTime
+  , ruleDatetimeDatetimeInterval
+  , ruleDayAfterTomorrow
+  , ruleDayBeforeDayBeforeYesterday
+  , ruleDayBeforeYesterday
+  , ruleDayofmonthNonOrdinalNamedmonth
+  , ruleDayofmonthNonOrdinalOfNamedmonth
+  , ruleDayofmonthOrdinal
+  , ruleDayofmonthOrdinalOfNamedmonth
+  , ruleDayofmonthordinalNamedmonth
+  , ruleDayofmonthordinalNamedmonthYear
+  , ruleDdmm
+  , ruleDdmmyyyy
+  , ruleDurationAfterTime
+  , ruleDurationBeforeTime
+  , ruleDurationFromNow
+  , ruleEarlyMorning
+  , ruleEomendOfMonth
+  , ruleEoyendOfYear
+  , ruleEveningnight
+  , ruleExactlyTimeofday
+  , ruleFathersDay
+  , ruleForDuration
+  , ruleFromDatetimeDatetimeInterval
+  , ruleFromTimeofdayTimeofdayInterval
+  , ruleHalfIntegerHrStyleHourofday
+  , ruleHalloweenDay
+  , ruleHhmm
+  , ruleHhmmMilitaryPrefixedWithMToAvoidAmbiguityWithYears
+  , ruleHhmmss
+  , ruleInDuration
+  , ruleInduringThePartofday
+  , ruleIntersect
+  , ruleIntersectBy
+  , ruleLastCycle
+  , ruleLastCycleOfTime
+  , ruleLastDayofweekTime
+  , ruleLastDayofweekTime2
+  , ruleLastNCycle
+  , ruleLastTime
+  , ruleLateNight
+  , ruleLunch
+  , ruleMidnighteodendOfDay
+  , ruleMonthDdddInterval
+  , ruleMorning
+  , ruleMothersDay
+  , ruleNamedday
+  , ruleNamedday2
+  , ruleNamedday3
+  , ruleNamedday4
+  , ruleNamedday5
+  , ruleNamedday6
+  , ruleNamedday7
+  , ruleNameddayDayofmonthOrdinal
+  , ruleNamedmonth
+  , ruleNamedmonth10
+  , ruleNamedmonth11
+  , ruleNamedmonth12
+  , ruleNamedmonth2
+  , ruleNamedmonth3
+  , ruleNamedmonth4
+  , ruleNamedmonth5
+  , ruleNamedmonth6
+  , ruleNamedmonth7
+  , ruleNamedmonth8
+  , ruleNamedmonth9
+  , ruleNamedmonthDayofmonthNonOrdinal
+  , ruleNamedmonthDayofmonthOrdinal
+  , ruleNewYearsDay
+  , ruleNewYearsEve
+  , ruleNextCycle
+  , ruleNextNCycle
+  , ruleNextTime
+  , ruleNoon
+  , ruleNow
+  , ruleNthTimeAfterTime
+  , ruleNthTimeOfTime
+  , ruleOrdinalCycleAfterTime
+  , ruleOrdinalCycleOfTime
+  , ruleOrdinalQuarter
+  , ruleOrdinalQuarterYear
+  , rulePrijeDuration
+  , ruleNumeralAfterpastHourofday
+  , ruleQuarterAfterpastHourofday
+  , ruleHalfAfterpastHourofday
+  , ruleNumeralTotillbeforeIntegerHourofday
+  , ruleQuarterTotillbeforeIntegerHourofday
+  , ruleHalfTotillbeforeIntegerHourofday
+  , ruleHourofdayNumeral
+  , ruleHourofdayQuarter
+  , ruleHourofdayHalf
+  , ruleSeason
+  , ruleSeason2
+  , ruleSeason3
+  , ruleSeason4
+  , ruleSinceTimeofday
+  , ruleThisCycle
+  , ruleThisPartofday
+  , ruleThisTime
+  , ruleThisnextDayofweek
+  , ruleTimeAfterNext
+  , ruleTimeBeforeLast
+  , ruleTimePartofday
+  , ruleTimezone
+  , ruleTimeofdayApproximately
+  , ruleTimeofdayLatent
+  , ruleTimeofdayOclock
+  , ruleTimeofdayTimeofdayInterval
+  , ruleToday
+  , ruleTomorrow
+  , ruleTonight
+  , ruleUNamedday
+  , ruleUNamedmonth
+  , ruleUntilTimeofday
+  , ruleValentinesDay
+  , ruleWeekend
+  , ruleWithinDuration
+  , ruleYear
+  , ruleYear2
+  , ruleYearLatent
+  , ruleYearLatent2
+  , ruleYesterday
+  , ruleYyyymmdd
+  , ruleZaNumeralHourofday
+  , ruleZaQuarterHourofday
+  , ruleZaHalfHourofday
+  ]
diff --git a/Duckling/Time/Helpers.hs b/Duckling/Time/Helpers.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/Helpers.hs
@@ -0,0 +1,508 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.Helpers
+  ( -- Patterns
+    isADayOfWeek, isAMonth, isAnHourOfDay, isAPartOfDay, isATimeOfDay
+  , isDOMInteger, isDOMOrdinal, isDOMValue, isGrain, isGrainFinerThan
+  , isGrainOfTime, isIntegerBetween, isNotLatent, isOrdinalBetween
+  , isMidnightOrNoon
+    -- Production
+  , cycleLastOf, cycleN, cycleNth, cycleNthAfter, dayOfMonth, dayOfWeek
+  , daysOfWeekOfMonth, durationAfter, durationAgo, durationBefore, form, hour
+  , hourMinute, hourMinuteSecond, inDuration, intersect, intersectDOM, interval
+  , inTimezone, longWEBefore, minute, minutesAfter, minutesBefore, mkLatent
+  , month, monthDay, notLatent, nthDOWOfMonth, partOfDay, predLastOf, predNth
+  , predNthAfter, second, timeOfDayAMPM, withDirection, year, yearMonthDay
+  , tt
+    -- Other
+  , getIntValue
+  ) where
+
+import Control.Monad (liftM2)
+import Data.Maybe
+import Prelude
+import Data.Text (Text)
+import qualified Data.Time as Time
+import qualified Data.Time.Calendar.WeekDate as Time
+import qualified Data.Time.LocalTime.TimeZone.Series as Series
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.Types (DurationData (DurationData))
+import qualified Duckling.Duration.Types as TDuration
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Ordinal.Types (OrdinalData (OrdinalData))
+import qualified Duckling.Ordinal.Types as TOrdinal
+import Duckling.Time.TimeZone.Parse (parseTimezone)
+import Duckling.Time.Types
+  ( TimeData(TimeData)
+  , mkSeriesPredicate
+  , mkSecondPredicate
+  , mkMinutePredicate
+  , mkHourPredicate
+  , mkAMPMPredicate
+  , mkMonthPredicate
+  , mkDayOfTheWeekPredicate
+  , mkDayOfTheMonthPredicate
+  , mkYearPredicate
+  , mkIntersectPredicate
+  , mkTimeIntervalsPredicate
+  , runPredicate
+  , AMPM(..)
+  )
+import qualified Duckling.Time.Types as TTime
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+getIntValue :: Token -> Maybe Int
+getIntValue (Token Numeral nd) = TNumeral.getIntValue $ TNumeral.value nd
+getIntValue (Token Ordinal OrdinalData {TOrdinal.value = x}) = Just x
+getIntValue _ = Nothing
+
+timeNegPeriod :: DurationData -> DurationData
+timeNegPeriod (DurationData v g) = DurationData
+  {TDuration.grain = g, TDuration.value = negate v}
+
+-- -----------------------------------------------------------------
+-- Time predicates
+
+timeCycle :: TG.Grain -> TTime.Predicate
+timeCycle grain = mkSeriesPredicate series
+  where
+  series t _ = TTime.timeSequence grain 1 $ TTime.timeRound t grain
+
+timeSecond :: Int -> TTime.Predicate
+timeSecond n = mkSecondPredicate n
+
+timeMinute :: Int -> TTime.Predicate
+timeMinute n = mkMinutePredicate n
+
+timeHour :: Bool -> Int -> TTime.Predicate
+timeHour is12H n = mkHourPredicate is12H n
+
+timeDayOfWeek :: Int -> TTime.Predicate
+timeDayOfWeek n = mkDayOfTheWeekPredicate n
+
+timeDayOfMonth :: Int -> TTime.Predicate
+timeDayOfMonth n = mkDayOfTheMonthPredicate n
+
+timeMonth :: Int -> TTime.Predicate
+timeMonth n = mkMonthPredicate n
+
+-- | Converts 2-digits to a year between 1950 and 2050
+timeYear :: Int -> TTime.Predicate
+timeYear n = mkYearPredicate n
+
+-- | Takes `n` cycles of `f`
+takeN :: Int -> Bool -> TTime.Predicate -> TTime.Predicate
+takeN n notImmediate f = mkSeriesPredicate series
+  where
+  series t context =
+    case slot of
+      Just nth -> if TTime.timeStartsBeforeTheEndOf t nth
+        then ([], [nth])
+        else ([nth], [])
+      Nothing -> ([], [])
+    where
+      baseTime = TTime.refTime context
+      (past, future) = runPredicate f baseTime context
+      fut = case future of
+        (ahead:rest)
+          | notImmediate && isJust (TTime.timeIntersect ahead baseTime) -> rest
+        _ -> future
+      slot = if n >= 0
+        then case fut of
+          (start:_) -> case drop n fut of
+            (end:_) -> Just $ TTime.timeInterval TTime.Open start end
+            _ -> Nothing
+          _ -> Nothing
+        else case past of
+          (end:_) -> case drop ((- n) - 1) past of
+            (start:_) -> Just $ TTime.timeInterval TTime.Closed start end
+            _ -> Nothing
+          _ -> Nothing
+
+-- | -1 is the first element in the past
+-- | 0 is the first element in the future
+takeNth :: Int -> Bool -> TTime.Predicate -> TTime.Predicate
+takeNth n notImmediate f = mkSeriesPredicate series
+  where
+  series t context =
+    case rest of
+      [] -> ([], [])
+      (nth:_) -> if TTime.timeStartsBeforeTheEndOf t nth
+        then ([], [nth])
+        else ([nth], [])
+    where
+      baseTime = TTime.refTime context
+      (past, future) = runPredicate f baseTime context
+      rest = if n >= 0
+        then case future of
+          (ahead:_) | notImmediate && isJust (TTime.timeIntersect ahead baseTime)
+            -> drop (n + 1) future
+          _ -> drop n future
+        else drop (- (n + 1)) past
+
+-- | Like `takeNth`, but takes the nth cyclic predicate after `basePred`
+takeNthAfter
+  :: Int
+  -> Bool
+  -> TTime.Predicate
+  -> TTime.Predicate
+  -> TTime.Predicate
+takeNthAfter n notImmediate cyclicPred basePred =
+  mkSeriesPredicate $! TTime.timeSeqMap False f basePred
+  where
+    f t ctx =
+      let (past, future) = runPredicate cyclicPred t ctx
+          rest = if n >= 0
+                   then case future of
+                     (ahead:_) | notImmediate && TTime.timeBefore ahead t
+                       -> drop (n + 1) future
+                     _ -> drop n future
+                   else drop (- (n + 1)) past
+      in case rest of
+           [] -> Nothing
+           (nth:_) -> Just nth
+
+-- | Takes the last occurrence of `cyclicPred` within `basePred`.
+takeLastOf :: TTime.Predicate -> TTime.Predicate -> TTime.Predicate
+takeLastOf cyclicPred basePred =
+  mkSeriesPredicate $! TTime.timeSeqMap False f basePred
+  where
+    f :: TTime.TimeObject -> TTime.TimeContext -> Maybe TTime.TimeObject
+    f t ctx =
+      case runPredicate cyclicPred (TTime.timeStartingAtTheEndOf t) ctx of
+        (nth:_, _) -> Just nth
+        _ -> Nothing
+
+-- | Assumes the grain of `pred1` is smaller than the one of `pred2`
+timeCompose :: TTime.Predicate -> TTime.Predicate -> TTime.Predicate
+timeCompose pred1 pred2 = mkIntersectPredicate pred1 pred2
+
+shiftDuration :: TTime.Predicate -> DurationData -> TTime.Predicate
+shiftDuration pred1 (DurationData n g) =
+  mkSeriesPredicate $! TTime.timeSeqMap False f pred1
+  where
+    grain = case g of
+      TG.Second -> TG.Second
+      TG.Year -> TG.Month
+      TG.Month -> TG.Day
+      _ -> pred g
+    f x _ = Just $ TTime.timePlus (TTime.timeRound x grain) g $ toInteger n
+
+shiftTimezone :: Series.TimeZoneSeries -> TTime.Predicate -> TTime.Predicate
+shiftTimezone providedSeries pred1 =
+  mkSeriesPredicate $! TTime.timeSeqMap False f pred1
+  where
+    f x@(TTime.TimeObject s _ _) ctx =
+      let Time.TimeZone ctxOffset _ _ =
+            Series.timeZoneFromSeries (TTime.tzSeries ctx) s
+          Time.TimeZone providedOffset _ _ =
+            Series.timeZoneFromSeries providedSeries s
+      -- This forgets about TTime.end, but it's OK since we act on time-of-days.
+      in Just . TTime.timePlus x TG.Minute . toInteger $
+           ctxOffset - providedOffset
+
+-- -----------------------------------------------------------------
+-- Patterns
+
+isGrain :: TG.Grain -> Predicate
+isGrain value (Token TimeGrain grain) = grain == value
+isGrain _ _ = False
+
+isGrainFinerThan :: TG.Grain -> Predicate
+isGrainFinerThan value (Token Time (TimeData {TTime.timeGrain = g})) = g < value
+isGrainFinerThan _ _ = False
+
+isGrainOfTime :: TG.Grain -> Predicate
+isGrainOfTime value (Token Time (TimeData {TTime.timeGrain = g})) = g == value
+isGrainOfTime _ _ = False
+
+isADayOfWeek :: Predicate
+isADayOfWeek (Token Time td) = case TTime.form td of
+  Just TTime.DayOfWeek -> True
+  _ -> False
+isADayOfWeek _ = False
+
+isATimeOfDay :: Predicate
+isATimeOfDay (Token Time td) = case TTime.form td of
+  Just (TTime.TimeOfDay _ _) -> True
+  _ -> False
+isATimeOfDay _ = False
+
+isAPartOfDay :: Predicate
+isAPartOfDay (Token Time td) = case TTime.form td of
+  Just TTime.PartOfDay -> True
+  _ -> False
+isAPartOfDay _ = False
+
+isAMonth :: Predicate
+isAMonth (Token Time td) = case TTime.form td of
+  Just (TTime.Month _) -> True
+  _ -> False
+isAMonth _ = False
+
+isAnHourOfDay :: Predicate
+isAnHourOfDay (Token Time td) = case TTime.form td of
+  Just (TTime.TimeOfDay (Just _) _) | TTime.timeGrain td > TG.Minute -> True
+  _ -> False
+isAnHourOfDay _ = False
+
+isMidnightOrNoon :: Predicate
+isMidnightOrNoon (Token Time td) = case TTime.form td of
+  Just (TTime.TimeOfDay (Just x) _) -> x == 0 || x == 12
+  _ -> False
+isMidnightOrNoon _ = False
+
+isNotLatent :: Predicate
+isNotLatent (Token Time td) = not $ TTime.latent td
+isNotLatent _ = False
+
+isIntegerBetween :: Int -> Int -> Predicate
+isIntegerBetween low high (Token Numeral nd) =
+  TNumeral.isIntegerBetween (TNumeral.value nd) low high
+isIntegerBetween _ _ _ = False
+
+isOrdinalBetween :: Int -> Int -> Predicate
+isOrdinalBetween low high (Token Ordinal od) =
+  TOrdinal.isBetween (TOrdinal.value od) low high
+isOrdinalBetween _ _ _ = False
+
+isDOMOrdinal :: Predicate
+isDOMOrdinal = isOrdinalBetween 1 31
+
+isDOMInteger :: Predicate
+isDOMInteger = isIntegerBetween 1 31
+
+isDOMValue :: Predicate
+isDOMValue = liftM2 (||) isDOMOrdinal isDOMInteger
+
+-- -----------------------------------------------------------------
+-- Production
+
+-- Pass the interval second
+intersect :: TimeData -> TimeData -> Maybe TimeData
+intersect td1 td2 =
+  case intersect' (td1, td2) of
+    TTime.TimeData { TTime.timePred = pred }
+      | TTime.isEmptyPredicate pred -> Nothing
+    res -> Just res
+
+intersect' :: (TimeData, TimeData) -> TimeData
+intersect' (TimeData pred1 _ g1 _ _ d1, TimeData pred2 _ g2 _ _ d2)
+  | g1 < g2 = TTime.timedata'
+    { TTime.timePred = timeCompose pred1 pred2
+    , TTime.timeGrain = g1
+    , TTime.direction = dir
+    }
+  | otherwise = TTime.timedata'
+    { TTime.timePred = timeCompose pred2 pred1
+    , TTime.timeGrain = g2
+    , TTime.direction = dir
+    }
+  where
+    dir = case catMaybes [d1, d2] of
+      [] -> Nothing
+      (x:_) -> Just x
+
+
+hour :: Bool -> Int -> TimeData
+hour is12H n = timeOfDay (Just n) is12H $ TTime.timedata'
+  {TTime.timePred = timeHour is12H n, TTime.timeGrain = TG.Hour}
+
+minute :: Int -> TimeData
+minute n = TTime.timedata'
+  {TTime.timePred = timeMinute n, TTime.timeGrain = TG.Minute}
+
+second :: Int -> TimeData
+second n = TTime.timedata'
+  {TTime.timePred = timeSecond n, TTime.timeGrain = TG.Second}
+
+dayOfWeek :: Int -> TimeData
+dayOfWeek n = form TTime.DayOfWeek $ TTime.timedata'
+  { TTime.timePred = timeDayOfWeek n
+  , TTime.timeGrain = TG.Day
+  , TTime.notImmediate = True
+  }
+
+dayOfMonth :: Int -> TimeData
+dayOfMonth n = TTime.timedata'
+  {TTime.timePred = timeDayOfMonth n, TTime.timeGrain = TG.Day}
+
+month :: Int -> TimeData
+month n = form TTime.Month {TTime.month = n} $ TTime.timedata'
+  {TTime.timePred = timeMonth n, TTime.timeGrain = TG.Month}
+
+year :: Int -> TimeData
+year n = TTime.timedata' {TTime.timePred = timeYear n, TTime.timeGrain = TG.Year}
+
+yearMonthDay :: Int -> Int -> Int -> TimeData
+yearMonthDay y m d = intersect' (intersect' (year y, month m), dayOfMonth d)
+
+monthDay :: Int -> Int -> TimeData
+monthDay m d = intersect' (month m, dayOfMonth d)
+
+hourMinute :: Bool -> Int -> Int -> TimeData
+hourMinute is12H h m = timeOfDay (Just h) is12H $
+  intersect' (hour is12H h, minute m)
+
+hourMinuteSecond :: Bool -> Int -> Int -> Int -> TimeData
+hourMinuteSecond is12H h m s = timeOfDay (Just h) is12H $
+  intersect' (intersect' (hour is12H h, minute m), second s)
+
+cycleN :: Bool -> TG.Grain -> Int -> TimeData
+cycleN notImmediate grain n = TTime.timedata'
+  { TTime.timePred = takeN n notImmediate $ timeCycle grain
+  , TTime.timeGrain = grain
+  }
+
+cycleNth :: TG.Grain -> Int -> TimeData
+cycleNth grain n = TTime.timedata'
+  {TTime.timePred = takeNth n False $ timeCycle grain, TTime.timeGrain = grain}
+
+cycleNthAfter :: Bool -> TG.Grain -> Int -> TimeData -> TimeData
+cycleNthAfter notImmediate grain n TimeData {TTime.timePred = p} =
+  TTime.timedata'
+    { TTime.timePred = takeNthAfter n notImmediate (timeCycle grain) p
+    , TTime.timeGrain = grain
+    }
+
+cycleLastOf :: TG.Grain -> TimeData -> TimeData
+cycleLastOf grain TimeData {TTime.timePred = p} = TTime.timedata'
+  { TTime.timePred = takeLastOf (timeCycle grain) p
+  , TTime.timeGrain = grain
+  }
+
+-- Generalized version of cycleLastOf with custom predicate
+predLastOf :: TimeData -> TimeData -> TimeData
+predLastOf TimeData {TTime.timePred = cyclicPred, TTime.timeGrain = g} base =
+  TTime.timedata'
+    { TTime.timePred = takeLastOf cyclicPred $ TTime.timePred base
+    , TTime.timeGrain = g
+    }
+
+-- Generalized version of cycleNth with custom predicate
+predNth :: Int -> Bool -> TimeData -> TimeData
+predNth n notImmediate TimeData {TTime.timePred = p, TTime.timeGrain = g} =
+  TTime.timedata'
+    {TTime.timePred = takeNth n notImmediate p, TTime.timeGrain = g}
+
+-- Generalized version of `cycleNthAfter` with custom predicate
+predNthAfter :: Int -> TimeData -> TimeData -> TimeData
+predNthAfter n TimeData {TTime.timePred = p, TTime.timeGrain = g} base =
+  TTime.timedata'
+    { TTime.timePred = takeNthAfter n True p $ TTime.timePred base
+    , TTime.timeGrain = g
+    }
+
+interval :: TTime.TimeIntervalType -> (TimeData, TimeData) -> TimeData
+interval intervalType (TimeData p1 _ g1 _ _ _, TimeData p2 _ g2 _ _ _) =
+  TTime.timedata'
+    { TTime.timePred = mkTimeIntervalsPredicate intervalType' p1 p2
+    , TTime.timeGrain = min g1 g2
+    }
+    where
+      intervalType'
+        | g1 == g2 && g1 == TG.Day = TTime.Closed
+        | otherwise = intervalType
+
+durationAgo :: DurationData -> TimeData
+durationAgo dd = inDuration $ timeNegPeriod dd
+
+durationAfter :: DurationData -> TimeData -> TimeData
+durationAfter dd TimeData {TTime.timePred = pred1} = TTime.timedata'
+  { TTime.timePred = shiftDuration pred1 dd
+  , TTime.timeGrain = TDuration.grain dd}
+
+durationBefore :: DurationData -> TimeData -> TimeData
+durationBefore dd pred1 = durationAfter (timeNegPeriod dd) pred1
+
+inDuration :: DurationData -> TimeData
+inDuration dd = TTime.timedata'
+  { TTime.timePred = shiftDuration (takeNth 0 False $ timeCycle TG.Second) dd
+  , TTime.timeGrain = TDuration.grain dd
+  }
+
+inTimezone :: Text -> TimeData -> Maybe TimeData
+inTimezone input td@TimeData {TTime.timePred = p} = do
+  tz <- parseTimezone input
+  Just $ td {TTime.timePred = shiftTimezone (Series.TimeZoneSeries tz []) p}
+
+mkLatent :: TimeData -> TimeData
+mkLatent td = td {TTime.latent = True}
+
+notLatent :: TimeData -> TimeData
+notLatent td = td {TTime.latent = False}
+
+form :: TTime.Form -> TimeData -> TimeData
+form f td = td {TTime.form = Just f}
+
+partOfDay :: TimeData -> TimeData
+partOfDay td = form TTime.PartOfDay td
+
+timeOfDay :: Maybe Int -> Bool -> TimeData -> TimeData
+timeOfDay h is12H = form TTime.TimeOfDay {TTime.hours = h, TTime.is12H = is12H}
+
+timeOfDayAMPM :: TimeData -> Bool -> TimeData
+timeOfDayAMPM tod isAM = timeOfDay Nothing False $ intersect' (tod, ampm)
+  where
+    ampm = TTime.timedata'
+           { TTime.timePred = ampmPred
+           , TTime.timeGrain = TG.Hour
+           }
+    ampmPred = if isAM then mkAMPMPredicate AM else mkAMPMPredicate PM
+
+withDirection :: TTime.IntervalDirection -> TimeData -> TimeData
+withDirection dir td = td {TTime.direction = Just dir}
+
+longWEBefore :: TimeData -> TimeData
+longWEBefore monday = interval TTime.Open (start, end)
+  where
+    start = intersect' (fri, hour False 18)
+    end = intersect' (tue, hour False 0)
+    fri = cycleNthAfter False TG.Day (- 3) monday
+    tue = cycleNthAfter False TG.Day 1 monday
+
+daysOfWeekOfMonth :: Int -> Int -> TimeData
+daysOfWeekOfMonth dow m = intersect' (dayOfWeek dow, month m)
+
+-- Zero-indexed weeks, Monday is 1
+-- Use `predLastOf` for last day of week of month
+nthDOWOfMonth :: Int -> Int -> Int -> TimeData
+nthDOWOfMonth n dow m = intersect' (dowsM, week)
+  where
+    dowsM = daysOfWeekOfMonth dow m
+    week = cycleNthAfter False TG.Week n $ monthDay m 1
+
+intersectDOM :: TimeData -> Token -> Maybe TimeData
+intersectDOM td token = do
+  n <- getIntValue token
+  intersect (dayOfMonth n) td
+
+minutesBefore :: Int -> TimeData -> Maybe TimeData
+minutesBefore n TimeData {TTime.form = Just (TTime.TimeOfDay (Just 0) is12H)} =
+  Just $ hourMinute is12H 23 (60 - n)
+minutesBefore n TimeData {TTime.form = Just (TTime.TimeOfDay (Just 1) True)} =
+  Just $ hourMinute True 12 (60 - n)
+minutesBefore n TimeData {TTime.form = Just (TTime.TimeOfDay (Just h) is12H)} =
+  Just $ hourMinute is12H (h - 1) (60 - n)
+minutesBefore _ _ = Nothing
+
+minutesAfter :: Int -> TimeData -> Maybe TimeData
+minutesAfter n TimeData {TTime.form = Just (TTime.TimeOfDay (Just h) is12H)} =
+  Just $ hourMinute is12H h n
+minutesAfter _ _ = Nothing
+
+-- | Convenience helper to return a time token from a rule
+tt :: TimeData -> Maybe Token
+tt = Just . Token Time
diff --git a/Duckling/Time/IT/Corpus.hs b/Duckling/Time/IT/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/IT/Corpus.hs
@@ -0,0 +1,814 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.IT.Corpus
+  ( corpus
+  , negativeCorpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Time.Corpus
+import Duckling.Time.Types hiding (Month)
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Testing.Types hiding (examples)
+
+corpus :: Corpus
+corpus = (testContext {lang = IT}, allExamples)
+
+negativeCorpus :: NegativeCorpus
+negativeCorpus = (testContext {lang = IT}, examples)
+  where
+    examples =
+      [ "ma"
+      ]
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (datetime (2013, 2, 12, 4, 30, 0) Second)
+             [ "subito"
+             , "immediatamente"
+             , "in questo momento"
+             , "ora"
+             , "adesso"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "di oggi"
+             , "oggi"
+             , "in giornata"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "ieri"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "domani"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "Il giorno dopo domani"
+             , "dopodomani"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "Lunedì 18 febbraio"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "martedì"
+             , "Martedì 19"
+             , "mar 19"
+             , "il 19"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "l'altro ieri"
+             , "altroieri"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "lunedi"
+             , "lun"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "lunedi 18 febbraio"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "Martedì"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "Mercoledì"
+             , "mer"
+             , "mer."
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "mercoledi 13 feb"
+             , "il 13 febbraio"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "il 13 febbraio 2013"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "giovedi"
+             , "gio"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "venerdi"
+             , "venerdì"
+             , "ven"
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "sabato"
+             , "sab"
+             , "sab."
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "domenica"
+             , "dom"
+             , "dom."
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "domenica 10 febbraio"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "il 1 marzo"
+             , "primo marzo"
+             , "primo di marzo"
+             , "il 1º marzo"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 3, 1, 0, 0, 0) Month)
+             [ "prima di marzo"
+             ]
+  , examples (datetime (2013, 3, 15, 0, 0, 0) Day)
+             [ "le idi di marzo"
+             , "idi di marzo"
+             ]
+  , examples (datetime (2015, 3, 3, 0, 0, 0) Day)
+             [ "3 marzo 2015"
+             , "3/3/2015"
+             , "3/3/15"
+             , "2015-3-3"
+             , "2015-03-03"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "il 15 febbraio"
+             , "15/2"
+             , "il 15/02"
+             ]
+  , examples (datetime (1974, 10, 31, 0, 0, 0) Day)
+             [ "31/10/1974"
+             , "31/10/74"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "martedì scorso"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "martedì prossimo"
+             , "il martedì dopo"
+             ]
+  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
+             [ "mercoledì prossimo"
+             ]
+  , examples (datetime (2014, 10, 0, 0, 0, 0) Month)
+             [ "ottobre 2014"
+             ]
+  , examples (datetime (2013, 2, 12, 3, 0, 0) Hour)
+             [ "l'ultima ora"
+             , "nell'ultima ora"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Week)
+             [ "questa settimana"
+             ]
+  , examples (datetime (2013, 2, 4, 0, 0, 0) Week)
+             [ "la settimana scorsa"
+             , "la scorsa settimana"
+             , "nella scorsa settimana"
+             , "della settimana scorsa"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Week)
+             [ "la settimana prossima"
+             , "la prossima settimana"
+             , "nella prossima settimana"
+             , "settimana prossima"
+             , "prossima settimana"
+             ]
+  , examples (datetime (2013, 1, 0, 0, 0, 0) Month)
+             [ "il mese scorso"
+             , "nel mese scorso"
+             , "nel mese passato"
+             , "lo scorso mese"
+             , "dello scorso mese"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "il mese prossimo"
+             , "il prossimo mese"
+             ]
+  , examples (datetime (2013, 1, 1, 0, 0, 0) Quarter)
+             [ "questo trimestre"
+             ]
+  , examples (datetime (2013, 4, 1, 0, 0, 0) Quarter)
+             [ "il prossimo trimestre"
+             , "nel prossimo trimestre"
+             ]
+  , examples (datetime (2013, 7, 1, 0, 0, 0) Quarter)
+             [ "terzo trimestre"
+             , "il terzo trimestre"
+             ]
+  , examples (datetime (2018, 10, 1, 0, 0, 0) Quarter)
+             [ "quarto trimestre 2018"
+             , "il quarto trimestre 2018"
+             , "del quarto trimestre 2018"
+             ]
+  , examples (datetime (2012, 0, 0, 0, 0, 0) Year)
+             [ "l'anno scorso"
+             ]
+  , examples (datetime (2013, 0, 0, 0, 0, 0) Year)
+             [ "quest'anno"
+             ]
+  , examples (datetime (2014, 0, 0, 0, 0, 0) Year)
+             [ "il prossimo anno"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "ultima domenica"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "lunedì di questa settimana"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "martedì di questa settimana"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "mercoledì di questa settimana"
+             ]
+  , examples (datetime (2013, 2, 14, 17, 0, 0) Hour)
+             [ "dopo domani alle 17"
+             , "dopodomani alle 5 del pomeriggio"
+             ]
+  , examples (datetime (2013, 3, 25, 0, 0, 0) Day)
+             [ "ultimo lunedì di marzo"
+             ]
+  , examples (datetime (2014, 3, 30, 0, 0, 0) Day)
+             [ "ultima domenica di marzo 2014"
+             ]
+  , examples (datetime (2013, 10, 3, 0, 0, 0) Day)
+             [ "il terzo giorno di ottobre"
+             ]
+  , examples (datetime (2014, 10, 6, 0, 0, 0) Week)
+             [ "prima settimana di ottobre 2014"
+             ]
+  , examples (datetime (2013, 10, 7, 0, 0, 0) Week)
+             [ "la settimana del 6 ottobre"
+             , "la settimana del 7 ott"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 18, 0, 0), (2013, 2, 18, 0, 0, 0)) Hour)
+             [ "il we del 15 febbraio"
+             ]
+  , examples (datetimeInterval ((2013, 4, 12, 18, 0, 0), (2013, 4, 15, 0, 0, 0)) Hour)
+             [ "il week-end del 10 aprile"
+             ]
+  , examples (datetime (2015, 10, 31, 0, 0, 0) Day)
+             [ "l'ultimo giorno di ottobre 2015"
+             , "l'ultimo giorno dell'ottobre 2015"
+             ]
+  , examples (datetime (2014, 9, 22, 0, 0, 0) Week)
+             [ "l'ultima settimana di settembre 2014"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Minute)
+             [ "tra un'ora"
+             , "tra 1 ora"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 45, 0) Second)
+             [ "tra un quarto d'ora"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 0, 0) Second)
+             [ "tra mezz'ora"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 15, 0) Second)
+             [ "tra tre quarti d'ora"
+             ]
+  , examples (datetime (2013, 10, 1, 0, 0, 0) Day)
+             [ "primo martedì di ottobre"
+             , "primo martedì in ottobre"
+             , "1° martedì del mese di ottobre"
+             , "1º martedì del mese di ottobre"
+             ]
+  , examples (datetime (2014, 9, 16, 0, 0, 0) Day)
+             [ "terzo martedì di settembre 2014"
+             ]
+  , examples (datetime (2014, 10, 1, 0, 0, 0) Day)
+             [ "primo mercoledì di ottobre 2014"
+             ]
+  , examples (datetime (2014, 10, 8, 0, 0, 0) Day)
+             [ "secondo mercoledì di ottobre 2014"
+             ]
+  , examples (datetime (2015, 1, 13, 0, 0, 0) Day)
+             [ "terzo martedì dopo natale 2014"
+             ]
+  , examples (datetime (2016, 1, 0, 0, 0, 0) Month)
+             [ "il mese dopo natale 2015"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "alle 3 di pomeriggio"
+             , "le tre di pomeriggio"
+             , "alle 3 del pomeriggio"
+             , "le tre del pomeriggio"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "circa alle 3 del pomeriggio"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "per le 15"
+             , "verso le 15"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 0, 0) Minute)
+             [ "3:00"
+             , "03:00"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 15, 0) Minute)
+             [ "15:15"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 15, 0) Minute)
+             [ "3:15 di pomeriggio"
+             , "3:15 del pomeriggio"
+             , "3 e un quarto di pomeriggio"
+             , "tre e un quarto di pomeriggio"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 20, 0) Minute)
+             [ "alle tre e venti di pomeriggio"
+             , "alle tre e venti del pomeriggio"
+             , "3:20 di pomeriggio"
+             , "3:20 del pomeriggio"
+             , "15:20 del pomeriggio"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 20, 0) Minute)
+             [ "alle tre e venti"
+             , "tre e 20"
+             , "3 e 20"
+             , "3:20"
+             , "3 20"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 30, 0) Minute)
+             [ "15:30"
+             ]
+  , examples (datetime (2013, 2, 12, 11, 45, 0) Minute)
+             [ "a mezzogiorno meno un quarto"
+             , "mezzogiorno meno un quarto"
+             , "un quarto a mezzogiorno"
+             , "11:45 del mattino"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 0, 0) Hour)
+             [ "alle 3 del mattino"
+             ]
+  , examples (datetime (2013, 9, 20, 19, 30, 0) Minute)
+             [ "alle 19:30 di venerdì 20 settembre"
+             , "alle 19:30 venerdì 20 settembre"
+             , "venerdì 20 settembre alle 19:30"
+             , "il 20 settembre alle 19:30"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 18, 0, 0), (2013, 2, 18, 0, 0, 0)) Hour)
+             [ "questo week-end"
+             , "questo fine settimana"
+             , "questo finesettimana"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 4, 0, 0), (2013, 2, 18, 12, 0, 0)) Hour)
+             [ "lunedi mattina"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 4, 0, 0), (2013, 2, 15, 12, 0, 0)) Hour)
+             [ "15 febbraio al mattino"
+             , "mattino di 15 febbraio"
+             ]
+  , examples (datetime (2013, 2, 12, 20, 0, 0) Hour)
+             [ "8 di stasera"
+             , "8 della sera"
+             ]
+  , examples (datetime (2013, 9, 20, 19, 30, 0) Minute)
+             [ "venerdì 20 settembre alle 7:30 del pomeriggio"
+             ]
+  , examples (datetime (2013, 2, 16, 9, 0, 0) Hour)
+             [ "alle 9 di sabato"
+             , "sabato alle 9"
+             ]
+  , examples (datetimeInterval ((2013, 6, 21, 0, 0, 0), (2013, 9, 24, 0, 0, 0)) Day)
+             [ "quest'estate"
+             , "questa estate"
+             , "in estate"
+             ]
+  , examples (datetimeInterval ((2012, 12, 21, 0, 0, 0), (2013, 3, 21, 0, 0, 0)) Day)
+             [ "quest'inverno"
+             , "questo inverno"
+             , "in inverno"
+             ]
+  , examples (datetimeInterval ((2014, 9, 23, 0, 0, 0), (2014, 12, 22, 0, 0, 0)) Day)
+             [ "il prossimo autunno"
+             ]
+  , examples (datetime (2013, 12, 25, 0, 0, 0) Day)
+             [ "natale"
+             , "il giorno di natale"
+             ]
+  , examples (datetime (2013, 12, 24, 0, 0, 0) Day)
+             [ "vigilia di natale"
+             , "alla vigilia"
+             , "la vigilia"
+             ]
+  , examples (datetime (2013, 12, 31, 0, 0, 0) Day)
+             [ "vigilia di capodanno"
+             , "san silvestro"
+             ]
+  , examples (datetimeInterval ((2014, 1, 1, 0, 0, 0), (2014, 1, 1, 4, 0, 0)) Hour)
+             [ "notte di san silvestro"
+             ]
+  , examples (datetime (2014, 1, 1, 0, 0, 0) Day)
+             [ "capodanno"
+             , "primo dell'anno"
+             ]
+  , examples (datetime (2014, 1, 6, 0, 0, 0) Day)
+             [ "epifania"
+             , "befana"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "san valentino"
+             , "festa degli innamorati"
+             ]
+  , examples (datetime (2013, 3, 19, 0, 0, 0) Day)
+             [ "festa del papà"
+             , "festa del papa"
+             , "festa di san giuseppe"
+             , "san giuseppe"
+             ]
+  , examples (datetime (2013, 4, 25, 0, 0, 0) Day)
+             [ "anniversario della liberazione"
+             , "la liberazione"
+             , "alla liberazione"
+             ]
+  , examples (datetime (2013, 5, 1, 0, 0, 0) Day)
+             [ "festa del lavoro"
+             , "festa dei lavoratori"
+             , "giorno dei lavoratori"
+             , "primo maggio"
+             ]
+  , examples (datetime (2013, 5, 12, 0, 0, 0) Day)
+             [ "festa della mamma"
+             ]
+  , examples (datetime (2013, 6, 2, 0, 0, 0) Day)
+             [ "festa della repubblica"
+             , "la repubblica"
+             , "repubblica"
+             ]
+  , examples (datetime (2013, 8, 15, 0, 0, 0) Day)
+             [ "ferragosto"
+             , "assunzione"
+             ]
+  , examples (datetime (2013, 10, 31, 0, 0, 0) Day)
+             [ "halloween"
+             ]
+  , examples (datetime (2013, 11, 1, 0, 0, 0) Day)
+             [ "tutti i santi"
+             , "ognissanti"
+             , "festa dei santi"
+             , "il giorno dei santi"
+             ]
+  , examples (datetime (2013, 11, 2, 0, 0, 0) Day)
+             [ "giorno dei morti"
+             , "commemorazione dei defunti"
+             ]
+  , examples (datetime (2013, 11, 2, 2, 0, 0) Hour)
+             [ "ai morti alle 2"
+             ]
+  , examples (datetime (2013, 12, 8, 0, 0, 0) Day)
+             [ "immacolata"
+             , "immacolata concezione"
+             , "all'immacolata"
+             ]
+  , examples (datetime (2013, 12, 8, 18, 0, 0) Hour)
+             [ "all'immacolata alle 18"
+             ]
+  , examples (datetime (2013, 12, 26, 0, 0, 0) Day)
+             [ "santo stefano"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 18, 0, 0), (2013, 2, 13, 0, 0, 0)) Hour)
+             [ "questa sera"
+             , "sta sera"
+             , "stasera"
+             , "in serata"
+             , "nella sera"
+             , "verso sera"
+             , "la sera"
+             , "alla sera"
+             , "la serata"
+             , "nella serata"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 4, 0, 0), (2013, 2, 13, 12, 0, 0)) Hour)
+             [ "domani mattina"
+             , "domattina"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 18, 0, 0, 0)) Second)
+             [ "in settimana"
+             , "per la settimana"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 13, 4, 0, 0)) Hour)
+             [ "stanotte"
+             , "nella notte"
+             , "in nottata"
+             ]
+  , examples (datetimeInterval ((2013, 2, 8, 18, 0, 0), (2013, 2, 11, 0, 0, 0)) Hour)
+             [ "ultimo weekend"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 18, 0, 0), (2013, 2, 14, 0, 0, 0)) Hour)
+             [ "domani in serata"
+             , "domani sera"
+             , "nella serata di domani"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 0, 0, 0), (2013, 2, 14, 4, 0, 0)) Hour)
+             [ "domani notte"
+             , "domani in nottata"
+             , "nella nottata di domani"
+             , "nella notte di domani"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 12, 0, 0), (2013, 2, 13, 14, 0, 0)) Hour)
+             [ "domani a pranzo"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 18, 0, 0), (2013, 2, 12, 0, 0, 0)) Hour)
+             [ "ieri sera"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 18, 0, 0), (2013, 2, 18, 0, 0, 0)) Hour)
+             [ "questo weekend"
+             , "questo week-end"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 4, 0, 0), (2013, 2, 18, 12, 0, 0)) Hour)
+             [ "lunedì mattina"
+             , "nella mattinata di lunedì"
+             , "lunedì in mattinata"
+             , "lunedì nella mattina"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 4, 0, 0), (2013, 2, 15, 12, 0, 0)) Hour)
+             [ "il 15 febbraio in mattinata"
+             , "mattina del 15 febbraio"
+             , "15 febbraio mattina"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 29, 58), (2013, 2, 12, 4, 30, 0)) Second)
+             [ "gli ultimi 2 secondi"
+             , "gli ultimi due secondi"
+             , "i 2 secondi passati"
+             , "i due secondi passati"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 1), (2013, 2, 12, 4, 30, 4)) Second)
+             [ "i prossimi 3 secondi"
+             , "i prossimi tre secondi"
+             , "nei prossimi tre secondi"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 28, 0), (2013, 2, 12, 4, 30, 0)) Minute)
+             [ "gli ultimi 2 minuti"
+             , "gli ultimi due minuti"
+             , "i 2 minuti passati"
+             , "i due minuti passati"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 31, 0), (2013, 2, 12, 4, 34, 0)) Minute)
+             [ "i prossimi 3 minuti"
+             , "nei prossimi 3 minuti"
+             , "i prossimi tre minuti"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 2, 0, 0), (2013, 2, 12, 4, 0, 0)) Hour)
+             [ "le ultime 2 ore"
+             , "le ultime due ore"
+             , "nelle ultime due ore"
+             , "le scorse due ore"
+             , "le due ore scorse"
+             , "le scorse 2 ore"
+             , "le 2 ore scorse"
+             , "nelle 2 ore scorse"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 4, 0, 0), (2013, 2, 12, 4, 0, 0)) Hour)
+             [ "le ultime 24 ore"
+             , "le ultime ventiquattro ore"
+             , "le 24 ore passate"
+             , "nelle 24 ore scorse"
+             , "le ventiquattro ore passate"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 5, 0, 0), (2013, 2, 12, 8, 0, 0)) Hour)
+             [ "le prossime 3 ore"
+             , "prossime tre ore"
+             , "nelle prossime 3 ore"
+             ]
+  , examples (datetimeInterval ((2013, 2, 10, 0, 0, 0), (2013, 2, 12, 0, 0, 0)) Day)
+             [ "gli ultimi 2 giorni"
+             , "gli ultimi due giorni"
+             , "negli ultimi 2 giorni"
+             , "i 2 giorni passati"
+             , "i due giorni passati"
+             , "nei due giorni passati"
+             , "gli scorsi due giorni"
+             , "i 2 giorni scorsi"
+             , "i due giorni scorsi"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
+             [ "prossimi 3 giorni"
+             , "i prossimi tre giorni"
+             , "nei prossimi 3 giorni"
+             , "prossimi giorni"
+             , "nei prossimi giorni"
+             ]
+  , examples (datetimeInterval ((2013, 1, 28, 0, 0, 0), (2013, 2, 11, 0, 0, 0)) Week)
+             [ "le ultime 2 settimane"
+             , "le ultime due settimane"
+             , "le 2 ultime settimane"
+             , "le due ultime settimane"
+             , "nelle 2 ultime settimane"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 0, 0, 0), (2013, 3, 11, 0, 0, 0)) Week)
+             [ "prossime 3 settimane"
+             , "le prossime tre settimane"
+             , "le 3 prossime settimane"
+             , "nelle prossime 3 settimane"
+             , "le tre prossime settimane"
+             ]
+  , examples (datetimeInterval ((2012, 12, 0, 0, 0, 0), (2013, 2, 0, 0, 0, 0)) Month)
+             [ "gli ultimi 2 mesi"
+             , "gli ultimi due mesi"
+             , "i 2 mesi passati"
+             , "nei 2 mesi passati"
+             , "i due mesi passati"
+             , "i due mesi scorsi"
+             , "i 2 mesi scorsi"
+             , "negli scorsi due mesi"
+             , "gli scorsi due mesi"
+             , "gli scorsi 2 mesi"
+             ]
+  , examples (datetimeInterval ((2013, 3, 0, 0, 0, 0), (2013, 6, 0, 0, 0, 0)) Month)
+             [ "i prossimi 3 mesi"
+             , "i prossimi tre mesi"
+             , "prossimi 3 mesi"
+             , "i 3 prossimi mesi"
+             , "i tre prossimi mesi"
+             , "nei prossimi tre mesi"
+             ]
+  , examples (datetimeInterval ((2011, 0, 0, 0, 0, 0), (2013, 0, 0, 0, 0, 0)) Year)
+             [ "gli ultimi 2 anni"
+             , "gli ultimi due anni"
+             , "negli ultimi 2 anni"
+             , "i 2 anni passati"
+             , "i due anni passati"
+             , "i 2 anni scorsi"
+             , "i due anni scorsi"
+             , "gli scorsi due anni"
+             , "gli scorsi 2 anni"
+             ]
+  , examples (datetimeInterval ((2014, 0, 0, 0, 0, 0), (2017, 0, 0, 0, 0, 0)) Year)
+             [ "i prossimi 3 anni"
+             , "i prossimi tre anni"
+             , "nei tre prossimi anni"
+             ]
+  , examples (datetimeInterval ((2013, 7, 13, 0, 0, 0), (2013, 7, 16, 0, 0, 0)) Day)
+             [ "13-15 luglio"
+             , "dal 13 al 15 luglio"
+             , "tra il 13 e il 15 luglio"
+             , "tra 13 e 15 luglio"
+             , "dal tredici al quindici luglio"
+             , "13 luglio - 15 luglio"
+             ]
+  , examples (datetimeInterval ((2013, 3, 3, 0, 0, 0), (2013, 3, 6, 0, 0, 0)) Day)
+             [ "dal 3 al 5"
+             , "tra il 3 e il 5"
+             , "dal tre al cinque"
+             ]
+  , examples (datetimeInterval ((2013, 8, 8, 0, 0, 0), (2013, 8, 13, 0, 0, 0)) Day)
+             [ "8 ago - 12 ago"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 15, 0, 0, 0)) Day)
+             [ "da domani a giovedì"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 9, 30, 0), (2013, 2, 12, 11, 1, 0)) Minute)
+             [ "9:30 - 11:00"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 30, 0), (2013, 2, 14, 11, 1, 0)) Minute)
+             [ "dalle 9:30 alle 11:00 di giovedì"
+             , "tra le 9:30 e le 11:00 di giovedì"
+             , "9:30 - 11:00 giovedì"
+             , "giovedì dalle 9:30 alle 11:00"
+             , "giovedì tra le 9:30 e le 11:00"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 0, 0), (2013, 2, 14, 12, 0, 0)) Hour)
+             [ "dalle 9 alle 11 di giovedì"
+             , "tra le 9 e le 11 di giovedì"
+             , "9 - 11 giovedì"
+             , "giovedì dalle nove alle undici"
+             , "giovedì tra le nove e le undici"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 3, 0, 0), (2013, 2, 14, 14, 0, 0)) Hour)
+             [ "dalle tre all'una di giovedì"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 13, 3, 0, 0)) Hour)
+             [ "dalla mezzanotte alle 2"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 15, 0, 0), (2013, 2, 13, 17, 1, 0)) Minute)
+             [ "domani dalle 15:00 alle 17:00"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 11, 30, 0), (2013, 2, 12, 13, 31, 0)) Minute)
+             [ "11:30-13:30"
+             ]
+  , examples (datetime (2013, 9, 21, 13, 30, 0) Minute)
+             [ "13:30 di sabato 21 settembre"
+             , "13:30 del 21 settembre"
+             ]
+  , examples (datetime (2013, 2, 26, 0, 0, 0) Day)
+             [ "in due settimane"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 12, 14, 0, 0)) Second)
+             [ "fino alle 14:00"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 14, 0, 0) Minute)
+             [ "entro le 14:00"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 3, 1, 0, 0, 0) Month)
+             [ "entro la fine del mese"
+             ]
+  , examples (datetimeOpenInterval Before (2014, 1, 1, 0, 0, 0) Year)
+             [ "entro la fine dell'anno"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 3, 1, 0, 0, 0)) Second)
+             [ "fino alla fine del mese"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2014, 1, 1, 0, 0, 0)) Second)
+             [ "fino alla fine dell'anno"
+             ]
+  , examples (datetime (2013, 2, 13, 1, 0, 0) Minute)
+             [ "alle 4 CET"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 0, 0) Minute)
+             [ "alle 16 CET"
+             ]
+  , examples (datetime (2013, 2, 14, 6, 0, 0) Minute)
+             [ "giovedì alle 8:00 GMT"
+             ]
+  , examples (datetime (2013, 2, 13, 14, 0, 0) Hour)
+             [ "domani alle 14"
+             ]
+  , examples (datetime (2013, 2, 12, 14, 0, 0) Hour)
+             [ "alle 14"
+             , "alle 2 del pomeriggio"
+             ]
+  , examples (datetime (2013, 4, 25, 16, 0, 0) Minute)
+             [ "25/4 alle 16:00"
+             ]
+  , examples (datetime (2013, 2, 13, 15, 0, 0) Hour)
+             [ "3 del pomeriggio di domani"
+             , "15 del pomeriggio di domani"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 12, 14, 0, 0) Hour)
+             [ "dopo le 14"
+             , "dalle 14"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 13, 0, 0, 0) Hour)
+             [ "dalla mezzanotte"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 13, 14, 0, 0) Hour)
+             [ "domani dopo le 14"
+             , "domani dalle 14"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 11, 0, 0) Hour)
+             [ "prima delle 11"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 14, 11, 0, 0) Hour)
+             [ "dopodomani prima delle 11"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 14, 12, 0, 0) Hour)
+             [ "giovedì entro mezzogiorno"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 14, 0, 0, 0) Day)
+             [ "da dopodomani"
+             , "da giovedì"
+             ]
+  , examples (datetimeOpenInterval After (2013, 3, 1, 0, 0, 0) Day)
+             [ "dal primo"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 20, 0, 0, 0) Day)
+             [ "dal 20"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 15, 0, 0, 0) Day)
+             [ "entro il 15"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 4, 20, 0, 0, 0) Day)
+             [ "prima del 20 aprile"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 12, 0, 0), (2013, 2, 12, 19, 0, 0)) Hour)
+             [ "nel pomeriggio"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 30, 0) Minute)
+             [ "alle 13:30"
+             , "13:30"
+             , "1:30 del pomeriggio"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 45, 0) Second)
+             [ "in 15 minuti"
+             , "tra 15 minuti"
+             ]
+  , examples (datetime (2013, 2, 12, 10, 30, 0) Minute)
+             [ "10:30"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 0, 0), (2013, 2, 12, 12, 0, 0)) Hour)
+             [ "questa mattina"
+             , "questa mattinata"
+             , "questo mattino"
+             ]
+  , examples (datetime (2013, 2, 25, 0, 0, 0) Day)
+             [ "prossimo lunedì"
+             ]
+  , examples (datetime (2013, 2, 12, 12, 0, 0) Hour)
+             [ "alle 12"
+             , "a mezzogiorno"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Hour)
+             [ "alle 24"
+             , "a mezzanotte"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Month)
+             [ "marzo"
+             , "in marzo"
+             ]
+  , examples (datetime (2013, 8, 15, 0, 0, 0) Day)
+             [ "gio 15"
+             ]
+  ]
diff --git a/Duckling/Time/IT/Rules.hs b/Duckling/Time/IT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/IT/Rules.hs
@@ -0,0 +1,2569 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.IT.Rules
+  ( rules ) where
+
+import Control.Monad (liftM2)
+import qualified Data.Text as Text
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Numeral.Types (NumeralData(..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Ordinal.Types (OrdinalData(..))
+import qualified Duckling.Ordinal.Types as TOrdinal
+import Duckling.Regex.Types
+import Duckling.Time.Helpers
+import Duckling.Time.Types (TimeData (..))
+import qualified Duckling.Time.Types as TTime
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleFestaDellaRepubblica :: Rule
+ruleFestaDellaRepubblica = Rule
+  { name = "festa della repubblica"
+  , pattern =
+    [ regex "((festa del)?la )?repubblica"
+    ]
+  , prod = \_ -> tt $ monthDay 6 2
+  }
+
+ruleEpifania :: Rule
+ruleEpifania = Rule
+  { name = "epifania"
+  , pattern =
+    [ regex "(epifania|befana)"
+    ]
+  , prod = \_ -> tt $ monthDay 1 6
+  }
+
+ruleNamedday :: Rule
+ruleNamedday = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "luned(i|\x00ec)|lun?\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 1
+  }
+
+ruleDayofmonthNamedmonth :: Rule
+ruleDayofmonthNamedmonth = Rule
+  { name = "<day-of-month> <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleTheDayAfterTomorrow :: Rule
+ruleTheDayAfterTomorrow = Rule
+  { name = "the day after tomorrow"
+  , pattern =
+    [ regex "(il giorno )?dopo\\s?domani"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 2
+  }
+
+ruleInafterDuration :: Rule
+ruleInafterDuration = Rule
+  { name = "in/after <duration>"
+  , pattern =
+    [ regex "[tf]ra|in|dopo"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleTheLastCycle :: Rule
+ruleTheLastCycle = Rule
+  { name = "the last <cycle>"
+  , pattern =
+    [ regex "(([nd]el)?l' ?ultim|(il|la) passat|([nd]el)?l[ao] scors)[oa]"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleStanotte :: Rule
+ruleStanotte = Rule
+  { name = "stanotte"
+  , pattern =
+    [ regex "(sta|nella )notte|(in|nella) nottata"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 1
+          td2 = interval TTime.Open (hour False 0, hour False 4)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleDomattina :: Rule
+ruleDomattina = Rule
+  { name = "domattina"
+  , pattern =
+    [ regex "domattina"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 1
+          td2 = interval TTime.Open (hour False 4, hour False 12)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleNamedmonth12 :: Rule
+ruleNamedmonth12 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "dicembre|dic\\.?"
+    ]
+  , prod = \_ -> tt $ month 12
+  }
+
+ruleTheCycleNext :: Rule
+ruleTheCycleNext = Rule
+  { name = "the <cycle> next"
+  , pattern =
+    [ regex "l'|il|la|[nd]el(la)?"
+    , dimension TimeGrain
+    , regex "prossim[oa]"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleCycleNext :: Rule
+ruleCycleNext = Rule
+  { name = "<cycle> next"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "prossim[oa]"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_) -> tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleFestaDellaLiberazione :: Rule
+ruleFestaDellaLiberazione = Rule
+  { name = "festa della liberazione"
+  , pattern =
+    [ regex "((festa|anniversario) della|(al)?la) liberazione"
+    ]
+  , prod = \_ -> tt $ monthDay 4 25
+  }
+
+ruleStamattina :: Rule
+ruleStamattina = Rule
+  { name = "stamattina"
+  , pattern =
+    [ regex "stamattina"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 4, hour False 12)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleNamedday2 :: Rule
+ruleNamedday2 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "marted(i|\x00ec)|mar\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 2
+  }
+
+ruleYearNotLatent :: Rule
+ruleYearNotLatent = Rule
+  { name = "year (1000-2100 not latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1000 2100
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt $ year v
+      _ -> Nothing
+  }
+
+ruleValentinesDay :: Rule
+ruleValentinesDay = Rule
+  { name = "valentine's day"
+  , pattern =
+    [ regex "san valentino|festa degli innamorati"
+    ]
+  , prod = \_ -> tt $ monthDay 2 14
+  }
+
+ruleTheOrdinalCycleOfTime :: Rule
+ruleTheOrdinalCycleOfTime = Rule
+  { name = "the <ordinal> <cycle> of <time>"
+  , pattern =
+    [ regex "il|l[a']|[nd]el(l[a'])?"
+    , dimension Ordinal
+    , dimension TimeGrain
+    , regex "di|del(l[a'])?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleOrdinalQuarter :: Rule
+ruleOrdinalQuarter = 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
+  }
+
+ruleTheOrdinalQuarter :: Rule
+ruleTheOrdinalQuarter = Rule
+  { name = "the <ordinal> quarter"
+  , pattern =
+    [ regex "il|[nd]el(l')?|l'"
+    , 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
+  }
+
+ruleCycleTheOrdinalTime :: Rule
+ruleCycleTheOrdinalTime = Rule
+  { name = "the <ordinal> <cycle> <time>"
+  , pattern =
+    [ regex "il|[nd]el(l')?|l'"
+    , dimension Ordinal
+    , dimension TimeGrain
+    , 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
+  }
+
+ruleDdddMonthInterval :: Rule
+ruleDdddMonthInterval = Rule
+  { name = "dd-dd <month> (interval)"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])"
+    , regex "\\-"
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:_)):
+       _:
+       Token RegexMatch (GroupMatch (m2:_)):
+       Token Time td:
+       _) -> do
+         v1 <- parseInt m1
+         v2 <- parseInt m2
+         from <- intersect (dayOfMonth v1) td
+         to <- intersect (dayOfMonth v2) td
+         tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleTimeLast :: Rule
+ruleTimeLast = Rule
+  { name = "<time> last"
+  , pattern =
+    [ dimension Time
+    , regex "(ultim|scors|passat)[oaie]"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleThisDayofweek :: Rule
+ruleThisDayofweek = Rule
+  { name = "this <day-of-week>"
+  , pattern =
+    [ regex "quest[oaie]"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleUna :: Rule
+ruleUna = Rule
+  { name = "una"
+  , pattern =
+    [ regex "una"
+    ]
+  , prod = \_ -> tt . mkLatent $ hour True 1
+  }
+
+ruleNthTimeOfTime2 :: Rule
+ruleNthTimeOfTime2 = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "di|del(l[oa'])|in"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal OrdinalData {TOrdinal.value = v}:
+       Token Time td1:
+       _:
+       Token Time td2:
+       _) -> Token Time . predNth (v - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNewYearsDay :: Rule
+ruleNewYearsDay = Rule
+  { name = "new year's day"
+  , pattern =
+    [ regex "(capodanno|primo dell' ?anno)"
+    ]
+  , prod = \_ -> tt $ monthDay 1 1
+  }
+
+ruleLastTime :: Rule
+ruleLastTime = Rule
+  { name = "last <time> "
+  , pattern =
+    [ regex "(ultim|scors|passat)[oaie]"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleIlDayofmonthDeNamedmonth :: Rule
+ruleIlDayofmonthDeNamedmonth = Rule
+  { name = "il <day-of-month> <named-month>"
+  , pattern =
+    [ regex "il|l'"
+    , Predicate isDOMInteger
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleNamedday6 :: Rule
+ruleNamedday6 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "sabato|sab\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 6
+  }
+
+ruleDatetimeDatetimeInterval :: Rule
+ruleDatetimeDatetimeInterval = Rule
+  { name = "<datetime> - <datetime> (interval)"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "\\-|[fs]ino a(l(l[e'])?)?"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth7 :: Rule
+ruleNamedmonth7 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "luglio|lug\\.?"
+    ]
+  , prod = \_ -> tt $ month 7
+  }
+
+ruleEvening :: Rule
+ruleEvening = Rule
+  { name = "evening"
+  , pattern =
+    [ regex "ser(ata|[ae])"
+    ]
+  , prod = \_ ->
+      let from = hour False 18
+          to = hour False 0
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleDayOfMonthSt :: Rule
+ruleDayOfMonthSt = Rule
+  { name = "day of month (1st)"
+  , pattern =
+    [ regex "(primo|1o|1\x00ba|1\x00b0)"
+    ]
+  , prod = \_ -> tt $ dayOfMonth 1
+  }
+
+ruleInDuration :: Rule
+ruleInDuration = Rule
+  { name = "in|entro <duration>"
+  , pattern =
+    [ regex "(in|entro)"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       Token Duration dd:
+       _) -> case Text.toLower match of
+        "entro" -> tt $
+          interval TTime.Open (cycleNth TG.Second 0, inDuration dd)
+        "in"    -> tt $ inDuration dd
+        _       -> Nothing
+      _ -> Nothing
+  }
+
+ruleInNamedmonth :: Rule
+ruleInNamedmonth = Rule
+  { name = "in <named-month>"
+  , pattern =
+    [ regex "in|del mese( di)?"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleLeIdiDiNamedmonth :: Rule
+ruleLeIdiDiNamedmonth = Rule
+  { name = "le idi di <named-month>"
+  , pattern =
+    [ regex "(le )?idi di"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td@TimeData {TTime.form = Just (TTime.Month m)}:_) ->
+        Token Time <$>
+          intersect (dayOfMonth $ if elem m [3, 5, 7, 10] then 15 else 13) td
+      _ -> Nothing
+  }
+
+ruleRightNow :: Rule
+ruleRightNow = Rule
+  { name = "right now"
+  , pattern =
+    [ regex "(subito|(immediata|attual)mente|(proprio )?adesso|(in questo|al) momento|ora)"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Second 0
+  }
+
+ruleToday :: Rule
+ruleToday = Rule
+  { name = "today"
+  , pattern =
+    [ regex "(di )?oggi|in giornata"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 0
+  }
+
+ruleLastCycleOfTime :: Rule
+ruleLastCycleOfTime = Rule
+  { name = "last <cycle> of <time>"
+  , pattern =
+    [ regex "(l')ultim[oa]"
+    , dimension TimeGrain
+    , regex "di|del(l[oa'])"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleLastOf grain td
+      _ -> Nothing
+  }
+
+ruleHhRelativeminutesDelPomeriggiotimeofday2 :: Rule
+ruleHhRelativeminutesDelPomeriggiotimeofday2 = Rule
+  { name = "hh <relative-minutes> del pomeriggio(time-of-day)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "e"
+    , Predicate $ isIntegerBetween 1 59
+    , regex "d(i|el(la)?) (pomeriggio|(sta)?(sera|notte))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _:
+       token:
+       _) -> do
+        n <- getIntValue token
+        let h = if hours > 12 then hours else hours + 12
+        tt $ hourMinute False h n
+      _ -> Nothing
+  }
+ruleHourofdayIntegerAsRelativeMinutes :: Rule
+ruleHourofdayIntegerAsRelativeMinutes = Rule
+  { name = "<hour-of-day> <integer> (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+ruleHourofdayAndRelativeMinutes :: Rule
+ruleHourofdayAndRelativeMinutes = Rule
+  { name = "<hour-of-day> and <relative minutes>"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "e"
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+ruleRelativeMinutesToIntegerAsHourofday :: Rule
+ruleRelativeMinutesToIntegerAsHourofday = Rule
+  { name = "relative minutes to <integer> (as hour-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "a"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+ruleHourofdayMinusIntegerAsRelativeMinutes :: Rule
+ruleHourofdayMinusIntegerAsRelativeMinutes = Rule
+  { name = "<hour-of-day> minus <integer> (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "meno"
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_:token:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+ruleHhRelativeminutesDelPomeriggiotimeofday :: Rule
+ruleHhRelativeminutesDelPomeriggiotimeofday = Rule
+  { name = "hh <relative-minutes> del pomeriggio(time-of-day)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    , regex "d(i|el(la)?) (pomeriggio|(sta)?(sera|notte))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        let h = if hours > 12 then hours else hours + 12
+        tt $ hourMinute False h n
+      _ -> Nothing
+  }
+
+ruleHhIntegerminutesDelPomeriggiotimeofday2 :: Rule
+ruleHhIntegerminutesDelPomeriggiotimeofday2 = Rule
+  { name = "hh <relative-minutes> minutes del pomeriggio(time-of-day)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "e"
+    , Predicate $ isIntegerBetween 1 59
+    , regex "min(ut[oi]|\\.)? d(i|el(la)?) (pomeriggio|(sta)?(sera|notte))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _:
+       token:
+       _) -> do
+        n <- getIntValue token
+        let h = if hours > 12 then hours else hours + 12
+        tt $ hourMinute False h n
+      _ -> Nothing
+  }
+ruleHourofdayIntegerMinutes :: Rule
+ruleHourofdayIntegerMinutes = Rule
+  { name = "<hour-of-day> <integer> minutes"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    , regex "min(ut[oi]|\\.)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+ruleHourofdayAndIntegerMinutes :: Rule
+ruleHourofdayAndIntegerMinutes = Rule
+  { name = "<hour-of-day> and <integer> minutes"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "e"
+    , Predicate $ isIntegerBetween 1 59
+    , regex "min(ut[oi]|\\.)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+ruleMinutesToIntegerAsHourofday :: Rule
+ruleMinutesToIntegerAsHourofday = Rule
+  { name = "minutes to <integer> (as hour-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "min(ut[oi]|\\.)? a"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+ruleHourofdayMinusIntegerMinutes :: Rule
+ruleHourofdayMinusIntegerMinutes = Rule
+  { name = "<hour-of-day> minus <integer> minutes"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "meno"
+    , Predicate $ isIntegerBetween 1 59
+    , regex "min(ut[oi]|\\.)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       _:
+       token:
+       _) -> do
+         v <- getIntValue token
+         Token Time <$> minutesBefore v td
+      _ -> Nothing
+  }
+ruleHhIntegerminutesDelPomeriggiotimeofday :: Rule
+ruleHhIntegerminutesDelPomeriggiotimeofday = Rule
+  { name = "hh <integer> minutes del pomeriggio(time-of-day)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    , regex "min(ut[oi]|\\.)? d(i|el(la)?) (pomeriggio|(sta)?(sera|notte))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       token:
+       _) -> do
+         v <- getIntValue token
+         let h = if hours > 12 then hours else hours + 12
+         tt $ hourMinute False h v
+      _ -> Nothing
+  }
+
+ruleHhQuartDelPomeriggiotimeofday2 :: Rule
+ruleHhQuartDelPomeriggiotimeofday2 = Rule
+  { name = "hh quart del pomeriggio(time-of-day)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "e un quarto d(i|el(la)?) (pomeriggio|(sta)?(sera|notte))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) ->
+         let h = if hours > 12 then hours else hours + 12
+         in tt $ hourMinute False h 15
+      _ -> Nothing
+  }
+ruleHourofdayQuart :: Rule
+ruleHourofdayQuart = Rule
+  { name = "<hour-of-day> quart"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "un quarto"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 15
+      _ -> Nothing
+  }
+ruleHourofdayAndAQuart :: Rule
+ruleHourofdayAndAQuart = Rule
+  { name = "<hour-of-day> and a quart"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "e un quarto"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 15
+      _ -> Nothing
+  }
+ruleQuartAsHourofday :: Rule
+ruleQuartAsHourofday = Rule
+  { name = "a quart to <integer> (as hour-of-day)"
+  , pattern =
+    [ regex "un quarto a"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesBefore 15 td
+      _ -> Nothing
+  }
+ruleHourofdayMinusQuart :: Rule
+ruleHourofdayMinusQuart = Rule
+  { name = "<hour-of-day> minus quart"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "meno un quarto"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> Token Time <$> minutesBefore 15 td
+      _ -> Nothing
+  }
+ruleHhQuartDelPomeriggiotimeofday :: Rule
+ruleHhQuartDelPomeriggiotimeofday = Rule
+  { name = "hh quart del pomeriggio(time-of-day)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "un quarto d(i|el(la)?) (pomeriggio|(sta)?(sera|notte))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) ->
+         let h = if hours > 12 then hours else hours + 12
+         in tt $ hourMinute False h 15
+      _ -> Nothing
+  }
+
+ruleHhHalfDelPomeriggiotimeofday2 :: Rule
+ruleHhHalfDelPomeriggiotimeofday2 = Rule
+  { name = "hh half del pomeriggio(time-of-day)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "e mezzo d(i|el(la)?) (pomeriggio|(sta)?(sera|notte))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) ->
+         let h = if hours > 12 then hours else hours + 12
+         in tt $ hourMinute False h 30
+      _ -> Nothing
+  }
+ruleHourofdayHalf :: Rule
+ruleHourofdayHalf = Rule
+  { name = "<hour-of-day> half"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "mezzo"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+ruleHourofdayAndHalf :: Rule
+ruleHourofdayAndHalf = Rule
+  { name = "<hour-of-day> and half"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "e mezzo"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+ruleHalfToIntegerAsHourofday :: Rule
+ruleHalfToIntegerAsHourofday = Rule
+  { name = "half to <integer> (as hour-of-day)"
+  , pattern =
+    [ regex "mezzo a"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesBefore 30 td
+      _ -> Nothing
+  }
+ruleHourofdayMinusHalf :: Rule
+ruleHourofdayMinusHalf = Rule
+  { name = "<hour-of-day> minus half"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "meno mezzo"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> Token Time <$> minutesBefore 30 td
+      _ -> Nothing
+  }
+ruleHhHalfDelPomeriggiotimeofday :: Rule
+ruleHhHalfDelPomeriggiotimeofday = Rule
+  { name = "hh half del pomeriggio(time-of-day)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "mezzo d(i|el(la)?) (pomeriggio|(sta)?(sera|notte))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) ->
+         let h = if hours > 12 then hours else hours + 12
+         in tt $ hourMinute False h 30
+      _ -> Nothing
+  }
+
+ruleHhThreeQuarterDelPomeriggiotimeofday2 :: Rule
+ruleHhThreeQuarterDelPomeriggiotimeofday2 = Rule
+  { name = "hh three quarters del pomeriggio(time-of-day)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "e (3|tre) quarti? d(i|el(la)?) (pomeriggio|(sta)?(sera|notte))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) ->
+         let h = if hours > 12 then hours else hours + 12
+         in tt $ hourMinute False h 45
+      _ -> Nothing
+  }
+ruleHourofdayThreeQuarters :: Rule
+ruleHourofdayThreeQuarters = Rule
+  { name = "<hour-of-day> three quarters"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "(3|tre) quarti?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 45
+      _ -> Nothing
+  }
+ruleHourofdayAndThreeQuarter :: Rule
+ruleHourofdayAndThreeQuarter = Rule
+  { name = "<hour-of-day> and three quarters"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "e (3|tre) quarti?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 45
+      _ -> Nothing
+  }
+ruleThreeQuarterToIntegerAsHourofday :: Rule
+ruleThreeQuarterToIntegerAsHourofday = Rule
+  { name = "three quarter to <integer> (as hour-of-day)"
+  , pattern =
+    [ regex "(3|tre) quarti? a"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesBefore 45 td
+      _ -> Nothing
+  }
+ruleHourofdayMinusThreeQuarter :: Rule
+ruleHourofdayMinusThreeQuarter = Rule
+  { name = "<hour-of-day> minus three quarter"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "meno (3|tre) quarti?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> Token Time <$> minutesBefore 45 td
+      _ -> Nothing
+  }
+ruleHhThreeQuarterDelPomeriggiotimeofday :: Rule
+ruleHhThreeQuarterDelPomeriggiotimeofday = Rule
+  { name = "hh three quarter del pomeriggio(time-of-day)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "(3|tre) quarti? d(i|el(la)?) (pomeriggio|(sta)?(sera|notte))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) ->
+         let h = if hours > 12 then hours else hours + 12
+         in tt $ hourMinute False h 45
+      _ -> Nothing
+  }
+
+ruleHhhmmTimeofday :: Rule
+ruleHhhmmTimeofday = Rule
+  { name = "hh(:|h)mm (time-of-day)"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))[:h]([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        h <- parseInt m1
+        m <- parseInt m2
+        tt $ hourMinute False h m
+      _ -> Nothing
+  }
+
+ruleDalIntAlInt :: Rule
+ruleDalIntAlInt = Rule
+  { name = "dal <integer> al <integer> (interval)"
+  , pattern =
+    [ regex "dal(?:l')?"
+    , Predicate isDOMInteger
+    , regex "([fs]ino )?al(l')?"
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token1:_:token2:_) -> do
+         v1 <- getIntValue token1
+         v2 <- getIntValue token2
+         tt $
+           interval TTime.Closed (dayOfMonth v1, dayOfMonth v2)
+      _ -> Nothing
+  }
+
+ruleTraIlIntEIlInt :: Rule
+ruleTraIlIntEIlInt = Rule
+  { name = "tra il <integer> e il <integer> (interval)"
+  , pattern =
+    [ regex "tra( (il|l'))?"
+    , Predicate isDOMInteger
+    , regex "e( (il|l'))?"
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token1:_:token2:_) -> do
+         v1 <- getIntValue token1
+         v2 <- getIntValue token2
+         tt $
+           interval TTime.Closed (dayOfMonth v1, dayOfMonth v2)
+      _ -> Nothing
+  }
+
+ruleTimeofdayOra :: Rule
+ruleTimeofdayOra = Rule
+  { name = "<time-of-day> ora"
+  , pattern =
+    [ regex "or[ea]"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNextTime2 :: Rule
+ruleNextTime2 = Rule
+  { name = "next <time>"
+  , pattern =
+    [ dimension Time
+    , regex "dopo|prossim[ao]"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 1 False td
+      _ -> Nothing
+  }
+
+ruleNamedday4 :: Rule
+ruleNamedday4 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "gioved(i|\x00ec)|gio\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 4
+  }
+
+ruleSantoStefano :: Rule
+ruleSantoStefano = Rule
+  { name = "santo stefano"
+  , pattern =
+    [ regex "s(anto|\\.) stefano"
+    ]
+  , prod = \_ -> tt $ monthDay 12 26
+  }
+
+ruleIlTime :: Rule
+ruleIlTime = Rule
+  { name = "il <time>"
+  , pattern =
+    [ regex "il|l'"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleFestaDelPap :: Rule
+ruleFestaDelPap = Rule
+  { name = "festa del papà"
+  , pattern =
+    [ regex "festa del pap(a|\x00e0)|(festa di )?s(an|\\.) giuseppe"
+    ]
+  , prod = \_ -> tt $ monthDay 3 19
+  }
+
+ruleEntroIlDuration :: Rule
+ruleEntroIlDuration = Rule
+  { name = "entro il <duration>"
+  , pattern =
+    [ regex "(entro|durante|per( tutt[ao])?) (il|l[a'])|in|nel(l[a'])?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        let from = cycleNth TG.Second 0
+            to = cycleNth grain 1
+        in tt $ interval TTime.Open (from, to)
+      _ -> Nothing
+  }
+
+ruleDimTimeAlPartofday :: Rule
+ruleDimTimeAlPartofday = Rule
+  { name = "<dim time> al <part-of-day>"
+  , pattern =
+    [ dimension Time
+    , regex "al|nel(la)?|in|d(i|el(la)?)"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleSeason4 :: Rule
+ruleSeason4 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "(in )?primavera"
+    ]
+  , prod = \_ ->
+      let from = monthDay 3 20
+          to = monthDay 6 21
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleYearLatent2 :: Rule
+ruleYearLatent2 = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2101 10000
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ year v
+      _ -> Nothing
+  }
+
+ruleNoon :: Rule
+ruleNoon = Rule
+  { name = "noon"
+  , pattern =
+    [ regex "mezz?ogiorno"
+    ]
+  , prod = \_ -> tt $ hour False 12
+  }
+
+ruleTheDayBeforeYesterday :: Rule
+ruleTheDayBeforeYesterday = Rule
+  { name = "the day before yesterday"
+  , pattern =
+    [ regex "(l')?altro\\s?ieri"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 2
+  }
+
+ruleNextCycle :: Rule
+ruleNextCycle = Rule
+  { name = "next <cycle> "
+  , pattern =
+    [ regex "((il|la|[nd]el(la)?) )?prossim[oa]"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleNamedmonth :: Rule
+ruleNamedmonth = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "gennaio|genn?\\.?"
+    ]
+  , prod = \_ -> tt $ month 1
+  }
+
+ruleTheCycleOfTime :: Rule
+ruleTheCycleOfTime = Rule
+  { name = "the <cycle> of <time>"
+  , pattern =
+    [ regex "il|la"
+    , dimension TimeGrain
+    , regex "del"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain 0 td
+      _ -> Nothing
+  }
+
+ruleNamedmonth3 :: Rule
+ruleNamedmonth3 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "marzo|mar\\.?"
+    ]
+  , prod = \_ -> tt $ month 3
+  }
+
+ruleLunch :: Rule
+ruleLunch = Rule
+  { name = "lunch"
+  , pattern =
+    [ regex "(a )?pranzo"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 14
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleDdmm :: Rule
+ruleDdmm = Rule
+  { name = "dd[/-]mm"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[/-](0?[1-9]|1[0-2])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        d <- parseInt m1
+        m <- parseInt m2
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleGliUltimiNCycle :: Rule
+ruleGliUltimiNCycle = Rule
+  { name = "gli ultimi <n> <cycle>"
+  , pattern =
+    [ regex "((([nd]el)?le|([nd]e)?gli) )?(scors|ultim)[ei]"
+    , Predicate $ isIntegerBetween 2 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain (- v)
+      _ -> Nothing
+  }
+
+ruleAfternoon :: Rule
+ruleAfternoon = Rule
+  { name = "afternoon"
+  , pattern =
+    [ regex "pomeriggio?"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 19
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleNamedmonth4 :: Rule
+ruleNamedmonth4 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "aprile|apr\\.?"
+    ]
+  , prod = \_ -> tt $ month 4
+  }
+
+rulePartofdayOfDimTime :: Rule
+rulePartofdayOfDimTime = Rule
+  { name = "<part-of-day> of <dim time>"
+  , pattern =
+    [ Predicate isAPartOfDay
+    , regex "d(i|el)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleDimTimeDelMattino :: Rule
+ruleDimTimeDelMattino = Rule
+  { name = "<dim time> del mattino"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "del mattino"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        let from = hour False 0
+            to = hour False 12
+            td2 = mkLatent . partOfDay $ interval TTime.Open (from, to)
+        in Token Time <$> intersect td td2
+      _ -> Nothing
+  }
+
+ruleMidnight :: Rule
+ruleMidnight = Rule
+  { name = "midnight"
+  , pattern =
+    [ regex "mez?zanott?e"
+    ]
+  , prod = \_ -> tt $ hour False 0
+  }
+
+ruleChristmasEve :: Rule
+ruleChristmasEve = Rule
+  { name = "christmas eve"
+  , pattern =
+    [ regex "((al)?la )?vigig?lia( di natale)?"
+    ]
+  , prod = \_ -> tt $ monthDay 12 24
+  }
+
+ruleNamedday5 :: Rule
+ruleNamedday5 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "venerd(i|\x00ec)|ven\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 5
+  }
+
+ruleNthTimeAfterTime :: Rule
+ruleNthTimeAfterTime = Rule
+  { name = "nth <time> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "dopo"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       Token Time td1:
+       _:
+       Token Time td2:
+       _) -> tt $ predNthAfter (v - 1) td1 td2
+      _ -> Nothing
+  }
+
+ruleHhhmmTimeofday2 :: Rule
+ruleHhhmmTimeofday2 = Rule
+  { name = "hh(:|h)mm (time-of-day)"
+  , pattern =
+    [ regex "((?:0?\\d)|(?:1[0-2]))[:h]([0-5]\\d) d(i|el(la)?) (pomeriggio|(sta)?(sera|notte))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        v1 <- parseInt m1
+        v2 <- parseInt m2
+        tt $ hourMinute False (v1 + 12) v2
+      _ -> Nothing
+  }
+
+ruleTimeNotte :: Rule
+ruleTimeNotte = Rule
+  { name = "<time> notte"
+  , pattern =
+    [ dimension Time
+    , regex "((in|nella|alla) )?nott(e|ata)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        let td1 = cycleNthAfter False TG.Day 1 td
+            td2 = interval TTime.Open (hour False 0, hour False 4)
+        in Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNamedmonth2 :: Rule
+ruleNamedmonth2 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "febbraio|febb?\\.?"
+    ]
+  , prod = \_ -> tt $ month 2
+  }
+
+ruleStasera :: Rule
+ruleStasera = Rule
+  { name = "stasera"
+  , pattern =
+    [ regex "stasera"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 18, hour False 0)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleSeason3 :: Rule
+ruleSeason3 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "(in )?inverno"
+    ]
+  , prod = \_ ->
+      let from = monthDay 12 21
+          to = monthDay 3 20
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleSeason :: Rule
+ruleSeason = Rule
+  { name = "season"
+  , pattern =
+    [ regex "(in )?estate"
+    ]
+  , prod = \_ ->
+      let from = monthDay 6 21
+          to = monthDay 9 23
+       in tt $ interval TTime.Open (from, to)
+  }
+
+ruleIntegerLatentTimeofday :: Rule
+ruleIntegerLatentTimeofday = Rule
+  { name = "<integer> (latent time-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 0 24
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ hour False v
+      _ -> Nothing
+  }
+
+ruleInThePartofdayOfDimTime :: Rule
+ruleInThePartofdayOfDimTime = Rule
+  { name = "in the <part-of-day> of <dim time>"
+  , pattern =
+    [ regex "nel(la)?"
+    , Predicate isAPartOfDay
+    , regex "d(i|el)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNewYearsEve :: Rule
+ruleNewYearsEve = Rule
+  { name = "new year's eve"
+  , pattern =
+    [ regex "((la )?vigig?lia di capodanno|san silvestro)"
+    ]
+  , prod = \_ -> tt $ monthDay 12 31
+  }
+
+ruleFerragosto :: Rule
+ruleFerragosto = Rule
+  { name = "ferragosto"
+  , pattern =
+    [ regex "ferragosto|assunzione"
+    ]
+  , prod = \_ -> tt $ monthDay 8 15
+  }
+
+ruleDurationAgo :: Rule
+ruleDurationAgo = Rule
+  { name = "<duration> ago"
+  , pattern =
+    [ dimension Duration
+    , regex "fa"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ durationAgo dd
+      _ -> Nothing
+  }
+
+ruleTimeNotte2 :: Rule
+ruleTimeNotte2 = Rule
+  { name = "<time> notte"
+  , pattern =
+    [ regex "((nella|alla) )?nott(e|ata)( d(i|el))"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        let td1 = cycleNthAfter False TG.Day 1 td
+            td2 = interval TTime.Open (hour False 0, hour False 4)
+        in Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleTimeofdayPrecise :: Rule
+ruleTimeofdayPrecise = Rule
+  { name = "<time-of-day> precise"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "precise"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleHhmmMilitaryTimeofday :: Rule
+ruleHhmmMilitaryTimeofday = Rule
+  { name = "hhmm (military time-of-day)"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        h <- parseInt m1
+        m <- parseInt m2
+        tt . mkLatent $ hourMinute False h m
+      _ -> Nothing
+  }
+
+ruleTheCycleLast :: Rule
+ruleTheCycleLast = Rule
+  { name = "the <cycle> last"
+  , pattern =
+    [ regex "l'|il|la|[nd]el(la)?"
+    , dimension TimeGrain
+    , regex "(scors|passat)[oa]"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleFinoAlDatetimeInterval :: Rule
+ruleFinoAlDatetimeInterval = Rule
+  { name = "fino al <datetime> (interval)"
+  , pattern =
+    [ regex "[fs]ino a(l(l[ae'])?)?"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        let now = cycleNth TG.Second 0
+        in tt $ interval TTime.Open (now, td)
+      _ -> Nothing
+  }
+
+ruleAtTimeofday :: Rule
+ruleAtTimeofday = Rule
+  { name = "at <time-of-day>"
+  , pattern =
+    [ regex "(al)?l[e']|a"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleTheNthTimeOfTime :: Rule
+ruleTheNthTimeOfTime = Rule
+  { name = "the nth <time> of <time>"
+  , pattern =
+    [ regex "il|l[a']|[nd]el(l[a'])?"
+    , dimension Ordinal
+    , dimension Time
+    , regex "di|del(l[a'])?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token Time td1:_:Token Time td2:_) -> Token Time .
+         predNth (TOrdinal.value od - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNamedmonth6 :: Rule
+ruleNamedmonth6 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "giugno|giu\\.?"
+    ]
+  , prod = \_ -> tt $ month 6
+  }
+
+ruleNthTimeOfTime :: Rule
+ruleNthTimeOfTime = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "di|del(l[a'])?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token Time td1:_:Token Time td2:_) -> Token Time .
+         predNth (TOrdinal.value od - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNamedmonth8 :: Rule
+ruleNamedmonth8 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "agosto|ago\\.?"
+    ]
+  , prod = \_ -> tt $ month 8
+  }
+
+ruleDalDatetimeAlDatetimeInterval :: Rule
+ruleDalDatetimeAlDatetimeInterval = Rule
+  { name = "dal <datetime> al <datetime> (interval)"
+  , pattern =
+    [ regex "da(l(l')?)?"
+    , Predicate isNotLatent
+    , regex "([fs]ino )?a(l(l')?)?"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleTimeofdayTimeofdayDayofmonthInterval :: Rule
+ruleTimeofdayTimeofdayDayofmonthInterval = Rule
+  { name = "<time-of-day> - <time-of-day> <day-of-month> (interval)"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "\\-"
+    , Predicate isATimeOfDay
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:Token Time td3:_) -> do
+        from <- intersect td1 td3
+        to <- intersect td2 td3
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleWeekend :: Rule
+ruleWeekend = Rule
+  { name = "week-end"
+  , pattern =
+    [ regex "week[ -]?end|fine ?settimana|we"
+    ]
+  , prod = \_ -> do
+      from <- intersect (dayOfWeek 5) (hour False 18)
+      to <- intersect (dayOfWeek 1) (hour False 0)
+      tt $ interval TTime.Open (from, to)
+  }
+
+ruleIlWeekendDelTime :: Rule
+ruleIlWeekendDelTime = Rule
+  { name = "il week-end del <time>"
+  , pattern =
+    [ regex "il (week[ -]?end|fine ?settimana|we) del"
+    , Predicate $ isGrainOfTime TG.Day
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> do
+        from1 <- intersect (cycleNthAfter False TG.Week 0 td) (dayOfWeek 5)
+        from <- intersect from1 (hour False 18)
+        to1 <- intersect (cycleNthAfter False TG.Week 1 td) (dayOfWeek 1)
+        to <- intersect to1 (hour False 0)
+        tt $ interval TTime.Open (from, to)
+      _ -> Nothing
+  }
+
+ruleEomendOfMonth :: Rule
+ruleEomendOfMonth = Rule
+  { name = "EOM|End of month"
+  , pattern =
+    [ regex "fine del mese"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Month 1
+  }
+
+ruleCommemorazioneDeiDefunti :: Rule
+ruleCommemorazioneDeiDefunti = Rule
+  { name = "commemorazione dei defunti"
+  , pattern =
+    [ regex "(((giorno|commemorazione) dei|ai) )?(morti|defunti)"
+    ]
+  , prod = \_ -> tt $ monthDay 11 2
+  }
+
+ruleImmacolataConcezione :: Rule
+ruleImmacolataConcezione = Rule
+  { name = "immacolata concezione"
+  , pattern =
+    [ regex "(all')?immacolata( concezione)?"
+    ]
+  , prod = \_ -> tt $ monthDay 12 8
+  }
+
+ruleDimTimePartofday :: Rule
+ruleDimTimePartofday = Rule
+  { name = "<dim time> <part-of-day>"
+  , pattern =
+    [ dimension Time
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleTraIlDatetimeEIlDatetimeInterval :: Rule
+ruleTraIlDatetimeEIlDatetimeInterval = Rule
+  { name = "tra il <datetime> e il <datetime> (interval)"
+  , pattern =
+    [ regex "tra( il| l')?"
+    , Predicate isATimeOfDay
+    , regex "e( il| l')?"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNthTimeAfterTime2 :: Rule
+ruleNthTimeAfterTime2 = Rule
+  { name = "nth <time> after <time>"
+  , pattern =
+    [ regex "il|l'"
+    , dimension Ordinal
+    , dimension Time
+    , regex "dopo"
+    , 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
+  }
+
+ruleDopoLeTimeofday :: Rule
+ruleDopoLeTimeofday = Rule
+  { name = "dopo le <time-of-day>"
+  , pattern =
+    [ regex "dopo( l['ea'])?|dal(l['ea'])?"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt . withDirection TTime.After $ notLatent td
+      _ -> Nothing
+  }
+
+ruleDopoTime :: Rule
+ruleDopoTime = Rule
+  { name = "dopo <time>"
+  , pattern =
+    [ regex "dopo|dal?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ withDirection TTime.After td
+      _ -> Nothing
+  }
+
+ruleDalInt :: Rule
+ruleDalInt = Rule
+  { name = "dal <integer"
+  , pattern =
+    [ regex "dal"
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        v <- getIntValue token
+        tt . withDirection TTime.After $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleTimeEntroLeTime :: Rule
+ruleTimeEntroLeTime = Rule
+  { name = "<time> entro le <time>"
+  , pattern =
+    [ dimension Time
+    , regex "entro( l[e'])?|prima d(i|ell['ea])"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time . withDirection TTime.Before <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleEntroTime :: Rule
+ruleEntroTime = Rule
+  { name = "entro <time>"
+  , pattern =
+    [ regex "entro( la)?|prima d(i|ella)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ withDirection TTime.Before td
+      _ -> Nothing
+  }
+
+ruleEntroIlInt :: Rule
+ruleEntroIlInt = Rule
+  { name = "entro il <integer>"
+  , pattern =
+    [ regex "entro il|prima del"
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        v <- getIntValue token
+        tt . withDirection TTime.Before $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleNextTime :: Rule
+ruleNextTime = Rule
+  { name = "next <time>"
+  , pattern =
+    [ regex "prossim[ao]"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 1 False td
+      _ -> Nothing
+  }
+
+ruleNthTimeOfTime3 :: Rule
+ruleNthTimeOfTime3 = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ regex "il|l'"
+    , dimension Ordinal
+    , dimension Time
+    , regex "di|del(l[oa'])|in"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token Time td1:_:Token Time td2:_) -> Token Time .
+         predNth (TOrdinal.value od - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleIlCycleDopoTime :: Rule
+ruleIlCycleDopoTime = Rule
+  { name = "il <cycle> dopo <time>"
+  , pattern =
+    [ regex "l[a']|il|[nd]el"
+    , dimension TimeGrain
+    , regex "dopo"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain 1 td
+      _ -> Nothing
+  }
+
+ruleYyyymmdd :: Rule
+ruleYyyymmdd = Rule
+  { name = "yyyy-mm-dd"
+  , pattern =
+    [ regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (yy:mm:dd:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleNextNCycle :: Rule
+ruleNextNCycle = Rule
+  { name = "next n <cycle>"
+  , pattern =
+    [ regex "((([nd]e)?i|([nd]el)?le) )?prossim[ei]"
+    , Predicate $ isIntegerBetween 2 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleMorning :: Rule
+ruleMorning = Rule
+  { name = "morning"
+  , pattern =
+    [ regex "mattin(ata|[aoe])"
+    ]
+  , prod = \_ ->
+      let from = hour False 4
+          to = hour False 12
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleTheDayofmonth :: Rule
+ruleTheDayofmonth = Rule
+  { name = "il <day-of-month>"
+  , pattern =
+    [ regex "il|l'"
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        v <- getIntValue token
+        tt $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleThisPartofday :: Rule
+ruleThisPartofday = Rule
+  { name = "this <part-of-day>"
+  , pattern =
+    [ regex "(que)?st[oa]|i[nl]|(al|nel)(la)?|la"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        Token Time . partOfDay <$> intersect (cycleNth TG.Day 0) td
+      _ -> Nothing
+  }
+
+ruleThisCycle :: Rule
+ruleThisCycle = Rule
+  { name = "this <cycle>"
+  , pattern =
+    [ regex "(in )?quest['oa]|per (il|l['a])"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 0
+      _ -> Nothing
+  }
+
+ruleThisTime :: Rule
+ruleThisTime = Rule
+  { name = "this <time>"
+  , pattern =
+    [ regex "quest[oaie']"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleTwoTimeTokensSeparatedByDi :: Rule
+ruleTwoTimeTokensSeparatedByDi = Rule
+  { name = "two time tokens separated by `di`"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "di"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleTimeofdayCirca :: Rule
+ruleTimeofdayCirca = Rule
+  { name = "<time-of-day> circa"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "circa"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleYearLatent :: Rule
+ruleYearLatent = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween (- 10000) (-1)
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ year n
+      _ -> Nothing
+  }
+
+ruleYesterday :: Rule
+ruleYesterday = Rule
+  { name = "yesterday"
+  , pattern =
+    [ regex "ieri"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 1
+  }
+
+ruleSeason2 :: Rule
+ruleSeason2 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "(in )?autunno"
+    ]
+  , prod = \_ ->
+      let from = monthDay 9 23
+          to = monthDay 12 21
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleCircaPerLeTimeofday :: Rule
+ruleCircaPerLeTimeofday = Rule
+  { name = "circa per le <time-of-day>"
+  , pattern =
+    [ regex "(circa )?per( le)?|circa( alle)?|verso( le)?"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleVersoPartOfDay :: Rule
+ruleVersoPartOfDay = Rule
+  { name = "verso <part-of-day>"
+  , pattern =
+    [ regex "verso( la| il)?"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleChristmas :: Rule
+ruleChristmas = Rule
+  { name = "christmas"
+  , pattern =
+    [ regex "((il )?giorno di )?natale"
+    ]
+  , prod = \_ -> tt $ monthDay 12 25
+  }
+
+ruleNight :: Rule
+ruleNight = Rule
+  { name = "night"
+  , pattern =
+    [ regex "nott(e|ata)"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 1
+          td2 = interval TTime.Open (hour False 0, hour False 4)
+      in Token Time . partOfDay . mkLatent <$> intersect td1 td2
+  }
+
+ruleOgnissanti :: Rule
+ruleOgnissanti = Rule
+  { name = "ognissanti"
+  , pattern =
+    [ regex "(tutti i |ognis|festa dei |([ia]l )?giorno dei )santi"
+    ]
+  , prod = \_ -> tt $ monthDay 11 1
+  }
+
+ruleIntegerDelPartOfDay :: Rule
+ruleIntegerDelPartOfDay = Rule
+  { name = "<integer 0 12> del <part of day>"
+  , pattern =
+    [ Predicate $ isIntegerBetween 0 12
+    , regex "d(i|el(la)?) (pomeriggio|(sta)?(sera|notte))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral (NumeralData {TNumeral.value = v}):_) ->
+        tt $ hour False (12 + floor v)
+      _ -> Nothing
+  }
+
+ruleOrdinalCycleOfTime :: Rule
+ruleOrdinalCycleOfTime = Rule
+  { name = "<ordinal> <cycle> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "di|del(l[a'])?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleGliNUltimiCycle :: Rule
+ruleGliNUltimiCycle = Rule
+  { name = "gli <n> ultimi <cycle>"
+  , pattern =
+    [ regex "([nd]e)?i|([nd]el)?le"
+    , Predicate $ isIntegerBetween 2 9999
+    , regex "(scors|ultim)[ei]"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain (- v)
+      _ -> Nothing
+  }
+
+ruleNamedmonth5 :: Rule
+ruleNamedmonth5 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "maggio|magg?\\.?"
+    ]
+  , prod = \_ -> tt $ month 5
+  }
+
+ruleNamedday7 :: Rule
+ruleNamedday7 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "domenica|dom\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 7
+  }
+
+ruleDalleTimeofdayAlleTimeofdayInterval :: Rule
+ruleDalleTimeofdayAlleTimeofdayInterval = Rule
+  { name = "dalle <time-of-day> alle <time-of-day> (interval)"
+  , pattern =
+    [ regex "da(ll[ae'])?"
+    , Predicate isATimeOfDay
+    , regex "\\-|([fs]ino )?a(ll[ae'])?"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth10 :: Rule
+ruleNamedmonth10 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "ottobre|ott\\.?"
+    ]
+  , prod = \_ -> tt $ month 10
+  }
+
+ruleEntroLeTimeofday :: Rule
+ruleEntroLeTimeofday = Rule
+  { name = "entro le <time-of-day>"
+  , pattern =
+    [ regex "entro( l[ea'])?|prima d(i|ell['e])"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt . withDirection TTime.Before $ notLatent td
+      _ -> Nothing
+  }
+
+ruleHalloweenDay :: Rule
+ruleHalloweenDay = Rule
+  { name = "halloween day"
+  , pattern =
+    [ regex "hall?owe?en"
+    ]
+  , prod = \_ -> tt $ monthDay 10 31
+  }
+
+ruleLastDayofweekOfTime :: Rule
+ruleLastDayofweekOfTime = Rule
+  { name = "last <day-of-week> of <time>"
+  , pattern =
+    [ regex "(([nd]el)?l')?ultim[oa]"
+    , Predicate isADayOfWeek
+    , regex "di|del(l[a'])?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ predLastOf td1 td2
+      _ -> Nothing
+  }
+
+ruleNameddayDayofmonth :: Rule
+ruleNameddayDayofmonth = Rule
+  { name = "<named-day> <day-of-month>"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleLastDayofweekOfTime2 :: Rule
+ruleLastDayofweekOfTime2 = Rule
+  { name = "last <day-of-week> of <time>"
+  , pattern =
+    [ regex "(l')ultim[oa]"
+    , Predicate isADayOfWeek
+    , regex "di"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ predLastOf td1 td2
+      _ -> Nothing
+  }
+
+ruleDdmmyyyy :: Rule
+ruleDdmmyyyy = Rule
+  { name = "dd[/-]mm[/-]yyyy"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[/-](0?[1-9]|1[0-2])[/-](\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
+        y <- parseInt m3
+        m <- parseInt m2
+        d <- parseInt m1
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleTwoTimeTokensInARow :: Rule
+ruleTwoTimeTokensInARow = Rule
+  { name = "two time tokens in a row"
+  , pattern =
+    [ Predicate isNotLatent
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNamedmonth11 :: Rule
+ruleNamedmonth11 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "novembre|nov\\.?"
+    ]
+  , prod = \_ -> tt $ month 11
+  }
+
+ruleIleNCyclePassatipassate :: Rule
+ruleIleNCyclePassatipassate = Rule
+  { name = "i|le n <cycle> passati|passate"
+  , pattern =
+    [ regex "([nd]e)?i|([nd]el)?le"
+    , Predicate $ isIntegerBetween 2 9999
+    , dimension TimeGrain
+    , regex "(scors|passat)[ie]"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain (- v)
+      _ -> Nothing
+  }
+
+ruleNamedday3 :: Rule
+ruleNamedday3 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "mercoled(i|\x00ec)|mer\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 3
+  }
+
+ruleEoyendOfYear :: Rule
+ruleEoyendOfYear = Rule
+  { name = "EOY|End of year"
+  , pattern =
+    [ regex "fine dell' ?anno"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Year 1
+  }
+
+ruleTomorrow :: Rule
+ruleTomorrow = Rule
+  { name = "tomorrow"
+  , pattern =
+    [ regex "domani"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 1
+  }
+
+ruleNextNCycle2 :: Rule
+ruleNextNCycle2 = Rule
+  { name = "next n <cycle>"
+  , pattern =
+    [ regex "([nd]e)?i|([nd]el)?le"
+    , Predicate $ isIntegerBetween 2 9999
+    , regex "prossim[ei]"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleProssimiUnitofduration :: Rule
+ruleProssimiUnitofduration = Rule
+  { name = "prossimi <unit-of-duration>"
+  , pattern =
+    [ regex "((([nd]e)?i|([nd]el)?le) )?prossim[ie]"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        let from = cycleNth grain 1
+            to = cycleNth grain 3
+        in tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleMothersDay :: Rule
+ruleMothersDay = Rule
+  { name = "Mother's Day"
+  , pattern =
+    [ regex "festa della mamma"
+    ]
+  , prod = \_ -> tt $ nthDOWOfMonth 1 7 5
+  }
+
+ruleFestaDelLavoro :: Rule
+ruleFestaDelLavoro = Rule
+  { name = "festa del lavoro"
+  , pattern =
+    [ regex "festa del lavoro|(festa|giorno) dei lavoratori"
+    ]
+  , prod = \_ -> tt $ monthDay 5 1
+  }
+
+ruleIntersectByDiDellaDel :: Rule
+ruleIntersectByDiDellaDel = Rule
+  { name = "intersect by \"di\", \"della\", \"del\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "di|del(la)?"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNamedmonth9 :: Rule
+ruleNamedmonth9 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "settembre|sett?\\.?"
+    ]
+  , prod = \_ -> tt $ month 9
+  }
+
+ruleTimezone :: Rule
+ruleTimezone = Rule
+  { name = "<time> timezone"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\b(YEKT|YEKST|YAKT|YAKST|WITA|WIT|WIB|WGT|WGST|WFT|WET|WEST|WAT|WAST|VUT|VLAT|VLAST|VET|UZT|UYT|UYST|UTC|ULAT|TVT|TMT|TLT|TKT|TJT|TFT|TAHT|SST|SRT|SGT|SCT|SBT|SAST|SAMT|RET|PYT|PYST|PWT|PST|PONT|PMST|PMDT|PKT|PHT|PHOT|PGT|PETT|PETST|PET|PDT|OMST|OMSST|NZST|NZDT|NUT|NST|NPT|NOVT|NOVST|NFT|NDT|NCT|MYT|MVT|MUT|MST|MSK|MSD|MMT|MHT|MDT|MAWT|MART|MAGT|MAGST|LINT|LHST|LHDT|KUYT|KST|KRAT|KRAST|KGT|JST|IST|IRST|IRKT|IRKST|IRDT|IOT|IDT|ICT|HOVT|HKT|GYT|GST|GMT|GILT|GFT|GET|GAMT|GALT|FNT|FKT|FKST|FJT|FJST|EST|EGT|EGST|EET|EEST|EDT|ECT|EAT|EAST|EASST|DAVT|ChST|CXT|CVT|CST|COT|CLT|CLST|CKT|CHAST|CHADT|CET|CEST|CDT|CCT|CAT|CAST|BTT|BST|BRT|BRST|BOT|BNT|AZT|AZST|AZOT|AZOST|AWST|AWDT|AST|ART|AQTT|ANAT|ANAST|AMT|AMST|ALMT|AKST|AKDT|AFT|AEST|AEDT|ADT|ACST|ACDT)\\b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       Token RegexMatch (GroupMatch (tz:_)):
+       _) -> Token Time <$> inTimezone tz td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAfternoon
+  , ruleAtTimeofday
+  , ruleChristmas
+  , ruleChristmasEve
+  , ruleCircaPerLeTimeofday
+  , ruleCommemorazioneDeiDefunti
+  , ruleDalDatetimeAlDatetimeInterval
+  , ruleDalleTimeofdayAlleTimeofdayInterval
+  , ruleDatetimeDatetimeInterval
+  , ruleDayOfMonthSt
+  , ruleDayofmonthNamedmonth
+  , ruleDdddMonthInterval
+  , ruleDdmm
+  , ruleDdmmyyyy
+  , ruleDimTimeAlPartofday
+  , ruleDimTimeDelMattino
+  , ruleDimTimePartofday
+  , ruleDopoLeTimeofday
+  , ruleDurationAgo
+  , ruleEntroIlDuration
+  , ruleEntroLeTimeofday
+  , ruleEomendOfMonth
+  , ruleEoyendOfYear
+  , ruleEpifania
+  , ruleEvening
+  , ruleFerragosto
+  , ruleFestaDelLavoro
+  , ruleFestaDelPap
+  , ruleFestaDellaLiberazione
+  , ruleFestaDellaRepubblica
+  , ruleFinoAlDatetimeInterval
+  , ruleGliNUltimiCycle
+  , ruleGliUltimiNCycle
+  , ruleHalloweenDay
+  , ruleHhRelativeminutesDelPomeriggiotimeofday
+  , ruleHhRelativeminutesDelPomeriggiotimeofday2
+  , ruleHhhmmTimeofday
+  , ruleHhhmmTimeofday2
+  , ruleHhmmMilitaryTimeofday
+  , ruleHourofdayAndRelativeMinutes
+  , ruleHourofdayIntegerAsRelativeMinutes
+  , ruleHourofdayMinusIntegerAsRelativeMinutes
+  , ruleIlCycleDopoTime
+  , ruleIlDayofmonthDeNamedmonth
+  , ruleIlTime
+  , ruleIleNCyclePassatipassate
+  , ruleInDuration
+  , ruleInNamedmonth
+  , ruleInThePartofdayOfDimTime
+  , ruleInafterDuration
+  , ruleIntegerDelPartOfDay
+  , ruleIntegerLatentTimeofday
+  , ruleIntersectByDiDellaDel
+  , ruleLastCycleOfTime
+  , ruleLastDayofweekOfTime
+  , ruleLastDayofweekOfTime2
+  , ruleLastTime
+  , ruleLeIdiDiNamedmonth
+  , ruleLunch
+  , ruleMidnight
+  , ruleMorning
+  , ruleMothersDay
+  , ruleNamedday
+  , ruleNamedday2
+  , ruleNamedday3
+  , ruleNamedday4
+  , ruleNamedday5
+  , ruleNamedday6
+  , ruleNamedday7
+  , ruleNameddayDayofmonth
+  , ruleNamedmonth
+  , ruleNamedmonth10
+  , ruleNamedmonth11
+  , ruleNamedmonth12
+  , ruleNamedmonth2
+  , ruleNamedmonth3
+  , ruleNamedmonth4
+  , ruleNamedmonth5
+  , ruleNamedmonth6
+  , ruleNamedmonth7
+  , ruleNamedmonth8
+  , ruleNamedmonth9
+  , ruleNewYearsDay
+  , ruleNewYearsEve
+  , ruleNextCycle
+  , ruleNextNCycle
+  , ruleNextNCycle2
+  , ruleNextTime
+  , ruleNextTime2
+  , ruleNight
+  , ruleNoon
+  , ruleNthTimeAfterTime
+  , ruleNthTimeAfterTime2
+  , ruleNthTimeOfTime
+  , ruleNthTimeOfTime2
+  , ruleNthTimeOfTime3
+  , ruleOgnissanti
+  , ruleOrdinalCycleOfTime
+  , rulePartofdayOfDimTime
+  , ruleProssimiUnitofduration
+  , ruleRelativeMinutesToIntegerAsHourofday
+  , ruleRightNow
+  , ruleSantoStefano
+  , ruleSeason
+  , ruleSeason2
+  , ruleSeason3
+  , ruleSeason4
+  , ruleStamattina
+  , ruleStanotte
+  , ruleStasera
+  , ruleTheCycleLast
+  , ruleTheCycleNext
+  , ruleTheCycleOfTime
+  , ruleTheDayAfterTomorrow
+  , ruleTheDayBeforeYesterday
+  , ruleTheDayofmonth
+  , ruleTheLastCycle
+  , ruleTheNthTimeOfTime
+  , ruleTheOrdinalCycleOfTime
+  , ruleThisCycle
+  , ruleThisDayofweek
+  , ruleThisPartofday
+  , ruleThisTime
+  , ruleTimeLast
+  , ruleTimeNotte
+  , ruleTimeNotte2
+  , ruleTimeofdayCirca
+  , ruleTimeofdayOra
+  , ruleTimeofdayPrecise
+  , ruleTimeofdayTimeofdayDayofmonthInterval
+  , ruleTomorrow
+  , ruleTraIlDatetimeEIlDatetimeInterval
+  , ruleTwoTimeTokensInARow
+  , ruleTwoTimeTokensSeparatedByDi
+  , ruleUna
+  , ruleValentinesDay
+  , ruleWeekend
+  , ruleYearLatent
+  , ruleYearLatent2
+  , ruleYearNotLatent
+  , ruleYesterday
+  , ruleYyyymmdd
+  , ruleHhThreeQuarterDelPomeriggiotimeofday
+  , ruleHhThreeQuarterDelPomeriggiotimeofday2
+  , ruleHourofdayMinusThreeQuarter
+  , ruleThreeQuarterToIntegerAsHourofday
+  , ruleHourofdayAndThreeQuarter
+  , ruleHourofdayThreeQuarters
+  , ruleHhQuartDelPomeriggiotimeofday
+  , ruleHhQuartDelPomeriggiotimeofday2
+  , ruleHourofdayMinusQuart
+  , ruleQuartAsHourofday
+  , ruleHourofdayAndAQuart
+  , ruleHourofdayQuart
+  , ruleHhHalfDelPomeriggiotimeofday
+  , ruleHhHalfDelPomeriggiotimeofday2
+  , ruleHourofdayMinusHalf
+  , ruleHalfToIntegerAsHourofday
+  , ruleHourofdayAndHalf
+  , ruleHourofdayHalf
+  , ruleHhIntegerminutesDelPomeriggiotimeofday
+  , ruleHhIntegerminutesDelPomeriggiotimeofday2
+  , ruleHourofdayMinusIntegerMinutes
+  , ruleMinutesToIntegerAsHourofday
+  , ruleHourofdayAndIntegerMinutes
+  , ruleHourofdayIntegerMinutes
+  , ruleTimezone
+  , ruleTimeEntroLeTime
+  , ruleImmacolataConcezione
+  , ruleOrdinalQuarter
+  , ruleTheOrdinalQuarter
+  , ruleCycleOrdinalQuarterYear
+  , ruleCycleTheOrdinalTime
+  , ruleCycleNext
+  , ruleDomattina
+  , ruleVersoPartOfDay
+  , ruleEntroIlInt
+  , ruleEntroTime
+  , ruleDalInt
+  , ruleDopoTime
+  , ruleToday
+  , ruleDalIntAlInt
+  , ruleTraIlIntEIlInt
+  , ruleIlWeekendDelTime
+  ]
diff --git a/Duckling/Time/KO/Corpus.hs b/Duckling/Time/KO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/KO/Corpus.hs
@@ -0,0 +1,548 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.KO.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Time.Corpus
+import Duckling.Time.Types hiding (Month)
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Testing.Types hiding (examples)
+
+corpus :: Corpus
+corpus = (testContext {lang = KO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (datetime (2013, 2, 12, 4, 30, 0) Second)
+             [ "방금"
+             , "지금"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "오늘"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "어제"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "내일"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "월요일"
+             , "이번주 월요일"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "2월18일 월요일"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "화요일"
+             , "19일 화요일"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "목요일"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "금요일"
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "토요일"
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "일요일"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "3월 1일"
+             ]
+  , examples (datetime (2013, 3, 3, 0, 0, 0) Day)
+             [ "3월 3일"
+             ]
+  , examples (datetime (2015, 3, 3, 0, 0, 0) Day)
+             [ "2015년 3월 3일"
+             , "이천십오년 삼월 삼일"
+             , "2015/3/3"
+             , "2015-3-3"
+             , "2015-03-03"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "15일에"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "2월 15일"
+             , "2/15"
+             ]
+  , examples (datetime (2013, 8, 8, 0, 0, 0) Day)
+             [ "8월 8일"
+             ]
+  , examples (datetime (2014, 10, 1, 0, 0, 0) Month)
+             [ "2014년 10월"
+             ]
+  , examples (datetime (1974, 10, 31, 0, 0, 0) Day)
+             [ "1974/10/31"
+             , "74/10/31"
+             ]
+  , examples (datetime (2015, 4, 14, 0, 0, 0) Day)
+             [ "2015년 4월 14일"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "다음주 화요일"
+             , "다음 화요일"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Month)
+             [ "다음 3월"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "2월 18일 월요일"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Week)
+             [ "이번주"
+             , "금주"
+             ]
+  , examples (datetime (2013, 2, 4, 0, 0, 0) Week)
+             [ "저번주"
+             , "전주"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Week)
+             [ "다음주"
+             , "오는주"
+             ]
+  , examples (datetime (2013, 1, 1, 0, 0, 0) Month)
+             [ "저번달"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Month)
+             [ "다음달"
+             ]
+  , examples (datetime (2013, 1, 1, 0, 0, 0) Quarter)
+             [ "이번분기"
+             ]
+  , examples (datetime (2013, 4, 1, 0, 0, 0) Quarter)
+             [ "다음분기"
+             ]
+  , examples (datetime (2013, 7, 1, 0, 0, 0) Quarter)
+             [ "삼분기"
+             ]
+  , examples (datetime (2018, 10, 1, 0, 0, 0) Quarter)
+             [ "2018년 4분기"
+             ]
+  , examples (datetime (2012, 1, 1, 0, 0, 0) Year)
+             [ "작년"
+             , "12년"
+             ]
+  , examples (datetime (2013, 1, 1, 0, 0, 0) Year)
+             [ "올해"
+             ]
+  , examples (datetime (2014, 1, 1, 0, 0, 0) Year)
+             [ "내년"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "저번주 일요일"
+             , "지난주 일요일"
+             , "지난 일요일"
+             , "저번 일요일"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "저번주 화요일"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "다음주 화요일"
+             ]
+  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
+             [ "다음주 수요일"
+             ]
+  , examples (datetime (2013, 2, 22, 0, 0, 0) Day)
+             [ "다음주 금요일"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "이번주 월요일"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "이번주 화요일"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "이번주 수요일"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "내일모래"
+             , "모래"
+             ]
+  , examples (datetime (2013, 2, 13, 17, 0, 0) Hour)
+             [ "내일 저녁다섯시"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "엊그제"
+             , "그제"
+             ]
+  , examples (datetime (2013, 2, 10, 8, 0, 0) Hour)
+             [ "엊그제 아침8시"
+             , "엊그제 오전8시"
+             ]
+  , examples (datetime (2013, 3, 25, 0, 0, 0) Day)
+             [ "3월 마지막 월요일"
+             ]
+  , examples (datetime (2014, 3, 30, 0, 0, 0) Day)
+             [ "2014년 3월 마지막일요일"
+             ]
+  , examples (datetime (2013, 10, 3, 0, 0, 0) Day)
+             [ "10월 3일"
+             ]
+  , examples (datetime (2014, 10, 6, 0, 0, 0) Week)
+             [ "2014년 10월 첫번째주"
+             ]
+  , examples (datetime (2015, 10, 31, 0, 0, 0) Day)
+             [ "2015년 10월 마지막날"
+             ]
+  , examples (datetime (2014, 9, 22, 0, 0, 0) Week)
+             [ "2014년 9월 마지막주"
+             ]
+  , examples (datetime (2013, 10, 1, 0, 0, 0) Day)
+             [ "10월 첫째화요일"
+             ]
+  , examples (datetime (2014, 9, 16, 0, 0, 0) Day)
+             [ "2014년 9월 셋째화요일"
+             , "2014년 9월 세번째화요일"
+             ]
+  , examples (datetime (2014, 10, 1, 0, 0, 0) Day)
+             [ "2014년 10월 첫번째 수요일"
+             , "2014년 10월 첫째 수요일"
+             ]
+  , examples (datetime (2014, 10, 8, 0, 0, 0) Day)
+             [ "2014년 10월 두번째 수요일"
+             , "2014년 10월 둘째 수요일"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 0, 0) Hour)
+             [ "아침 3시"
+             , "오전 세시"
+             , "3AM"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 18, 0) Minute)
+             [ "3:18am"
+             , "3:18a"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "오후 세시"
+             , "3PM"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 20, 0) Minute)
+             [ "오후세시이십분"
+             , "3:20p"
+             , "15:20"
+             , "3:20pm"
+             , "3:20PM"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 30, 0) Minute)
+             [ "오후세시반"
+             , "15:30"
+             , "3:30pm"
+             , "3:30PM"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 45, 0) Minute)
+             [ "네시십오분전"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 30, 0) Minute)
+             [ "3:30"
+             , "세시반"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 23, 24) Second)
+             [ "15:23:24"
+             , "세시이십삼분이십사초"
+             ]
+  , examples (datetime (2013, 2, 12, 20, 0, 0) Hour)
+             [ "오늘밤 8시"
+             , "저녁 8시"
+             ]
+  , examples (datetime (2013, 9, 20, 19, 30, 0) Minute)
+             [ "9월 20일 저녁 7시 30분"
+             ]
+  , examples (datetime (2013, 2, 16, 9, 0, 0) Hour)
+             [ "토요일 9시"
+             ]
+  , examples (datetime (2014, 7, 18, 19, 0, 0) Hour)
+             [ "2014년 7월 18일 금요일 오후 7시"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 30, 1) Second)
+             [ "1초안에"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 31, 0) Second)
+             [ "일분안에"
+             , "일분내에"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 32, 0) Second)
+             [ "이분안에"
+             , "이분내에"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Minute)
+             [ "한시간안에"
+             , "한시간내"
+             ]
+  , examples (datetime (2013, 2, 12, 6, 0, 0) Second)
+             [ "한시간반안"
+             , "한시간반내"
+             ]
+  , examples (datetime (2013, 2, 12, 7, 0, 0) Second)
+             [ "두시간반안"
+             , "두시간반내"
+             ]
+  , examples (datetime (2013, 2, 12, 7, 30, 0) Minute)
+             [ "몇시간안"
+             , "몇시간내"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 12, 7, 30, 0) Minute)
+             [ "몇시간후"
+             , "몇시간이후"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 30, 0) Minute)
+             [ "24시간안에"
+             , "24시간내"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 0, 0) Hour)
+             [ "하루안에"
+             , "하루내"
+             ]
+  , examples (datetime (2016, 2, 1, 0, 0, 0) Month)
+             [ "삼년안에"
+             , "삼년내"
+             ]
+  , examples (datetime (2013, 2, 19, 4, 0, 0) Hour)
+             [ "7일안에"
+             , "7일내"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "1주일안에"
+             , "1주일내"
+             ]
+  , examples (datetime (2013, 2, 12, 6, 0, 0) Second)
+             [ "약 한시간반 안에"
+             ]
+  , examples (datetime (2013, 2, 5, 4, 0, 0) Hour)
+             [ "7일전"
+             ]
+  , examples (datetime (2013, 1, 29, 4, 0, 0) Hour)
+             [ "14일전"
+             , "14일전에"
+             ]
+  , examples (datetime (2013, 1, 22, 0, 0, 0) Day)
+             [ "3주전"
+             , "3주이전"
+             ]
+  , examples (datetime (2011, 2, 1, 0, 0, 0) Month)
+             [ "2년전"
+             , "2년이전"
+             ]
+  , examples (datetime (1954, 1, 1, 0, 0, 0) Year)
+             [ "1954"
+             ]
+  , examples (datetimeInterval ((2013, 6, 21, 0, 0, 0), (2013, 9, 24, 0, 0, 0)) Day)
+             [ "이번여름"
+             ]
+  , examples (datetimeInterval ((2012, 12, 21, 0, 0, 0), (2013, 3, 21, 0, 0, 0)) Day)
+             [ "이번겨울"
+             ]
+  , examples (datetime (2013, 12, 25, 0, 0, 0) Day)
+             [ "크리스마스"
+             ]
+  , examples (datetime (2013, 12, 24, 0, 0, 0) Day)
+             [ "크리스마스이브"
+             ]
+  , examples (datetime (2014, 1, 1, 0, 0, 0) Day)
+             [ "신정"
+             , "설날"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "삼일절"
+             ]
+  , examples (datetime (2013, 5, 5, 0, 0, 0) Day)
+             [ "어린이날"
+             ]
+  , examples (datetime (2013, 6, 6, 0, 0, 0) Day)
+             [ "현충일"
+             ]
+  , examples (datetime (2013, 6, 17, 0, 0, 0) Day)
+             [ "제헌절"
+             ]
+  , examples (datetime (2013, 8, 15, 0, 0, 0) Day)
+             [ "광복절"
+             ]
+  , examples (datetime (2013, 10, 3, 0, 0, 0) Day)
+             [ "개천절"
+             ]
+  , examples (datetime (2013, 10, 9, 0, 0, 0) Day)
+             [ "한글날"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 18, 0, 0), (2013, 2, 13, 0, 0, 0)) Hour)
+             [ "오늘저녁"
+             , "오늘밤"
+             ]
+  , examples (datetimeInterval ((2013, 2, 8, 18, 0, 0), (2013, 2, 11, 0, 0, 0)) Hour)
+             [ "저번주말"
+             , "지난주말"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 18, 0, 0), (2013, 2, 14, 0, 0, 0)) Hour)
+             [ "내일저녁"
+             , "내일밤"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 12, 0, 0), (2013, 2, 13, 14, 0, 0)) Hour)
+             [ "내일점심"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 18, 0, 0), (2013, 2, 12, 0, 0, 0)) Hour)
+             [ "어제저녁"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 18, 0, 0), (2013, 2, 18, 0, 0, 0)) Hour)
+             [ "이번주말"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 4, 0, 0), (2013, 2, 18, 12, 0, 0)) Hour)
+             [ "월요일 아침"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 4, 0, 0), (2013, 2, 15, 12, 0, 0)) Hour)
+             [ "2월 15일 아침"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 29, 58), (2013, 2, 12, 4, 30, 0)) Second)
+             [ "지난 2초"
+             , "지난 이초"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 1), (2013, 2, 12, 4, 30, 4)) Second)
+             [ --"다음 3초" t14899520
+               --"다음 삼초" t14899520
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 28, 0), (2013, 2, 12, 4, 30, 0)) Minute)
+             [ "지난 2분"
+             , "지난 이분"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 31, 0), (2013, 2, 12, 4, 34, 0)) Minute)
+             [ "다음 3분"
+             , "다음 삼분"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 3, 0, 0), (2013, 2, 12, 4, 0, 0)) Hour)
+             [ "지난 1시간"
+             , "지난 한시간"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 4, 0, 0), (2013, 2, 12, 4, 0, 0)) Hour)
+             [ "지난 24시간"
+             , "지난 스물네시간"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 5, 0, 0), (2013, 2, 12, 8, 0, 0)) Hour)
+             [ "다음 3시간"
+             ]
+  , examples (datetimeInterval ((2013, 2, 10, 0, 0, 0), (2013, 2, 12, 0, 0, 0)) Day)
+             [ --"지난 2일" t14899546
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
+             [ --"다음 3일" t14899546
+             --, "다음 몇일" t14899546
+             ]
+  , examples (datetimeInterval ((2013, 1, 28, 0, 0, 0), (2013, 2, 11, 0, 0, 0)) Week)
+             [ "지난 2주"
+             , "지난 이주"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 0, 0, 0), (2013, 3, 11, 0, 0, 0)) Week)
+             [ "다음 3주"
+             , "다음 삼주"
+             ]
+  , examples (datetimeInterval ((2012, 12, 1, 0, 0, 0), (2013, 2, 1, 0, 0, 0)) Month)
+             [ "지난 2달"
+             , "지난 두달"
+             ]
+  , examples (datetimeInterval ((2013, 3, 1, 0, 0, 0), (2013, 6, 1, 0, 0, 0)) Month)
+             [ "다음 3달"
+             , "다음 세달"
+             ]
+  , examples (datetimeInterval ((2011, 1, 1, 0, 0, 0), (2013, 1, 1, 0, 0, 0)) Year)
+             [ --"지난 2년" t14899546
+             --, "지난 이년" t14899546
+             ]
+  , examples (datetimeInterval ((2014, 1, 1, 0, 0, 0), (2017, 1, 1, 0, 0, 0)) Year)
+             [ "다음 3년"
+             , "다음 삼년"
+             ]
+  , examples (datetimeInterval ((2013, 7, 13, 0, 0, 0), (2013, 7, 16, 0, 0, 0)) Day)
+             [ "7월 13일 - 15일"
+             , "7월 13일 부터 15일"
+             ]
+  , examples (datetimeInterval ((2013, 8, 8, 0, 0, 0), (2013, 8, 14, 0, 0, 0)) Day)
+             [ "8월 8일 - 8월 13일"
+             , "8월 8일부터 8월 13일"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 9, 30, 0), (2013, 2, 12, 11, 1, 0)) Minute)
+             [ "9:30 부터 11:00"
+             , "9:30 ~ 11:00"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 30, 0), (2013, 2, 14, 11, 1, 0)) Minute)
+             [ "목요일 9:30 부터 11:00"
+             , "목요일 9:30 ~ 11:00"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 0, 0), (2013, 2, 14, 12, 0, 0)) Hour)
+             [ "목요일 오전9시 부터 오전11시"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 11, 30, 0), (2013, 2, 12, 13, 31, 0)) Minute)
+             [ "11:30-1:30"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 26, 0, 0, 0)) Second)
+             [ "2주 이내에"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 12, 14, 0, 0)) Second)
+             [ "오후 2시까지"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 0, 0) Minute)
+             [ "4pm CET"
+             ]
+  , examples (datetime (2013, 2, 14, 6, 0, 0) Minute)
+             [ "목요일 8:00 GMT"
+             ]
+  , examples (datetime (2013, 2, 12, 14, 0, 0) Hour)
+             [ "오늘 오후두시에"
+             , "오후두시에"
+             ]
+  , examples (datetime (2013, 4, 25, 16, 0, 0) Hour)
+             [ "4/25 오후4시에"
+             ]
+  , examples (datetime (2013, 2, 13, 15, 0, 0) Hour)
+             [ "내일 3pm"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 12, 14, 0, 0) Hour)
+             [ "오후2시 이후"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 17, 4, 0, 0) Hour)
+             [ "5일 후"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 11, 0, 0) Hour)
+             [ "오전11시 전"
+             , "오전11시 이전"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 12, 0, 0), (2013, 2, 12, 19, 0, 0)) Hour)
+             [ "오후에"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 45, 0) Second)
+             [ "15분안"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 12, 12, 0, 0) Hour)
+             [ "점심이후"
+             ]
+  , examples (datetime (2013, 2, 12, 10, 30, 0) Minute)
+             [ "10:30"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 0, 0), (2013, 2, 12, 12, 0, 0)) Hour)
+             [ "이번아침"
+             ]
+  , examples (datetime (2013, 2, 12, 12, 0, 0) Hour)
+             [ "오후12시"
+             , "정오"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Hour)
+             [ "오전12시"
+             , "자정"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Month)
+             [ "3월"
+             , "3월에"
+             ]
+  ]
diff --git a/Duckling/Time/KO/Rules.hs b/Duckling/Time/KO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/KO/Rules.hs
@@ -0,0 +1,1358 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.KO.Rules
+  ( rules ) where
+
+import Control.Monad (liftM2)
+import qualified Data.Text as Text
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Types (OrdinalData (..))
+import qualified Duckling.Ordinal.Types as TOrdinal
+import Duckling.Regex.Types
+import Duckling.Time.Helpers
+import Duckling.Time.Types (TimeData (..))
+import qualified Duckling.Time.Types as TTime
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleNamedday :: Rule
+ruleNamedday = Rule
+  { name = "<named-day>에"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex "\xc5d0"
+    ]
+  , prod = \tokens -> case tokens of
+      (x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleLiberationDay :: Rule
+ruleLiberationDay = Rule
+  { name = "Liberation Day"
+  , pattern =
+    [ regex "\xad11\xbcf5\xc808"
+    ]
+  , prod = \_ -> tt $ monthDay 8 15
+  }
+
+ruleTheDayAfterTomorrow :: Rule
+ruleTheDayAfterTomorrow = Rule
+  { name = "the day after tomorrow - 내일모래"
+  , pattern =
+    [ regex "(\xb0b4\xc77c)?\xbaa8\xb798"
+    ]
+  , prod = \_ ->
+      tt . cycleNthAfter False TG.Day 1 $ cycleNth TG.Day 1
+  }
+
+ruleConstitutionDay :: Rule
+ruleConstitutionDay = Rule
+  { name = "Constitution Day"
+  , pattern =
+    [ regex "\xc81c\xd5cc\xc808"
+    ]
+  , prod = \_ -> tt $ monthDay 6 17
+  }
+
+ruleTimeofday4 :: Rule
+ruleTimeofday4 = Rule
+  { name = "<time-of-day>이전"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(\xc774)?\xc804"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ withDirection TTime.Before td
+      _ -> Nothing
+  }
+
+ruleDay :: Rule
+ruleDay = Rule
+  { name = "day"
+  , pattern =
+    [ Predicate isDOMInteger
+    , regex "\xc77c"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleSinceTimeofday :: Rule
+ruleSinceTimeofday = Rule
+  { name = "since <time-of-day>"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "\xc774\xb798\xb85c"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt . withDirection TTime.After $ predNth (- 1) False td
+      _ -> Nothing
+  }
+
+ruleThisDayofweek :: Rule
+ruleThisDayofweek = Rule
+  { name = "this <day-of-week>"
+  , pattern =
+    [ regex "\xc774\xbc88(\xc8fc)?|\xae08\xc8fc"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleNewYearsDay :: Rule
+ruleNewYearsDay = Rule
+  { name = "New Year's Day"
+  , pattern =
+    [ regex "\xc2e0\xc815|\xc124\xb0a0"
+    ]
+  , prod = \_ -> tt $ monthDay 1 1
+  }
+
+ruleLastTime :: Rule
+ruleLastTime = Rule
+  { name = "last <time>"
+  , pattern =
+    [ regex "\xc804|\xc800\xbc88|\xc9c0\xb09c"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleDatetimeDatetimeInterval :: Rule
+ruleDatetimeDatetimeInterval = Rule
+  { name = "<datetime> - <datetime> (interval)"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "\\-|\\~|\xbd80\xd130"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleInDuration :: Rule
+ruleInDuration = Rule
+  { name = "in <duration>"
+  , pattern =
+    [ dimension Duration
+    , regex "(\xc548|\xb0b4)(\xc5d0)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleNow :: Rule
+ruleNow = Rule
+  { name = "now"
+  , pattern =
+    [ regex "\xbc29\xae08|\xc9c0\xae08|\xbc29\xae08|\xb9c9"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Second 0
+  }
+
+ruleMonth :: Rule
+ruleMonth = Rule
+  { name = "month"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 12
+    , regex "\xc6d4"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt $ month v
+      _ -> Nothing
+  }
+
+ruleSeason4 :: Rule
+ruleSeason4 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "\xbd04"
+    ]
+  , prod = \_ ->
+      let from = monthDay 3 20
+          to = monthDay 6 21
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleYearLatent2 :: Rule
+ruleYearLatent2 = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2101 10000
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ year v
+      _ -> Nothing
+  }
+
+ruleTimeAfterNext :: Rule
+ruleTimeAfterNext = Rule
+  { name = "<time> after next"
+  , pattern =
+    [ dimension Time
+    , regex "after next"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 1 True td
+      _ -> Nothing
+  }
+
+ruleNoon :: Rule
+ruleNoon = Rule
+  { name = "noon"
+  , pattern =
+    [ regex "\xc815\xc624"
+    ]
+  , prod = \_ -> tt $ hour False 12
+  }
+
+ruleToday :: Rule
+ruleToday = Rule
+  { name = "today"
+  , pattern =
+    [ regex "\xc624\xb298|\xb2f9\xc77c|\xae08\xc77c"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 0
+  }
+
+ruleIntegerHourofdayRelativeMinutes :: Rule
+ruleIntegerHourofdayRelativeMinutes = Rule
+  { name = "<integer> (hour-of-day) relative minutes 전"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    , regex "\xbd84\xc804"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> do
+        v <- getIntValue token
+        t <- minutesBefore v td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleHourofdayIntegerAsRelativeMinutes :: Rule
+ruleHourofdayIntegerAsRelativeMinutes = Rule
+  { name = "<hour-of-day> <integer> (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    , regex "\xbd84"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute True hours n
+      _ -> Nothing
+  }
+
+ruleHalfHourofdayRelativeMinutes :: Rule
+ruleHalfHourofdayRelativeMinutes = Rule
+  { name = "half (hour-of-day) relative minutes 전"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "\xbc18\xc804"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> Token Time <$> minutesBefore 30 td
+      _ -> Nothing
+  }
+
+ruleHourofdayHalfAsRelativeMinutes :: Rule
+ruleHourofdayHalfAsRelativeMinutes = Rule
+  { name = "<hour-of-day> half (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "\xbc18"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> tt $ hourMinute True hours 30
+      _ -> Nothing
+  }
+
+ruleSeconds :: Rule
+ruleSeconds = Rule
+  { name = "seconds"
+  , pattern =
+    [ Predicate $ isIntegerBetween 0 59
+    , regex "\xcd08"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt $ second v
+      _ -> Nothing
+  }
+
+ruleTimeOrdinalCycle :: Rule
+ruleTimeOrdinalCycle = Rule
+  { name = "<time> <ordinal> <cycle>"
+  , pattern =
+    [ dimension Time
+    , dimension Ordinal
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:Token Ordinal od:Token TimeGrain grain:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleTheDayBeforeYesterday :: Rule
+ruleTheDayBeforeYesterday = Rule
+  { name = "the day before yesterday - 엊그제"
+  , pattern =
+    [ regex "(\xc5ca)?\xadf8(\xc81c|\xc7ac)"
+    ]
+  , prod = \_ ->
+      tt . cycleNthAfter False TG.Day (-1) $ cycleNth TG.Day (-1)
+  }
+
+ruleDayofweek :: Rule
+ruleDayofweek = Rule
+  { name = "day-of-week"
+  , pattern =
+    [ regex "(\xc6d4|\xd654|\xc218|\xbaa9|\xae08|\xd1a0|\xc77c)(\xc694\xc77c|\xc69c)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case match of
+        "\xc6d4" -> tt $ dayOfWeek 1
+        "\xd654" -> tt $ dayOfWeek 2
+        "\xc218" -> tt $ dayOfWeek 3
+        "\xbaa9" -> tt $ dayOfWeek 4
+        "\xae08" -> tt $ dayOfWeek 5
+        "\xd1a0" -> tt $ dayOfWeek 6
+        "\xc77c" -> tt $ dayOfWeek 7
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleNextCycle :: Rule
+ruleNextCycle = Rule
+  { name = "next <cycle>"
+  , pattern =
+    [ regex "\xb2e4\xc74c|\xc624\xb294|\xcc28|\xb0b4"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleNamedmonth :: Rule
+ruleNamedmonth = Rule
+  { name = "<named-month>에"
+  , pattern =
+    [ Predicate isAMonth
+    , regex "\xc5d0"
+    ]
+  , prod = \tokens -> case tokens of
+      (x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleTimeofdayApproximately :: Rule
+ruleTimeofdayApproximately = Rule
+  { name = "<time-of-day> approximately"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "\xc815\xb3c4|\xcbe4"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleDurationFromNow :: Rule
+ruleDurationFromNow = Rule
+  { name = "<duration> from now"
+  , pattern =
+    [ regex "\xc9c0\xae08\xbd80\xd130"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleLunch :: Rule
+ruleLunch = Rule
+  { name = "lunch"
+  , pattern =
+    [ regex "\xc810\xc2ec"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 14
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleLastCycle :: Rule
+ruleLastCycle = Rule
+  { name = "last <cycle>"
+  , pattern =
+    [ regex "\xc9c0\xb09c|\xc791|\xc804|\xc800\xbc88"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleAfternoon :: Rule
+ruleAfternoon = Rule
+  { name = "afternoon"
+  , pattern =
+    [ regex "\xc624\xd6c4"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 19
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleChristmasEve :: Rule
+ruleChristmasEve = Rule
+  { name = "christmas eve"
+  , pattern =
+    [ regex "(\xd06c\xb9ac\xc2a4\xb9c8\xc2a4)?\xc774\xbe0c"
+    ]
+  , prod = \_ -> tt $ monthDay 12 24
+  }
+
+ruleInduringThePartofday :: Rule
+ruleInduringThePartofday = Rule
+  { name = "in|during the <part-of-day>"
+  , pattern =
+    [ Predicate isAPartOfDay
+    , regex "\xc5d0|\xb3d9\xc548"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleIntersectBy :: Rule
+ruleIntersectBy = Rule
+  { name = "intersect by \",\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex ","
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleMmdd :: Rule
+ruleMmdd = Rule
+  { name = "mm/dd"
+  , pattern =
+    [ regex "(0?[1-9]|1[0-2])/(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (mm:dd:_)):_) -> do
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleAfterDuration :: Rule
+ruleAfterDuration = Rule
+  { name = "after <duration>"
+  , pattern =
+    [ dimension Duration
+    , regex "(\xc774)?\xd6c4"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt . withDirection TTime.After $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleTimeofdayLatent :: Rule
+ruleTimeofdayLatent = Rule
+  { name = "time-of-day (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 0 23
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ hour True v
+      _ -> Nothing
+  }
+
+ruleExactlyTimeofday :: Rule
+ruleExactlyTimeofday = Rule
+  { name = "exactly <time-of-day>"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "\xc815\xac01"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleSeason3 :: Rule
+ruleSeason3 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "\xaca8\xc6b8"
+    ]
+  , prod = \_ ->
+      let from = monthDay 12 21
+          to = monthDay 3 20
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleSeason :: Rule
+ruleSeason = Rule
+  { name = "season"
+  , pattern =
+    [ regex "\xc5ec\xb984"
+    ]
+  , prod = \_ ->
+      let from = monthDay 6 21
+          to = monthDay 9 23
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleDayWithKoreanNumeral :: Rule
+ruleDayWithKoreanNumeral = Rule
+  { name = "day with korean number - 십일..삼십일일"
+  , pattern =
+    [ regex "((\xc774|\xc0bc)?\xc2ed(\xc77c|\xc774|\xc0bc|\xc0ac|\xc624|\xc721|\xce60|\xd314|\xad6c)?)\xc77c"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (_:m1:m2:_)):_) ->
+        let dozens = case m1 of
+              "\xc774" -> 2
+              "\xc0bc" -> 3
+              _        -> 1
+            units = case m2 of
+              "\xc77c" -> 1
+              "\xc774" -> 2
+              "\xc0bc" -> 3
+              "\xc0ac" -> 4
+              "\xc624" -> 5
+              "\xc721" -> 6
+              "\xce60" -> 7
+              "\xd314" -> 8
+              "\xad6c" -> 9
+              _        -> 1
+        in tt . dayOfMonth $ 10 * dozens + units
+      _ -> Nothing
+  }
+
+ruleDayWithKoreanNumeral2 :: Rule
+ruleDayWithKoreanNumeral2 = Rule
+  { name = "day with korean number - 일일..구일"
+  , pattern =
+    [ regex "(\xc77c|\xc774|\xc0bc|\xc0ac|\xc624|\xc721|\xce60|\xd314|\xad6c)\xc77c"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):_) -> case match of
+        "\xc77c" -> tt $ dayOfMonth 1
+        "\xc774" -> tt $ dayOfMonth 2
+        "\xc0bc" -> tt $ dayOfMonth 3
+        "\xc0ac" -> tt $ dayOfMonth 4
+        "\xc624" -> tt $ dayOfMonth 5
+        "\xc721" -> tt $ dayOfMonth 6
+        "\xce60" -> tt $ dayOfMonth 7
+        "\xd314" -> tt $ dayOfMonth 8
+        "\xad6c" -> tt $ dayOfMonth 9
+        _ -> Nothing
+      _ -> Nothing
+  }
+
+ruleTimeofday2 :: Rule
+ruleTimeofday2 = Rule
+  { name = "<time-of-day>에"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "\xc5d0"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleDurationAgo :: Rule
+ruleDurationAgo = Rule
+  { name = "<duration> ago"
+  , pattern =
+    [ dimension Duration
+    , regex "(\xc774)?\xc804"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt $ durationAgo dd
+      _ -> Nothing
+  }
+
+ruleLastNCycle :: Rule
+ruleLastNCycle = Rule
+  { name = "last n <cycle>"
+  , pattern =
+    [ regex "\xc9c0\xb09c"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain (- v)
+      _ -> Nothing
+  }
+
+ruleTimeNthTime :: Rule
+ruleTimeNthTime = Rule
+  { name = "<time> nth <time> - 3월 첫째 화요일"
+  , pattern =
+    [ dimension Time
+    , dimension Ordinal
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:
+       Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       Token Time td2:
+       _) -> Token Time . predNth (v - 1) False <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleWithinDuration :: Rule
+ruleWithinDuration = Rule
+  { name = "within <duration>"
+  , pattern =
+    [ dimension Duration
+    , regex "\xc774\xb0b4(\xc5d0)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        let from = cycleNth TG.Second 0
+            to = inDuration dd
+        in tt $ interval TTime.Open (from, to)
+      _ -> Nothing
+  }
+
+ruleMidnighteodendOfDay :: Rule
+ruleMidnighteodendOfDay = Rule
+  { name = "midnight|EOD|end of day"
+  , pattern =
+    [ regex "\xc790\xc815"
+    ]
+  , prod = \_ -> tt $ hour False 0
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ Predicate isNotLatent
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleAboutTimeofday :: Rule
+ruleAboutTimeofday = Rule
+  { name = "about <time-of-day>"
+  , pattern =
+    [ regex "\xb300\xcda9|\xc57d"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleEndOfTime :: Rule
+ruleEndOfTime = Rule
+  { name = "end of <time>"
+  , pattern =
+    [ dimension Time
+    , regex "\xb9d0"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 1 False td
+      _ -> Nothing
+  }
+
+ruleTimePartofday :: Rule
+ruleTimePartofday = Rule
+  { name = "<time> <part-of-day>"
+  , pattern =
+    [ dimension Time
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleWeekend :: Rule
+ruleWeekend = Rule
+  { name = "week-end"
+  , pattern =
+    [ regex "\xc8fc\xb9d0"
+    ]
+  , prod = \_ -> do
+      from <- intersect (dayOfWeek 5) (hour False 18)
+      to <- intersect (dayOfWeek 1) (hour False 0)
+      tt $ interval TTime.Open (from, to)
+  }
+
+ruleTimeDayofweek :: Rule
+ruleTimeDayofweek = Rule
+  { name = "<time> 마지막 <day-of-week>"
+  , pattern =
+    [ dimension Time
+    , regex "\xb9c8\xc9c0\xb9c9"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ predLastOf td2 td1
+      _ -> Nothing
+  }
+
+ruleDate :: Rule
+ruleDate = Rule
+  { name = "<date>에"
+  , pattern =
+    [ dimension Time
+    , regex "\xc5d0"
+    ]
+  , prod = \tokens -> case tokens of
+      (x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleNextTime :: Rule
+ruleNextTime = Rule
+  { name = "next <time>"
+  , pattern =
+    [ regex "\xb2e4\xc74c|\xc624\xb294"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleTimeCycle2 :: Rule
+ruleTimeCycle2 = Rule
+  { name = "<time> 마지막 <cycle>"
+  , pattern =
+    [ dimension Time
+    , regex "\xb9c8\xc9c0\xb9c9"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_:Token TimeGrain grain:_) ->
+        tt $ cycleLastOf grain td
+      _ -> Nothing
+  }
+
+ruleYyyymmdd :: Rule
+ruleYyyymmdd = Rule
+  { name = "yyyy-mm-dd"
+  , pattern =
+    [ regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (yy:mm:dd:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleNextNCycle :: Rule
+ruleNextNCycle = Rule
+  { name = "next n <cycle>"
+  , pattern =
+    [ regex "\xb2e4\xc74c"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleMorning :: Rule
+ruleMorning = Rule
+  { name = "morning"
+  , pattern =
+    [ regex "\xc544\xce68"
+    ]
+  , prod = \_ ->
+      let from = hour False 4
+          to = hour False 12
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleThisCycle :: Rule
+ruleThisCycle = Rule
+  { name = "this <cycle>"
+  , pattern =
+    [ regex "\xc774\xbc88|\xae08|\xc62c"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 0
+      _ -> Nothing
+  }
+
+ruleThisTime :: Rule
+ruleThisTime = Rule
+  { name = "this <time>"
+  , pattern =
+    [ regex "\xc774\xbc88"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleTimeofday3 :: Rule
+ruleTimeofday3 = Rule
+  { name = "<time-of-day> 정각"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "\xc815\xac01"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleMemorialDay :: Rule
+ruleMemorialDay = Rule
+  { name = "Memorial Day"
+  , pattern =
+    [ regex "\xd604\xcda9\xc77c"
+    ]
+  , prod = \_ -> tt $ monthDay 6 6
+  }
+
+ruleYearLatent :: Rule
+ruleYearLatent = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween (- 10000) 999
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ year v
+      _ -> Nothing
+  }
+
+ruleYesterday :: Rule
+ruleYesterday = Rule
+  { name = "yesterday"
+  , pattern =
+    [ regex "\xc5b4\xc81c"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 1
+  }
+
+ruleSeason2 :: Rule
+ruleSeason2 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "\xac00\xc744"
+    ]
+  , prod = \_ ->
+      let from = monthDay 9 23
+          to = monthDay 12 21
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleAfterTimeofday :: Rule
+ruleAfterTimeofday = Rule
+  { name = "after <time-of-day>"
+  , pattern =
+    [ Predicate $ liftM2 (||) isATimeOfDay isAPartOfDay
+    , regex "\xc9c0\xb098\xc11c|(\xc774)?\xd6c4(\xc5d0)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt . notLatent $ withDirection TTime.After td
+      _ -> Nothing
+  }
+
+ruleChristmas :: Rule
+ruleChristmas = Rule
+  { name = "christmas"
+  , pattern =
+    [ regex "\xd06c\xb9ac\xc2a4\xb9c8\xc2a4"
+    ]
+  , prod = \_ -> tt $ monthDay 12 25
+  }
+
+ruleTimeofdayAmpm :: Rule
+ruleTimeofdayAmpm = Rule
+  { name = "<time-of-day> am|pm"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(in the )?([ap])(\\s|\\.)?m?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:Token RegexMatch (GroupMatch (_:ap:_)):_) ->
+        tt . timeOfDayAMPM td $ Text.toLower ap == "a"
+      _ -> Nothing
+  }
+
+ruleTimeCycle :: Rule
+ruleTimeCycle = Rule
+  { name = "<time> 마지막 <cycle> "
+  , pattern =
+    [ dimension Time
+    , regex "\xb9c8\xc9c0\xb9c9"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleLastOf grain td
+      _ -> Nothing
+  }
+
+-- (assoc (intersect (cycle-nth :day 0)
+--                   (interval (hour (inc (:start %1)) false)
+--                             (hour (inc (:end %1)) false) false))
+--        :form :part-of-day)
+ruleAfterPartofday :: Rule
+ruleAfterPartofday = Rule
+  { name = "after <part-of-day>"
+  , pattern =
+    [ Predicate isAPartOfDay
+    , regex "\xc9c0\xb098\xc11c|\xd6c4\xc5d0"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        Token Time . partOfDay <$> intersect (cycleNth TG.Day 0) td
+      _ -> Nothing
+  }
+
+ruleTimeofday :: Rule
+ruleTimeofday = Rule
+  { name = "time-of-day"
+  , pattern =
+    [ Predicate $ isIntegerBetween 0 24
+    , regex "\xc2dc"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt $ hour True v
+      _ -> Nothing
+  }
+
+ruleHangulDay :: Rule
+ruleHangulDay = Rule
+  { name = "Hangul Day"
+  , pattern =
+    [ regex "\xd55c\xae00\xb0a0"
+    ]
+  , prod = \_ -> tt $ monthDay 10 9
+  }
+
+ruleHhmm :: Rule
+ruleHhmm = Rule
+  { name = "hh:mm"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt $ hourMinute True h m
+      _ -> Nothing
+  }
+
+ruleYearQuarter :: Rule
+ruleYearQuarter = Rule
+  { name = "<year> <1..4>quarter"
+  , pattern =
+    [ dimension Time
+    , Predicate $ isIntegerBetween 1 4
+    , Predicate $ isGrain TG.Quarter
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> do
+        v <- getIntValue token
+        tt $ cycleNthAfter False TG.Quarter (v - 1) td
+      _ -> Nothing
+  }
+
+ruleYear :: Rule
+ruleYear = Rule
+  { name = "year"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1000 2100
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt $ year v
+      _ -> Nothing
+  }
+
+ruleChildrensDay :: Rule
+ruleChildrensDay = Rule
+  { name = "Children's Day"
+  , pattern =
+    [ regex "\xc5b4\xb9b0\xc774\xb0a0"
+    ]
+  , prod = \_ -> tt $ monthDay 5 5
+  }
+
+ruleAbsorptionOfAfterNamedDay :: Rule
+ruleAbsorptionOfAfterNamedDay = Rule
+  { name = "absorption of , after named day"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex ","
+    ]
+  , prod = \tokens -> case tokens of
+      (x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleByTime :: Rule
+ruleByTime = Rule
+  { name = "by <time> - 까지"
+  , pattern =
+    [ dimension Time
+    , regex "\xae4c\xc9c0"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $
+        interval TTime.Open (cycleNth TG.Second 0, td)
+      _ -> Nothing
+  }
+
+ruleHhmmMilitaryAmpm :: Rule
+ruleHhmmMilitaryAmpm = Rule
+  { name = "hhmm (military) am|pm"
+  , pattern =
+    [ regex "((?:1[012]|0?\\d))([0-5]\\d)"
+    , regex "([ap])\\.?m?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):Token RegexMatch (GroupMatch (ap:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt . timeOfDayAMPM (hourMinute True h m) $
+          Text.toLower ap == "a"
+      _ -> Nothing
+  }
+
+ruleQuarter :: Rule
+ruleQuarter = Rule
+  { name = "<1..4> quarter"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 4
+    , Predicate $ isGrain TG.Quarter
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . cycleNthAfter False TG.Quarter (v - 1) $
+          cycleNth TG.Year 0
+      _ -> Nothing
+  }
+
+ruleTimeofdayTimeofdayInterval :: Rule
+ruleTimeofdayTimeofdayInterval = Rule
+  { name = "<time-of-day> - <time-of-day> (interval)"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\-|\\~|\xbd80\xd130"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNationalFoundationDay :: Rule
+ruleNationalFoundationDay = Rule
+  { name = "National Foundation Day"
+  , pattern =
+    [ regex "\xac1c\xcc9c\xc808"
+    ]
+  , prod = \_ -> tt $ monthDay 10 3
+  }
+
+ruleEveningnight :: Rule
+ruleEveningnight = Rule
+  { name = "evening|night"
+  , pattern =
+    [ regex "\xc800\xb141|\xbc24"
+    ]
+  , prod = \_ ->
+      let from = hour False 18
+          to = hour False 0
+      in tt . partOfDay . mkLatent $
+           interval TTime.Open (from, to)
+  }
+
+ruleIndependenceMovementDay :: Rule
+ruleIndependenceMovementDay = Rule
+  { name = "Independence Movement Day"
+  , pattern =
+    [ regex "\xc0bc\xc77c\xc808"
+    ]
+  , prod = \_ -> tt $ monthDay 3 1
+  }
+
+ruleMmddyyyy :: Rule
+ruleMmddyyyy = Rule
+  { name = "mm/dd/yyyy"
+  , pattern =
+    [ regex "(\\d{2,4})[-/](0?[1-9]|1[0-2])[/-](3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (yy:mm:dd:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleTomorrow :: Rule
+ruleTomorrow = Rule
+  { name = "tomorrow"
+  , pattern =
+    [ regex "\xb0b4\xc77c|\xba85\xc77c|\xb0bc"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 1
+  }
+
+ruleAmpmTimeofday :: Rule
+ruleAmpmTimeofday = Rule
+  { name = "am|pm <time-of-day>"
+  , pattern =
+    [ regex "(\xc624\xc804|\xc544\xce68|\xc624\xd6c4|\xc800\xb141)"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (match:_)):
+       Token Time td:
+       _) -> tt . timeOfDayAMPM td $
+         elem match ["\xc624\xc804", "\xc544\xce68"]
+      _ -> Nothing
+  }
+
+ruleYear2 :: Rule
+ruleYear2 = Rule
+  { name = "year"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 2100
+    , regex "\xb144"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt $ year v
+      _ -> Nothing
+  }
+
+ruleHhmmss :: Rule
+ruleHhmmss = Rule
+  { name = "hh:mm:ss"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)[:.]([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:ss:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        s <- parseInt ss
+        tt $ hourMinuteSecond True h m s
+      _ -> Nothing
+  }
+
+ruleTimezone :: Rule
+ruleTimezone = Rule
+  { name = "<time> timezone"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\b(YEKT|YEKST|YAKT|YAKST|WITA|WIT|WIB|WGT|WGST|WFT|WET|WEST|WAT|WAST|VUT|VLAT|VLAST|VET|UZT|UYT|UYST|UTC|ULAT|TVT|TMT|TLT|TKT|TJT|TFT|TAHT|SST|SRT|SGT|SCT|SBT|SAST|SAMT|RET|PYT|PYST|PWT|PST|PONT|PMST|PMDT|PKT|PHT|PHOT|PGT|PETT|PETST|PET|PDT|OMST|OMSST|NZST|NZDT|NUT|NST|NPT|NOVT|NOVST|NFT|NDT|NCT|MYT|MVT|MUT|MST|MSK|MSD|MMT|MHT|MDT|MAWT|MART|MAGT|MAGST|LINT|LHST|LHDT|KUYT|KST|KRAT|KRAST|KGT|JST|IST|IRST|IRKT|IRKST|IRDT|IOT|IDT|ICT|HOVT|HKT|GYT|GST|GMT|GILT|GFT|GET|GAMT|GALT|FNT|FKT|FKST|FJT|FJST|EST|EGT|EGST|EET|EEST|EDT|ECT|EAT|EAST|EASST|DAVT|ChST|CXT|CVT|CST|COT|CLT|CLST|CKT|CHAST|CHADT|CET|CEST|CDT|CCT|CAT|CAST|BTT|BST|BRT|BRST|BOT|BNT|AZT|AZST|AZOT|AZOST|AWST|AWDT|AST|ART|AQTT|ANAT|ANAST|AMT|AMST|ALMT|AKST|AKDT|AFT|AEST|AEDT|ADT|ACST|ACDT)\\b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       Token RegexMatch (GroupMatch (tz:_)):
+       _) -> Token Time <$> inTimezone tz td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAboutTimeofday
+  , ruleAbsorptionOfAfterNamedDay
+  , ruleAfterDuration
+  , ruleAfterPartofday
+  , ruleAfterTimeofday
+  , ruleAfternoon
+  , ruleAmpmTimeofday
+  , ruleByTime
+  , ruleChildrensDay
+  , ruleChristmas
+  , ruleChristmasEve
+  , ruleConstitutionDay
+  , ruleDate
+  , ruleDatetimeDatetimeInterval
+  , ruleDay
+  , ruleDayWithKoreanNumeral
+  , ruleDayWithKoreanNumeral2
+  , ruleDayofweek
+  , ruleDurationAgo
+  , ruleDurationFromNow
+  , ruleEndOfTime
+  , ruleEveningnight
+  , ruleExactlyTimeofday
+  , ruleHangulDay
+  , ruleHhmm
+  , ruleHhmmMilitaryAmpm
+  , ruleHhmmss
+  , ruleHourofdayIntegerAsRelativeMinutes
+  , ruleHourofdayHalfAsRelativeMinutes
+  , ruleInDuration
+  , ruleIndependenceMovementDay
+  , ruleInduringThePartofday
+  , ruleIntegerHourofdayRelativeMinutes
+  , ruleHalfHourofdayRelativeMinutes
+  , ruleIntersect
+  , ruleIntersectBy
+  , ruleLastCycle
+  , ruleLastNCycle
+  , ruleLastTime
+  , ruleLiberationDay
+  , ruleLunch
+  , ruleMemorialDay
+  , ruleMidnighteodendOfDay
+  , ruleMmdd
+  , ruleMmddyyyy
+  , ruleMonth
+  , ruleMorning
+  , ruleNamedday
+  , ruleNamedmonth
+  , ruleNationalFoundationDay
+  , ruleNewYearsDay
+  , ruleNextCycle
+  , ruleNextNCycle
+  , ruleNextTime
+  , ruleNoon
+  , ruleNow
+  , ruleQuarter
+  , ruleSeason
+  , ruleSeason2
+  , ruleSeason3
+  , ruleSeason4
+  , ruleSinceTimeofday
+  , ruleTheDayAfterTomorrow
+  , ruleTheDayBeforeYesterday
+  , ruleThisCycle
+  , ruleThisDayofweek
+  , ruleThisTime
+  , ruleTimeAfterNext
+  , ruleTimeCycle
+  , ruleTimeCycle2
+  , ruleTimeDayofweek
+  , ruleTimeNthTime
+  , ruleTimeOrdinalCycle
+  , ruleTimePartofday
+  , ruleTimeofday
+  , ruleTimeofday2
+  , ruleTimeofday3
+  , ruleTimeofday4
+  , ruleTimeofdayAmpm
+  , ruleTimeofdayApproximately
+  , ruleTimeofdayLatent
+  , ruleTimeofdayTimeofdayInterval
+  , ruleToday
+  , ruleTomorrow
+  , ruleWeekend
+  , ruleWithinDuration
+  , ruleYear
+  , ruleYear2
+  , ruleYearLatent
+  , ruleYearLatent2
+  , ruleYearQuarter
+  , ruleYesterday
+  , ruleYyyymmdd
+  , ruleSeconds
+  , ruleTimezone
+  ]
diff --git a/Duckling/Time/NB/Corpus.hs b/Duckling/Time/NB/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/NB/Corpus.hs
@@ -0,0 +1,644 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.NB.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Time.Corpus
+import Duckling.Time.Types hiding (Month)
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Testing.Types hiding (examples)
+
+corpus :: Corpus
+corpus = (testContext {lang = NB}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (datetime (2013, 2, 12, 4, 30, 0) Second)
+             [ "nå"
+             , "akkurat nå"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "i dag"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "i går"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "i morgen"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "mandag"
+             , "man."
+             , "på mandag"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "Mandag den 18. februar"
+             , "Man, 18 februar"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "tirsdag"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "torsdag"
+             , "tors"
+             , "tors."
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "fredag"
+             , "fre"
+             , "fre."
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "lørdag"
+             , "lør"
+             , "lør."
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "søndag"
+             , "søn"
+             , "søn."
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "Den første mars"
+             , "1. mars"
+             , "Den 1. mars"
+             ]
+  , examples (datetime (2013, 3, 3, 0, 0, 0) Day)
+             [ "3 mars"
+             , "den tredje mars"
+             , "den 3. mars"
+             ]
+  , examples (datetime (2015, 3, 3, 0, 0, 0) Day)
+             [ "3 mars 2015"
+             , "tredje mars 2015"
+             , "3. mars 2015"
+             , "3-3-2015"
+             , "03-03-2015"
+             , "3/3/2015"
+             , "3/3/15"
+             , "2015-3-3"
+             , "2015-03-03"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "På den 15."
+             , "På den 15"
+             , "Den 15."
+             , "Den femtende"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "den 15. februar"
+             , "15. februar"
+             , "februar 15"
+             , "15-02"
+             , "15/02"
+             ]
+  , examples (datetime (2013, 8, 8, 0, 0, 0) Day)
+             [ "8 Aug"
+             ]
+  , examples (datetime (2014, 10, 0, 0, 0, 0) Month)
+             [ "Oktober 2014"
+             ]
+  , examples (datetime (1974, 10, 31, 0, 0, 0) Day)
+             [ "31/10/1974"
+             , "31/10/74"
+             , "31-10-74"
+             ]
+  , examples (datetime (2015, 4, 14, 0, 0, 0) Day)
+             [ "14april 2015"
+             , "April 14, 2015"
+             , "fjortende April 15"
+             ]
+  , examples (datetime (2013, 2, 22, 0, 0, 0) Day)
+             [ "neste fredag igjen"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "neste mars"
+             ]
+  , examples (datetime (2014, 3, 0, 0, 0, 0) Month)
+             [ "neste mars igjen"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "Søndag, 10 feb"
+             , "Søndag 10 Feb"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "Ons, Feb13"
+             , "Ons feb13"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "Mandag, Feb 18"
+             , "Man, februar 18"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Week)
+             [ "denne uken"
+             ]
+  , examples (datetime (2013, 2, 4, 0, 0, 0) Week)
+             [ "forrige uke"
+             , "sist uke"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Week)
+             [ "neste uke"
+             ]
+  , examples (datetime (2013, 1, 0, 0, 0, 0) Month)
+             [ "forrige måned"
+             , "sist måned"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "neste måned"
+             ]
+  , examples (datetime (2013, 1, 1, 0, 0, 0) Quarter)
+             [ "dette kvartalet"
+             ]
+  , examples (datetime (2013, 4, 1, 0, 0, 0) Quarter)
+             [ "neste kvartal"
+             ]
+  , examples (datetime (2013, 7, 1, 0, 0, 0) Quarter)
+             [ "tredje kvartal"
+             , "3. kvartal"
+             ]
+  , examples (datetime (2018, 10, 1, 0, 0, 0) Quarter)
+             [ "4. kvartal 2018"
+             , "fjerde kvartal 2018"
+             ]
+  , examples (datetime (2012, 0, 0, 0, 0, 0) Year)
+             [ "forrige år"
+             , "sist år"
+             ]
+  , examples (datetime (2012, 0, 0, 0, 0, 0) Year)
+             [ "i fjor"
+             ]
+  , examples (datetime (2013, 0, 0, 0, 0, 0) Year)
+             [ "i år"
+             , "dette år"
+             ]
+  , examples (datetime (2014, 0, 0, 0, 0, 0) Year)
+             [ "neste år"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "forrige søndag"
+             , "sist søndag"
+             , "søndag i forrige uke"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "forrige tirsdag"
+             , "sist tirsdag"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "neste tirsdag"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "neste onsdag"
+             ]
+  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
+             [ "onsdag i neste uke"
+             , "onsdag neste uke"
+             , "neste onsdag igjen"
+             ]
+  , examples (datetime (2013, 2, 22, 0, 0, 0) Day)
+             [ "neste fredag igjen"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "mandag denne uken"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "tirsdag denne uken"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "onsdag denne uken"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "i overimorgen"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "i forigårs"
+             ]
+  , examples (datetime (2013, 3, 25, 0, 0, 0) Day)
+             [ "siste mandag i mars"
+             , "siste mandag i mars"
+             ]
+  , examples (datetime (2014, 3, 30, 0, 0, 0) Day)
+             [ "siste søndag i mars 2014"
+             , "siste søndag i mars 2014"
+             ]
+  , examples (datetime (2013, 10, 3, 0, 0, 0) Day)
+             [ "tredje dag i oktober"
+             , "tredje dag i Oktober"
+             ]
+  , examples (datetime (2014, 10, 6, 0, 0, 0) Week)
+             [ "første uke i oktober 2014"
+             , "første uke i Oktober 2014"
+             ]
+  , examples (datetime (2015, 10, 31, 0, 0, 0) Day)
+             [ "siste dag i oktober 2015"
+             , "siste dag i Oktober 2015"
+             ]
+  , examples (datetime (2014, 9, 22, 0, 0, 0) Week)
+             [ "siste uke i september 2014"
+             , "siste uke i September 2014"
+             ]
+  , examples (datetime (2013, 10, 1, 0, 0, 0) Day)
+             [ "første tirsdag i oktober"
+             , "første tirsdag i Oktober"
+             ]
+  , examples (datetime (2014, 9, 16, 0, 0, 0) Day)
+             [ "tredje tirsdag i september 2014"
+             , "tredje tirsdag i September 2014"
+             ]
+  , examples (datetime (2014, 10, 1, 0, 0, 0) Day)
+             [ "første onsdag i oktober 2014"
+             , "første onsdag i Oktober 2014"
+             ]
+  , examples (datetime (2014, 10, 8, 0, 0, 0) Day)
+             [ "andre onsdag i oktober 2014"
+             , "andre onsdag i Oktober 2014"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 0, 0) Hour)
+             [ "klokken 3"
+             , "kl. 3"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 18, 0) Minute)
+             [ "3:18"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "klokken 15"
+             , "kl. 15"
+             , "15h"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "ca. kl. 15"
+             , "cirka kl. 15"
+             , "omkring klokken 15"
+             ]
+  , examples (datetime (2013, 2, 13, 17, 0, 0) Hour)
+             [ "imorgen klokken 17 sharp"
+             , "imorgen kl. 17 presis"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 15, 0) Minute)
+             [ "kvarter over 15"
+             , "kvart over 15"
+             , "15:15"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 20, 0) Minute)
+             [ "kl. 20 over 15"
+             , "klokken 20 over 15"
+             , "kl. 15:20"
+             , "15:20"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 30, 0) Minute)
+             [ "15:30"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 23, 24) Second)
+             [ "15:23:24"
+             ]
+  , examples (datetime (2013, 2, 12, 11, 45, 0) Minute)
+             [ "kvarter på 12"
+             , "kvart på 12"
+             , "11:45"
+             ]
+  , examples (datetime (2013, 2, 16, 9, 0, 0) Hour)
+             [ "klokken 9 på lørdag"
+             ]
+  , examples (datetime (2014, 7, 18, 19, 0, 0) Minute)
+             [ "Fre, Jul 18, 2014 19:00"
+             ]
+  , examples (datetime (2014, 7, 18, 0, 0, 0) Day)
+             [ "Fre, Jul 18"
+             , "Jul 18, Fre"
+             ]
+  , examples (datetime (2014, 9, 20, 19, 30, 0) Minute)
+             [ "kl. 19:30, Lør, 20 sep"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 30, 1) Second)
+             [ "om 1 sekund"
+             , "om ett sekund"
+             , "om et sekund"
+             , "ett sekund fra nå"
+             , "et sekund fra nå"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 31, 0) Second)
+             [ "om 1 minutt"
+             , "om et minutt"
+             , "om ett minutt"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 32, 0) Second)
+             [ "om 2 minutter"
+             , "om to minutter"
+             , "om 2 minutter mer"
+             , "om to minutter mer"
+             , "2 minutter fra nå"
+             , "to minutter fra nå"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Second)
+             [ "om 60 minutter"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 0, 0) Second)
+             [ "om en halv time"
+             ]
+  , examples (datetime (2013, 2, 12, 7, 0, 0) Second)
+             [ "om 2,5 time"
+             , "om 2 og en halv time"
+             , "om to og en halv time"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Minute)
+             [ "om én time"
+             , "om 1 time"
+             , "om 1t"
+             ]
+  , examples (datetime (2013, 2, 12, 6, 30, 0) Minute)
+             [ "om et par timer"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 30, 0) Minute)
+             [ "om 24 timer"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 0, 0) Hour)
+             [ "om en dag"
+             ]
+  , examples (datetime (2016, 2, 0, 0, 0, 0) Month)
+             [ "3 år fra i dag"
+             ]
+  , examples (datetime (2013, 2, 19, 4, 0, 0) Hour)
+             [ "om 7 dager"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "om en uke"
+             , "om én uke"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 0, 0) Second)
+             [ "om ca. en halv time"
+             , "om cirka en halv time"
+             ]
+  , examples (datetime (2013, 2, 5, 4, 0, 0) Hour)
+             [ "7 dager siden"
+             , "syv dager siden"
+             ]
+  , examples (datetime (2013, 1, 29, 4, 0, 0) Hour)
+             [ "14 dager siden"
+             , "fjorten dager siden"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "en uke siden"
+             , "én uke siden"
+             , "1 uke siden"
+             ]
+  , examples (datetime (2013, 1, 22, 0, 0, 0) Day)
+             [ "3 uker siden"
+             , "tre uker siden"
+             ]
+  , examples (datetime (2012, 11, 12, 0, 0, 0) Day)
+             [ "3 måneder siden"
+             , "tre måneder siden"
+             ]
+  , examples (datetime (2011, 2, 0, 0, 0, 0) Month)
+             [ "to år siden"
+             , "2 år siden"
+             ]
+  , examples (datetime (1954, 0, 0, 0, 0, 0) Year)
+             [ "1954"
+             ]
+  , examples (datetime (2013, 12, 0, 0, 0, 0) Month)
+             [ "et år etter julaften"
+             , "ett år etter julaften"
+             ]
+  , examples (datetimeInterval ((2013, 6, 21, 0, 0, 0), (2013, 9, 24, 0, 0, 0)) Day)
+             [ "denne sommeren"
+             , "den her sommeren"
+             ]
+  , examples (datetimeInterval ((2012, 12, 21, 0, 0, 0), (2013, 3, 21, 0, 0, 0)) Day)
+             [ "denne vinteren"
+             , "den her vinteren"
+             ]
+  , examples (datetime (2013, 12, 25, 0, 0, 0) Day)
+             [ "1 juledag"
+             , "1. juledag"
+             , "første juledag"
+             ]
+  , examples (datetime (2013, 12, 31, 0, 0, 0) Day)
+             [ "nyttårsaften"
+             ]
+  , examples (datetime (2014, 1, 1, 0, 0, 0) Day)
+             [ "nyttårsdag"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 18, 0, 0), (2013, 2, 13, 0, 0, 0)) Hour)
+             [ "i kveld"
+             ]
+  , examples (datetimeInterval ((2013, 2, 8, 18, 0, 0), (2013, 2, 11, 0, 0, 0)) Hour)
+             [ "forrige helg"
+             , "sist helg"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 18, 0, 0), (2013, 2, 14, 0, 0, 0)) Hour)
+             [ "i morgen kveld"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 12, 0, 0), (2013, 2, 13, 14, 0, 0)) Hour)
+             [ "i morgen middag"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 18, 0, 0), (2013, 2, 12, 0, 0, 0)) Hour)
+             [ "i går kveld"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 18, 0, 0), (2013, 2, 18, 0, 0, 0)) Hour)
+             [ "denne helgen"
+             , "denne helga"
+             , "i helga"
+             , "i helgen"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 4, 0, 0), (2013, 2, 18, 12, 0, 0)) Hour)
+             [ "mandag morgen"
+             ]
+  , examples (datetimeInterval ((2013, 12, 24, 0, 0, 0), (2013, 12, 31, 0, 0, 0)) Day)
+             [ "i romjulen"
+             , "i romjula"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 29, 58), (2013, 2, 12, 4, 30, 0)) Second)
+             [ "siste 2 sekunder"
+             , "siste to sekunder"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 1), (2013, 2, 12, 4, 30, 4)) Second)
+             [ "neste 3 sekunder"
+             , "neste tre sekunder"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 28, 0), (2013, 2, 12, 4, 30, 0)) Minute)
+             [ "siste 2 minutter"
+             , "siste to minutter"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 31, 0), (2013, 2, 12, 4, 34, 0)) Minute)
+             [ "neste 3 minutter"
+             , "neste tre minutter"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 3, 0, 0), (2013, 2, 12, 4, 0, 0)) Hour)
+             [ "siste 1 time"
+             , "seneste 1 time"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 5, 0, 0), (2013, 2, 12, 8, 0, 0)) Hour)
+             [ "neste 3 timer"
+             , "neste tre timer"
+             ]
+  , examples (datetimeInterval ((2013, 2, 10, 0, 0, 0), (2013, 2, 12, 0, 0, 0)) Day)
+             [ "siste 2 dager"
+             , "siste to dager"
+             , "seneste 2 dager"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
+             [ "neste 3 dager"
+             , "neste tre dager"
+             ]
+  , examples (datetimeInterval ((2013, 1, 28, 0, 0, 0), (2013, 2, 11, 0, 0, 0)) Week)
+             [ "siste 2 uker"
+             , "siste to uker"
+             , "seneste to uker"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 0, 0, 0), (2013, 3, 11, 0, 0, 0)) Week)
+             [ "neste 3 uker"
+             , "neste tre uker"
+             ]
+  , examples (datetimeInterval ((2012, 12, 0, 0, 0, 0), (2013, 2, 0, 0, 0, 0)) Month)
+             [ "siste 2 måneder"
+             , "siste to måneder"
+             , "seneste to måneder"
+             ]
+  , examples (datetimeInterval ((2013, 3, 0, 0, 0, 0), (2013, 6, 0, 0, 0, 0)) Month)
+             [ "neste 3 måneder"
+             , "neste tre måneder"
+             ]
+  , examples (datetimeInterval ((2011, 0, 0, 0, 0, 0), (2013, 0, 0, 0, 0, 0)) Year)
+             [ "siste 2 år"
+             , "siste to år"
+             , "seneste 2 år"
+             ]
+  , examples (datetimeInterval ((2014, 0, 0, 0, 0, 0), (2017, 0, 0, 0, 0, 0)) Year)
+             [ "neste 3 år"
+             , "neste tre år"
+             ]
+  , examples (datetimeInterval ((2013, 7, 13, 0, 0, 0), (2013, 7, 16, 0, 0, 0)) Day)
+             [ "13-15 juli"
+             , "13-15 Juli"
+             , "13 til 15 Juli"
+             , "13 juli til 15 juli"
+             ]
+  , examples (datetimeInterval ((2013, 8, 8, 0, 0, 0), (2013, 8, 13, 0, 0, 0)) Day)
+             [ "8 Aug - 12 Aug"
+             , "8 Aug - 12 aug"
+             , "8 aug - 12 aug"
+             , "8 august - 12 august"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 9, 30, 0), (2013, 2, 12, 11, 1, 0)) Minute)
+             [ "9:30 - 11:00"
+             , "9:30 til 11:00"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 30, 0), (2013, 2, 14, 11, 1, 0)) Minute)
+             [ "fra 9:30 - 11:00 på torsdag"
+             , "fra 9:30 til 11:00 på torsdag"
+             , "mellom 9:30 og 11:00 på torsdag"
+             , "9:30 - 11:00 på torsdag"
+             , "9:30 til 11:00 på torsdag"
+             , "etter 9:30 men før 11:00 på torsdag"
+             , "torsdag fra 9:30 til 11:00"
+             , "torsdag mellom 9:30 og 11:00"
+             , "fra 9:30 til 11:00 på torsdag"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 0, 0), (2013, 2, 14, 12, 0, 0)) Hour)
+             [ "torsdag fra 9 til 11"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 11, 30, 0), (2013, 2, 12, 13, 31, 0)) Minute)
+             [ "11:30-13:30"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 26, 0, 0, 0)) Second)
+             [ "innenfor 2 uker"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 14, 0, 0) Hour)
+             [ "innen kl. 14"
+             , "innen klokken 14"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 0, 0) Minute)
+             [ "16h CET"
+             , "kl. 16 CET"
+             , "klokken 16 CET"
+             ]
+  , examples (datetime (2013, 2, 14, 6, 0, 0) Minute)
+             [ "torsdag kl. 8:00 GMT"
+             , "torsdag klokken 8:00 GMT"
+             , "torsdag 08:00 GMT"
+             ]
+  , examples (datetime (2013, 2, 12, 14, 0, 0) Hour)
+             [ "idag kl. 14"
+             , "idag klokken 14"
+             , "kl. 14"
+             , "klokken 14"
+             ]
+  , examples (datetime (2013, 4, 25, 16, 0, 0) Minute)
+             [ "25/4 kl. 16:00"
+             , "25/4 klokken 16:00"
+             , "25-04 klokken 16:00"
+             , "25-4 kl. 16:00"
+             ]
+  , examples (datetime (2013, 2, 13, 15, 0, 0) Minute)
+             [ "15:00 i morgen"
+             , "kl. 15:00 i morgen"
+             , "klokken 15:00 i morgen"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 12, 14, 0, 0) Hour)
+             [ "etter kl. 14"
+             , "etter klokken 14"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 17, 4, 0, 0) Hour)
+             [ "etter 5 dager"
+             , "etter fem dager"
+             ]
+  , examples (datetime (2013, 2, 17, 4, 0, 0) Hour)
+             [ "om 5 dager"
+             , "om fem dager"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 13, 14, 0, 0) Hour)
+             [ "etter i morgen kl. 14"
+             , "etter i morgen klokken 14"
+             , "i morgen etter kl. 14"
+             , "i morgen etter klokken 14"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 11, 0, 0) Hour)
+             [ "før kl. 11"
+             , "før klokken 11"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 13, 11, 0, 0) Hour)
+             [ "i morgen før kl. 11"
+             , "i morgen før klokken 11"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 12, 0, 0), (2013, 2, 12, 19, 0, 0)) Hour)
+             [ "om ettermiddagen"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 30, 0) Minute)
+             [ "kl. 13:30"
+             , "klokken 13:30"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 45, 0) Second)
+             [ "om 15 minutter"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 13, 0, 0), (2013, 2, 12, 17, 0, 0)) Hour)
+             [ "etter frokost"
+             ]
+  , examples (datetime (2013, 2, 12, 10, 30, 0) Minute)
+             [ "10:30"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 0, 0), (2013, 2, 12, 12, 0, 0)) Hour)
+             [ "denne morgen"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "neste mandag"
+             ]
+  , examples (datetime (2014, 2, 9, 0, 0, 0) Day)
+             [ "morsdag"
+             ]
+  , examples (datetime (2013, 11, 10, 0, 0, 0) Day)
+             [ "farsdag"
+             ]
+  ]
diff --git a/Duckling/Time/NB/Rules.hs b/Duckling/Time/NB/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/NB/Rules.hs
@@ -0,0 +1,1965 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.NB.Rules
+  ( rules ) where
+
+import Control.Monad (liftM2)
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Types (OrdinalData (..))
+import qualified Duckling.Ordinal.Types as TOrdinal
+import Duckling.Regex.Types
+import Duckling.Time.Helpers
+import Duckling.Time.Types (TimeData (..))
+import qualified Duckling.Time.Types as TTime
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleNamedday :: Rule
+ruleNamedday = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "mandag|man\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 1
+  }
+
+ruleTheDayAfterTomorrow :: Rule
+ruleTheDayAfterTomorrow = Rule
+  { name = "the day after tomorrow"
+  , pattern =
+    [ regex "i overimorgen"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 2
+  }
+
+ruleNamedmonth12 :: Rule
+ruleNamedmonth12 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "desember|des\\.?"
+    ]
+  , prod = \_ -> tt $ month 12
+  }
+
+ruleRelativeMinutesTotillbeforeIntegerHourofday :: Rule
+ruleRelativeMinutesTotillbeforeIntegerHourofday = Rule
+  { name = "relative minutes to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "p\x00e5"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleRelativeMinutesAfterpastIntegerHourofday :: Rule
+ruleRelativeMinutesAfterpastIntegerHourofday = Rule
+  { name = "relative minutes after|past <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "over"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:
+       _:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute True hours n
+      _ -> Nothing
+  }
+
+ruleHourofdayIntegerAsRelativeMinutes :: Rule
+ruleHourofdayIntegerAsRelativeMinutes = Rule
+  { name = "<hour-of-day> <integer> (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute True hours n
+      _ -> Nothing
+  }
+
+ruleQuarterTotillbeforeIntegerHourofday :: Rule
+ruleQuarterTotillbeforeIntegerHourofday = Rule
+  { name = "quarter to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ regex "(et)? ?(kvart)(er)? *p\x00e5"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesBefore 15 td
+      _ -> Nothing
+  }
+
+ruleQuarterAfterpastIntegerHourofday :: Rule
+ruleQuarterAfterpastIntegerHourofday = Rule
+  { name = "quarter after|past <integer> (hour-of-day)"
+  , pattern =
+    [ regex "(et)? ?(kvart)(er)? *over"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> tt $ hourMinute True hours 15
+      _ -> Nothing
+  }
+
+ruleHourofdayQuarter :: Rule
+ruleHourofdayQuarter = Rule
+  { name = "<hour-of-day> quarter (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "(et)? ?(kvart)(er)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:_) ->
+        tt $ hourMinute True hours 15
+      _ -> Nothing
+  }
+
+ruleHalfTotillbeforeIntegerHourofday :: Rule
+ruleHalfTotillbeforeIntegerHourofday = Rule
+  { name = "half to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ regex "halv time *p\x00e5"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesBefore 30 td
+      _ -> Nothing
+  }
+
+ruleHalfAfterpastIntegerHourofday :: Rule
+ruleHalfAfterpastIntegerHourofday = Rule
+  { name = "half after|past <integer> (hour-of-day)"
+  , pattern =
+    [ regex "halv time *over"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> tt $ hourMinute True hours 30
+      _ -> Nothing
+  }
+
+ruleHourofdayHalf :: Rule
+ruleHourofdayHalf = Rule
+  { name = "<hour-of-day> half (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "halv time"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:_) ->
+        tt $ hourMinute True hours 30
+      _ -> Nothing
+  }
+
+ruleConstitutionDay :: Rule
+ruleConstitutionDay = Rule
+  { name = "constitution day"
+  , pattern =
+    [ regex "grunnlovsdag(en)"
+    ]
+  , prod = \_ -> tt $ monthDay 5 17
+  }
+
+ruleNamedday2 :: Rule
+ruleNamedday2 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "tirsdag|tirs?\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 2
+  }
+
+ruleValentinesDay :: Rule
+ruleValentinesDay = Rule
+  { name = "valentine's day"
+  , pattern =
+    [ regex "valentine'?s?( dag)?"
+    ]
+  , prod = \_ -> tt $ monthDay 2 14
+  }
+
+ruleTheOrdinalCycleOfTime :: Rule
+ruleTheOrdinalCycleOfTime = Rule
+  { name = "the <ordinal> <cycle> of <time>"
+  , pattern =
+    [ regex "den"
+    , dimension Ordinal
+    , dimension TimeGrain
+    , regex "av|i|fra"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleNthTimeOfTime2 :: Rule
+ruleNthTimeOfTime2 = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ regex "den"
+    , dimension Ordinal
+    , dimension Time
+    , regex "af|i"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token Time td1:_:Token Time td2:_) -> Token Time .
+        predNth (TOrdinal.value od - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNewYearsDay :: Rule
+ruleNewYearsDay = Rule
+  { name = "new year's day"
+  , pattern =
+    [ regex "nytt\x00e5rsdag"
+    ]
+  , prod = \_ -> tt $ monthDay 1 1
+  }
+
+ruleLastTime :: Rule
+ruleLastTime = Rule
+  { name = "last <time>"
+  , pattern =
+    [ regex "(siste?|forrige|seneste)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleNamedday6 :: Rule
+ruleNamedday6 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "l\x00f8rdag|l\x00f8r\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 6
+  }
+
+ruleDatetimeDatetimeInterval :: Rule
+ruleDatetimeDatetimeInterval = Rule
+  { name = "<datetime> - <datetime> (interval)"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "\\-|til|tilogmed"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth7 :: Rule
+ruleNamedmonth7 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "juli|jul\\.?"
+    ]
+  , prod = \_ -> tt $ month 7
+  }
+
+ruleEvening :: Rule
+ruleEvening = Rule
+  { name = "evening"
+  , pattern =
+    [ regex "kveld(en)?"
+    ]
+  , prod = \_ ->
+      let from = hour False 18
+          to = hour False 0
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleTheDayofmonthNonOrdinal :: Rule
+ruleTheDayofmonthNonOrdinal = Rule
+  { name = "the <day-of-month> (non ordinal)"
+  , pattern =
+    [ regex "den"
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleCycleAfterTime :: Rule
+ruleCycleAfterTime = Rule
+  { name = "<cycle> after <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "etter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain 1 td
+      _ -> Nothing
+  }
+
+ruleInDuration :: Rule
+ruleInDuration = Rule
+  { name = "in <duration>"
+  , pattern =
+    [ regex "om"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleNow :: Rule
+ruleNow = Rule
+  { name = "now"
+  , pattern =
+    [ regex "akkurat n\x00e5|n\x00e5|(i )?dette \x00f8yeblikk"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Second 0
+  }
+
+ruleLastCycleOfTime :: Rule
+ruleLastCycleOfTime = Rule
+  { name = "last <cycle> of <time>"
+  , pattern =
+    [ regex "siste"
+    , dimension TimeGrain
+    , regex "av|i"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleLastOf grain td
+      _ -> Nothing
+  }
+
+ruleFromDatetimeDatetimeInterval :: Rule
+ruleFromDatetimeDatetimeInterval = Rule
+  { name = "from <datetime> - <datetime> (interval)"
+  , pattern =
+    [ regex "fra"
+    , dimension Time
+    , regex "\\-|til|tilogmed"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleMonthDdddInterval :: Rule
+ruleMonthDdddInterval = Rule
+  { name = "<month> dd-dd (interval)"
+  , pattern =
+    [ regex "([012]?\\d|30|31)(ter|\\.)?"
+    , regex "\\-|til"
+    , regex "([012]?\\d|30|31)(ter|\\.)?"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (d1:_)):
+       _:
+       Token RegexMatch (GroupMatch (d2:_)):
+       Token Time td:
+       _) -> do
+        dd1 <- parseInt d1
+        dd2 <- parseInt d2
+        dom1 <- intersect (dayOfMonth dd1) td
+        dom2 <- intersect (dayOfMonth dd2) td
+        tt $ interval TTime.Closed (dom1, dom2)
+      _ -> Nothing
+  }
+
+ruleNamedday4 :: Rule
+ruleNamedday4 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "torsdag|tors?\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 4
+  }
+
+ruleTheCycleAfterTime :: Rule
+ruleTheCycleAfterTime = Rule
+  { name = "the <cycle> after <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "(en|tet|et)? etter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain 1 td
+      _ -> Nothing
+  }
+
+ruleTheCycleBeforeTime :: Rule
+ruleTheCycleBeforeTime = Rule
+  { name = "the <cycle> before <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "(en|tet|et)? f\x00f8r"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain (-1) td
+      _ -> Nothing
+  }
+
+ruleYearLatent2 :: Rule
+ruleYearLatent2 = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2101 10000
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ year n
+      _ -> Nothing
+  }
+
+ruleTimeAfterNext :: Rule
+ruleTimeAfterNext = Rule
+  { name = "<time> after next"
+  , pattern =
+    [ regex "neste"
+    , dimension Time
+    , regex "igjen"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 1 True td
+      _ -> Nothing
+  }
+
+ruleTheIdesOfNamedmonth :: Rule
+ruleTheIdesOfNamedmonth = Rule
+  { name = "the ides of <named-month>"
+  , pattern =
+    [ regex "midten af"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td@TimeData {TTime.form = Just (TTime.Month m)}:_) ->
+        Token Time <$>
+          intersect (dayOfMonth $ if elem m [3, 5, 7, 10] then 15 else 13) td
+      _ -> Nothing
+  }
+
+ruleNoon :: Rule
+ruleNoon = Rule
+  { name = "noon"
+  , pattern =
+    [ regex "middag|(kl(\\.|okken)?)? tolv"
+    ]
+  , prod = \_ -> tt $ hour False 12
+  }
+
+ruleToday :: Rule
+ruleToday = Rule
+  { name = "today"
+  , pattern =
+    [ regex "i dag|idag"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 0
+  }
+
+ruleThisnextDayofweek :: Rule
+ruleThisnextDayofweek = Rule
+  { name = "this|next <day-of-week>"
+  , pattern =
+    [ regex "(kommende|neste)"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleTheDayBeforeYesterday :: Rule
+ruleTheDayBeforeYesterday = Rule
+  { name = "the day before yesterday"
+  , pattern =
+    [ regex "i forig\x00e5rs"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 2
+  }
+
+ruleBetweenTimeofdayAndTimeofdayInterval :: Rule
+ruleBetweenTimeofdayAndTimeofdayInterval = Rule
+  { name = "between <time-of-day> and <time-of-day> (interval)"
+  , pattern =
+    [ regex "mellom"
+    , Predicate isATimeOfDay
+    , regex "og"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNextCycle :: Rule
+ruleNextCycle = Rule
+  { name = "next <cycle>"
+  , pattern =
+    [ regex "neste|kommende"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleNamedmonth :: Rule
+ruleNamedmonth = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "januar|jan\\.?"
+    ]
+  , prod = \_ -> tt $ month 1
+  }
+
+ruleTheCycleOfTime :: Rule
+ruleTheCycleOfTime = Rule
+  { name = "the <cycle> of <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "(en|tet|et)? av"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain 0 td
+      _ -> Nothing
+  }
+
+ruleTimeofdayApproximately :: Rule
+ruleTimeofdayApproximately = Rule
+  { name = "<time-of-day> approximately"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(cirka|ca\\.|-?ish)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleOnDate :: Rule
+ruleOnDate = Rule
+  { name = "on <date>"
+  , pattern =
+    [ regex "den|p\x00e5"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth3 :: Rule
+ruleNamedmonth3 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "mars|mar\\.?"
+    ]
+  , prod = \_ -> tt $ month 3
+  }
+
+ruleDurationFromNow :: Rule
+ruleDurationFromNow = Rule
+  { name = "<duration> from now"
+  , pattern =
+    [ dimension Duration
+    , regex "fra (i dag|idag|n\x00e5)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleLunch :: Rule
+ruleLunch = Rule
+  { name = "lunch"
+  , pattern =
+    [ regex "(til )?middag"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 14
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleLastCycle :: Rule
+ruleLastCycle = Rule
+  { name = "last <cycle>"
+  , pattern =
+    [ regex "siste?|seneste|forrige"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleDdmm :: Rule
+ruleDdmm = Rule
+  { name = "dd/mm"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[\\/-](0?[1-9]|1[0-2])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (dd:mm:_)):_) -> do
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleAfternoon :: Rule
+ruleAfternoon = Rule
+  { name = "afternoon"
+  , pattern =
+    [ regex "ettermiddag(en)?"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 19
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleNamedmonth4 :: Rule
+ruleNamedmonth4 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "april|apr\\.?"
+    ]
+  , prod = \_ -> tt $ month 4
+  }
+
+ruleTimeBeforeLast :: Rule
+ruleTimeBeforeLast = Rule
+  { name = "<time> before last"
+  , pattern =
+    [ regex "siste"
+    , dimension Time
+    , regex "igjen"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth (-2) False td
+      _ -> Nothing
+  }
+
+ruleNamedmonthDayofmonthOrdinal :: Rule
+ruleNamedmonthDayofmonthOrdinal = Rule
+  { name = "<named-month> <day-of-month> (ordinal)"
+  , pattern =
+    [ Predicate isAMonth
+    , Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleChristmasEve :: Rule
+ruleChristmasEve = Rule
+  { name = "christmas eve"
+  , pattern =
+    [ regex "julaften?"
+    ]
+  , prod = \_ -> tt $ monthDay 12 24
+  }
+
+ruleInduringThePartofday :: Rule
+ruleInduringThePartofday = Rule
+  { name = "in|during the <part-of-day>"
+  , pattern =
+    [ regex "om|i"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedday5 :: Rule
+ruleNamedday5 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "fredag|fre\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 5
+  }
+
+ruleDayofmonthordinalNamedmonth :: Rule
+ruleDayofmonthordinalNamedmonth = Rule
+  { name = "<day-of-month>(ordinal) <named-month>"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleIntersectBy :: Rule
+ruleIntersectBy = Rule
+  { name = "intersect by \",\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex ","
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNthTimeAfterTime :: Rule
+ruleNthTimeAfterTime = Rule
+  { name = "nth <time> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "etter"
+    , 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
+  }
+
+ruleAfterDuration :: Rule
+ruleAfterDuration = Rule
+  { name = "after <duration>"
+  , pattern =
+    [ regex "etter"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt . withDirection TTime.After $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleTimeofdayLatent :: Rule
+ruleTimeofdayLatent = Rule
+  { name = "time-of-day (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 0 23
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ hour False v
+      _ -> Nothing
+  }
+
+ruleDayofmonthOrdinalOfNamedmonth :: Rule
+ruleDayofmonthOrdinalOfNamedmonth = Rule
+  { name = "<day-of-month> (ordinal) of <named-month>"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    , regex "av|i"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleFromTimeofdayTimeofdayInterval :: Rule
+ruleFromTimeofdayTimeofdayInterval = Rule
+  { name = "from <time-of-day> - <time-of-day> (interval)"
+  , pattern =
+    [ regex "(etter|fra)"
+    , Predicate isATimeOfDay
+    , regex "((men )?f\x00f8r)|\\-|tilogmed|til"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth2 :: Rule
+ruleNamedmonth2 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "februar|feb\\.?"
+    ]
+  , prod = \_ -> tt $ month 2
+  }
+
+ruleExactlyTimeofday :: Rule
+ruleExactlyTimeofday = Rule
+  { name = "exactly <time-of-day>"
+  , pattern =
+    [ regex "presis( kl.| klokken)?"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleInduringThePartofday2 :: Rule
+ruleInduringThePartofday2 = Rule
+  { name = "in|during the <part-of-day>"
+  , pattern =
+    [ regex "om|i"
+    , Predicate isAPartOfDay
+    , regex "en|ten"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleSeason :: Rule
+ruleSeason = Rule
+  { name = "season"
+  , pattern =
+    [ regex "sommer(en)"
+    ]
+  , prod = \_ ->
+      let from = monthDay 6 21
+          to = monthDay 9 23
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleBetweenDatetimeAndDatetimeInterval :: Rule
+ruleBetweenDatetimeAndDatetimeInterval = Rule
+  { name = "between <datetime> and <datetime> (interval)"
+  , pattern =
+    [ regex "mellom"
+    , dimension Time
+    , regex "og"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNewYearsEve :: Rule
+ruleNewYearsEve = Rule
+  { name = "new year's eve"
+  , pattern =
+    [ regex "nytt\x00e5rsaften?"
+    ]
+  , prod = \_ -> tt $ monthDay 12 31
+  }
+
+ruleDurationAgo :: Rule
+ruleDurationAgo = Rule
+  { name = "<duration> ago"
+  , pattern =
+    [ dimension Duration
+    , regex "siden"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt $ durationAgo dd
+      _ -> Nothing
+  }
+
+ruleByTheEndOfTime :: Rule
+ruleByTheEndOfTime = Rule
+  { name = "by the end of <time>"
+  , pattern =
+    [ regex "i slutten av"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $
+        interval TTime.Closed (cycleNth TG.Second 0, td)
+      _ -> Nothing
+  }
+
+ruleAfterWork :: Rule
+ruleAfterWork = Rule
+  { name = "after work"
+  , pattern =
+    [ regex "etter jobb"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 17, hour False 21)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleLastNCycle :: Rule
+ruleLastNCycle = Rule
+  { name = "last n <cycle>"
+  , pattern =
+    [ regex "siste?|seneste|forrige"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain (- v)
+      _ -> Nothing
+  }
+
+ruleChristmasDays :: Rule
+ruleChristmasDays = Rule
+  { name = "christmas days"
+  , pattern =
+    [ regex "romjul(a|en)"
+    ]
+  , prod = \_ -> tt $ interval TTime.Open (monthDay 12 24, monthDay 12 30)
+  }
+
+ruleTimeofdaySharp :: Rule
+ruleTimeofdaySharp = Rule
+  { name = "<time-of-day> sharp"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(sharp|presis)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleWithinDuration :: Rule
+ruleWithinDuration = Rule
+  { name = "within <duration>"
+  , pattern =
+    [ regex "innenfor"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        let from = cycleNth TG.Second 0
+            to = inDuration dd
+        in tt $ interval TTime.Open (from, to)
+      _ -> Nothing
+  }
+
+ruleMidnighteodendOfDay :: Rule
+ruleMidnighteodendOfDay = Rule
+  { name = "midnight|EOD|end of day"
+  , pattern =
+    [ regex "midnatt|EOD"
+    ]
+  , prod = \_ -> tt $ hour False 0
+  }
+
+ruleDayofmonthNonOrdinalNamedmonth :: Rule
+ruleDayofmonthNonOrdinalNamedmonth = Rule
+  { name = "<day-of-month> (non ordinal) <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ Predicate isNotLatent
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleAboutTimeofday :: Rule
+ruleAboutTimeofday = Rule
+  { name = "about <time-of-day>"
+  , pattern =
+    [ regex "(omkring|cirka|ca\\.)( kl\\.| klokken)?"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleUntilTimeofday :: Rule
+ruleUntilTimeofday = Rule
+  { name = "until <time-of-day>"
+  , pattern =
+    [ regex "(engang )?innen|f\x00f8r|opptil"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ withDirection TTime.Before td
+      _ -> Nothing
+  }
+
+ruleAtTimeofday :: Rule
+ruleAtTimeofday = Rule
+  { name = "at <time-of-day>"
+  , pattern =
+    [ regex "klokken|kl.|@"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth6 :: Rule
+ruleNamedmonth6 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "juni|jun\\.?"
+    ]
+  , prod = \_ -> tt $ month 6
+  }
+
+ruleNthTimeOfTime :: Rule
+ruleNthTimeOfTime = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "av|i"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token Time td1:_:Token Time td2:_) -> Token Time .
+        predNth (TOrdinal.value od - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNamedmonth8 :: Rule
+ruleNamedmonth8 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "august|aug\\.?"
+    ]
+  , prod = \_ -> tt $ month 8
+  }
+
+ruleTimePartofday :: Rule
+ruleTimePartofday = Rule
+  { name = "<time> <part-of-day>"
+  , pattern =
+    [ dimension Time
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleWeekend :: Rule
+ruleWeekend = Rule
+  { name = "week-end"
+  , pattern =
+    [ regex "((week(\\s|-)?end)|helg)(en|a)?"
+    ]
+  , prod = \_ -> do
+      from <- intersect (dayOfWeek 5) (hour False 18)
+      to <- intersect (dayOfWeek 1) (hour False 0)
+      tt $ interval TTime.Open (from, to)
+  }
+
+ruleEomendOfMonth :: Rule
+ruleEomendOfMonth = Rule
+  { name = "EOM|End of month"
+  , pattern =
+    [ regex "EOM"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Month 1
+  }
+
+ruleLastYear :: Rule
+ruleLastYear = Rule
+  { name = "Last year"
+  , pattern =
+    [ regex "i fjor"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Year $ - 1
+  }
+
+ruleNthTimeAfterTime2 :: Rule
+ruleNthTimeAfterTime2 = Rule
+  { name = "nth <time> after <time>"
+  , pattern =
+    [ regex "den"
+    , dimension Ordinal
+    , dimension Time
+    , regex "etter"
+    , 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
+  }
+
+ruleNextTime :: Rule
+ruleNextTime = Rule
+  { name = "next <time>"
+  , pattern =
+    [ regex "neste|kommende"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleOrdinalQuarterYear :: Rule
+ruleOrdinalQuarterYear = Rule
+  { name = "<ordinal> quarter <year>"
+  , pattern =
+    [ dimension Ordinal
+    , Predicate $ isGrain TG.Quarter
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:_:Token Time td:_) ->
+        tt $ cycleNthAfter False TG.Quarter (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleYyyymmdd :: Rule
+ruleYyyymmdd = Rule
+  { name = "yyyy-mm-dd"
+  , pattern =
+    [ regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (yy:mm:dd:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleTheOrdinalCycleAfterTime :: Rule
+ruleTheOrdinalCycleAfterTime = Rule
+  { name = "the <ordinal> <cycle> after <time>"
+  , pattern =
+    [ regex "den"
+    , dimension Ordinal
+    , dimension TimeGrain
+    , regex "etter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleIntersectByOfFromS :: Rule
+ruleIntersectByOfFromS = Rule
+  { name = "intersect by \"of\", \"from\", \"'s\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "i"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNextNCycle :: Rule
+ruleNextNCycle = Rule
+  { name = "next n <cycle>"
+  , pattern =
+    [ regex "neste"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleADuration :: Rule
+ruleADuration = Rule
+  { name = "a <duration>"
+  , pattern =
+    [ regex "(om )?en|ett|\x00e9n|et"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleMorning :: Rule
+ruleMorning = Rule
+  { name = "morning"
+  , pattern =
+    [ regex "morgen(en)?"
+    ]
+  , prod = \_ ->
+      let from = hour False 4
+          to = hour False 12
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleThisPartofday :: Rule
+ruleThisPartofday = Rule
+  { name = "this <part-of-day>"
+  , pattern =
+    [ regex "i|denne"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time . partOfDay <$>
+        intersect (cycleNth TG.Day 0) td
+      _ -> Nothing
+  }
+
+ruleThisCycle :: Rule
+ruleThisCycle = Rule
+  { name = "this <cycle>"
+  , pattern =
+    [ regex "denne|dette|i|n\x00e5v\x00e6rende"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 0
+      _ -> Nothing
+  }
+
+ruleThisTime :: Rule
+ruleThisTime = Rule
+  { name = "this <time>"
+  , pattern =
+    [ regex "(denne|dette|i|den her)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleDayofmonthNonOrdinalOfNamedmonth :: Rule
+ruleDayofmonthNonOrdinalOfNamedmonth = Rule
+  { name = "<day-of-month> (non ordinal) of <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , regex "av|i"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleAfterLunch :: Rule
+ruleAfterLunch = Rule
+  { name = "after lunch"
+  , pattern =
+    [ regex "etter (frokost|middag|lunsj|lunch)"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 13, hour False 17)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleOnANamedday :: Rule
+ruleOnANamedday = Rule
+  { name = "on a named-day"
+  , pattern =
+    [ regex "p\x00e5 en"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleYearLatent :: Rule
+ruleYearLatent = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $
+       liftM2 (||) (isIntegerBetween (- 10000) 0) (isIntegerBetween 25 999)
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ year n
+      _ -> Nothing
+  }
+
+ruleYesterday :: Rule
+ruleYesterday = Rule
+  { name = "yesterday"
+  , pattern =
+    [ regex "i g\x00e5r|ig\x00e5r"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 1
+  }
+
+ruleSeason2 :: Rule
+ruleSeason2 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "vinter(en)"
+    ]
+  , prod = \_ ->
+      let from = monthDay 12 21
+          to = monthDay 3 20
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleAfterTimeofday :: Rule
+ruleAfterTimeofday = Rule
+  { name = "after <time-of-day>"
+  , pattern =
+    [ regex "(engang )?etter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ withDirection TTime.After td
+      _ -> Nothing
+  }
+
+ruleChristmas :: Rule
+ruleChristmas = Rule
+  { name = "christmas"
+  , pattern =
+    [ regex "((1\\.?)|f\x00f8rste)? ?juledag"
+    ]
+  , prod = \_ -> tt $ monthDay 12 25
+  }
+
+ruleNight :: Rule
+ruleNight = Rule
+  { name = "night"
+  , pattern =
+    [ regex "natt(en)?"
+    ]
+  , prod = \_ ->
+      let from = hour False 0
+          to = hour False 4
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleDayofmonthOrdinal :: Rule
+ruleDayofmonthOrdinal = Rule
+  { name = "<day-of-month> (ordinal)"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):_) ->
+        tt . mkLatent $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleOrdinalCycleAfterTime :: Rule
+ruleOrdinalCycleAfterTime = Rule
+  { name = "<ordinal> <cycle> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "etter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleOrdinalCycleOfTime :: Rule
+ruleOrdinalCycleOfTime = Rule
+  { name = "<ordinal> <cycle> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "av|i|fra"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleNamedmonth5 :: Rule
+ruleNamedmonth5 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "mai"
+    ]
+  , prod = \_ -> tt $ month 5
+  }
+
+ruleNamedday7 :: Rule
+ruleNamedday7 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "s\x00f8ndag|s\x00f8n\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 7
+  }
+
+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 False h m
+      _ -> Nothing
+  }
+
+ruleTonight :: Rule
+ruleTonight = Rule
+  { name = "tonight"
+  , pattern =
+    [ regex "i kveld"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 18, hour False 0)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleYear :: Rule
+ruleYear = Rule
+  { name = "year"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1000 2100
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt $ year n
+      _ -> Nothing
+  }
+
+ruleNamedmonth10 :: Rule
+ruleNamedmonth10 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "oktober|okt\\.?"
+    ]
+  , prod = \_ -> tt $ month 10
+  }
+
+ruleHalloweenDay :: Rule
+ruleHalloweenDay = Rule
+  { name = "halloween day"
+  , pattern =
+    [ regex "hall?owe?en"
+    ]
+  , prod = \_ -> tt $ monthDay 10 31
+  }
+
+ruleNamedmonthDayofmonthNonOrdinal :: Rule
+ruleNamedmonthDayofmonthNonOrdinal = 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
+  }
+
+ruleAbsorptionOfAfterNamedDay :: Rule
+ruleAbsorptionOfAfterNamedDay = Rule
+  { name = "absorption of , after named day"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex ","
+    ]
+  , prod = \tokens -> case tokens of
+      (x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleLastDayofweekOfTime :: Rule
+ruleLastDayofweekOfTime = Rule
+  { name = "last <day-of-week> of <time>"
+  , pattern =
+    [ regex "siste"
+    , Predicate isADayOfWeek
+    , regex "av|i"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ predLastOf td1 td2
+      _ -> Nothing
+  }
+
+ruleFathersDay :: Rule
+ruleFathersDay = Rule
+  { name = "Father's Day"
+  , pattern =
+    [ regex "farsdag"
+    ]
+  , prod = \_ -> tt $ nthDOWOfMonth 1 7 11
+  }
+
+ruleCycleBeforeTime :: Rule
+ruleCycleBeforeTime = Rule
+  { name = "<cycle> before <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "f\x00f8r"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain (-1) td
+      _ -> Nothing
+  }
+
+ruleDdmmyyyy :: Rule
+ruleDdmmyyyy = Rule
+  { name = "dd/mm/yyyy"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[\\/-](0?[1-9]|1[0-2])[\\/-](\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (dd:mm:yy:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleTimeofdayTimeofdayInterval :: Rule
+ruleTimeofdayTimeofdayInterval = Rule
+  { name = "<time-of-day> - <time-of-day> (interval)"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\-|tilogmed|til"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth11 :: Rule
+ruleNamedmonth11 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "november|nov\\.?"
+    ]
+  , prod = \_ -> tt $ month 11
+  }
+
+ruleDurationAfterTime :: Rule
+ruleDurationAfterTime = Rule
+  { name = "<duration> after <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "etter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_:Token Time td:_) ->
+        tt $ durationAfter dd td
+      _ -> Nothing
+  }
+
+ruleOrdinalQuarter :: Rule
+ruleOrdinalQuarter = 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
+  }
+
+ruleNamedday3 :: Rule
+ruleNamedday3 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "onsdag|ons\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 3
+  }
+
+ruleTheDayofmonthOrdinal :: Rule
+ruleTheDayofmonthOrdinal = Rule
+  { name = "the <day-of-month> (ordinal)"
+  , pattern =
+    [ regex "den"
+    , Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal (OrdinalData {TOrdinal.value = v}):_) ->
+        tt $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleDurationBeforeTime :: Rule
+ruleDurationBeforeTime = Rule
+  { name = "<duration> before <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "f\x00f8r"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_:Token Time td:_) ->
+        tt $ durationBefore dd td
+      _ -> Nothing
+  }
+
+rulePartofdayOfTime :: Rule
+rulePartofdayOfTime = Rule
+  { name = "<part-of-day> of <time>"
+  , pattern =
+    [ Predicate isAPartOfDay
+    , regex "(en |ten )?den"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleEoyendOfYear :: Rule
+ruleEoyendOfYear = Rule
+  { name = "EOY|End of year"
+  , pattern =
+    [ regex "EOY"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Year 1
+  }
+
+ruleTomorrow :: Rule
+ruleTomorrow = Rule
+  { name = "tomorrow"
+  , pattern =
+    [ regex "i morgen|imorgen"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 1
+  }
+
+ruleMothersDay :: Rule
+ruleMothersDay = Rule
+  { name = "Mother's Day"
+  , pattern =
+    [ regex "morsdag"
+    ]
+  , prod = \_ -> tt $ nthDOWOfMonth 1 7 2
+  }
+
+ruleTimeofdayOclock :: Rule
+ruleTimeofdayOclock = Rule
+  { name = "<time-of-day> o'clock"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "h"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth9 :: Rule
+ruleNamedmonth9 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "september|sept?\\.?"
+    ]
+  , prod = \_ -> tt $ month 9
+  }
+
+ruleDayofmonthordinalNamedmonthYear :: Rule
+ruleDayofmonthordinalNamedmonthYear = 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
+         y <- parseInt match
+         dom <- intersectDOM td token
+         Token Time <$> intersect dom (year y)
+      _ -> 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 False h m s
+      _ -> Nothing
+  }
+
+ruleTimezone :: Rule
+ruleTimezone = Rule
+  { name = "<time> timezone"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\b(YEKT|YEKST|YAKT|YAKST|WITA|WIT|WIB|WGT|WGST|WFT|WET|WEST|WAT|WAST|VUT|VLAT|VLAST|VET|UZT|UYT|UYST|UTC|ULAT|TVT|TMT|TLT|TKT|TJT|TFT|TAHT|SST|SRT|SGT|SCT|SBT|SAST|SAMT|RET|PYT|PYST|PWT|PST|PONT|PMST|PMDT|PKT|PHT|PHOT|PGT|PETT|PETST|PET|PDT|OMST|OMSST|NZST|NZDT|NUT|NST|NPT|NOVT|NOVST|NFT|NDT|NCT|MYT|MVT|MUT|MST|MSK|MSD|MMT|MHT|MDT|MAWT|MART|MAGT|MAGST|LINT|LHST|LHDT|KUYT|KST|KRAT|KRAST|KGT|JST|IST|IRST|IRKT|IRKST|IRDT|IOT|IDT|ICT|HOVT|HKT|GYT|GST|GMT|GILT|GFT|GET|GAMT|GALT|FNT|FKT|FKST|FJT|FJST|EST|EGT|EGST|EET|EEST|EDT|ECT|EAT|EAST|EASST|DAVT|ChST|CXT|CVT|CST|COT|CLT|CLST|CKT|CHAST|CHADT|CET|CEST|CDT|CCT|CAT|CAST|BTT|BST|BRT|BRST|BOT|BNT|AZT|AZST|AZOT|AZOST|AWST|AWDT|AST|ART|AQTT|ANAT|ANAST|AMT|AMST|ALMT|AKST|AKDT|AFT|AEST|AEDT|ADT|ACST|ACDT)\\b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       Token RegexMatch (GroupMatch (tz:_)):
+       _) -> Token Time <$> inTimezone tz td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleADuration
+  , ruleAboutTimeofday
+  , ruleAbsorptionOfAfterNamedDay
+  , ruleAfterDuration
+  , ruleAfterLunch
+  , ruleAfterTimeofday
+  , ruleAfterWork
+  , ruleAfternoon
+  , ruleAtTimeofday
+  , ruleBetweenDatetimeAndDatetimeInterval
+  , ruleBetweenTimeofdayAndTimeofdayInterval
+  , ruleByTheEndOfTime
+  , ruleChristmas
+  , ruleChristmasEve
+  , ruleChristmasDays
+  , ruleConstitutionDay
+  , ruleCycleAfterTime
+  , ruleCycleBeforeTime
+  , ruleDatetimeDatetimeInterval
+  , ruleDayofmonthNonOrdinalNamedmonth
+  , ruleDayofmonthNonOrdinalOfNamedmonth
+  , ruleDayofmonthOrdinal
+  , ruleDayofmonthOrdinalOfNamedmonth
+  , ruleDayofmonthordinalNamedmonth
+  , ruleDayofmonthordinalNamedmonthYear
+  , ruleDdmm
+  , ruleDdmmyyyy
+  , ruleDurationAfterTime
+  , ruleDurationAgo
+  , ruleDurationBeforeTime
+  , ruleDurationFromNow
+  , ruleEomendOfMonth
+  , ruleEoyendOfYear
+  , ruleEvening
+  , ruleExactlyTimeofday
+  , ruleFathersDay
+  , ruleFromDatetimeDatetimeInterval
+  , ruleFromTimeofdayTimeofdayInterval
+  , ruleHalloweenDay
+  , ruleHhmm
+  , ruleHhmmss
+  , ruleHourofdayIntegerAsRelativeMinutes
+  , ruleHourofdayQuarter
+  , ruleHourofdayHalf
+  , ruleInDuration
+  , ruleInduringThePartofday
+  , ruleInduringThePartofday2
+  , ruleIntersect
+  , ruleIntersectBy
+  , ruleIntersectByOfFromS
+  , ruleLastCycle
+  , ruleLastCycleOfTime
+  , ruleLastDayofweekOfTime
+  , ruleLastNCycle
+  , ruleLastTime
+  , ruleLastYear
+  , ruleLunch
+  , ruleMidnighteodendOfDay
+  , ruleMonthDdddInterval
+  , ruleMorning
+  , ruleMothersDay
+  , ruleNamedday
+  , ruleNamedday2
+  , ruleNamedday3
+  , ruleNamedday4
+  , ruleNamedday5
+  , ruleNamedday6
+  , ruleNamedday7
+  , ruleNamedmonth
+  , ruleNamedmonth10
+  , ruleNamedmonth11
+  , ruleNamedmonth12
+  , ruleNamedmonth2
+  , ruleNamedmonth3
+  , ruleNamedmonth4
+  , ruleNamedmonth5
+  , ruleNamedmonth6
+  , ruleNamedmonth7
+  , ruleNamedmonth8
+  , ruleNamedmonth9
+  , ruleNamedmonthDayofmonthNonOrdinal
+  , ruleNamedmonthDayofmonthOrdinal
+  , ruleNewYearsDay
+  , ruleNewYearsEve
+  , ruleNextCycle
+  , ruleNextNCycle
+  , ruleNextTime
+  , ruleNight
+  , ruleNoon
+  , ruleNow
+  , ruleNthTimeAfterTime
+  , ruleNthTimeAfterTime2
+  , ruleNthTimeOfTime
+  , ruleNthTimeOfTime2
+  , ruleOnANamedday
+  , ruleOnDate
+  , ruleOrdinalCycleAfterTime
+  , ruleOrdinalCycleOfTime
+  , ruleOrdinalQuarter
+  , ruleOrdinalQuarterYear
+  , rulePartofdayOfTime
+  , ruleRelativeMinutesAfterpastIntegerHourofday
+  , ruleRelativeMinutesTotillbeforeIntegerHourofday
+  , ruleHalfAfterpastIntegerHourofday
+  , ruleHalfTotillbeforeIntegerHourofday
+  , ruleQuarterAfterpastIntegerHourofday
+  , ruleQuarterTotillbeforeIntegerHourofday
+  , ruleSeason
+  , ruleSeason2
+  , ruleTheCycleAfterTime
+  , ruleTheCycleBeforeTime
+  , ruleTheCycleOfTime
+  , ruleTheDayAfterTomorrow
+  , ruleTheDayBeforeYesterday
+  , ruleTheDayofmonthNonOrdinal
+  , ruleTheDayofmonthOrdinal
+  , ruleTheIdesOfNamedmonth
+  , ruleTheOrdinalCycleAfterTime
+  , ruleTheOrdinalCycleOfTime
+  , ruleThisCycle
+  , ruleThisPartofday
+  , ruleThisTime
+  , ruleThisnextDayofweek
+  , ruleTimeAfterNext
+  , ruleTimeBeforeLast
+  , ruleTimePartofday
+  , ruleTimeofdayApproximately
+  , ruleTimeofdayLatent
+  , ruleTimeofdayOclock
+  , ruleTimeofdaySharp
+  , ruleTimeofdayTimeofdayInterval
+  , ruleToday
+  , ruleTomorrow
+  , ruleTonight
+  , ruleUntilTimeofday
+  , ruleValentinesDay
+  , ruleWeekend
+  , ruleWithinDuration
+  , ruleYear
+  , ruleYearLatent
+  , ruleYearLatent2
+  , ruleYesterday
+  , ruleYyyymmdd
+  , ruleTimezone
+  ]
diff --git a/Duckling/Time/PL/Corpus.hs b/Duckling/Time/PL/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/PL/Corpus.hs
@@ -0,0 +1,761 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.PL.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Time.Corpus
+import Duckling.Time.Types hiding (Month)
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Testing.Types hiding (examples)
+
+corpus :: Corpus
+corpus = (testContext {lang = PL}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (datetime (2013, 2, 12, 4, 30, 0) Second)
+             [ "teraz"
+             , "w tej chwili"
+             , "w tym momencie"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "dziś"
+             , "dzis"
+             , "dzisiaj"
+             , "obecnego dnia"
+             , "tego dnia"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "wczoraj"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "jutro"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "pojutrze"
+             , "po jutrze"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "poniedziałek"
+             , "pon."
+             , "ten poniedziałek"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "Poniedziałek, 18 Luty"
+             , "Poniedziałek, Luty 18"
+             , "Poniedziałek 18tego Lutego"
+             , "Poniedziałek 18-tego Lutego"
+             , "Poniedziałek, 18-tego Lutego"
+             , "poniedzialek, 18go Lutego"
+             , "Pon, 18 Luty"
+             ]
+  , examples (datetime (2013, 2, 2, 0, 0, 0) Day)
+             [ "Sobota, 2ego Lutego"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "Wtorek"
+             , "nastepny wtorek"
+             , "wt."
+             , "wtr."
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "czwartek"
+             , "ten czwartek"
+             , "czw"
+             , "czw."
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "piatek"
+             , "ten piatek"
+             , "pia"
+             , "pia."
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "sobota"
+             , "ta sobota"
+             , "sob"
+             , "sob."
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "niedziela"
+             , "ta niedziela"
+             , "niedz"
+             , "niedz."
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "pierwszy marca"
+             , "pierwszego marca"
+             , "marzec pierwszy"
+             , "1szy marca"
+             , "1szy marzec"
+             ]
+  , examples (datetime (2013, 3, 3, 0, 0, 0) Day)
+             [ "marzec 3"
+             , "marzec 3ci"
+             , "3go marca"
+             ]
+  , examples (datetime (2015, 3, 3, 0, 0, 0) Day)
+             [ "3ci marca 2015"
+             , "marzec 3ci 2015"
+             , "3 marzec 2015"
+             , "marzec 3 2015"
+             , "trzeci marca 2015"
+             , "3/3/2015"
+             , "3/3/15"
+             , "2015-3-3"
+             , "2015-03-03"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "15 Luty"
+             , "15 Lutego"
+             , "Luty 15"
+             , "15-tego Lutego"
+             , "2/15"
+             , "Pietnastego Lutego"
+             , "Piętnasty Luty"
+             , "Luty Piętnastego"
+             ]
+  , examples (datetime (2013, 8, 8, 0, 0, 0) Day)
+             [ "Sierpień 8"
+             , "Sie 8"
+             , "Sier 8"
+             , "Sierp. 8"
+             , "8 Sie."
+             , "Ósmy Sie."
+             , "Osmego Sie."
+             ]
+  , examples (datetime (2014, 10, 0, 0, 0, 0) Month)
+             [ "Październik 2014"
+             , "Pazdziernika 2014"
+             ]
+  , examples (datetime (1974, 10, 31, 0, 0, 0) Day)
+             [ "10/31/1974"
+             , "10/31/74"
+             , "10-31-74"
+             ]
+  , examples (datetime (2015, 4, 14, 0, 0, 0) Day)
+             [ "14kwiecien 2015"
+             , "Kwiecień 14, 2015"
+             , "14tego Kwietnia 15"
+             , "14-tego Kwietnia 15"
+             , "14-ty Kwietnia 15"
+             , "Czternasty Kwietnia 15"
+             , "Czternastego Kwietnia 15"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "nastepny wtorek"
+             , "kolejny wtorek"
+             , "kolejnego wtorku"
+             , "nastepnego wtorku"
+             , "wtorek w przyszłym tygodniu"
+             , "wtorek za tydzień"
+             ]
+  , examples (datetime (2013, 2, 22, 0, 0, 0) Day)
+             [ "piatek po nastepnym"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "nastepny Marzec"
+             ]
+  , examples (datetime (2014, 3, 0, 0, 0, 0) Month)
+             [ "Marzec po nastepnym"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "Niedziela, 10 Luty"
+             , "Niedziela, Luty 10"
+             , "Niedziela, 10tego Luty"
+             , "Niedziela, 10-tego Luty"
+             , "Niedziela, 10-ty Lutego"
+             , "Niedziela, 10tego Lutego"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "Śr., Luty13"
+             , "Śr., 13Luty"
+             , "sr, 13Luty"
+             , "sr, 13tego Lutego"
+             , "Śro., 13Lutego"
+             , "Środa trzynastego lutego"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "Poniedziałek, Luty 18"
+             , "Poniedziałek, 18 Lutego"
+             , "Pon, Luty 18"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Week)
+             [ "ten tydzien"
+             , "ten tydzień"
+             , "ten tyg"
+             , "tym tygodniu"
+             ]
+  , examples (datetime (2013, 2, 4, 0, 0, 0) Week)
+             [ "ostatni tydzien"
+             , "poprzedniego tygodnia"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Week)
+             [ "nastepny tydzien"
+             , "nastepnego tygodnia"
+             ]
+  , examples (datetime (2013, 1, 0, 0, 0, 0) Month)
+             [ "ostatni miesiac"
+             , "poprzedni miesiac"
+             , "poprzedniego miesiąca"
+             , "po przedniego miesiąca"
+             , "ostatniego miesiaca"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "nastepnego miesiaca"
+             ]
+  , examples (datetime (2013, 1, 1, 0, 0, 0) Quarter)
+             [ "ten kwartał"
+             , "tego kwartału"
+             , "tym kwartale"
+             ]
+  , examples (datetime (2013, 4, 1, 0, 0, 0) Quarter)
+             [ "nastepny kwartał"
+             , "następny kwartal"
+             , "kolejnym kwartale"
+             ]
+  , examples (datetime (2013, 7, 1, 0, 0, 0) Quarter)
+             [ "trzeci kwartał"
+             ]
+  , examples (datetime (2018, 10, 1, 0, 0, 0) Quarter)
+             [ "4ty kwartał 2018"
+             ]
+  , examples (datetime (2012, 0, 0, 0, 0, 0) Year)
+             [ "poprzedni rok"
+             , "ostatni rok"
+             ]
+  , examples (datetime (2013, 0, 0, 0, 0, 0) Year)
+             [ "ten rok"
+             , "tym roku"
+             , "obecny rok"
+             , "w obecny rok"
+             , "w obecnym roku"
+             ]
+  , examples (datetime (2014, 0, 0, 0, 0, 0) Year)
+             [ "w kolejnym roku"
+             , "kolejny rok"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "poprzednia niedziela"
+             , "niedziela z ostatniego tygodnia"
+             , "niedziela ostatniego tygodnia"
+             , "niedziela poprzedniego tygodnia"
+             , "ostatnia niedziela"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "ostatni wtorek"
+             , "poprzedni wtorek"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "nastepny wtorek"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "nastepna środa"
+             ]
+  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
+             [ "sroda nastepnego tygodnia"
+             , "środa w przyszłym tygodniu"
+             , "środa za tydzień"
+             ]
+  , examples (datetime (2013, 2, 22, 0, 0, 0) Day)
+             [ "piatek nastepnego tygodnia"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "poniedzialek tego tygodnia"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "wtorek tego tygodnia"
+             , "wtorek w tym tygodniu"
+             , "ten wtorek"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "środa w tym tygodniu"
+             , "ta środa"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "pojutrze"
+             , "po jutrze"
+             , "dzień po jutrze"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "dzień przed wczoraj"
+             ]
+  , examples (datetime (2013, 3, 25, 0, 0, 0) Day)
+             [ "ostatni Poniedziałek Marca"
+             ]
+  , examples (datetime (2014, 3, 30, 0, 0, 0) Day)
+             [ "ostatnia Niedziela w Marcu 2014"
+             , "ostatnia Niedziela marca 2014"
+             ]
+  , examples (datetime (2013, 10, 3, 0, 0, 0) Day)
+             [ "trzeci dzień października"
+             , "trzeci dzień w październiku"
+             ]
+  , examples (datetime (2014, 10, 6, 0, 0, 0) Week)
+             [ "pierwszy tydzień października 2014"
+             , "pierwszy tydzien w październiku 2014"
+             ]
+  , examples (datetime (2015, 10, 31, 0, 0, 0) Day)
+             [ "ostatni dzień w październiku 2015"
+             , "ostatni dzień października 2015"
+             ]
+  , examples (datetime (2014, 9, 22, 0, 0, 0) Week)
+             [ "ostatni tydzień we wrześniu 2014"
+             , "ostatni tydzień września 2014"
+             ]
+  , examples (datetime (2013, 10, 1, 0, 0, 0) Day)
+             [ "pierwszy wtorek w październiku"
+             , "pierwszy wtorek października"
+             ]
+  , examples (datetime (2014, 9, 16, 0, 0, 0) Day)
+             [ "trzeci wtorek we wrześniu 2014"
+             , "trzeci wtorek września 2014"
+             ]
+  , examples (datetime (2014, 10, 1, 0, 0, 0) Day)
+             [ "pierwsza środa w październiku 2014"
+             , "pierwsza środa października 2014"
+             ]
+  , examples (datetime (2014, 10, 8, 0, 0, 0) Day)
+             [ "druga środa w październiku 2014"
+             , "druga środa października 2014"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 0, 0) Hour)
+             [ "o 3 rano"
+             , "3 rano"
+             , "3 z rana"
+             , "o trzeciej rano"
+             , "o trzeciej z rana"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 18, 0) Minute)
+             [ "3:18 rano"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "o trzeciej"
+             , "o 3 po południu"
+             -- , "3 po południu" t14902576
+             , "3 popołudniu"
+             , "trzecia popoludniu"
+             , "o trzeciej popoludniu"
+             , "piętnasta godzina"
+             , "15sta godzina"
+             , "o piętnastej"
+             , "o 15stej"
+             ]
+  , examples (datetime (2013, 2, 12, 18, 0, 0) Hour)
+             [ -- "6 po południu" t14902576
+               "6 popołudniu"
+             , "szósta popoludniu"
+             , "o szostej popoludniu"
+             , "o 18stej"
+             , "osiemnasta godzina"
+             , "o osiemnastej"
+             ]
+  , examples (datetime (2013, 2, 12, 19, 0, 0) Hour)
+             [ -- "7 po południu" t14902576
+               "7 popołudniu"
+             , "siódma popoludniu"
+             , "o siodmej popoludniu"
+             , "o dziewiętnastej"
+             , "dziewietnasta godzina"
+             ]
+  , examples (datetime (2013, 2, 12, 20, 0, 0) Hour)
+             [ "8 wieczorem"
+             , "8 popołudniu"
+             , "osma w nocy"
+             , "ósma wieczorem"
+             , "dwudziesta godzina"
+             ]
+  , examples (datetime (2013, 2, 12, 21, 0, 0) Hour)
+             [ "dziewiata wieczorem"
+             , "dziewiąta popołudniu"
+             , "dziewiata po południu"
+             , "dziewiąta wieczorem"
+             , "dziewiąta nocą"
+             , "dziewiąta w nocy"
+             , "9 wieczorem"
+             , "9 popołudniu"
+             -- , "9 po południu" t14902576
+             , "9 wieczorem"
+             , "9 nocą"
+             , "9 w nocy"
+             , "o dziewiatej w nocy"
+             , "dwudziesta pierwsza godzina"
+             , "dwudziestapierwsza godzina"
+             ]
+  , examples (datetime (2013, 2, 12, 22, 0, 0) Hour)
+             [ "dziesiąta wieczorem"
+             , "dziesiata popołudniu"
+             , "dziesiata po południu"
+             , "dziesiata wieczorem"
+             , "dziesiata nocą"
+             , "10 w nocy"
+             , "o dziesiatej w nocy"
+             , "o dwudziestej drugiej"
+             ]
+  , examples (datetime (2013, 2, 12, 23, 0, 0) Hour)
+             [ "jedenasta wieczorem"
+             , "jedenasta w nocy"
+             , "11 w nocy"
+             , "11 wieczorem"
+             , "o jedenastej wieczorem"
+             , "o dwudziestejtrzeciej"
+             ]
+  , examples (datetime (2013, 2, 13, 2, 0, 0) Hour)
+             [ "jutro o drugiej"
+             ]
+  , examples (datetime (2013, 2, 14, 2, 0, 0) Hour)
+             [ "po jutrze o drugiej"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "około 3 po południu"
+             , "około trzeciej"
+             , "koło trzeciej"
+             , "o koło trzeciej"
+             , "mniej wiecej o 3"
+             , "tak o 15stej"
+             ]
+  , examples (datetime (2013, 2, 13, 17, 0, 0) Hour)
+             [ "jutro równo o piątej popołudniu"
+             , "jutro równo o 17-stej"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 15, 0) Minute)
+             [ "piętnaście po trzeciej"
+             , "15 po trzeciej"
+             , "kwadrans po 3"
+             , "trzecia piętnaście"
+             , "15:15"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 20, 0) Minute)
+             [ "20 po 3"
+             , "3:20"
+             , "3:20 w poludnie"
+             , "trzecia dwadzieścia"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 30, 0) Minute)
+             [ "w pół do szesnastej"
+             , "pol po trzeciej"
+             , "15:30"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 30, 0) Minute)
+             [ "3:30"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 23, 24) Second)
+             [ "15:23:24"
+             ]
+  , examples (datetime (2013, 2, 12, 11, 45, 0) Minute)
+             [ "kwadrans do południa"
+             , "kwadrans przed południem"
+             , "kwadrans do 12stej"
+             , "11:45"
+             ]
+  , examples (datetime (2013, 2, 12, 20, 0, 0) Hour)
+             [ "8 wieczorem"
+             , "8 tego wieczora"
+             ]
+  , examples (datetime (2013, 9, 20, 19, 30, 0) Minute)
+             [ "o 7:30 popołudniu Piatek, 20 Wrzesień"
+             , "o 7:30 popołudniu Piatek, Wrzesień 20"
+             ]
+  , examples (datetime (2013, 2, 16, 9, 0, 0) Hour)
+             [ "o 9 rano w Sobote"
+             , "w Sobote na 9 rano"
+             ]
+  , examples (datetime (2014, 7, 18, 19, 0, 0) Minute)
+             [ "Pia, Lip 18, 2014 19:00"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 30, 1) Second)
+             [ "w sekundę"
+             , "za sekundę"
+             , "sekunde od teraz"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 31, 0) Second)
+             [ -- "za minutę" t14902624
+               "za jedną minutę"
+             , "przez minutę"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 32, 0) Second)
+             [ "w 2 minuty"
+             , "za jeszcze 2 minuty"
+             , "2 minuty od teraz"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Second)
+             [ "w 60 minut"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 0, 0) Second)
+             [ "w pół godziny"
+             ]
+  , examples (datetime (2013, 2, 12, 7, 0, 0) Second)
+             [ "w 2.5 godziny"
+             , "w 2 i pół godziny"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Minute)
+             [ "w godzinę"
+             , "w 1h"
+             , "w przeciągu godziny"
+             ]
+  , examples (datetime (2013, 2, 12, 7, 30, 0) Minute)
+             [ "w kilka godzin"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 30, 0) Minute)
+             [ "w 24 godziny"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 0, 0) Hour)
+             [ "w jeden dzień"
+             , "dzień od dziś"
+             ]
+  , examples (datetime (2016, 2, 0, 0, 0, 0) Month)
+             [ "3 lata od dziś"
+             ]
+  , examples (datetime (2013, 2, 19, 4, 0, 0) Hour)
+             [ "w 7 dni"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "w jeden tydzień"
+             , "w tydzień"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 0, 0) Second)
+             [ "za około pół godziny"
+             , "za jakieś pół godziny"
+             ]
+  , examples (datetime (2013, 2, 5, 4, 0, 0) Hour)
+             [ "7 dni temu"
+             ]
+  , examples (datetime (2013, 1, 29, 4, 0, 0) Hour)
+             [ "14 dni temu"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "tydzien temu"
+             , "jeden tydzień temu"
+             , "1 tydzień temu"
+             ]
+  , examples (datetime (2013, 1, 22, 0, 0, 0) Day)
+             [ "trzy tygodnie temu"
+             ]
+  , examples (datetime (2012, 11, 12, 0, 0, 0) Day)
+             [ "trzy miesiące temu"
+             ]
+  , examples (datetime (2011, 2, 0, 0, 0, 0) Month)
+             [ "dwa lata temu"
+             ]
+  , examples (datetime (2013, 2, 19, 4, 0, 0) Hour)
+             [ "7 dni potem"
+             ]
+  , examples (datetime (2013, 2, 26, 4, 0, 0) Hour)
+             [ "14 dni później"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "tydzień później"
+             , "jeden tydzień później"
+             , "1 tydzień później"
+             ]
+  , examples (datetime (2013, 3, 5, 0, 0, 0) Day)
+             [ "trzy tygodnie później"
+             ]
+  , examples (datetime (2013, 5, 12, 0, 0, 0) Day)
+             [ "trzy miesiące później"
+             ]
+  , examples (datetime (2015, 2, 0, 0, 0, 0) Month)
+             [ "dwa lata później"
+             ]
+  , examples (datetimeInterval ((2013, 6, 21, 0, 0, 0), (2013, 9, 24, 0, 0, 0)) Day)
+             [ "to lato"
+             , "w to lato"
+             ]
+  , examples (datetimeInterval ((2012, 12, 21, 0, 0, 0), (2013, 3, 21, 0, 0, 0)) Day)
+             [ "ta zima"
+             , "tej zimy"
+             ]
+  , examples (datetime (2013, 12, 24, 0, 0, 0) Day)
+             [ "Wigilia Bożego Narodzenia"
+             , "Wigilia"
+             ]
+  , examples (datetime (2013, 12, 25, 0, 0, 0) Day)
+             [ "święta Bożego Narodzenia"
+             , "boże narodzenie"
+             ]
+  , examples (datetime (2013, 12, 31, 0, 0, 0) Day)
+             [ "sylwester"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "walentynki"
+             ]
+  , examples (datetime (2013, 5, 12, 0, 0, 0) Day)
+             [ "Dzień Mamy"
+             ]
+  , examples (datetime (2013, 6, 16, 0, 0, 0) Day)
+             [ "Dzień Taty"
+             ]
+  , examples (datetime (2013, 10, 31, 0, 0, 0) Day)
+             [ "halloween"
+             ]
+  , examples (datetime (2013, 11, 28, 0, 0, 0) Day)
+             [ "dzień dziękczynienia"
+             , "dziękczynienie"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 18, 0, 0), (2013, 2, 13, 0, 0, 0)) Hour)
+             [ "ten wieczór"
+             , "dzisiejszy wieczór"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 18, 0, 0), (2013, 2, 14, 0, 0, 0)) Hour)
+             [ "jutrzejszy wieczór"
+             , "Środowy wieczór"
+             , "jutrzejsza noc"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 18, 0, 0), (2013, 2, 12, 0, 0, 0)) Hour)
+             [ "wczorajszy wieczór"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 18, 0, 0), (2013, 2, 18, 0, 0, 0)) Hour)
+             [ "ten week-end"
+             , "ten weekend"
+             , "ten wekend"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 4, 0, 0), (2013, 2, 18, 12, 0, 0)) Hour)
+             [ "poniedziałkowy poranek"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 29, 58), (2013, 2, 12, 4, 30, 0)) Second)
+             [ "ostatnie 2 sekundy"
+             , "ostatnie dwie sekundy"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 1), (2013, 2, 12, 4, 30, 4)) Second)
+             [ "kolejne 3 sekundy"
+             , "kolejne trzy sekundy"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 28, 0), (2013, 2, 12, 4, 30, 0)) Minute)
+             [ "ostatnie 2 minuty"
+             , "ostatnie dwie minuty"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 31, 0), (2013, 2, 12, 4, 34, 0)) Minute)
+             [ "kolejne 3 minuty"
+             , "nastepne trzy minuty"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 3, 0, 0), (2013, 2, 12, 4, 0, 0)) Hour)
+             [ "ostatnia 1 godzina"
+             , "poprzednia jedna godzina"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 5, 0, 0), (2013, 2, 12, 8, 0, 0)) Hour)
+             [ "kolejne 3 godziny"
+             , "kolejne trzy godziny"
+             ]
+  , examples (datetimeInterval ((2013, 2, 10, 0, 0, 0), (2013, 2, 12, 0, 0, 0)) Day)
+             [ "ostatnie 2 dni"
+             , "ostatnie dwa dni"
+             , "poprzednie 2 dni"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
+             [ "nastepne 3 dni"
+             , "nastepne trzy dni"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
+             [ "nastepne kilka dni"
+             ]
+  , examples (datetimeInterval ((2013, 1, 28, 0, 0, 0), (2013, 2, 11, 0, 0, 0)) Week)
+             [ "ostatnie 2 tygodnie"
+             , "ostatnie dwa tygodnie"
+             , "poprzednie 2 tygodnie"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 0, 0, 0), (2013, 3, 11, 0, 0, 0)) Week)
+             [ "nastepne 3 tygodnie"
+             , "nastepne trzy tygodnie"
+             ]
+  , examples (datetimeInterval ((2012, 12, 0, 0, 0, 0), (2013, 2, 0, 0, 0, 0)) Month)
+             [ "ostatnie 2 miesiace"
+             , "ostatnie dwa miesiące"
+             ]
+  , examples (datetimeInterval ((2013, 3, 0, 0, 0, 0), (2013, 6, 0, 0, 0, 0)) Month)
+             [ "nastepne trzy miesiące"
+             ]
+  , examples (datetimeInterval ((2011, 0, 0, 0, 0, 0), (2013, 0, 0, 0, 0, 0)) Year)
+             [ "ostatnie 2 lata"
+             , "ostatnie dwa lata"
+             ]
+  , examples (datetimeInterval ((2014, 0, 0, 0, 0, 0), (2017, 0, 0, 0, 0, 0)) Year)
+             [ "nastepne 3 lata"
+             , "kolejne trzy lata"
+             ]
+  , examples (datetimeInterval ((2013, 7, 13, 0, 0, 0), (2013, 7, 16, 0, 0, 0)) Day)
+             [ "Lipiec 13-15"
+             , "Lipca 13 do 15"
+             , "Lipiec 13 - Lipiec 15"
+             ]
+  , examples (datetimeInterval ((2013, 8, 8, 0, 0, 0), (2013, 8, 13, 0, 0, 0)) Day)
+             [ "Sie 8 - Sie 12"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 9, 30, 0), (2013, 2, 12, 11, 1, 0)) Minute)
+             [ "9:30 - 11:00"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 30, 0), (2013, 2, 14, 11, 1, 0)) Minute)
+             [ "od 9:30 - 11:00 w Czwartek"
+             -- , "miedzy 9:30 a 11:00 w czwartek" t14902649
+             , "9:30 - 11:00 w czwartek"
+             -- , "pozniej niż 9:30 ale przed 11:00 w Czwartek" t14902649
+             , "Czwartek od 9:30 do 11:00"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 0, 0), (2013, 2, 14, 12, 0, 0)) Hour)
+             [ "Czwartek od 9 rano do 11 rano"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 11, 30, 0), (2013, 2, 12, 13, 31, 0)) Minute)
+             [ "11:30-1:30"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 26, 0, 0, 0)) Second)
+             [ "w ciągu 2 tygodni"
+             , "w ciągu dwóch tygodni"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 14, 0, 0) Hour)
+             [ "przed drugą po południu"
+             , "przed drugą"
+             ]
+  , examples (datetime (2013, 2, 12, 14, 0, 0) Hour)
+             [ "dziś o drugiej w południe"
+             , "o drugiej popołudniu"
+             ]
+  , examples (datetime (2013, 4, 25, 16, 0, 0) Hour)
+             [ "4/25 o 4 popołudniu"
+             , "4/25 o 16"
+             , "4/25 o szesnastej"
+             ]
+  , examples (datetime (2013, 2, 13, 15, 0, 0) Hour)
+             [ "3 popoludniu jutro"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 12, 14, 0, 0) Hour)
+             [ "po drugiej po poludniu"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 17, 4, 0, 0) Hour)
+             [ "po pięciu dniach"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 12, 14, 0, 0) Hour)
+             [ "po drugiej po południu"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 11, 0, 0) Hour)
+             [ "przed 11 rano"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 13, 11, 0, 0) Hour)
+             [ "jutro przed 11 rano"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 12, 0, 0), (2013, 2, 12, 19, 0, 0)) Hour)
+             [ "to w południe"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 45, 0) Second)
+             [ "w 15 minut"
+             , "w piętnaście minut"
+             ]
+  , examples (datetime (2013, 2, 12, 10, 30, 0) Minute)
+             [ "10:30"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "nastepny pon"
+             , "kolejny poniedziałek"
+             ]
+  ]
diff --git a/Duckling/Time/PL/Rules.hs b/Duckling/Time/PL/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/PL/Rules.hs
@@ -0,0 +1,1959 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.PL.Rules
+  ( rules ) where
+
+import Control.Monad (liftM2)
+import qualified Data.Text as Text
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Types (OrdinalData(..))
+import qualified Duckling.Ordinal.Types as TOrdinal
+import Duckling.Regex.Types
+import Duckling.Time.Helpers
+import Duckling.Time.Types (TimeData (..))
+import qualified Duckling.Time.Types as TTime
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleOrdinalCycleTime :: Rule
+ruleOrdinalCycleTime = Rule
+  { name = "<ordinal> <cycle> <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token TimeGrain grain:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleNamedday :: Rule
+ruleNamedday = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "poniedzia(l|\x0142)(ek|ku|kowi|kiem|kowy)|pon\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 1
+  }
+
+ruleNamedmonth12 :: Rule
+ruleNamedmonth12 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "grudzie\x0144|grudzien|grudnia|grudniowi|grudniem|grudniu|gru\\.?|grud\\.?"
+    ]
+  , prod = \_ -> tt $ month 12
+  }
+
+ruleRelativeMinutesTotillbeforeIntegerHourofday :: Rule
+ruleRelativeMinutesTotillbeforeIntegerHourofday = Rule
+  { name = "relative minutes to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "do|przed"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleRelativeMinutesAfterpastIntegerHourofday :: Rule
+ruleRelativeMinutesAfterpastIntegerHourofday = Rule
+  { name = "relative minutes after|past <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "po"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:
+       _:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute True hours n
+      _ -> Nothing
+  }
+
+ruleHourofdayIntegerAsRelativeMinutes :: Rule
+ruleHourofdayIntegerAsRelativeMinutes = Rule
+  { name = "<hour-of-day> <integer> (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute True hours n
+      _ -> Nothing
+  }
+
+ruleQuarterTotillbeforeIntegerHourofday :: Rule
+ruleQuarterTotillbeforeIntegerHourofday = Rule
+  { name = "quarter to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ regex "kwadrans(ie|owi|em|a)? *(do|przed)"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesBefore 15 td
+      _ -> Nothing
+  }
+
+ruleQuarterAfterpastIntegerHourofday :: Rule
+ruleQuarterAfterpastIntegerHourofday = Rule
+  { name = "quarter after|past <integer> (hour-of-day)"
+  , pattern =
+    [ regex "kwadrans(ie|owi|em|a)? *po"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> tt $ hourMinute True hours 15
+      _ -> Nothing
+  }
+
+ruleHourofdayQuarter :: Rule
+ruleHourofdayQuarter = Rule
+  { name = "<hour-of-day> quarter (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "kwadrans(ie|owi|em|a)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> tt $ hourMinute True hours 15
+      _ -> Nothing
+  }
+
+ruleHalfTotillbeforeIntegerHourofday :: Rule
+ruleHalfTotillbeforeIntegerHourofday = Rule
+  { name = "half to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ regex "p(o|\x00f3)(l|\x0142) *(do|przed)"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesBefore 30 td
+      _ -> Nothing
+  }
+
+ruleHalfAfterpastIntegerHourofday :: Rule
+ruleHalfAfterpastIntegerHourofday = Rule
+  { name = "half after|past <integer> (hour-of-day)"
+  , pattern =
+    [ regex "p(o|\x00f3)(l|\x0142) *po"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> tt $ hourMinute True hours 30
+      _ -> Nothing
+  }
+
+ruleHourofdayHalf :: Rule
+ruleHourofdayHalf = Rule
+  { name = "<hour-of-day> half (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "p(o|\x00f3)(l|\x0142)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> tt $ hourMinute True hours 30
+      _ -> Nothing
+  }
+
+ruleNamedday2 :: Rule
+ruleNamedday2 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "wtorek|wtorku|wtorkowi|wtorkiem|wtr?\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 2
+  }
+
+ruleValentinesDay :: Rule
+ruleValentinesDay = Rule
+  { name = "valentine's day"
+  , pattern =
+    [ regex "walentynki"
+    ]
+  , prod = \_ -> tt $ monthDay 2 14
+  }
+
+ruleLastTime :: Rule
+ruleLastTime = Rule
+  { name = "last <time>"
+  , pattern =
+    [ regex "ostatni(ego|ch|emu|mi|m|(a|\x0105)|ej)?|(po ?)?przedni(ego|ch|emu|e|mi|m|a)?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleNamedday6 :: Rule
+ruleNamedday6 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "sobota|soboty|sobocie|sobot\x0119|sobote|sobot\x0105|sobota|sobocie|soboto|sob\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 6
+  }
+
+ruleDatetimeDatetimeInterval :: Rule
+ruleDatetimeDatetimeInterval = Rule
+  { name = "<datetime> - <datetime> (interval)"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "\\-|(p|d)o|a(\x017c|z) (p|d)o"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth7 :: Rule
+ruleNamedmonth7 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "lipiec|lipca|lipcowi|lipcem|lipcu|lip\\.?"
+    ]
+  , prod = \_ -> tt $ month 7
+  }
+
+ruleCycleAfterTime :: Rule
+ruleCycleAfterTime = Rule
+  { name = "<cycle> after <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "po"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain 1 td
+      _ -> Nothing
+  }
+
+ruleInDuration :: Rule
+ruleInDuration = Rule
+  { name = "in <duration>"
+  , pattern =
+    [ regex "(w( ?(prze)?ci\x0105gu)?|za) ?(jeszcze)?|przez"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleNow :: Rule
+ruleNow = Rule
+  { name = "now"
+  , pattern =
+    [ regex "(w)? ?(tym|tej)? ?(teraz|momencie|chwili|mome\x0144cie)"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Second 0
+  }
+
+ruleLastCycleOfTime :: Rule
+ruleLastCycleOfTime = Rule
+  { name = "last <cycle> of <time>"
+  , pattern =
+    [ regex "ostatni(ego|ch|emu|mi|m|(a|\x0105)|ej)?"
+    , dimension TimeGrain
+    , regex "w(e)?|z(e)?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleLastOf grain td
+      _ -> Nothing
+  }
+
+ruleFromDatetimeDatetimeInterval :: Rule
+ruleFromDatetimeDatetimeInterval = Rule
+  { name = "from <datetime> - <datetime> (interval)"
+  , pattern =
+    [ regex "od|p(o|\x00f3)(z|\x017a)niej ni(z|\x017c)"
+    , dimension Time
+    , regex "\\-|do|po|a\x017c do|az do|a\x017c po|az po|ale przed"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleOrdinalAsHour :: Rule
+ruleOrdinalAsHour = Rule
+  { name = "<ordinal> (as hour)"
+  , pattern =
+    [ Predicate $ isOrdinalBetween 1 24
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):_) ->
+        tt $ hour True v
+      _ -> Nothing
+  }
+
+ruleMonthDdddInterval :: Rule
+ruleMonthDdddInterval = Rule
+  { name = "<month> dd-dd (interval)"
+  , pattern =
+    [ Predicate isAMonth
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    , regex "\\-|do|po|a\x017c do|az do|a\x017c po|az po"
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      ( Token Time td
+       :Token RegexMatch (GroupMatch (d1:_))
+       :_
+       :Token RegexMatch (GroupMatch (d2:_))
+       :_) -> do
+        dd1 <- parseInt d1
+        dd2 <- parseInt d2
+        dom1 <- intersect (dayOfMonth dd1) td
+        dom2 <- intersect (dayOfMonth dd2) td
+        tt $ interval TTime.Closed (dom1, dom2)
+      _ -> Nothing
+  }
+
+ruleNamedday4 :: Rule
+ruleNamedday4 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "czwartek|czwartku|czwartkowi|czwartkiem|czwr?\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 4
+  }
+
+ruleSeason4 :: Rule
+ruleSeason4 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "wiosna|wiosny|wio\x015bnie|wiosnie|wiosn\x0119|wiosne|wiosn\x0105|wiosna|wio\x015bnie|wiosnie|wiosno"
+    ]
+  , prod = \_ ->
+      let from = monthDay 3 20
+          to = monthDay 6 21
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleYearLatent2 :: Rule
+ruleYearLatent2 = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2101 10000
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ year n
+      _ -> Nothing
+  }
+
+ruleTimeAfterNext :: Rule
+ruleTimeAfterNext = Rule
+  { name = "<time> after next"
+  , pattern =
+    [ dimension Time
+    , regex "po kolejnym|po nast(e|\x0119)pn(ym|y|ego|emu|(a|\x0105)|ej|e)|po przysz(l|\x0142)(ym|y|ego|emu|(a|\x0105)|ej)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 1 True td
+      _ -> Nothing
+  }
+
+ruleTheIdesOfNamedmonth :: Rule
+ruleTheIdesOfNamedmonth = 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 (dayOfMonth $ if elem m [3, 5, 7, 10] then 15 else 13) td
+      _ -> Nothing
+  }
+
+ruleNoon :: Rule
+ruleNoon = Rule
+  { name = "noon"
+  , pattern =
+    [ regex "po(l|\x0142)udni(em|e|a|u)"
+    ]
+  , prod = \_ -> tt $ hour False 12
+  }
+
+ruleToday :: Rule
+ruleToday = Rule
+  { name = "today"
+  , pattern =
+    [ regex "dzisiejszy|dzisiaj|dzi\x015b|dzis|w ten dzie\x0144|w ten dzien|w obecny dzie\x0144|w obecny dzien|obecnego dnia"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 0
+  }
+
+ruleThisnextDayofweek :: Rule
+ruleThisnextDayofweek = Rule
+  { name = "this|next <day-of-week>"
+  , pattern =
+    [ regex "kolejn(ym|y|ego|emu|e)|nast(e|\x0119)pn(ym|y|ego|emu|e|(a|\x0105)|ej|e)"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleBetweenTimeofdayAndTimeofdayInterval :: Rule
+ruleBetweenTimeofdayAndTimeofdayInterval = Rule
+  { name = "between <time-of-day> and <time-of-day> (interval)"
+  , pattern =
+    [ regex "(po|po )?miedzy|mi\x0119dzy"
+    , Predicate isATimeOfDay
+    , regex "a|i"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNextCycle :: Rule
+ruleNextCycle = Rule
+  { name = "next <cycle>"
+  , pattern =
+    [ regex "kolejn(ym|y|ego|emu|(a|\x0105)|ej|e)|nast(e|\x0119)pn(ym|y|ego|emu|(a|\x0105)|ej|e)|przysz(l|\x0142)(ego|emu|ym|(a|\x0105)|ej|ych|i|ymi|y|e)|za"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleNamedmonth :: Rule
+ruleNamedmonth = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "stycze\x0144|styczen|stycznia|styczniowi|styczniem|styczniu|sty(cz)?\\.?"
+    ]
+  , prod = \_ -> tt $ month 1
+  }
+
+ruleTheCycleOfTime :: Rule
+ruleTheCycleOfTime = 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
+  }
+
+ruleTimeofdayApproximately :: Rule
+ruleTimeofdayApproximately = Rule
+  { name = "<time-of-day> approximately"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "o?ko(l|\x0142)o|mniej wi(e|\x0119)cej"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+rulePolishIndependenceDay :: Rule
+rulePolishIndependenceDay = Rule
+  { name = "Polish independence day"
+  , pattern =
+    [ regex "(s|\x015b)wiet(a|o) niepodleg(l|\x0142)o(s|\x015b)ci|(\x015b|s)w\\.? niepodleg(l|\x0142)o(s|\x015b)ci"
+    ]
+  , prod = \_ -> tt $ monthDay 11 11
+  }
+
+ruleOnDate :: Rule
+ruleOnDate = Rule
+  { name = "on <date>"
+  , pattern =
+    [ regex "we?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleNamedmonth3 :: Rule
+ruleNamedmonth3 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "marzec|marca|marcowi|marcem|marcu|marz?\\.?"
+    ]
+  , prod = \_ -> tt $ month 3
+  }
+
+ruleLastDayofweekTime :: Rule
+ruleLastDayofweekTime = Rule
+  { name = "last <day-of-week> <time>"
+  , pattern =
+    [ regex "ostatni(ego|ch|emu|mi|m|(a|\x0105)|ej)?"
+    , Predicate isADayOfWeek
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:Token Time td2:_) ->
+        tt $ predLastOf td1 td2
+      _ -> Nothing
+  }
+
+ruleDurationFromNow :: Rule
+ruleDurationFromNow = Rule
+  { name = "<duration> from now"
+  , pattern =
+    [ dimension Duration
+    , regex "od (dzi(s|\x015b)|teraz)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleLunch :: Rule
+ruleLunch = Rule
+  { name = "lunch"
+  , pattern =
+    [ regex "(na )?la?u?nc(z|h)|obiad"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 14
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleLastCycle :: Rule
+ruleLastCycle = Rule
+  { name = "last <cycle>"
+  , pattern =
+    [ regex "ostatni(ego|ch|emu|mi|m|(a|\x0105)|ej|e)?|(po ?)?przedni(ego|ch|emu|mi|m|e|(a|\x0105)|ej)?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleAfternoon :: Rule
+ruleAfternoon = Rule
+  { name = "afternoon"
+  , pattern =
+    [ regex "po(l|\x0142)udni(em|e|a|u)"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 19
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleHourofdayHourofdayInterval :: Rule
+ruleHourofdayHourofdayInterval = Rule
+  { name = "<hour-of-day> - <hour-of-day> (interval)"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "-|do|a\x017c po|po"
+    , Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth4 :: Rule
+ruleNamedmonth4 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "kwiecie\x0144|kwiecien|kwietnia|kwietniowi|kwietniem|kwietniu|kwiet?\\.?"
+    ]
+  , prod = \_ -> tt $ month 4
+  }
+
+ruleTimeBeforeLast :: Rule
+ruleTimeBeforeLast = Rule
+  { name = "<time> before last"
+  , pattern =
+    [ dimension Time
+    , regex "przed ?ostatni(ego|ch|emu|mi|m|(a|\x0105)|ej)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth (-2) False td
+      _ -> Nothing
+  }
+
+ruleNamedmonthDayofmonthOrdinal :: Rule
+ruleNamedmonthDayofmonthOrdinal = Rule
+  { name = "<named-month> <day-of-month> (ordinal)"
+  , pattern =
+    [ Predicate isAMonth
+    , Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleChristmasEve :: Rule
+ruleChristmasEve = Rule
+  { name = "christmas eve"
+  , pattern =
+    [ regex "(wigilia|wigilii|wigili(e|\x0119)|wigili(a|\x0105)|wigilio) ?(bo(z|\x017c)ego narodzenia)?"
+    ]
+  , prod = \_ -> tt $ monthDay 12 24
+  }
+
+ruleInduringThePartofday :: Rule
+ruleInduringThePartofday = Rule
+  { name = "in|during the <part-of-day>"
+  , pattern =
+    [ regex "(w|na) ?(czas(ie)?)"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleThanksgivingDay :: Rule
+ruleThanksgivingDay = Rule
+  { name = "thanksgiving day"
+  , pattern =
+    [ regex "((s|\x015b)wiet(a|o)|(dzie(n|\x0144)))? ?dzi(e|\x0119)kczynieni(e|a)"
+    ]
+  , prod = \_ -> tt $ nthDOWOfMonth 4 4 11
+  }
+
+ruleNamedday5 :: Rule
+ruleNamedday5 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "pi\x0105tek|piatek|pi\x0105tku|piatku|pi\x0105tkowi|piatkowi|pi\x0105tkiem|piatkiem|pi(\x0105|a)tkowy|pia\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 5
+  }
+
+ruleDayofmonthordinalNamedmonth :: Rule
+ruleDayofmonthordinalNamedmonth = Rule
+  { name = "<day-of-month>(ordinal) <named-month>"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleIntersectBy :: Rule
+ruleIntersectBy = Rule
+  { name = "intersect by \",\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex ","
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+ruleIntersectBy2 :: Rule
+ruleIntersectBy2 = Rule
+  { name = "intersect by \",\" 2"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex ","
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNthTimeAfterTime :: Rule
+ruleNthTimeAfterTime = Rule
+  { name = "nth <time> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "po"
+    , 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
+  }
+
+ruleMmdd :: Rule
+ruleMmdd = Rule
+  { name = "mm/dd"
+  , pattern =
+    [ regex "(0?[1-9]|1[0-2])/(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (mm:dd:_)):_) -> do
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleAfterDuration :: Rule
+ruleAfterDuration = Rule
+  { name = "after <duration>"
+  , pattern =
+    [ regex "po"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt . notLatent . withDirection TTime.After $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleDayofmonthOrdinalOfNamedmonth :: Rule
+ruleDayofmonthOrdinalOfNamedmonth = Rule
+  { name = "<day-of-month> (ordinal) of <named-month>"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    , regex "of|in"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleFromTimeofdayTimeofdayInterval :: Rule
+ruleFromTimeofdayTimeofdayInterval = Rule
+  { name = "from <time-of-day> - <time-of-day> (interval)"
+  , pattern =
+    [ regex "(ni\x017c|niz|od)"
+    , Predicate isATimeOfDay
+    , regex "((but )?before)|\\-|do|po|a\x017c do|az do|a\x017c po|az po"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth2 :: Rule
+ruleNamedmonth2 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "luty|lutego|lutemu|lut?\\.?"
+    ]
+  , prod = \_ -> tt $ month 2
+  }
+
+ruleExactlyTimeofday :: Rule
+ruleExactlyTimeofday = Rule
+  { name = "exactly <time-of-day>"
+  , pattern =
+    [ regex "(r(o|\x00f3)wno|dok(l|\x0142)adnie)( o)?"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleSeason3 :: Rule
+ruleSeason3 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "zima|zimy|zimie|zim\x0119|zime|zim\x0105|zima|zimie|zimo"
+    ]
+  , prod = \_ ->
+      let from = monthDay 12 21
+          to = monthDay 3 20
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleSeason :: Rule
+ruleSeason = Rule
+  { name = "season"
+  , pattern =
+    [ regex "lato|lata|latu|latem|lecie"
+    ]
+  , prod = \_ ->
+      let from = monthDay 6 21
+          to = monthDay 9 23
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleIntegerLatentTimeofday :: Rule
+ruleIntegerLatentTimeofday = Rule
+  { name = "<integer> (latent time-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 0 23
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ hour True v
+      _ -> Nothing
+  }
+
+ruleBetweenDatetimeAndDatetimeInterval :: Rule
+ruleBetweenDatetimeAndDatetimeInterval = Rule
+  { name = "between <datetime> and <datetime> (interval)"
+  , pattern =
+    [ regex "(po|po )?mi(e|\x0119)dzy"
+    , dimension Time
+    , regex "a|i"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNewYearsEve :: Rule
+ruleNewYearsEve = Rule
+  { name = "new year's eve"
+  , pattern =
+    [ regex "sylwester|nowy rok"
+    ]
+  , prod = \_ -> tt $ monthDay 12 31
+  }
+
+ruleDurationAgo :: Rule
+ruleDurationAgo = Rule
+  { name = "<duration> ago"
+  , pattern =
+    [ dimension Duration
+    , regex "temu"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt $ durationAgo dd
+      _ -> Nothing
+  }
+
+ruleByTheEndOfTime :: Rule
+ruleByTheEndOfTime = Rule
+  { name = "by the end of <time>"
+  , pattern =
+    [ regex "do (ko[\x0144n]ca )?(tego)?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $
+        interval TTime.Closed (cycleNth TG.Second 0, td)
+      _ -> Nothing
+  }
+
+ruleDaybeforeyesterdaySingleword :: Rule
+ruleDaybeforeyesterdaySingleword = Rule
+  { name = "day-before-yesterday (single-word)"
+  , pattern =
+    [ regex "przedwczoraj"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 2
+  }
+
+ruleLastNCycle :: Rule
+ruleLastNCycle = Rule
+  { name = "last n <cycle>"
+  , pattern =
+    [ regex "ostatni(ego|ch|emu|mi|m|(a|\x0105)|ej|e)?|(po ?)?przedni(ego|ch|emu|mi|m|e|(a|\x0105)|ej)?"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain (- v)
+      _ -> Nothing
+  }
+
+ruleTimeofdaySharp :: Rule
+ruleTimeofdaySharp = Rule
+  { name = "<time-of-day> sharp"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "r(o|\x00f3)wno|dok(l|\x0142)adnie"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleWithinDuration :: Rule
+ruleWithinDuration = Rule
+  { name = "within <duration>"
+  , pattern =
+    [ regex "(w )?ci(a|\x0105)gu|zakresie|obr\x0119bie|obrebie"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        let from = cycleNth TG.Second 0
+            to = inDuration dd
+        in tt $ interval TTime.Open (from, to)
+      _ -> Nothing
+  }
+
+ruleTimeofdayRano :: Rule
+ruleTimeofdayRano = Rule
+  { name = "<time-of-day> rano"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(z )?ran(o|a|u|em)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ timeOfDayAMPM td True
+      _ -> Nothing
+  }
+
+ruleDayofmonthNonOrdinalNamedmonth :: Rule
+ruleDayofmonthNonOrdinalNamedmonth = Rule
+  { name = "<day-of-month> (non ordinal) <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ Predicate isNotLatent
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleAboutTimeofday :: Rule
+ruleAboutTimeofday = Rule
+  { name = "about <time-of-day>"
+  , pattern =
+    [ regex "o?ko(l|\x0142)o|mniej wi(e|\x0119)cej|tak o"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleUntilTimeofday :: Rule
+ruleUntilTimeofday = Rule
+  { name = "until <time-of-day>"
+  , pattern =
+    [ regex "(a[\x017cz] )?do|przed"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ withDirection TTime.Before td
+      _ -> Nothing
+  }
+
+ruleAtTimeofday :: Rule
+ruleAtTimeofday = Rule
+  { name = "at <time-of-day>"
+  , pattern =
+    [ regex "o|na|@"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth6 :: Rule
+ruleNamedmonth6 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "czerwiec|czerwca|czerwcowi|czerwcem|czerwcu|czer?\\.?"
+    ]
+  , prod = \_ -> tt $ month 6
+  }
+
+ruleNthTimeOfTime :: Rule
+ruleNthTimeOfTime = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "w(e)?|z(e)?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token Time td1:_:Token Time td2:_) -> Token Time .
+        predNth (TOrdinal.value od - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNamedmonth8 :: Rule
+ruleNamedmonth8 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "sierpie\x0144|sierpien|sierpnia|sierpniowi|sierpniem|sierpniu|sierp\\.?|sier\\.?|sie\\.?"
+    ]
+  , prod = \_ -> tt $ month 8
+  }
+
+ruleTimePartofday :: Rule
+ruleTimePartofday = Rule
+  { name = "<time> <part-of-day>"
+  , pattern =
+    [ dimension Time
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleWeekend :: Rule
+ruleWeekend = Rule
+  { name = "week-end"
+  , pattern =
+    [ regex "((wek|week|wik)(\\s|-)?end|wkend)"
+    ]
+  , prod = \_ -> do
+      from <- intersect (dayOfWeek 5) (hour False 18)
+      to <- intersect (dayOfWeek 1) (hour False 0)
+      tt $ interval TTime.Open (from, to)
+  }
+
+ruleEomendOfMonth :: Rule
+ruleEomendOfMonth = Rule
+  { name = "EOM|End of month"
+  , pattern =
+    [ regex "(na |w )?(koniec|ko(n|\x0144)ca|ko(n|\x0144)cu|ko(n|\x0144)cowi|ko(n|\x0144)cem|ko(n|\x0144)c(o|\x00f3)wke) (miesi(a|\x0105)ca|msc)"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Month 1
+  }
+
+ruleNextTime :: Rule
+ruleNextTime = Rule
+  { name = "next <time>"
+  , pattern =
+    [ regex "kolejn(ym|y|ego|emu|(a|\x0105)|ej|e)|nast(e|\x0119)pn(ym|y|ego|emu|e|(a|\x0105)|ej|e)|przysz(l|\x0142)(ego|emu|ym|(a|\x0105)|ej|ych|i|ymi|y|e)"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleDayaftertomorrowSingleword :: Rule
+ruleDayaftertomorrowSingleword = Rule
+  { name = "day-after-tomorrow (single-word)"
+  , pattern =
+    [ regex "(po ?jutr(o|ze))"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 2
+  }
+
+ruleOrdinalQuarterYear :: Rule
+ruleOrdinalQuarterYear = Rule
+  { name = "<ordinal> quarter <year>"
+  , pattern =
+    [ dimension Ordinal
+    , Predicate $ isGrain TG.Quarter
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:_:Token Time td:_) ->
+        tt $ cycleNthAfter False TG.Quarter (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleYyyymmdd :: Rule
+ruleYyyymmdd = Rule
+  { name = "yyyy-mm-dd"
+  , pattern =
+    [ regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (yy:mm:dd:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleTheOrdinalCycleAfterTime :: Rule
+ruleTheOrdinalCycleAfterTime = Rule
+  { name = "the <ordinal> <cycle> after <time>"
+  , pattern =
+    [ regex "the"
+    , dimension Ordinal
+    , dimension TimeGrain
+    , regex "after"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleIntersectByOfFromS :: Rule
+ruleIntersectByOfFromS = Rule
+  { name = "intersect by \"of\", \"from\", \"'s\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "z"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNextNCycle :: Rule
+ruleNextNCycle = Rule
+  { name = "next n <cycle>"
+  , pattern =
+    [ regex "kolejn(ym|y|ego|emu|(a|\x0105)|ej|e)|nast(e|\x0119)pn(ym|y|ego|emu|(a|\x0105)|ej|e)"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleMorning :: Rule
+ruleMorning = Rule
+  { name = "morning"
+  , pattern =
+    [ regex "rano|poran(ek|ku|ka)|z rana|(s|\x015b)witem"
+    ]
+  , prod = \_ ->
+      let from = hour False 4
+          to = hour False 12
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleThisPartofday :: Rule
+ruleThisPartofday = Rule
+  { name = "this <part-of-day>"
+  , pattern =
+    [ regex "ten|tego|ta|to"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        Token Time . partOfDay <$> intersect (cycleNth TG.Day 0) td
+      _ -> Nothing
+  }
+
+ruleThisCycle :: Rule
+ruleThisCycle = Rule
+  { name = "this <cycle>"
+  , pattern =
+    [ regex "te(mu|n|go|j)|tym|t(a|\x0105)|nadchodz(a|\x0105)c(ym|y|ego|emu|(a|\x0105)|ej)|obecn(ym|y|emu|ego|nym|(a|\x0105)|ej)"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 0
+      _ -> Nothing
+  }
+
+ruleThisTime :: Rule
+ruleThisTime = Rule
+  { name = "this <time>"
+  , pattern =
+    [ regex "te(mu|n|go|j)|ta|to|tym|nadchodz(a|\x0105)c(ym|y|ego|emu|(a|\x0105)|ej)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleDurationHence :: Rule
+ruleDurationHence = Rule
+  { name = "<duration> hence"
+  , pattern =
+    [ dimension Duration
+    , regex "p(o|\x00f3)(z|\x017a)niej|potem"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleDayofmonthNonOrdinalOfNamedmonth :: Rule
+ruleDayofmonthNonOrdinalOfNamedmonth = Rule
+  { name = "<day-of-month> (non ordinal) of <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , regex "of|in"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleNthTimeTime :: Rule
+ruleNthTimeTime = Rule
+  { name = "nth <time> <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token Time td1:Token Time td2:_) -> Token Time .
+        predNth (TOrdinal.value od - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleTimeofdayPopoudniuwieczoremwNocy :: Rule
+ruleTimeofdayPopoudniuwieczoremwNocy = Rule
+  { name = "<time-of-day> popołudniu/wieczorem/w nocy"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(w )?noc(y|(a|\x0105))|(po ?)?po(l|\x0142)udni(em|e|a|u)|wiecz(o|\x00f3)r(i|u|a|owi|em|rze)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ timeOfDayAMPM td False
+      _ -> Nothing
+  }
+
+ruleOnANamedday :: Rule
+ruleOnANamedday = Rule
+  { name = "on a named-day"
+  , pattern =
+    [ regex "we?"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleYearLatent :: Rule
+ruleYearLatent = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween (- 10000) 999
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+         n <- getIntValue token
+         tt . mkLatent $ year n
+      _ -> Nothing
+  }
+
+ruleYesterday :: Rule
+ruleYesterday = Rule
+  { name = "yesterday"
+  , pattern =
+    [ regex "wczoraj(szym|szy)?|wczrj|wczor"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 1
+  }
+
+ruleSeason2 :: Rule
+ruleSeason2 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "jesie\x0144|jesien|jesieni|jesieni\x0105|jesienia"
+    ]
+  , prod = \_ ->
+      let from = monthDay 9 23
+          to = monthDay 12 21
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleAfterTimeofday :: Rule
+ruleAfterTimeofday = Rule
+  { name = "after <time-of-day>"
+  , pattern =
+    [ regex "po"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ withDirection TTime.After td
+      _ -> Nothing
+  }
+
+ruleChristmas :: Rule
+ruleChristmas = Rule
+  { name = "christmas"
+  , pattern =
+    [ regex "((\x015a|\x015b|s)wi(e|\x0119)ta)? ?bo(z|\x017c)(ym|ego|e) narodzeni(e|a|u)"
+    ]
+  , prod = \_ -> tt $ monthDay 12 25
+  }
+
+ruleDayofmonthOrdinal :: Rule
+ruleDayofmonthOrdinal = Rule
+  { name = "<day-of-month> (ordinal)"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):_) ->
+        tt . mkLatent $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleOrdinalCycleAfterTime :: Rule
+ruleOrdinalCycleAfterTime = Rule
+  { name = "<ordinal> <cycle> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "after"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleOrdinalCycleOfTime :: Rule
+ruleOrdinalCycleOfTime = Rule
+  { name = "<ordinal> <cycle> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "w(e)?|z(e)?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleNamedmonth5 :: Rule
+ruleNamedmonth5 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "maj|maja|majowi|majem|maju"
+    ]
+  , prod = \_ -> tt $ month 5
+  }
+
+ruleNamedday7 :: Rule
+ruleNamedday7 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "niedziela|niedzieli|niedziel\x0119|niedziele|niedziela|niedziel\x0105|niedzieli|niedzielo|nied?z?\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 7
+  }
+
+ruleHhmm :: Rule
+ruleHhmm = Rule
+  { name = "hh:mm"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt $ hourMinute True h m
+      _ -> Nothing
+  }
+
+ruleFromHourofdayHourofdayInterval :: Rule
+ruleFromHourofdayHourofdayInterval = Rule
+  { name = "from <hour-of-day> - <hour-of-day> (interval)"
+  , pattern =
+    [ regex "od"
+    , Predicate isATimeOfDay
+    , regex "-|to|th?ru|through|until"
+    , Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleYear :: Rule
+ruleYear = Rule
+  { name = "year"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1000 2100
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt $ year n
+      _ -> Nothing
+  }
+
+ruleNamedmonth10 :: Rule
+ruleNamedmonth10 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "pa(z|\x017a)dziernik(a|owi|iem|u)?|pa\x017a\\.?|paz\\.?"
+    ]
+  , prod = \_ -> tt $ month 10
+  }
+
+ruleHalloweenDay :: Rule
+ruleHalloweenDay = Rule
+  { name = "halloween day"
+  , pattern =
+    [ regex "hall?owe?en( day)?"
+    ]
+  , prod = \_ -> tt $ monthDay 10 31
+  }
+
+ruleNamedmonthDayofmonthNonOrdinal :: Rule
+ruleNamedmonthDayofmonthNonOrdinal = 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
+  }
+
+ruleHhmmMilitary :: Rule
+ruleHhmmMilitary = Rule
+  { name = "hhmm (military)"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt . mkLatent $ hourMinute False h m
+      _ -> Nothing
+  }
+
+ruleAbsorptionOfAfterNamedDay :: Rule
+ruleAbsorptionOfAfterNamedDay = Rule
+  { name = "absorption of , after named day"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex ","
+    ]
+  , prod = \tokens -> case tokens of
+      (x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleLastDayofweekOfTime :: Rule
+ruleLastDayofweekOfTime = Rule
+  { name = "last <day-of-week> of <time>"
+  , pattern =
+    [ regex "ostatni(ego|ch|emu|mi|m|(a|\x0105)|ej)?"
+    , Predicate isADayOfWeek
+    , regex "w(e)?|z(e)?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ predLastOf td1 td2
+      _ -> Nothing
+  }
+
+ruleFathersDay :: Rule
+ruleFathersDay = Rule
+  { name = "Father's Day"
+  , pattern =
+    [ regex "dzie(n|\x0144) ?(taty|ojca)"
+    ]
+  , prod = \_ -> tt $ nthDOWOfMonth 2 7 6
+  }
+
+ruleLastCycleTime :: Rule
+ruleLastCycleTime = Rule
+  { name = "last <cycle> <time>"
+  , pattern =
+    [ regex "ostatni(ego|ch|emu|mi|m|(a|\x0105)|ej)?"
+    , dimension TimeGrain
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:Token Time td:_) ->
+        tt $ cycleLastOf grain td
+      _ -> Nothing
+  }
+
+ruleByTime :: Rule
+ruleByTime = Rule
+  { name = "by <time>"
+  , pattern =
+    [ regex "(a(z|\x017c) )?do"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ interval TTime.Open (cycleNth TG.Second 0, td)
+      _ -> Nothing
+  }
+
+ruleHhmmMilitaryAmpm :: Rule
+ruleHhmmMilitaryAmpm = Rule
+  { name = "hhmm (military) am|pm"
+  , pattern =
+    [ regex "((?:1[012]|0?\\d))([0-5]\\d)"
+    , regex "([ap])\\.?m?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):
+       Token RegexMatch (GroupMatch (ap:_)):
+       _) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt . timeOfDayAMPM (hourMinute True h m) $
+          Text.toLower ap == "a"
+      _ -> Nothing
+  }
+
+ruleCycleBeforeTime :: Rule
+ruleCycleBeforeTime = Rule
+  { name = "<cycle> before <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "przed"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain (-1) td
+      _ -> Nothing
+  }
+
+ruleTimeofdayTimeofdayInterval :: Rule
+ruleTimeofdayTimeofdayInterval = Rule
+  { name = "<time-of-day> - <time-of-day> (interval)"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\-|:|do|po|a\x017c do|az do|a\x017c po|az po"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth11 :: Rule
+ruleNamedmonth11 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "listopad|listopada|listopadowi|listopadem|listopadzie|lis\\.?|list\\.?"
+    ]
+  , prod = \_ -> tt $ month 11
+  }
+
+ruleDurationAfterTime :: Rule
+ruleDurationAfterTime = Rule
+  { name = "<duration> after <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "po"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_:Token Time td:_) ->
+        tt $ durationAfter dd td
+      _ -> Nothing
+  }
+
+ruleEveningnight :: Rule
+ruleEveningnight = Rule
+  { name = "evening|night"
+  , pattern =
+    [ regex "wiecz(o|\x00f3)r(em|owi|ze|a|u)?|noc(\x0105)?"
+    ]
+  , prod = \_ ->
+      let from = hour False 18
+          to = hour False 0
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleOrdinalQuarter :: Rule
+ruleOrdinalQuarter = 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
+  }
+
+ruleNamedday3 :: Rule
+ruleNamedday3 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "(\x015a|\x015b|s)rod(a|\x0105|y|e|\x0119|zie|owy|o)|(s|\x015b|\x015a)ro?\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 3
+  }
+
+ruleDurationBeforeTime :: Rule
+ruleDurationBeforeTime = Rule
+  { name = "<duration> before <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "do|przed"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_:Token Time td:_) ->
+        tt $ durationBefore dd td
+      _ -> Nothing
+  }
+
+ruleMmddyyyy :: Rule
+ruleMmddyyyy = Rule
+  { name = "mm/dd/yyyy"
+  , pattern =
+    [ regex "(0?[1-9]|1[0-2])[/-](3[01]|[12]\\d|0?[1-9])[-/](\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (mm:dd:yy:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleEoyendOfYear :: Rule
+ruleEoyendOfYear = Rule
+  { name = "EOY|End of year"
+  , pattern =
+    [ regex "(na |w )?(koniec|ko(n|\x0144)ca|ko(n|\x0144)cu|ko(n|\x0144)cowi|ko(n|\x0144)cem|ko(n|\x0144)c(o|\x00f3)wke) (rok(u|owi|iem))"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Year 1
+  }
+
+ruleTomorrow :: Rule
+ruleTomorrow = Rule
+  { name = "tomorrow"
+  , pattern =
+    [ regex "jutr(o|a|u|em|ze(jszy|jsza)?)|jtr|jutr"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 1
+  }
+
+ruleMothersDay :: Rule
+ruleMothersDay = Rule
+  { name = "Mother's Day"
+  , pattern =
+    [ regex "dzie(n|\x0144) ? ma(my|tki|m)"
+    ]
+  , prod = \_ -> tt $ nthDOWOfMonth 1 7 5
+  }
+
+ruleTimeofdayOclock :: Rule
+ruleTimeofdayOclock = Rule
+  { name = "<time-of-day> o'clock"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "godzina"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth9 :: Rule
+ruleNamedmonth9 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "wrzesie\x0144|wrzesien|wrze\x015bnia|wrzesnia|wrze\x015bniowi|wrzesniowi|wrzesie\x0144|wrzesien|wrze\x015bniem|wrzesniem|wrze\x015bniu|wrzesniu|wrz\\.?|wrze\\.?"
+    ]
+  , prod = \_ -> tt $ month 9
+  }
+
+ruleDayofmonthordinalNamedmonthYear :: Rule
+ruleDayofmonthordinalNamedmonthYear = 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
+         y <- parseInt match
+         dom <- intersectDOM td token
+         Token Time <$> intersect dom (year y)
+      _ -> Nothing
+  }
+
+ruleHhmmss :: Rule
+ruleHhmmss = Rule
+  { name = "hh:mm:ss"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)[:.]([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:ss:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        s <- parseInt ss
+        tt $ hourMinuteSecond True h m s
+      _ -> Nothing
+  }
+
+ruleTimezone :: Rule
+ruleTimezone = Rule
+  { name = "<time> timezone"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\b(YEKT|YEKST|YAKT|YAKST|WITA|WIT|WIB|WGT|WGST|WFT|WET|WEST|WAT|WAST|VUT|VLAT|VLAST|VET|UZT|UYT|UYST|UTC|ULAT|TVT|TMT|TLT|TKT|TJT|TFT|TAHT|SST|SRT|SGT|SCT|SBT|SAST|SAMT|RET|PYT|PYST|PWT|PST|PONT|PMST|PMDT|PKT|PHT|PHOT|PGT|PETT|PETST|PET|PDT|OMST|OMSST|NZST|NZDT|NUT|NST|NPT|NOVT|NOVST|NFT|NDT|NCT|MYT|MVT|MUT|MST|MSK|MSD|MMT|MHT|MDT|MAWT|MART|MAGT|MAGST|LINT|LHST|LHDT|KUYT|KST|KRAT|KRAST|KGT|JST|IST|IRST|IRKT|IRKST|IRDT|IOT|IDT|ICT|HOVT|HKT|GYT|GST|GMT|GILT|GFT|GET|GAMT|GALT|FNT|FKT|FKST|FJT|FJST|EST|EGT|EGST|EET|EEST|EDT|ECT|EAT|EAST|EASST|DAVT|ChST|CXT|CVT|CST|COT|CLT|CLST|CKT|CHAST|CHADT|CET|CEST|CDT|CCT|CAT|CAST|BTT|BST|BRT|BRST|BOT|BNT|AZT|AZST|AZOT|AZOST|AWST|AWDT|AST|ART|AQTT|ANAT|ANAST|AMT|AMST|ALMT|AKST|AKDT|AFT|AEST|AEDT|ADT|ACST|ACDT)\\b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       Token RegexMatch (GroupMatch (tz:_)):
+       _) -> Token Time <$> inTimezone tz td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAboutTimeofday
+  , ruleAbsorptionOfAfterNamedDay
+  , ruleAfterDuration
+  , ruleAfterTimeofday
+  , ruleAfternoon
+  , ruleAtTimeofday
+  , ruleBetweenDatetimeAndDatetimeInterval
+  , ruleBetweenTimeofdayAndTimeofdayInterval
+  , ruleByTheEndOfTime
+  , ruleByTime
+  , ruleChristmas
+  , ruleChristmasEve
+  , ruleCycleAfterTime
+  , ruleCycleBeforeTime
+  , ruleDatetimeDatetimeInterval
+  , ruleDayaftertomorrowSingleword
+  , ruleDaybeforeyesterdaySingleword
+  , ruleDayofmonthNonOrdinalNamedmonth
+  , ruleDayofmonthNonOrdinalOfNamedmonth
+  , ruleDayofmonthOrdinal
+  , ruleDayofmonthOrdinalOfNamedmonth
+  , ruleDayofmonthordinalNamedmonth
+  , ruleDayofmonthordinalNamedmonthYear
+  , ruleDurationAfterTime
+  , ruleDurationAgo
+  , ruleDurationBeforeTime
+  , ruleDurationFromNow
+  , ruleDurationHence
+  , ruleEomendOfMonth
+  , ruleEoyendOfYear
+  , ruleEveningnight
+  , ruleExactlyTimeofday
+  , ruleFathersDay
+  , ruleFromDatetimeDatetimeInterval
+  , ruleFromHourofdayHourofdayInterval
+  , ruleFromTimeofdayTimeofdayInterval
+  , ruleHalloweenDay
+  , ruleHhmm
+  , ruleHhmmMilitary
+  , ruleHhmmMilitaryAmpm
+  , ruleHhmmss
+  , ruleHourofdayHourofdayInterval
+  , ruleHourofdayIntegerAsRelativeMinutes
+  , ruleHourofdayQuarter
+  , ruleHourofdayHalf
+  , ruleInDuration
+  , ruleInduringThePartofday
+  , ruleIntegerLatentTimeofday
+  , ruleIntersect
+  , ruleIntersectBy
+  , ruleIntersectBy2
+  , ruleIntersectByOfFromS
+  , ruleLastCycle
+  , ruleLastCycleOfTime
+  , ruleLastCycleTime
+  , ruleLastDayofweekOfTime
+  , ruleLastDayofweekTime
+  , ruleLastNCycle
+  , ruleLastTime
+  , ruleLunch
+  , ruleMmdd
+  , ruleMmddyyyy
+  , ruleMonthDdddInterval
+  , ruleMorning
+  , ruleMothersDay
+  , ruleNamedday
+  , ruleNamedday2
+  , ruleNamedday3
+  , ruleNamedday4
+  , ruleNamedday5
+  , ruleNamedday6
+  , ruleNamedday7
+  , ruleNamedmonth
+  , ruleNamedmonth10
+  , ruleNamedmonth11
+  , ruleNamedmonth12
+  , ruleNamedmonth2
+  , ruleNamedmonth3
+  , ruleNamedmonth4
+  , ruleNamedmonth5
+  , ruleNamedmonth6
+  , ruleNamedmonth7
+  , ruleNamedmonth8
+  , ruleNamedmonth9
+  , ruleNamedmonthDayofmonthNonOrdinal
+  , ruleNamedmonthDayofmonthOrdinal
+  , ruleNewYearsEve
+  , ruleNextCycle
+  , ruleNextNCycle
+  , ruleNextTime
+  , ruleNoon
+  , ruleNow
+  , ruleNthTimeAfterTime
+  , ruleNthTimeOfTime
+  , ruleNthTimeTime
+  , ruleOnANamedday
+  , ruleOnDate
+  , ruleOrdinalAsHour
+  , ruleOrdinalCycleAfterTime
+  , ruleOrdinalCycleOfTime
+  , ruleOrdinalCycleTime
+  , ruleOrdinalQuarter
+  , ruleOrdinalQuarterYear
+  , rulePolishIndependenceDay
+  , ruleRelativeMinutesAfterpastIntegerHourofday
+  , ruleQuarterAfterpastIntegerHourofday
+  , ruleHalfAfterpastIntegerHourofday
+  , ruleRelativeMinutesTotillbeforeIntegerHourofday
+  , ruleQuarterTotillbeforeIntegerHourofday
+  , ruleHalfTotillbeforeIntegerHourofday
+  , ruleSeason
+  , ruleSeason2
+  , ruleSeason3
+  , ruleSeason4
+  , ruleThanksgivingDay
+  , ruleTheCycleOfTime
+  , ruleTheIdesOfNamedmonth
+  , ruleTheOrdinalCycleAfterTime
+  , ruleThisCycle
+  , ruleThisPartofday
+  , ruleThisTime
+  , ruleThisnextDayofweek
+  , ruleTimeAfterNext
+  , ruleTimeBeforeLast
+  , ruleTimePartofday
+  , ruleTimeofdayApproximately
+  , ruleTimeofdayOclock
+  , ruleTimeofdayPopoudniuwieczoremwNocy
+  , ruleTimeofdayRano
+  , ruleTimeofdaySharp
+  , ruleTimeofdayTimeofdayInterval
+  , ruleToday
+  , ruleTomorrow
+  , ruleUntilTimeofday
+  , ruleValentinesDay
+  , ruleWeekend
+  , ruleWithinDuration
+  , ruleYear
+  , ruleYearLatent
+  , ruleYearLatent2
+  , ruleYesterday
+  , ruleYyyymmdd
+  , ruleTimezone
+  ]
diff --git a/Duckling/Time/PT/Corpus.hs b/Duckling/Time/PT/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/PT/Corpus.hs
@@ -0,0 +1,475 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.PT.Corpus
+  ( corpus
+  , negativeCorpus
+  ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Time.Corpus
+import Duckling.Time.Types hiding (Month)
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Testing.Types hiding (examples)
+
+corpus :: Corpus
+corpus = (testContext {lang = PT}, allExamples)
+
+negativeCorpus :: NegativeCorpus
+negativeCorpus = (testContext {lang = PT}, examples)
+  where
+    examples =
+      [ "no 987"
+      ]
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (datetime (2013, 2, 12, 4, 30, 0) Second)
+             [ "agora"
+             , "já"
+             , "ja"
+             , "nesse instante"
+             , "neste instante"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "hoje"
+             , "nesse momento"
+             , "neste momento"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "ontem"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "antes de ontem"
+             , "anteontem"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "amanhã"
+             , "amanha"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "depois de amanhã"
+             , "depois de amanha"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "segunda-feira"
+             , "segunda feira"
+             , "segunda"
+             , "seg."
+             , "seg"
+             , "essa segunda-feira"
+             , "essa segunda feira"
+             , "essa segunda"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "segunda, 18 de fevereiro"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "terça-feira"
+             , "terça feira"
+             , "terça"
+             , "terca-feira"
+             , "terca feira"
+             , "terca"
+             , "ter."
+             , "ter"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "quarta-feira"
+             , "quarta feira"
+             , "quarta"
+             , "qua."
+             , "qua"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "quinta-feira"
+             , "quinta feira"
+             , "quinta"
+             , "qui."
+             , "qui"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "sexta-feira"
+             , "sexta feira"
+             , "sexta"
+             , "sex."
+             , "sex"
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "sábado"
+             , "sabado"
+             , "sáb."
+             , "sáb"
+             , "sab."
+             , "sab"
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "domingo"
+             , "dom."
+             , "dom"
+             ]
+  , examples (datetime (2013, 5, 5, 0, 0, 0) Day)
+             [ "5 de maio"
+             , "cinco de maio"
+             ]
+  , examples (datetime (2013, 5, 5, 0, 0, 0) Day)
+             [ "cinco de maio de 2013"
+             , "5 de maio de 2013"
+             , "5/5"
+             , "5/5/2013"
+             ]
+  , examples (datetime (2013, 7, 4, 0, 0, 0) Day)
+             [ "4 de julho"
+             , "quatro de julho"
+             , "4/7"
+             , "4/7/2013"
+             ]
+  , examples (datetime (2013, 3, 3, 0, 0, 0) Day)
+             [ "3 de março"
+             , "três de março"
+             , "tres de março"
+             , "3/3"
+             , "3/3/2013"
+             ]
+  , examples (datetime (2013, 4, 5, 0, 0, 0) Day)
+             [ "5 de abril"
+             , "cinco de abril"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "1 de março"
+             , "primeiro de março"
+             , "um de março"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "1-3-2013"
+             , "1.3.2013"
+             , "1/3/2013"
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "essa dia 16"
+             , "16 de fevereiro"
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "este dia 17"
+             , "17 de fevereiro"
+             , "17/2"
+             , "no domingo"
+             , "no dia 17"
+             ]
+  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
+             [ "esse dia 20"
+             , "20 de fevereiro"
+             , "20/2"
+             ]
+  , examples (datetime (1974, 10, 31, 0, 0, 0) Day)
+             [ "31/10/1974"
+             , "31/10/74"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "próxima terça-feira"
+             , "próxima terça feira"
+             , "próxima terça"
+             , "proxima terça-feira"
+             , "proxima terça feira"
+             , "proxima terça"
+             ]
+  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
+             [ "quarta que vem"
+             , "quarta da semana que vem"
+             , "quarta da próxima semana"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "terça desta semana"
+             , "terça dessa semana"
+             , "terça agora"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Week)
+             [ "esta semana"
+             ]
+  , examples (datetime (2013, 2, 4, 0, 0, 0) Week)
+             [ "semana passada"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Week)
+             [ "semana que vem"
+             , "proxima semana"
+             ]
+  , examples (datetime (2013, 1, 0, 0, 0, 0) Month)
+             [ "mês passado"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "mes que vem"
+             , "próximo mês"
+             ]
+  , examples (datetime (2012, 0, 0, 0, 0, 0) Year)
+             [ "ano passado"
+             ]
+  , examples (datetime (2013, 0, 0, 0, 0, 0) Year)
+             [ "este ano"
+             ]
+  , examples (datetime (2014, 0, 0, 0, 0, 0) Year)
+             [ "ano que vem"
+             , "proximo ano"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "domingo passado"
+             , "domingo da semana passada"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "terça passada"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "às tres da tarde"
+             , "às tres"
+             , "às 3 pm"
+             , "às 15 horas"
+             ]
+  , examples (datetime (2013, 2, 12, 20, 0, 0) Hour)
+             [ "às oito da noite"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Minute)
+             [ "15:00"
+             , "15.00"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Hour)
+             [ "meianoite"
+             , "meia noite"
+             ]
+  , examples (datetime (2013, 2, 12, 12, 0, 0) Hour)
+             [ "meio dia"
+             , "meiodia"
+             ]
+  , examples (datetime (2013, 2, 12, 12, 15, 0) Minute)
+             [ "meio dia e quinze"
+             ]
+  , examples (datetime (2013, 2, 12, 11, 55, 0) Minute)
+             [ "5 para meio dia"
+             ]
+  , examples (datetime (2013, 2, 12, 12, 30, 0) Minute)
+             [ "meio dia e meia"
+             ]
+  , examples (datetime (2013, 2, 12, 6, 0, 0) Hour)
+             [ "as seis da manha"
+             , "as seis pela manha"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 15, 0) Minute)
+             [ "às tres e quinze"
+             , "às tres e quinze da tarde"
+             , "às tres e quinze pela tarde"
+             , "15:15"
+             , "15.15"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 30, 0) Minute)
+             [ "às tres e meia"
+             , "às 3 e trinta"
+             , "às tres e meia da tarde"
+             , "às 3 e trinta da tarde"
+             , "15:30"
+             , "15.30"
+             ]
+  , examples (datetime (2013, 2, 12, 11, 45, 0) Minute)
+             [ "quinze para meio dia"
+             , "quinze para o meio dia"
+             , "11:45"
+             , "as onze e quarenta e cinco"
+             , "hoje quinze para o meio dia"
+             , "hoje às onze e quarenta e cinco"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 15, 0) Minute)
+             [ "5 e quinze"
+             ]
+  , examples (datetime (2013, 2, 12, 6, 0, 0) Hour)
+             [ "6 da manhã"
+             ]
+  , examples (datetime (2013, 2, 13, 11, 0, 0) Hour)
+             [ "quarta às onze da manhã"
+             ]
+  , examples (datetime (2014, 9, 12, 0, 0, 0) Day)
+             [ "sexta, 12 de setembro de 2014"
+             , "sexta feira, 12 de setembro de 2014"
+             , "12 de setembro de 2014, sexta"
+             , "12 de setembro de 2014 sexta feira"
+             , "sexta feira 12 de setembro de 2014"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 30, 1) Second)
+             [ "em um segundo"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 31, 0) Second)
+             [ "em um minuto"
+             , "em 1 min"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 32, 0) Second)
+             [ "em 2 minutos"
+             , "em dois minutos"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Second)
+             [ "em 60 minutos"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Minute)
+             [ "em uma hora"
+             ]
+  , examples (datetime (2013, 2, 12, 2, 30, 0) Minute)
+             [ "fazem duas horas"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 30, 0) Minute)
+             [ "em 24 horas"
+             , "em vinte e quatro horas"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 0, 0) Hour)
+             [ "em um dia"
+             ]
+  , examples (datetime (2013, 2, 19, 4, 0, 0) Hour)
+             [ "em 7 dias"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "em uma semana"
+             ]
+  , examples (datetime (2013, 1, 22, 0, 0, 0) Day)
+             [ "faz tres semanas"
+             , "faz três semanas"
+             ]
+  , examples (datetime (2013, 4, 12, 0, 0, 0) Day)
+             [ "em dois meses"
+             ]
+  , examples (datetime (2012, 11, 12, 0, 0, 0) Day)
+             [ "faz tres meses"
+             ]
+  , examples (datetime (2014, 2, 0, 0, 0, 0) Month)
+             [ "em um ano"
+             , "em 1 ano"
+             ]
+  , examples (datetime (2011, 2, 0, 0, 0, 0) Month)
+             [ "faz dois anos"
+             ]
+  , examples (datetimeInterval ((2013, 6, 21, 0, 0, 0), (2013, 9, 24, 0, 0, 0)) Day)
+             [ "este verão"
+             , "este verao"
+             ]
+  , examples (datetimeInterval ((2012, 12, 21, 0, 0, 0), (2013, 3, 21, 0, 0, 0)) Day)
+             [ "este inverno"
+             ]
+  , examples (datetime (2013, 12, 25, 0, 0, 0) Day)
+             [ "Natal"
+             ]
+  , examples (datetime (2013, 12, 31, 0, 0, 0) Day)
+             [ "véspera de ano novo"
+             ]
+  , examples (datetime (2014, 1, 1, 0, 0, 0) Day)
+             [ "ano novo"
+             , "reveillon"
+             , "Reveillon"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 18, 0, 0), (2013, 2, 13, 0, 0, 0)) Hour)
+             [ "esta noite"
+             , "essa noite"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 18, 0, 0), (2013, 2, 14, 0, 0, 0)) Hour)
+             [ "amanhã a noite"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 18, 0, 0), (2013, 2, 12, 0, 0, 0)) Hour)
+             [ "ontem a noite"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 18, 0, 0), (2013, 2, 18, 0, 0, 0)) Hour)
+             [ "este final de semana"
+             , "este fim de semana"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 4, 0, 0), (2013, 2, 18, 12, 0, 0)) Hour)
+             [ "segunda de manhã"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 4, 0, 0), (2013, 2, 15, 12, 0, 0)) Hour)
+             [ "dia 15 de fevereiro pela manhã"
+             , "dia 15 de fevereiro de manhã"
+             ]
+  , examples (datetime (2013, 2, 12, 20, 0, 0) Hour)
+             [ "às 8 da noite"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 29, 58), (2013, 2, 12, 4, 30, 0)) Second)
+             [ "2 segundos atras"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 1), (2013, 2, 12, 4, 30, 4)) Second)
+             [ "proximos 3 segundos"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 28, 0), (2013, 2, 12, 4, 30, 0)) Minute)
+             [ "2 minutos atrás"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 31, 0), (2013, 2, 12, 4, 34, 0)) Minute)
+             [ "proximos 3 minutos"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 5, 0, 0), (2013, 2, 12, 8, 0, 0)) Hour)
+             [ "proximas 3 horas"
+             ]
+  , examples (datetimeInterval ((2013, 2, 10, 0, 0, 0), (2013, 2, 12, 0, 0, 0)) Day)
+             [ "passados 2 dias"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
+             [ "proximos 3 dias"
+             ]
+  , examples (datetimeInterval ((2013, 1, 28, 0, 0, 0), (2013, 2, 11, 0, 0, 0)) Week)
+             [ "duas semanas atras"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 0, 0, 0), (2013, 3, 11, 0, 0, 0)) Week)
+             [ "3 proximas semanas"
+             , "3 semanas que vem"
+             ]
+  , examples (datetimeInterval ((2012, 12, 0, 0, 0, 0), (2013, 2, 0, 0, 0, 0)) Month)
+             [ "passados 2 meses"
+             ]
+  , examples (datetimeInterval ((2013, 3, 0, 0, 0, 0), (2013, 6, 0, 0, 0, 0)) Month)
+             [ "3 próximos meses"
+             , "proximos tres meses"
+             , "tres meses que vem"
+             ]
+  , examples (datetimeInterval ((2011, 0, 0, 0, 0, 0), (2013, 0, 0, 0, 0, 0)) Year)
+             [ "passados 2 anos"
+             ]
+  , examples (datetimeInterval ((2014, 0, 0, 0, 0, 0), (2017, 0, 0, 0, 0, 0)) Year)
+             [ "3 próximos anos"
+             , "proximo tres anos"
+             , "3 anos que vem"
+             ]
+  , examples (datetimeInterval ((2013, 7, 13, 0, 0, 0), (2013, 7, 16, 0, 0, 0)) Day)
+             [ "13 a 15 de julho"
+             , "13 - 15 de julho de 2013"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 9, 30, 0), (2013, 2, 12, 11, 0, 0)) Minute)
+             [ "9:30 - 11:00"
+             ]
+  , examples (datetimeInterval ((2013, 12, 21, 0, 0, 0), (2014, 1, 7, 0, 0, 0)) Day)
+             [ "21 de Dez. a 6 de Jan"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 12, 7, 30, 0)) Second)
+             [ "dentro de tres horas"
+             ]
+  , examples (datetime (2013, 2, 12, 16, 0, 0) Hour)
+             [ "as quatro da tarde"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 0, 0) Minute)
+             [ "as quatro CET"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 12, 12, 0, 0) Hour)
+             [ "após ao meio dia"
+             , "depois do meio dia"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 12, 0, 0) Hour)
+             [ "antes do meio dia"
+             , "não mais que meio dia"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 13, 15, 0, 0) Hour)
+             [ "amanhã depois das 15hs"
+             , "amanha após as quinze horas"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 13, 0, 0, 0) Hour)
+             [ "antes da meia noite"
+             , "até a meia noite"
+             ]
+  ]
diff --git a/Duckling/Time/PT/Rules.hs b/Duckling/Time/PT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/PT/Rules.hs
@@ -0,0 +1,1655 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.PT.Rules
+  ( rules ) where
+
+import qualified Data.Text as Text
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Regex.Types
+import Duckling.Time.Helpers
+import Duckling.Time.Types (TimeData (..))
+import qualified Duckling.Time.Types as TTime
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleNamedday :: Rule
+ruleNamedday = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "segunda((\\s|\\-)feira)?|seg\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 1
+  }
+
+ruleSHourmintimeofday :: Rule
+ruleSHourmintimeofday = Rule
+  { name = "às <hour-min>(time-of-day)"
+  , pattern =
+    [ regex "(\x00e0|a)s?"
+    , Predicate isATimeOfDay
+    , regex "horas?"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleTheDayAfterTomorrow :: Rule
+ruleTheDayAfterTomorrow = Rule
+  { name = "the day after tomorrow"
+  , pattern =
+    [ regex "depois de amanh(\x00e3|a)"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 2
+  }
+
+ruleNamedmonth12 :: Rule
+ruleNamedmonth12 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "dezembro|dez\\.?"
+    ]
+  , prod = \_ -> tt $ month 12
+  }
+
+ruleNamedday2 :: Rule
+ruleNamedday2 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "ter(\x00e7|c)a((\\s|\\-)feira)?|ter\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 2
+  }
+
+ruleNatal :: Rule
+ruleNatal = Rule
+  { name = "natal"
+  , pattern =
+    [ regex "natal"
+    ]
+  , prod = \_ -> tt $ monthDay 12 25
+  }
+
+ruleNaoDate :: Rule
+ruleNaoDate = Rule
+  { name = "n[ao] <date>"
+  , pattern =
+    [ regex "n[ao]"
+    , Predicate $ isGrainOfTime TG.Day
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleIntersectByDaOrDe :: Rule
+ruleIntersectByDaOrDe = Rule
+  { name = "intersect by `da` or `de`"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "d[ae]"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+rulePassadosNCycle :: Rule
+rulePassadosNCycle = Rule
+  { name = "passados n <cycle>"
+  , pattern =
+    [ regex "passad(a|o)s?"
+    , Predicate $ isIntegerBetween 2 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain (- v)
+      _ -> Nothing
+  }
+
+ruleLastTime :: Rule
+ruleLastTime = Rule
+  { name = "last <time>"
+  , pattern =
+    [ regex "(\x00fa|u)ltim[ao]s?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleNamedday6 :: Rule
+ruleNamedday6 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "s(\x00e1|a)bado|s(\x00e1|a)b\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 6
+  }
+
+ruleDatetimeDatetimeInterval :: Rule
+ruleDatetimeDatetimeInterval = Rule
+  { name = "<datetime> - <datetime> (interval)"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "\\-|a"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Open (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth7 :: Rule
+ruleNamedmonth7 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "julho|jul\\.?"
+    ]
+  , prod = \_ -> tt $ month 7
+  }
+
+ruleEvening :: Rule
+ruleEvening = Rule
+  { name = "evening"
+  , pattern =
+    [ regex "noite"
+    ]
+  , prod = \_ ->
+      let from = hour False 18
+          to = hour False 0
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleDayOfMonthSt :: Rule
+ruleDayOfMonthSt = Rule
+  { name = "day of month (1st)"
+  , pattern =
+    [ regex "primeiro|um|1o"
+    ]
+  , prod = \_ -> tt $ dayOfMonth 1
+  }
+
+ruleNow :: Rule
+ruleNow = Rule
+  { name = "now"
+  , pattern =
+    [ regex "(hoje)|(neste|nesse) momento"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 0
+  }
+
+ruleDimTimeDaMadrugada :: Rule
+ruleDimTimeDaMadrugada = Rule
+  { name = "<dim time> da madrugada"
+  , pattern =
+    [ dimension Time
+    , regex "(da|na|pela) madruga(da)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        let td2 = mkLatent . partOfDay $
+                    interval TTime.Open (hour False 1, hour False 4)
+        in Token Time <$> intersect td td2
+      _ -> Nothing
+  }
+
+ruleHhhmmTimeofday :: Rule
+ruleHhhmmTimeofday = Rule
+  { name = "hh(:|.|h)mm (time-of-day)"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))[:h\\.]([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        h <- parseInt m1
+        m <- parseInt m2
+        tt $ hourMinute True h m
+      _ -> Nothing
+  }
+
+ruleNamedday4 :: Rule
+ruleNamedday4 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "quinta((\\s|\\-)feira)?|qui\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 4
+  }
+
+ruleProximoCycle :: Rule
+ruleProximoCycle = Rule
+  { name = "proximo <cycle> "
+  , pattern =
+    [ regex "pr(\x00f3|o)xim(o|a)s?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleCycleAntesDeTime :: Rule
+ruleCycleAntesDeTime = Rule
+  { name = "<cycle> antes de <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "antes d[eo]"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain (-1) td
+      _ -> Nothing
+  }
+
+ruleEsteCycle :: Rule
+ruleEsteCycle = Rule
+  { name = "este <cycle>"
+  , pattern =
+    [ regex "(n?es[st](es?|as?))"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 0
+      _ -> Nothing
+  }
+
+ruleSHourminTimeofday :: Rule
+ruleSHourminTimeofday = Rule
+  { name = "às <hour-min> <time-of-day>"
+  , pattern =
+    [ regex "(\x00e0|a)s"
+    , Predicate isNotLatent
+    , regex "da"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleSeason4 :: Rule
+ruleSeason4 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "primavera"
+    ]
+  , prod = \_ ->
+      let from = monthDay 3 20
+          to = monthDay 6 21
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleYearLatent2 :: Rule
+ruleYearLatent2 = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2101 10000
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ year n
+      _ -> Nothing
+  }
+
+ruleDiaDayofmonthDeNamedmonth :: Rule
+ruleDiaDayofmonthDeNamedmonth = Rule
+  { name = "dia <day-of-month> de <named-month>"
+  , pattern =
+    [ regex "dia"
+    , Predicate isDOMInteger
+    , regex "de|\\/"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleNoon :: Rule
+ruleNoon = Rule
+  { name = "noon"
+  , pattern =
+    [ regex "meio[\\s\\-]?dia"
+    ]
+  , prod = \_ -> tt $ hour False 12
+  }
+
+ruleProximasNCycle :: Rule
+ruleProximasNCycle = Rule
+  { name = "proximas n <cycle>"
+  , pattern =
+    [ regex "pr(\x00f3|o)xim(o|a)s?"
+    , Predicate $ isIntegerBetween 2 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleThisnextDayofweek :: Rule
+ruleThisnextDayofweek = Rule
+  { name = "this|next <day-of-week>"
+  , pattern =
+    [ regex "es[ts][ae]|pr(\x00f3|o)xim[ao]"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleTheDayBeforeYesterday :: Rule
+ruleTheDayBeforeYesterday = Rule
+  { name = "the day before yesterday"
+  , pattern =
+    [ regex "anteontem|antes de ontem"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 2
+  }
+
+ruleHourofdayIntegerAsRelativeMinutes :: Rule
+ruleHourofdayIntegerAsRelativeMinutes = Rule
+  { name = "<hour-of-day> <integer> (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+ruleHourofdayAndRelativeMinutes :: Rule
+ruleHourofdayAndRelativeMinutes = Rule
+  { name = "<hour-of-day> and <relative minutes>"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "e"
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+ruleIntegerParaAsHourofdayAsRelativeMinutes :: Rule
+ruleIntegerParaAsHourofdayAsRelativeMinutes = Rule
+  { name = "<integer> para as <hour-of-day> (as relative minutes)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "para ((o|a|\x00e0)s?)?"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleHourofdayIntegerAsRelativeMinutes2 :: Rule
+ruleHourofdayIntegerAsRelativeMinutes2 = Rule
+  { name = "<hour-of-day> <integer> (as relative minutes) minutos"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    , regex "min\\.?(uto)?s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+ruleHourofdayAndRelativeMinutes2 :: Rule
+ruleHourofdayAndRelativeMinutes2 = Rule
+  { name = "<hour-of-day> and <relative minutes> minutos"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "e"
+    , Predicate $ isIntegerBetween 1 59
+    , regex "min\\.?(uto)?s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+ruleIntegerParaAsHourofdayAsRelativeMinutes2 :: Rule
+ruleIntegerParaAsHourofdayAsRelativeMinutes2 = Rule
+  { name = "<integer> minutos para as <hour-of-day> (as relative minutes)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "min\\.?(uto)?s?"
+    , regex "para ((o|a|\x00e0)s?)?"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:_:Token Time td:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleHourofdayQuarter :: Rule
+ruleHourofdayQuarter = Rule
+  { name = "<hour-of-day> quarter (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "quinze"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 15
+      _ -> Nothing
+  }
+ruleHourofdayAndQuarter :: Rule
+ruleHourofdayAndQuarter = Rule
+  { name = "<hour-of-day> and quinze"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "e quinze"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 15
+      _ -> Nothing
+  }
+ruleQuarterParaAsHourofdayAsRelativeMinutes :: Rule
+ruleQuarterParaAsHourofdayAsRelativeMinutes = Rule
+  { name = "quinze para as <hour-of-day> (as relative minutes)"
+  , pattern =
+    [ regex "quinze para ((o|a|\x00e0)s?)?"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesBefore 15 td
+      _ -> Nothing
+  }
+
+ruleHourofdayHalf :: Rule
+ruleHourofdayHalf = Rule
+  { name = "<hour-of-day> half (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "meia|trinta"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+ruleHourofdayAndHalf :: Rule
+ruleHourofdayAndHalf = Rule
+  { name = "<hour-of-day> and half"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "e (meia|trinta)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+ruleHalfParaAsHourofdayAsRelativeMinutes :: Rule
+ruleHalfParaAsHourofdayAsRelativeMinutes = Rule
+  { name = "half para as <hour-of-day> (as relative minutes)"
+  , pattern =
+    [ regex "(meia|trinta) para ((o|a|\x00e0)s?)?"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesBefore 30 td
+      _ -> Nothing
+  }
+
+ruleHourofdayThreeQuarter :: Rule
+ruleHourofdayThreeQuarter = Rule
+  { name = "<hour-of-day> 3/4 (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "quarenta e cinco"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 45
+      _ -> Nothing
+  }
+ruleHourofdayAndThreeQuarter :: Rule
+ruleHourofdayAndThreeQuarter = Rule
+  { name = "<hour-of-day> and 3/4"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "e quarenta e cinco"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 45
+      _ -> Nothing
+  }
+ruleThreeQuarterParaAsHourofdayAsRelativeMinutes :: Rule
+ruleThreeQuarterParaAsHourofdayAsRelativeMinutes = Rule
+  { name = "3/4 para as <hour-of-day> (as relative minutes)"
+  , pattern =
+    [ regex "quarenta e cinco para ((o|a|\x00e0)s?)?"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> Token Time <$> minutesBefore 45 td
+      _ -> Nothing
+  }
+
+ruleNamedmonth :: Rule
+ruleNamedmonth = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "janeiro|jan\\.?"
+    ]
+  , prod = \_ -> tt $ month 1
+  }
+
+ruleTiradentes :: Rule
+ruleTiradentes = Rule
+  { name = "Tiradentes"
+  , pattern =
+    [ regex "tiradentes"
+    ]
+  , prod = \_ -> tt $ monthDay 4 21
+  }
+
+ruleInThePartofday :: Rule
+ruleInThePartofday = Rule
+  { name = "in the <part-of-day>"
+  , pattern =
+    [ regex "(de|pela)"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+rulePartofdayDessaSemana :: Rule
+rulePartofdayDessaSemana = Rule
+  { name = "<part-of-day> dessa semana"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "(d?es[ts]a semana)|agora"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        Token Time . partOfDay <$> intersect (cycleNth TG.Day 0) td
+      _ -> Nothing
+  }
+
+ruleNamedmonth3 :: Rule
+ruleNamedmonth3 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "mar(\x00e7|c)o|mar\\.?"
+    ]
+  , prod = \_ -> tt $ month 3
+  }
+
+ruleDepoisDasTimeofday :: Rule
+ruleDepoisDasTimeofday = Rule
+  { name = "depois das <time-of-day>"
+  , pattern =
+    [ regex "(depois|ap(\x00f3|o)s) d?((a|\x00e1|\x00e0)[so]?|os?)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ withDirection TTime.After td
+      _ -> Nothing
+  }
+
+ruleDdmm :: Rule
+ruleDdmm = Rule
+  { name = "dd[/-]mm"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[\\/\\-](0?[1-9]|1[0-2])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (dd:mm:_)):_) -> do
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleEmDuration :: Rule
+ruleEmDuration = Rule
+  { name = "em <duration>"
+  , pattern =
+    [ regex "em"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleAfternoon :: Rule
+ruleAfternoon = Rule
+  { name = "afternoon"
+  , pattern =
+    [ regex "tarde"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 19
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleNamedmonth4 :: Rule
+ruleNamedmonth4 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "abril|abr\\.?"
+    ]
+  , prod = \_ -> tt $ month 4
+  }
+
+ruleDimTimeDaManha :: Rule
+ruleDimTimeDaManha = Rule
+  { name = "<dim time> da manha"
+  , pattern =
+    [ Predicate $ isGrainFinerThan TG.Year
+    , regex "(da|na|pela) manh(\x00e3|a)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        let td2 = mkLatent . partOfDay $
+                    interval TTime.Open (hour False 4, hour False 12)
+        in Token Time <$> intersect td td2
+      _ -> Nothing
+  }
+
+ruleNCycleProximoqueVem :: Rule
+ruleNCycleProximoqueVem = Rule
+  { name = "n <cycle> (proximo|que vem)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2 9999
+    , dimension TimeGrain
+    , regex "(pr(\x00f3|o)xim(o|a)s?|que vem?|seguintes?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleMidnight :: Rule
+ruleMidnight = Rule
+  { name = "midnight"
+  , pattern =
+    [ regex "meia[\\s\\-]?noite"
+    ]
+  , prod = \_ -> tt $ hour False 0
+  }
+
+ruleNamedday5 :: Rule
+ruleNamedday5 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "sexta((\\s|\\-)feira)?|sex\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 5
+  }
+
+ruleDdddMonthinterval :: Rule
+ruleDdddMonthinterval = Rule
+  { name = "dd-dd <month>(interval)"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])"
+    , regex "\\-|a"
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    , regex "de"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:_)):
+       _:
+       Token RegexMatch (GroupMatch (m2:_)):
+       _:
+       Token Time td:
+       _) -> do
+        d1 <- parseInt m1
+        d2 <- parseInt m2
+        from <- intersect (dayOfMonth d1) td
+        to <- intersect (dayOfMonth d2) td
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleTimeofdayLatent :: Rule
+ruleTimeofdayLatent = Rule
+  { name = "time-of-day (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 0 23
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ hour True v
+      _ -> Nothing
+  }
+
+ruleUltimoTime :: Rule
+ruleUltimoTime = Rule
+  { name = "ultimo <time>"
+  , pattern =
+    [ regex "(u|\x00fa)ltimo"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleNamedmonth2 :: Rule
+ruleNamedmonth2 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "fevereiro|fev\\.?"
+    ]
+  , prod = \_ -> tt $ month 2
+  }
+
+ruleNamedmonthnameddayPast :: Rule
+ruleNamedmonthnameddayPast = Rule
+  { name = "<named-month|named-day> past"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "(da semana)? passad(o|a)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleSeason3 :: Rule
+ruleSeason3 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "inverno"
+    ]
+  , prod = \_ ->
+      let from = monthDay 12 21
+          to = monthDay 3 20
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleSeason :: Rule
+ruleSeason = Rule
+  { name = "season"
+  , pattern =
+    [ regex "ver(\x00e3|a)o"
+    ]
+  , prod = \_ ->
+      let from = monthDay 6 21
+          to = monthDay 9 23
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleRightNow :: Rule
+ruleRightNow = Rule
+  { name = "right now"
+  , pattern =
+    [ regex "agora|j(\x00e1|a)|(nesse|neste) instante"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Second 0
+  }
+
+ruleFazemDuration :: Rule
+ruleFazemDuration = Rule
+  { name = "fazem <duration>"
+  , pattern =
+    [ regex "faz(em)?"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ durationAgo dd
+      _ -> Nothing
+  }
+
+ruleAmanhPelaPartofday :: Rule
+ruleAmanhPelaPartofday = Rule
+  { name = "amanhã pela <part-of-day>"
+  , pattern =
+    [ dimension Time
+    , regex "(da|na|pela|a)"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleHhmmMilitaryTimeofday :: Rule
+ruleHhmmMilitaryTimeofday = Rule
+  { name = "hhmm (military time-of-day)"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        h <- parseInt m1
+        m <- parseInt m2
+        tt . mkLatent $ hourMinute False h m
+      _ -> Nothing
+  }
+
+ruleNamedmonthnameddayNext :: Rule
+ruleNamedmonthnameddayNext = Rule
+  { name = "<named-month|named-day> next"
+  , pattern =
+    [ dimension Time
+    , regex "(da pr(o|\x00f3)xima semana)|(da semana)? que vem"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 1 False td
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ Predicate isNotLatent
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleTimeofdayPartofday :: Rule
+ruleTimeofdayPartofday = Rule
+  { name = "<time-of-day> <part-of-day>"
+  , pattern =
+    [ dimension Time
+    , regex "(da|na|pela)"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleDeDatetimeDatetimeInterval :: Rule
+ruleDeDatetimeDatetimeInterval = Rule
+  { name = "de <datetime> - <datetime> (interval)"
+  , pattern =
+    [ regex "de?"
+    , dimension Time
+    , regex "\\-|a"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Open (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth6 :: Rule
+ruleNamedmonth6 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "junho|jun\\.?"
+    ]
+  , prod = \_ -> tt $ month 6
+  }
+
+ruleDentroDeDuration :: Rule
+ruleDentroDeDuration = Rule
+  { name = "dentro de <duration>"
+  , pattern =
+    [ regex "(dentro de)|em"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        let from = cycleNth TG.Second 0
+            to = inDuration dd
+        in tt $ interval TTime.Open (from, to)
+      _ -> Nothing
+  }
+
+ruleSTimeofday :: Rule
+ruleSTimeofday = Rule
+  { name = "às <time-of-day>"
+  , pattern =
+    [ regex "(\x00e0|a)s?"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth8 :: Rule
+ruleNamedmonth8 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "agosto|ago\\.?"
+    ]
+  , prod = \_ -> tt $ month 8
+  }
+
+ruleDimTimeDaTarde :: Rule
+ruleDimTimeDaTarde = Rule
+  { name = "<dim time> da tarde"
+  , pattern =
+    [ dimension Time
+    , regex "(da|na|pela) tarde"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        let td2 = mkLatent . partOfDay $
+                    interval TTime.Open (hour False 12, hour False 18)
+        in Token Time <$> intersect td td2
+      _ -> Nothing
+  }
+
+ruleWeekend :: Rule
+ruleWeekend = Rule
+  { name = "week-end"
+  , pattern =
+    [ regex "final de semana|fim de semana|fds"
+    ]
+  , prod = \_ -> do
+      from <- intersect (dayOfWeek 5) (hour False 18)
+      to <- intersect (dayOfWeek 1) (hour False 0)
+      tt $ interval TTime.Open (from, to)
+  }
+
+ruleDayofweekSHourmin :: Rule
+ruleDayofweekSHourmin = Rule
+  { name = "<day-of-week> às <hour-min>"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(\x00e0|a)s"
+    , Predicate isNotLatent
+    , regex "da|pela"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_:Token Time td3:_) -> do
+        td <- intersect td1 td2
+        Token Time <$> intersect td td3
+      _ -> Nothing
+  }
+
+ruleCycleQueVem :: Rule
+ruleCycleQueVem = Rule
+  { name = "<cycle> (que vem)"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "que vem|seguintes?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_) -> tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleAnoNovo :: Rule
+ruleAnoNovo = Rule
+  { name = "ano novo"
+  , pattern =
+    [ regex "ano novo|reveillon"
+    ]
+  , prod = \_ -> tt $ monthDay 1 1
+  }
+
+ruleNextTime :: Rule
+ruleNextTime = Rule
+  { name = "next <time>"
+  , pattern =
+    [ regex "(d[ao]) pr(\x00f3|o)xim[ao]s?|que vem"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleDeYear :: Rule
+ruleDeYear = Rule
+  { name = "de <year>"
+  , pattern =
+    [ regex "de|do ano"
+    , Predicate $ isIntegerBetween 1000 2100
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        n <- getIntValue token
+        tt $ year n
+      _ -> Nothing
+  }
+
+ruleVesperaDeAnoNovo :: Rule
+ruleVesperaDeAnoNovo = Rule
+  { name = "vespera de ano novo"
+  , pattern =
+    [ regex "v(\x00e9|e)spera d[eo] ano[\\s\\-]novo"
+    ]
+  , prod = \_ -> tt $ monthDay 12 31
+  }
+
+ruleNPassadosCycle :: Rule
+ruleNPassadosCycle = Rule
+  { name = "n passados <cycle>"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2 9999
+    , regex "passad(a|o)s?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain (- v)
+      _ -> Nothing
+  }
+
+ruleDiaDayofmonthNonOrdinal :: Rule
+ruleDiaDayofmonthNonOrdinal = Rule
+  { name = "dia <day-of-month> (non ordinal)"
+  , pattern =
+    [ regex "dia"
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleYyyymmdd :: Rule
+ruleYyyymmdd = Rule
+  { name = "yyyy-mm-dd"
+  , pattern =
+    [ regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (yy:mm:dd:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleTimeofdayHoras :: Rule
+ruleTimeofdayHoras = Rule
+  { name = "<time-of-day> horas"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "h\\.?(ora)?s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleTwoTimeTokensSeparatedBy :: Rule
+ruleTwoTimeTokensSeparatedBy = Rule
+  { name = "two time tokens separated by \",\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex ","
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleTwoTimeTokensSeparatedBy2 :: Rule
+ruleTwoTimeTokensSeparatedBy2 = Rule
+  { name = "two time tokens separated by \",\"2"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex ","
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleMorning :: Rule
+ruleMorning = Rule
+  { name = "morning"
+  , pattern =
+    [ regex "manh(\x00e3|a)"
+    ]
+  , prod = \_ ->
+      let from = hour False 4
+          to = hour False 12
+      in tt . mkLatent .partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleThisPartofday :: Rule
+ruleThisPartofday = Rule
+  { name = "this <part-of-day>"
+  , pattern =
+    [ regex "es[ts]a"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        Token Time . partOfDay <$> intersect (cycleNth TG.Day 0) td
+      _ -> Nothing
+  }
+
+ruleThisTime :: Rule
+ruleThisTime = Rule
+  { name = "this <time>"
+  , pattern =
+    [ regex "es[ts][ae]"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleProclamaoDaRepblica :: Rule
+ruleProclamaoDaRepblica = Rule
+  { name = "Proclamação da República"
+  , pattern =
+    [ regex "proclama(c|\x00e7)(a|\x00e3)o da rep(\x00fa|u)blica"
+    ]
+  , prod = \_ -> tt $ monthDay 11 15
+  }
+
+ruleYearLatent :: Rule
+ruleYearLatent = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween (- 10000) 999
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ year n
+      _ -> Nothing
+  }
+
+ruleYesterday :: Rule
+ruleYesterday = Rule
+  { name = "yesterday"
+  , pattern =
+    [ regex "ontem"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 1
+  }
+
+ruleSeason2 :: Rule
+ruleSeason2 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "outono"
+    ]
+  , prod = \_ ->
+      let from = monthDay 9 23
+          to = monthDay 12 21
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleDiaDoTrabalhador :: Rule
+ruleDiaDoTrabalhador = Rule
+  { name = "Dia do trabalhador"
+  , pattern =
+    [ regex "dia do trabalh(o|ador)"
+    ]
+  , prod = \_ -> tt $ monthDay 5 1
+  }
+
+ruleNCycleAtras :: Rule
+ruleNCycleAtras = Rule
+  { name = "n <cycle> atras"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2 9999
+    , dimension TimeGrain
+    , regex "atr(a|\x00e1)s"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain (- v)
+      _ -> Nothing
+  }
+
+ruleTimeofdayAmpm :: Rule
+ruleTimeofdayAmpm = Rule
+  { name = "<time-of-day> am|pm"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "([ap])\\.?m?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:Token RegexMatch (GroupMatch (ap:_)):_) ->
+        tt . timeOfDayAMPM td $ Text.toLower ap == "a"
+      _ -> Nothing
+  }
+
+ruleDayofmonthDeNamedmonth :: Rule
+ruleDayofmonthDeNamedmonth = Rule
+  { name = "<day-of-month> de <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , regex "de|\\/"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleEntreDatetimeEDatetimeInterval :: Rule
+ruleEntreDatetimeEDatetimeInterval = Rule
+  { name = "entre <datetime> e <datetime> (interval)"
+  , pattern =
+    [ regex "entre"
+    , dimension Time
+    , regex "e"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Open (td1, td2)
+      _ -> Nothing
+  }
+
+ruleEntreDdEtDdMonthinterval :: Rule
+ruleEntreDdEtDdMonthinterval = Rule
+  { name = "entre dd et dd <month>(interval)"
+  , pattern =
+    [ regex "entre"
+    , regex "(0?[1-9]|[12]\\d|3[01])"
+    , regex "e?"
+    , regex "(0?[1-9]|[12]\\d|3[01])"
+    , regex "de"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token RegexMatch (GroupMatch (d1:_)):
+       _:
+       Token RegexMatch (GroupMatch (d2:_)):
+       _:
+       Token Time td:
+       _) -> do
+        dd1 <- parseInt d1
+        dd2 <- parseInt d2
+        dom1 <- intersect (dayOfMonth dd1) td
+        dom2 <- intersect (dayOfMonth dd2) td
+        tt $ interval TTime.Closed (dom1, dom2)
+      _ -> Nothing
+  }
+
+ruleCyclePassado :: Rule
+ruleCyclePassado = Rule
+  { name = "<cycle> passado"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "passad(a|o)s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_) ->
+        tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleNamedmonth5 :: Rule
+ruleNamedmonth5 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "maio|mai\\.?"
+    ]
+  , prod = \_ -> tt $ month 5
+  }
+
+ruleNamedday7 :: Rule
+ruleNamedday7 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "domingo|dom\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 7
+  }
+
+ruleNossaSenhoraAparecida :: Rule
+ruleNossaSenhoraAparecida = Rule
+  { name = "Nossa Senhora Aparecida"
+  , pattern =
+    [ regex "nossa senhora (aparecida)?"
+    ]
+  , prod = \_ -> tt $ monthDay 10 12
+  }
+
+ruleFinados :: Rule
+ruleFinados = Rule
+  { name = "Finados"
+  , pattern =
+    [ regex "finados|dia dos mortos"
+    ]
+  , prod = \_ -> tt $ monthDay 11 2
+  }
+
+ruleYear :: Rule
+ruleYear = Rule
+  { name = "year"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1000 2100
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt $ year n
+      _ -> Nothing
+  }
+
+ruleNamedmonth10 :: Rule
+ruleNamedmonth10 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "outubro|out\\.?"
+    ]
+  , prod = \_ -> tt $ month 10
+  }
+
+ruleAntesDasTimeofday :: Rule
+ruleAntesDasTimeofday = Rule
+  { name = "antes das <time-of-day>"
+  , pattern =
+    [ regex "(antes|at(e|\x00e9)|n(a|\x00e3)o mais que) (d?(o|a|\x00e0)s?)?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ withDirection TTime.Before td
+      _ -> Nothing
+  }
+
+ruleNProximasCycle :: Rule
+ruleNProximasCycle = Rule
+  { name = "n proximas <cycle>"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2 9999
+    , regex "pr(\x00f3|o)xim(o|a)s?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleDdmmyyyy :: Rule
+ruleDdmmyyyy = Rule
+  { name = "dd[/-.]mm[/-.]yyyy"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[\\.\\/\\-](0?[1-9]|1[0-2])[\\.\\/\\-](\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
+        y <- parseInt m3
+        m <- parseInt m2
+        d <- parseInt m1
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleNamedmonth11 :: Rule
+ruleNamedmonth11 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "novembro|nov\\.?"
+    ]
+  , prod = \_ -> tt $ month 11
+  }
+
+ruleIndependecia :: Rule
+ruleIndependecia = Rule
+  { name = "Independecia"
+  , pattern =
+    [ regex "independ(\x00ea|e)ncia"
+    ]
+  , prod = \_ -> tt $ monthDay 9 7
+  }
+
+ruleNamedday3 :: Rule
+ruleNamedday3 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "quarta((\\s|\\-)feira)?|qua\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 3
+  }
+
+ruleTomorrow :: Rule
+ruleTomorrow = Rule
+  { name = "tomorrow"
+  , pattern =
+    [ regex "amanh(\x00e3|a)"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 1
+  }
+
+ruleNamedmonth9 :: Rule
+ruleNamedmonth9 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "setembro|set\\.?"
+    ]
+  , prod = \_ -> tt $ month 9
+  }
+
+ruleTimezone :: Rule
+ruleTimezone = Rule
+  { name = "<time> timezone"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "\\b(YEKT|YEKST|YAKT|YAKST|WITA|WIT|WIB|WGT|WGST|WFT|WET|WEST|WAT|WAST|VUT|VLAT|VLAST|VET|UZT|UYT|UYST|UTC|ULAT|TVT|TMT|TLT|TKT|TJT|TFT|TAHT|SST|SRT|SGT|SCT|SBT|SAST|SAMT|RET|PYT|PYST|PWT|PST|PONT|PMST|PMDT|PKT|PHT|PHOT|PGT|PETT|PETST|PET|PDT|OMST|OMSST|NZST|NZDT|NUT|NST|NPT|NOVT|NOVST|NFT|NDT|NCT|MYT|MVT|MUT|MST|MSK|MSD|MMT|MHT|MDT|MAWT|MART|MAGT|MAGST|LINT|LHST|LHDT|KUYT|KST|KRAT|KRAST|KGT|JST|IST|IRST|IRKT|IRKST|IRDT|IOT|IDT|ICT|HOVT|HKT|GYT|GST|GMT|GILT|GFT|GET|GAMT|GALT|FNT|FKT|FKST|FJT|FJST|EST|EGT|EGST|EET|EEST|EDT|ECT|EAT|EAST|EASST|DAVT|ChST|CXT|CVT|CST|COT|CLT|CLST|CKT|CHAST|CHADT|CET|CEST|CDT|CCT|CAT|CAST|BTT|BST|BRT|BRST|BOT|BNT|AZT|AZST|AZOT|AZOST|AWST|AWDT|AST|ART|AQTT|ANAT|ANAST|AMT|AMST|ALMT|AKST|AKDT|AFT|AEST|AEDT|ADT|ACST|ACDT)\\b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       Token RegexMatch (GroupMatch (tz:_)):
+       _) -> Token Time <$> inTimezone tz td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAfternoon
+  , ruleAmanhPelaPartofday
+  , ruleAnoNovo
+  , ruleAntesDasTimeofday
+  , ruleDatetimeDatetimeInterval
+  , ruleDayOfMonthSt
+  , ruleDayofmonthDeNamedmonth
+  , ruleDayofweekSHourmin
+  , ruleDdddMonthinterval
+  , ruleDdmm
+  , ruleDdmmyyyy
+  , ruleDeDatetimeDatetimeInterval
+  , ruleDeYear
+  , ruleDentroDeDuration
+  , ruleDepoisDasTimeofday
+  , ruleDiaDayofmonthDeNamedmonth
+  , ruleDiaDayofmonthNonOrdinal
+  , ruleDiaDoTrabalhador
+  , ruleDimTimeDaMadrugada
+  , ruleDimTimeDaManha
+  , ruleDimTimeDaTarde
+  , ruleEmDuration
+  , ruleEntreDatetimeEDatetimeInterval
+  , ruleEntreDdEtDdMonthinterval
+  , ruleEsteCycle
+  , ruleEvening
+  , ruleFazemDuration
+  , ruleFinados
+  , ruleHhhmmTimeofday
+  , ruleHhmmMilitaryTimeofday
+  , ruleHourofdayAndRelativeMinutes
+  , ruleHourofdayAndRelativeMinutes2
+  , ruleHourofdayAndQuarter
+  , ruleHourofdayAndThreeQuarter
+  , ruleHourofdayAndHalf
+  , ruleHourofdayIntegerAsRelativeMinutes
+  , ruleHourofdayIntegerAsRelativeMinutes2
+  , ruleHourofdayQuarter
+  , ruleHourofdayHalf
+  , ruleHourofdayThreeQuarter
+  , ruleInThePartofday
+  , ruleIndependecia
+  , ruleIntegerParaAsHourofdayAsRelativeMinutes
+  , ruleIntegerParaAsHourofdayAsRelativeMinutes2
+  , ruleHalfParaAsHourofdayAsRelativeMinutes
+  , ruleQuarterParaAsHourofdayAsRelativeMinutes
+  , ruleThreeQuarterParaAsHourofdayAsRelativeMinutes
+  , ruleIntersect
+  , ruleIntersectByDaOrDe
+  , ruleLastTime
+  , ruleMidnight
+  , ruleMorning
+  , ruleNCycleAtras
+  , ruleNCycleProximoqueVem
+  , ruleNPassadosCycle
+  , ruleNProximasCycle
+  , ruleNamedday
+  , ruleNamedday2
+  , ruleNamedday3
+  , ruleNamedday4
+  , ruleNamedday5
+  , ruleNamedday6
+  , ruleNamedday7
+  , ruleNamedmonth
+  , ruleNamedmonth10
+  , ruleNamedmonth11
+  , ruleNamedmonth12
+  , ruleNamedmonth2
+  , ruleNamedmonth3
+  , ruleNamedmonth4
+  , ruleNamedmonth5
+  , ruleNamedmonth6
+  , ruleNamedmonth7
+  , ruleNamedmonth8
+  , ruleNamedmonth9
+  , ruleNamedmonthnameddayNext
+  , ruleNamedmonthnameddayPast
+  , ruleNaoDate
+  , ruleNatal
+  , ruleNextTime
+  , ruleCycleQueVem
+  , ruleProximoCycle
+  , ruleNoon
+  , ruleNossaSenhoraAparecida
+  , ruleNow
+  , ruleCycleAntesDeTime
+  , ruleCyclePassado
+  , rulePartofdayDessaSemana
+  , rulePassadosNCycle
+  , ruleProclamaoDaRepblica
+  , ruleProximasNCycle
+  , ruleRightNow
+  , ruleSHourminTimeofday
+  , ruleSHourmintimeofday
+  , ruleSTimeofday
+  , ruleSeason
+  , ruleSeason2
+  , ruleSeason3
+  , ruleSeason4
+  , ruleTheDayAfterTomorrow
+  , ruleTheDayBeforeYesterday
+  , ruleThisPartofday
+  , ruleThisTime
+  , ruleThisnextDayofweek
+  , ruleTimeofdayAmpm
+  , ruleTimeofdayHoras
+  , ruleTimeofdayLatent
+  , ruleTimeofdayPartofday
+  , ruleTiradentes
+  , ruleTomorrow
+  , ruleTwoTimeTokensSeparatedBy
+  , ruleTwoTimeTokensSeparatedBy2
+  , ruleUltimoTime
+  , ruleVesperaDeAnoNovo
+  , ruleWeekend
+  , ruleYear
+  , ruleYearLatent
+  , ruleYearLatent2
+  , ruleYesterday
+  , ruleYyyymmdd
+  , ruleTimezone
+  ]
diff --git a/Duckling/Time/RO/Corpus.hs b/Duckling/Time/RO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/RO/Corpus.hs
@@ -0,0 +1,91 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.RO.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Time.Corpus
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Testing.Types hiding (examples)
+
+corpus :: Corpus
+corpus = (testContext {lang = RO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (datetime (2013, 2, 12, 4, 30, 0) Second)
+             [ "acum"
+             , "chiar acum"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "azi"
+             , "astazi"
+             , "astăzi"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "ieri"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "maine"
+             , "mâine"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "luni"
+             , "lunea asta"
+             , "lunea aceasta"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "Luni, 18 Feb"
+             , "Luni, 18 Februarie"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "marti"
+             , "marți"
+             , "Marti 19"
+             , "Marti pe 19"
+             , "Marți 19"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "joi"
+             , "jo"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "vineri"
+             , "vi"
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "sambata"
+             , "sâmbătă"
+             , "sam"
+             , "sa"
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "duminica"
+             , "duminică"
+             , "du"
+             , "dum"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "1 martie"
+             , "intai martie"
+             ]
+  , examples (datetimeInterval ((2013, 6, 19, 0, 0, 0), (2013, 6, 21, 0, 0, 0)) Day)
+             [ "iunie 19-20"
+             ]
+  , examples (datetime (2013, 12, 25, 0, 0, 0) Day)
+             [ "ziua de craciun"
+             , "crăciun"
+             ]
+  ]
diff --git a/Duckling/Time/RO/Rules.hs b/Duckling/Time/RO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/RO/Rules.hs
@@ -0,0 +1,1503 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.RO.Rules
+  ( rules ) where
+
+import Control.Monad (liftM2)
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Regex.Types
+import Duckling.Time.Helpers
+import Duckling.Time.Types (TimeData (..))
+import qualified Duckling.Time.Types as TTime
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleAcum :: Rule
+ruleAcum = Rule
+  { name = "acum"
+  , pattern =
+    [ regex "(chiar)? ?acum|imediat"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Second 0
+  }
+
+ruleNamedday :: Rule
+ruleNamedday = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "lu(n(ea|i)?)?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 1
+  }
+
+ruleDupamiaza :: Rule
+ruleDupamiaza = Rule
+  { name = "dupamiaza"
+  , pattern =
+    [ regex "dupamiaz(a|\x0103)|dup(a|\x0103) amiaz(a|\x0103)"
+    ]
+  , prod = \_ -> tt . mkLatent . partOfDay $
+      interval TTime.Open (hour False 12, hour False 19)
+  }
+
+ruleNamedmonth12 :: Rule
+ruleNamedmonth12 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "dec(embrie)?"
+    ]
+  , prod = \_ -> tt $ month 12
+  }
+
+ruleNamedday2 :: Rule
+ruleNamedday2 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "ma(r((t|\x021b)(ea|i))?)?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 2
+  }
+
+ruleValentinesDay :: Rule
+ruleValentinesDay = Rule
+  { name = "valentine's day"
+  , pattern =
+    [ regex "sf\\.?((a|\x00e2)ntul)? Valentin"
+    ]
+  , prod = \_ -> tt $ monthDay 2 14
+  }
+
+ruleSinceTimeofday :: Rule
+ruleSinceTimeofday = Rule
+  { name = "since <time-of-day>"
+  , pattern =
+    [ regex "de|din"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ withDirection TTime.After td
+      _ -> Nothing
+  }
+
+ruleNewYearsDay :: Rule
+ruleNewYearsDay = Rule
+  { name = "new year's day"
+  , pattern =
+    [ regex "(siua de )? an(ul)? nou"
+    ]
+  , prod = \_ -> tt $ monthDay 1 1
+  }
+
+ruleNamedday6 :: Rule
+ruleNamedday6 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "s(a|\x00e2)mb(a|\x0103)t(a|\x0103)|s(a|\x00e2)m|s(a|\x00e2)"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 6
+  }
+
+ruleNamedmonth7 :: Rule
+ruleNamedmonth7 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "iul(ie)?"
+    ]
+  , prod = \_ -> tt $ month 7
+  }
+
+ruleOrdinalTrimestruYear :: Rule
+ruleOrdinalTrimestruYear = Rule
+  { name = "<ordinal> trimestru <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
+  }
+
+ruleInNamedmonth :: Rule
+ruleInNamedmonth = Rule
+  { name = "in <named-month>"
+  , pattern =
+    [ regex "(i|\x00ee)n"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleLastCycleOfTime :: Rule
+ruleLastCycleOfTime = Rule
+  { name = "last <cycle> of <time>"
+  , pattern =
+    [ regex "ultim(ul|a)"
+    , dimension TimeGrain
+    , regex "din"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleLastOf grain td
+      _ -> Nothing
+  }
+
+ruleIntroNamedday :: Rule
+ruleIntroNamedday = Rule
+  { name = "intr-o <named-day>"
+  , pattern =
+    [ regex "((i|\x00ee)n(tr)?(\\-?o)?)"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleMonthDdddInterval :: Rule
+ruleMonthDdddInterval = Rule
+  { name = "<month> dd-dd (interval)"
+  , pattern =
+    [ Predicate isAMonth
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    , regex "\\-"
+    , regex "(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       Token RegexMatch (GroupMatch (m1:_)):
+       _:
+       Token RegexMatch (GroupMatch (m2:_)):
+       _) -> do
+        d1 <- parseInt m1
+        d2 <- parseInt m2
+        dom1 <- intersect (dayOfMonth d1) td
+        dom2 <- intersect (dayOfMonth d2) td
+        tt $ interval TTime.Closed (dom1, dom2)
+      _ -> Nothing
+  }
+
+ruleNamedday4 :: Rule
+ruleNamedday4 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "jo(ia?)?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 4
+  }
+
+ruleInDuration :: Rule
+ruleInDuration = Rule
+  { name = "in <duration>"
+  , pattern =
+    [ regex "(i|\x00ee)n"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) -> tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleIeri :: Rule
+ruleIeri = Rule
+  { name = "ieri"
+  , pattern =
+    [ regex "ieri"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 1
+  }
+
+ruleCycleAcesta :: Rule
+ruleCycleAcesta = Rule
+  { name = "<cycle> acesta"
+  , pattern =
+    [ regex "aceasta|acest|(a|\x0103)sta"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) -> tt $ cycleNth grain 0
+      _ -> Nothing
+  }
+
+ruleYearLatent2 :: Rule
+ruleYearLatent2 = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2101 10000
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ year v
+      _ -> Nothing
+  }
+
+ruleAzi :: Rule
+ruleAzi = Rule
+  { name = "azi"
+  , pattern =
+    [ regex "a(st(a|\x0103))?zi"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 0
+  }
+
+ruleNoon :: Rule
+ruleNoon = Rule
+  { name = "noon"
+  , pattern =
+    [ regex "noon"
+    ]
+  , prod = \_ -> tt $ hour False 12
+  }
+
+ruleTimeTrecuta :: Rule
+ruleTimeTrecuta = Rule
+  { name = "<time> trecut[aă]?"
+  , pattern =
+    [ dimension Time
+    , regex "(trecut(a|\x0103)?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleThisnextDayofweek :: Rule
+ruleThisnextDayofweek = Rule
+  { name = "this|next <day-of-week>"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex "aceasta|(a|\x0103)sta|urm(a|\x0103)toare"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleBetweenTimeofdayAndTimeofdayInterval :: Rule
+ruleBetweenTimeofdayAndTimeofdayInterval = Rule
+  { name = "between <time-of-day> and <time-of-day> (interval)"
+  , pattern =
+    [ regex "(i|\x00ee)ntre"
+    , Predicate isATimeOfDay
+    , regex "(s|\x0219)i"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth :: Rule
+ruleNamedmonth = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "ian(uarie)?"
+    ]
+  , prod = \_ -> tt $ month 1
+  }
+
+ruleUrmatoareaCycle :: Rule
+ruleUrmatoareaCycle = Rule
+  { name = "urmatoarea <cycle>"
+  , pattern =
+    [ regex "(urm(a|\x0103)to(area|rul)|viito(are|r))"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) -> tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleCycleAcesta2 :: Rule
+ruleCycleAcesta2 = Rule
+  { name = "<cycle> acesta"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "aceasta|acest|(a|\x0103)sta|curent(a|\x0103)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_) -> tt $ cycleNth grain 0
+      _ -> Nothing
+  }
+
+ruleNamedmonth3 :: Rule
+ruleNamedmonth3 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "martie|mar"
+    ]
+  , prod = \_ -> tt $ month 3
+  }
+
+ruleCraciun :: Rule
+ruleCraciun = Rule
+  { name = "craciun"
+  , pattern =
+    [ regex "(ziua de )?cr(a|\x0103)ciun"
+    ]
+  , prod = \_ -> tt $ monthDay 12 25
+  }
+
+ruleTrimestruNumeralYear :: Rule
+ruleTrimestruNumeralYear = Rule
+  { name = "trimestru <number> <year>"
+  , pattern =
+    [ Predicate $ isGrain TG.Quarter
+    , dimension Numeral
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token Time td:_) -> do
+        v <- getIntValue token
+        tt $ cycleNthAfter False TG.Quarter (v - 1) td
+      _ -> Nothing
+  }
+
+ruleDdmm :: Rule
+ruleDdmm = Rule
+  { name = "dd/mm"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])/(0?[1-9]|1[0-2])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (dd:mm:_)):_) -> do
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleLaTimeofday :: Rule
+ruleLaTimeofday = Rule
+  { name = "la <time-of-day>"
+  , pattern =
+    [ regex "la|@ (ora)?"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth4 :: Rule
+ruleNamedmonth4 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "apr(ilie)?"
+    ]
+  , prod = \_ -> tt $ month 4
+  }
+
+ruleBlackFriday :: Rule
+ruleBlackFriday = Rule
+  { name = "black friday"
+  , pattern =
+    [ regex "black frid?day"
+    ]
+  , prod = \_ -> tt $ nthDOWOfMonth 4 5 11
+  }
+
+ruleChristmasEve :: Rule
+ruleChristmasEve = Rule
+  { name = "christmas eve"
+  , pattern =
+    [ regex "ajun(ul)? (de )?cr(a|\x0103)ciun"
+    ]
+  , prod = \_ -> tt $ monthDay 12 24
+  }
+
+rulePentruDuration :: Rule
+rulePentruDuration = Rule
+  { name = "pentru <duration>"
+  , pattern =
+    [ regex "pentru"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) -> tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleHourofdayIntegerAsRelativeMinutes :: Rule
+ruleHourofdayIntegerAsRelativeMinutes = Rule
+  { name = "<hour-of-day> <integer> (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleHourofdaySfert :: Rule
+ruleHourofdaySfert = Rule
+  { name = "<hour-of-day> sfert"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "((s|\x0219)i )?(un )?sfert"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 15
+      _ -> Nothing
+  }
+
+ruleHourofdayJumatate :: Rule
+ruleHourofdayJumatate = Rule
+  { name = "<hour-of-day> sfert"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "((s|\x0219)i )?jum(a|\x0103)tate|jumate"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       _) -> tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+
+ruleNamedday5 :: Rule
+ruleNamedday5 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "vi(n(er(ea|i))?)?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 5
+  }
+
+ruleDiseara :: Rule
+ruleDiseara = Rule
+  { name = "diseara"
+  , pattern =
+    [ regex "disear(a|\x0103)|((i|\x00ee)n aceas(a|\x0103) )?sear(a|\x0103)"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 18, hour False 0)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleIntersectBy :: Rule
+ruleIntersectBy = Rule
+  { name = "intersect by \",\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex ","
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleDayofmonthNumeral :: Rule
+ruleDayofmonthNumeral = Rule
+  { name = "<day-of-month> (number)"
+  , pattern =
+    [ Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleNthTimeAfterTime :: Rule
+ruleNthTimeAfterTime = Rule
+  { name = "nth <time> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "dup(a|\x0103)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td1:_:Token Time td2:_) -> do
+        v <- getIntValue token
+        tt $ predNthAfter (v - 1) td1 td2
+      _ -> Nothing
+  }
+
+ruleMmdd :: Rule
+ruleMmdd = Rule
+  { name = "mm/dd"
+  , pattern =
+    [ regex "(0?[1-9]|1[0-2])/(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (mm:dd:_)):_) -> do
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleTimeofdayLatent :: Rule
+ruleTimeofdayLatent = Rule
+  { name = "time-of-day (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 0 23
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ hour True v
+      _ -> Nothing
+  }
+
+ruleFromTimeofdayTimeofdayInterval :: Rule
+ruleFromTimeofdayTimeofdayInterval = Rule
+  { name = "from <time-of-day> - <time-of-day> (interval)"
+  , pattern =
+    [ regex "(dup(a|\x0103)|(i|\x00ee)ncep(a|\x00e2)nd cu)"
+    , Predicate isATimeOfDay
+    , regex "(dar |(s|\x0219)i )?((i|\x00ee)nainte|p(a|\x00e2)n(a|\x0103) la( de)?)"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth2 :: Rule
+ruleNamedmonth2 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "feb(ruarie)?"
+    ]
+  , prod = \_ -> tt $ month 2
+  }
+
+ruleSeason3 :: Rule
+ruleSeason3 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "primavar(a|\x0103)"
+    ]
+  , prod = \_ -> tt $
+      interval TTime.Open (monthDay 3 20, monthDay 6 21)
+  }
+
+ruleUrmatoareleNCycle :: Rule
+ruleUrmatoareleNCycle = Rule
+  { name = "urmatoarele n <cycle>"
+  , pattern =
+    [ regex "urm(a|\x0103)to(arele|rii|area)"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleSeason :: Rule
+ruleSeason = Rule
+  { name = "season"
+  , pattern =
+    [ regex "toamn(a|\x0103)"
+    ]
+  , prod = \_ -> tt $
+      interval TTime.Open (monthDay 9 23, monthDay 12 21)
+  }
+
+ruleDupaDuration :: Rule
+ruleDupaDuration = Rule
+  { name = "dupa <duration>"
+  , pattern =
+    [ regex "dup(a|\x0103)"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt . withDirection TTime.After $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleNewYearsEve :: Rule
+ruleNewYearsEve = Rule
+  { name = "new year's eve"
+  , pattern =
+    [ regex "(ajun(ul)? )?(de )?an(ul)? nou"
+    ]
+  , prod = \_ -> tt $ monthDay 12 31
+  }
+
+ruleByTheEndOfTime :: Rule
+ruleByTheEndOfTime = Rule
+  { name = "by the end of <time>"
+  , pattern =
+    [ regex "p(a|\x00ee)n(a|\x0103) ((i|\x00ee)n|la)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ interval TTime.Closed (cycleNth TG.Second 0, td)
+      _ -> Nothing
+  }
+
+ruleNameddayPeDayofmonthNumeral :: Rule
+ruleNameddayPeDayofmonthNumeral = Rule
+  { name = "<named-day> pe <day-of-month> (number)"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex "pe"
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleDayofmonthNonOrdinalNamedmonth :: Rule
+ruleDayofmonthNonOrdinalNamedmonth = Rule
+  { name = "<day-of-month> (non ordinal) <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ Predicate isNotLatent
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleAboutTimeofday :: Rule
+ruleAboutTimeofday = Rule
+  { name = "about <time-of-day>"
+  , pattern =
+    [ regex "(cam|aproximativ|(i|\x00ee)n jur de)"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleUntilTimeofday :: Rule
+ruleUntilTimeofday = Rule
+  { name = "until <time-of-day>"
+  , pattern =
+    [ regex "p(a|\x00ee)n(a|\x0103) ((i|\x00ee)n|la)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ withDirection TTime.Before td
+      _ -> Nothing
+  }
+
+ruleDayofmonthnumberNamedmonth :: Rule
+ruleDayofmonthnumberNamedmonth = Rule
+  { name = "<day-of-month>(number) <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleNamedmonth6 :: Rule
+ruleNamedmonth6 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "iun(ie)?"
+    ]
+  , prod = \_ -> tt $ month 6
+  }
+
+ruleIntreDatetimeSiDatetimeInterval :: Rule
+ruleIntreDatetimeSiDatetimeInterval = Rule
+  { name = "intre <datetime> si <datetime> (interval)"
+  , pattern =
+    [ regex "(i|\x00ee)nre"
+    , dimension Time
+    , regex "(s|\x0219)i"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNthTimeOfTime :: Rule
+ruleNthTimeOfTime = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "din"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td1:_:Token Time td2:_) -> do
+        v <- getIntValue token
+        Token Time . predNth (v - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNamedmonth8 :: Rule
+ruleNamedmonth8 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "aug(ust)?"
+    ]
+  , prod = \_ -> tt $ month 8
+  }
+
+ruleTimePartofday :: Rule
+ruleTimePartofday = Rule
+  { name = "<time> <part-of-day>"
+  , pattern =
+    [ dimension Time
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleWeekend :: Rule
+ruleWeekend = Rule
+  { name = "week-end"
+  , pattern =
+    [ regex "(week(\\s|\\-)?end|wkend)"
+    ]
+  , prod = \_ -> do
+      fri <- intersect (dayOfWeek 5) (hour False 18)
+      mon <- intersect (dayOfWeek 1) (hour False 0)
+      tt $ interval TTime.Open (fri, mon)
+  }
+
+rulePeDayofmonthNonOrdinal :: Rule
+rulePeDayofmonthNonOrdinal = Rule
+  { name = "pe <day-of-month> (non ordinal)"
+  , pattern =
+    [ regex "pe"
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleTimeAceastaacestaasta :: Rule
+ruleTimeAceastaacestaasta = Rule
+  { name = "<time> (aceasta|acesta|[aă]sta)"
+  , pattern =
+    [ dimension Time
+    , regex "aceasta|(a|\x0103)sta|urm(a|\x0103)toare"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleEomendOfMonth :: Rule
+ruleEomendOfMonth = Rule
+  { name = "EOM|End of month"
+  , pattern =
+    [ regex "sf(a|\x00e2)r(s|\x0219)itul lunii"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Month 1
+  }
+
+rulePartofdayAsta :: Rule
+rulePartofdayAsta = Rule
+  { name = "<part-of-day> asta"
+  , pattern =
+    [ Predicate isAPartOfDay
+    , regex "asta"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        Token Time . partOfDay <$> intersect (cycleNth TG.Day 0) td
+      _ -> Nothing
+  }
+
+ruleUltimaCycle :: Rule
+ruleUltimaCycle = Rule
+  { name = "ultima <cycle>"
+  , pattern =
+    [ regex "ultim(a|ul)"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) -> tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleCycleUrmatoare :: Rule
+ruleCycleUrmatoare = Rule
+  { name = "<cycle> urmatoare"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "(urm(a|\x0103)to(are|r)|viito(are|r))"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) -> tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleTheDayofmonthNumeral :: Rule
+ruleTheDayofmonthNumeral = Rule
+  { name = "the <day-of-month> (number)"
+  , pattern =
+    [ regex "pe"
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        v <- getIntValue token
+        tt $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleYyyymmdd :: Rule
+ruleYyyymmdd = Rule
+  { name = "yyyy-mm-dd"
+  , pattern =
+    [ regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (yy:mm:dd:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleCycleTrecut :: Rule
+ruleCycleTrecut = Rule
+  { name = "<cycle> trecut"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "trecut(a|\x0103)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_) ->
+        tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleDayofmonthNumeralOfNamedmonth :: Rule
+ruleDayofmonthNumeralOfNamedmonth = Rule
+  { name = "<day-of-month> (number) of <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , regex "din"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleDurationInainteDeTime :: Rule
+ruleDurationInainteDeTime = Rule
+  { name = "<duration> inainte de <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "(i|\x00ee)nainte de"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_:Token Time td:_) ->
+        tt $ durationBefore dd td
+      _ -> Nothing
+  }
+
+ruleDayofmonthNonOrdinalOfNamedmonth :: Rule
+ruleDayofmonthNonOrdinalOfNamedmonth = Rule
+  { name = "<day-of-month> (non ordinal) of <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , regex "din"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleDurationInUrma :: Rule
+ruleDurationInUrma = Rule
+  { name = "<duration> in urma"
+  , pattern =
+    [ dimension Duration
+    , regex "(i|\x00ee)n urm(a|\x0103)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) -> tt $ durationAgo dd
+      _ -> Nothing
+  }
+
+ruleDurationDeAcum :: Rule
+ruleDurationDeAcum = Rule
+  { name = "<duration> de acum"
+  , pattern =
+    [ dimension Duration
+    , regex "de (acum|azi)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) -> tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleSezonAnotimp :: Rule
+ruleSezonAnotimp = Rule
+  { name = "sezon anotimp"
+  , pattern =
+    [ regex "var(a|\x0103)"
+    ]
+  , prod = \_ ->
+      tt $ interval TTime.Open (monthDay 6 21, monthDay 9 23)
+  }
+
+ruleSearaNoapte :: Rule
+ruleSearaNoapte = Rule
+  { name = "sear[aă] noapte"
+  , pattern =
+    [ regex "sear(a|\x0103)|noapte"
+    ]
+  , prod = \_ -> tt . mkLatent . partOfDay $
+      interval TTime.Open (hour False 18, hour False 0)
+  }
+
+ruleYearLatent :: Rule
+ruleYearLatent = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween (- 10000) 999
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ year v
+      _ -> Nothing
+  }
+
+ruleSeason2 :: Rule
+ruleSeason2 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "iarn(a|\x0103)"
+    ]
+  , prod = \_ ->
+      tt $ interval TTime.Open (monthDay 12 21, monthDay 3 20)
+  }
+
+ruleUltimeleNCycle :: Rule
+ruleUltimeleNCycle = Rule
+  { name = "ultimele n <cycle>"
+  , pattern =
+    [ regex "ultim(ele|ii|a)"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain (- v)
+      _ -> Nothing
+  }
+
+ruleAfterTimeofday :: Rule
+ruleAfterTimeofday = Rule
+  { name = "after <time-of-day>"
+  , pattern =
+    [ regex "dup(a|\x0103)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ withDirection TTime.After td
+      _ -> Nothing
+  }
+
+ruleDimineata :: Rule
+ruleDimineata = Rule
+  { name = "diminea[tț][aă]"
+  , pattern =
+    [ regex "diminea(t|\x021b)(a|\x0103)"
+    ]
+  , prod = \_ -> tt . mkLatent . partOfDay $
+      interval TTime.Open (hour False 4, hour False 12)
+  }
+
+ruleTimeUrmatoarer :: Rule
+ruleTimeUrmatoarer = Rule
+  { name = "<time> urm[aă]to(are|r)"
+  , pattern =
+    [ regex "urm(a|\x0103)to(are|r)"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleNamedmonth5 :: Rule
+ruleNamedmonth5 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "mai"
+    ]
+  , prod = \_ -> tt $ month 5
+  }
+
+ruleTimeofdayFix :: Rule
+ruleTimeofdayFix = Rule
+  { name = "<time-of-day> fix"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(fix|exact)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedday7 :: Rule
+ruleNamedday7 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "du(m(inic(a|\x0103))?)?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 7
+  }
+
+ruleHhmm :: Rule
+ruleHhmm = Rule
+  { name = "hh:mm"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt $ hourMinute True h m
+      _ -> Nothing
+  }
+
+ruleMaine :: Rule
+ruleMaine = Rule
+  { name = "maine"
+  , pattern =
+    [ regex "m(a|\x00e2)ine"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 1
+  }
+
+ruleTimezone :: Rule
+ruleTimezone = Rule
+  { name = "<time> timezone"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\b(YEKT|YEKST|YAKT|YAKST|WITA|WIT|WIB|WGT|WGST|WFT|WET|WEST|WAT|WAST|VUT|VLAT|VLAST|VET|UZT|UYT|UYST|UTC|ULAT|TVT|TMT|TLT|TKT|TJT|TFT|TAHT|SST|SRT|SGT|SCT|SBT|SAST|SAMT|RET|PYT|PYST|PWT|PST|PONT|PMST|PMDT|PKT|PHT|PHOT|PGT|PETT|PETST|PET|PDT|OMST|OMSST|NZST|NZDT|NUT|NST|NPT|NOVT|NOVST|NFT|NDT|NCT|MYT|MVT|MUT|MST|MSK|MSD|MMT|MHT|MDT|MAWT|MART|MAGT|MAGST|LINT|LHST|LHDT|KUYT|KST|KRAT|KRAST|KGT|JST|IST|IRST|IRKT|IRKST|IRDT|IOT|IDT|ICT|HOVT|HKT|GYT|GST|GMT|GILT|GFT|GET|GAMT|GALT|FNT|FKT|FKST|FJT|FJST|EST|EGT|EGST|EET|EEST|EDT|ECT|EAT|EAST|EASST|DAVT|ChST|CXT|CVT|CST|COT|CLT|CLST|CKT|CHAST|CHADT|CET|CEST|CDT|CCT|CAT|CAST|BTT|BST|BRT|BRST|BOT|BNT|AZT|AZST|AZOT|AZOST|AWST|AWDT|AST|ART|AQTT|ANAT|ANAST|AMT|AMST|ALMT|AKST|AKDT|AFT|AEST|AEDT|ADT|ACST|ACDT)\\b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       Token RegexMatch (GroupMatch (tz:_)):
+       _) -> Token Time <$> inTimezone tz td
+      _ -> Nothing
+  }
+
+ruleYear :: Rule
+ruleYear = Rule
+  { name = "year"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1000 2100
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt $ year v
+      _ -> Nothing
+  }
+
+ruleNamedmonth10 :: Rule
+ruleNamedmonth10 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "oct(ombrie)?"
+    ]
+  , prod = \_ -> tt $ month 10
+  }
+
+ruleHalloweenDay :: Rule
+ruleHalloweenDay = Rule
+  { name = "halloween day"
+  , pattern =
+    [ regex "hall?owe?en"
+    ]
+  , prod = \_ -> tt $ monthDay 10 31
+  }
+
+ruleNamedmonthDayofmonthNonOrdinal :: Rule
+ruleNamedmonthDayofmonthNonOrdinal = 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
+  }
+
+rulePeDate :: Rule
+rulePeDate = Rule
+  { name = "pe <date>"
+  , pattern =
+    [ regex "pe"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleDayofmonthnumberNamedmonthYear :: Rule
+ruleDayofmonthnumberNamedmonthYear = Rule
+  { name = "<day-of-month>(number) <named-month> year"
+  , pattern =
+    [ Predicate isDOMInteger
+    , Predicate isAMonth
+    , regex "(\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:
+       Token Time td:
+       Token RegexMatch (GroupMatch (match:_)):
+       _) -> do
+        v <- parseInt match
+        dom <- intersectDOM td token
+        Token Time <$> intersect dom (year v)
+      _ -> Nothing
+  }
+
+ruleAbsorptionOfAfterNamedDay :: Rule
+ruleAbsorptionOfAfterNamedDay = Rule
+  { name = "absorption of , after named day"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex ","
+    ]
+  , prod = \tokens -> case tokens of
+      (x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleLastDayofweekOfTime :: Rule
+ruleLastDayofweekOfTime = Rule
+  { name = "last <day-of-week> of <time>"
+  , pattern =
+    [ regex "ultima"
+    , Predicate isADayOfWeek
+    , regex "din"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ predLastOf td1 td2
+      _ -> Nothing
+  }
+
+ruleOrdinalTrimestru :: Rule
+ruleOrdinalTrimestru = Rule
+  { name = "<ordinal> trimestru"
+  , 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
+  }
+
+ruleByTime :: Rule
+ruleByTime = Rule
+  { name = "by <time>"
+  , pattern =
+    [ regex "p(a|\x00e2)n(a|\x0103) (la|(i|\x00ee)n)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $
+        interval TTime.Open (cycleNth TG.Second 0, td)
+      _ -> Nothing
+  }
+
+ruleDdmmyyyy :: Rule
+ruleDdmmyyyy = Rule
+  { name = "dd/mm/yyyy"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[-/](0?[1-9]|1[0-2])[/-](\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (dd:mm:yy:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleNamedmonth11 :: Rule
+ruleNamedmonth11 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "noi(embrie)?"
+    ]
+  , prod = \_ -> tt $ month 11
+  }
+
+ruleNamedday3 :: Rule
+ruleNamedday3 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "mi(e(rcur(ea|i))?)?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 3
+  }
+
+ruleMmddyyyy :: Rule
+ruleMmddyyyy = Rule
+  { name = "mm/dd/yyyy"
+  , pattern =
+    [ regex "(0?[1-9]|1[0-2])[/-](3[01]|[12]\\d|0?[1-9])[-/](\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (mm:dd:yy:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleEoyendOfYear :: Rule
+ruleEoyendOfYear = Rule
+  { name = "EOY|End of year"
+  , pattern =
+    [ regex "sf(a|\x00e2)r(s|\x0219)itul anului"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Year 1
+  }
+
+ruleMothersDay :: Rule
+ruleMothersDay = Rule
+  { name = "Mother's Day"
+  , pattern =
+    [ regex "ziua (mamei|memeii)"
+    ]
+  , prod = \_ -> tt $ nthDOWOfMonth 1 7 5
+  }
+
+ruleNameddayDayofmonthNumeral :: Rule
+ruleNameddayDayofmonthNumeral = Rule
+  { name = "<named-day> <day-of-month> (number)"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleNamedmonth9 :: Rule
+ruleNamedmonth9 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "sept(embrie)?"
+    ]
+  , prod = \_ -> tt $ month 9
+  }
+
+ruleHhmmss :: Rule
+ruleHhmmss = Rule
+  { name = "hh:mm:ss"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)[:.]([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:ss:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        s <- parseInt ss
+        tt $ hourMinuteSecond True h m s
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAboutTimeofday
+  , ruleAbsorptionOfAfterNamedDay
+  , ruleAcum
+  , ruleAfterTimeofday
+  , ruleAzi
+  , ruleBetweenTimeofdayAndTimeofdayInterval
+  , ruleBlackFriday
+  , ruleByTheEndOfTime
+  , ruleByTime
+  , ruleChristmasEve
+  , ruleCraciun
+  , ruleCycleAcesta
+  , ruleCycleAcesta2
+  , ruleCycleTrecut
+  , ruleCycleUrmatoare
+  , ruleDayofmonthNonOrdinalNamedmonth
+  , ruleDayofmonthNonOrdinalOfNamedmonth
+  , ruleDayofmonthNumeral
+  , ruleDayofmonthNumeralOfNamedmonth
+  , ruleDayofmonthnumberNamedmonth
+  , ruleDayofmonthnumberNamedmonthYear
+  , ruleDdmm
+  , ruleDdmmyyyy
+  , ruleDimineata
+  , ruleDiseara
+  , ruleDupaDuration
+  , ruleDupamiaza
+  , ruleDurationDeAcum
+  , ruleDurationInUrma
+  , ruleDurationInainteDeTime
+  , ruleEomendOfMonth
+  , ruleEoyendOfYear
+  , ruleFromTimeofdayTimeofdayInterval
+  , ruleHalloweenDay
+  , ruleHhmm
+  , ruleHhmmss
+  , ruleHourofdayIntegerAsRelativeMinutes
+  , ruleHourofdayJumatate
+  , ruleHourofdaySfert
+  , ruleIeri
+  , ruleInDuration
+  , ruleInNamedmonth
+  , ruleIntersect
+  , ruleIntersectBy
+  , ruleIntreDatetimeSiDatetimeInterval
+  , ruleIntroNamedday
+  , ruleLaTimeofday
+  , ruleLastCycleOfTime
+  , ruleLastDayofweekOfTime
+  , ruleMaine
+  , ruleMmdd
+  , ruleMmddyyyy
+  , ruleMonthDdddInterval
+  , ruleMothersDay
+  , ruleNamedday
+  , ruleNamedday2
+  , ruleNamedday3
+  , ruleNamedday4
+  , ruleNamedday5
+  , ruleNamedday6
+  , ruleNamedday7
+  , ruleNameddayDayofmonthNumeral
+  , ruleNameddayPeDayofmonthNumeral
+  , ruleNamedmonth
+  , ruleNamedmonth10
+  , ruleNamedmonth11
+  , ruleNamedmonth12
+  , ruleNamedmonth2
+  , ruleNamedmonth3
+  , ruleNamedmonth4
+  , ruleNamedmonth5
+  , ruleNamedmonth6
+  , ruleNamedmonth7
+  , ruleNamedmonth8
+  , ruleNamedmonth9
+  , ruleNamedmonthDayofmonthNonOrdinal
+  , ruleNewYearsDay
+  , ruleNewYearsEve
+  , ruleNoon
+  , ruleNthTimeAfterTime
+  , ruleNthTimeOfTime
+  , ruleOrdinalTrimestru
+  , ruleOrdinalTrimestruYear
+  , rulePartofdayAsta
+  , rulePeDate
+  , rulePeDayofmonthNonOrdinal
+  , rulePentruDuration
+  , ruleSearaNoapte
+  , ruleSeason
+  , ruleSeason2
+  , ruleSeason3
+  , ruleSezonAnotimp
+  , ruleSinceTimeofday
+  , ruleTheDayofmonthNumeral
+  , ruleThisnextDayofweek
+  , ruleTimeAceastaacestaasta
+  , ruleTimePartofday
+  , ruleTimezone
+  , ruleTimeTrecuta
+  , ruleTimeUrmatoarer
+  , ruleTimeofdayFix
+  , ruleTimeofdayLatent
+  , ruleTrimestruNumeralYear
+  , ruleUltimaCycle
+  , ruleUltimeleNCycle
+  , ruleUntilTimeofday
+  , ruleUrmatoareaCycle
+  , ruleUrmatoareleNCycle
+  , ruleValentinesDay
+  , ruleWeekend
+  , ruleYear
+  , ruleYearLatent
+  , ruleYearLatent2
+  , ruleYyyymmdd
+  ]
diff --git a/Duckling/Time/SV/Corpus.hs b/Duckling/Time/SV/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/SV/Corpus.hs
@@ -0,0 +1,615 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.SV.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Time.Corpus
+import Duckling.Time.Types hiding (Month)
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Testing.Types hiding (examples)
+
+corpus :: Corpus
+corpus = (testContext {lang = SV}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (datetime (2013, 2, 12, 4, 30, 0) Second)
+             [ "nu"
+             , "just nu"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "idag"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "igår"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "imorgon"
+             , "i morgon"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "måndag"
+             , "mån"
+             , "på måndag"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "Måndag den 18 februari"
+             , "Mån, 18 februari"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "tisdag"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "torsdag"
+             , "tors"
+             , "tors."
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "fredag"
+             , "fre"
+             , "fre."
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "lördag"
+             , "lör"
+             , "lör."
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "söndag"
+             , "sön"
+             , "sön."
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "Den förste mars"
+             , "Den första mars"
+             , "1. mars"
+             , "Den 1. mars"
+             ]
+  , examples (datetime (2013, 3, 3, 0, 0, 0) Day)
+             [ "3 mars"
+             , "den tredje mars"
+             , "den 3. mars"
+             ]
+  , examples (datetime (2015, 3, 3, 0, 0, 0) Day)
+             [ "3 mars 2015"
+             , "tredje mars 2015"
+             , "3. mars 2015"
+             , "3-3-2015"
+             , "03-03-2015"
+             , "3/3/2015"
+             , "3/3/15"
+             , "2015-3-3"
+             , "2015-03-03"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "På den 15."
+             , "På den 15"
+             , "Den 15."
+             , "Den 15"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "den 15. februari"
+             , "15. februari"
+             , "februari 15"
+             , "15-02"
+             , "15/02"
+             ]
+  , examples (datetime (2013, 8, 8, 0, 0, 0) Day)
+             [ "8 Aug"
+             ]
+  , examples (datetime (2014, 10, 0, 0, 0, 0) Month)
+             [ "Oktober 2014"
+             ]
+  , examples (datetime (1974, 10, 31, 0, 0, 0) Day)
+             [ "31/10/1974"
+             , "31/10/74"
+             , "31-10-74"
+             ]
+  , examples (datetime (2015, 4, 14, 0, 0, 0) Day)
+             [ "14april 2015"
+             , "April 14, 2015"
+             , "fjortonde April 15"
+             ]
+  , examples (datetime (2013, 2, 22, 0, 0, 0) Day)
+             [ "nästa fredag igen"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "nästa mars"
+             ]
+  , examples (datetime (2014, 3, 0, 0, 0, 0) Month)
+             [ "nästa mars igen"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "Söndag, 10 feb"
+             , "Söndag 10 Feb"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "Ons, Feb13"
+             , "Ons feb13"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "Måndag, Feb 18"
+             , "Mån, februari 18"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Week)
+             [ "denna vecka"
+             ]
+  , examples (datetime (2013, 2, 4, 0, 0, 0) Week)
+             [ "förra vecka"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Week)
+             [ "nästa vecka"
+             ]
+  , examples (datetime (2013, 1, 0, 0, 0, 0) Month)
+             [ "förra månad"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "nästa månad"
+             ]
+  , examples (datetime (2013, 1, 1, 0, 0, 0) Quarter)
+             [ "detta kvartal"
+             ]
+  , examples (datetime (2013, 4, 1, 0, 0, 0) Quarter)
+             [ "nästa kvartal"
+             ]
+  , examples (datetime (2013, 7, 1, 0, 0, 0) Quarter)
+             [ "tredje kvartalet"
+             , "3. kvartal"
+             , "3 kvartal"
+             ]
+  , examples (datetime (2018, 10, 1, 0, 0, 0) Quarter)
+             [ "4. kvartal 2018"
+             , "fjärde kvartalet 2018"
+             ]
+  , examples (datetime (2012, 0, 0, 0, 0, 0) Year)
+             [ "förra år"
+             ]
+  , examples (datetime (2012, 0, 0, 0, 0, 0) Year)
+             [ "i fjol"
+             ]
+  , examples (datetime (2013, 0, 0, 0, 0, 0) Year)
+             [ "i år"
+             , "detta år"
+             ]
+  , examples (datetime (2014, 0, 0, 0, 0, 0) Year)
+             [ "nästa år"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "förra söndag"
+             , "söndag i förra veckan"
+             , "söndag förra veckan"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "förra tisdag"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "nästa tisdag"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "nästa onsdag"
+             ]
+  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
+             [ "onsdag i nästa vecka"
+             , "onsdag nästa vecka"
+             , "nästa onsdag igen"
+             ]
+  , examples (datetime (2013, 2, 22, 0, 0, 0) Day)
+             [ "nästa fredag igen"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "måndag denna veckan"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "tisdag denna vecka"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "onsdag denna vecka"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "i överimorgon"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "i förrgår"
+             ]
+  , examples (datetime (2013, 3, 25, 0, 0, 0) Day)
+             [ "sista måndag i mars"
+             ]
+  , examples (datetime (2014, 3, 30, 0, 0, 0) Day)
+             [ "sista söndag i mars 2014"
+             ]
+  , examples (datetime (2013, 10, 3, 0, 0, 0) Day)
+             [ "tredje dagen i oktober"
+             , "tredje dagen i Oktober"
+             ]
+  , examples (datetime (2014, 10, 6, 0, 0, 0) Week)
+             [ "första veckan i oktober 2014"
+             , "första veckan i Oktober 2014"
+             ]
+  , examples (datetime (2015, 10, 31, 0, 0, 0) Day)
+             [ "sista dagen i oktober 2015"
+             , "sista dagen i Oktober 2015"
+             ]
+  , examples (datetime (2014, 9, 22, 0, 0, 0) Week)
+             [ "sista veckan i september 2014"
+             , "sista veckan i September 2014"
+             ]
+  , examples (datetime (2013, 10, 1, 0, 0, 0) Day)
+             [ "första tisdag i oktober"
+             , "första tisdagen i Oktober"
+             ]
+  , examples (datetime (2014, 9, 16, 0, 0, 0) Day)
+             [ "tredje tisdagen i september 2014"
+             , "tredje tisdagen i September 2014"
+             ]
+  , examples (datetime (2014, 10, 1, 0, 0, 0) Day)
+             [ "första onsdagen i oktober 2014"
+             , "första onsdagen i Oktober 2014"
+             ]
+  , examples (datetime (2014, 10, 8, 0, 0, 0) Day)
+             [ "andra onsdagen i oktober 2014"
+             , "andra onsdagen i Oktober 2014"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 0, 0) Hour)
+             [ "klockan 3"
+             , "kl. 3"
+             ]
+  , examples (datetime (2013, 2, 13, 3, 18, 0) Minute)
+             [ "3:18"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "klockan 15"
+             , "kl. 15"
+             , "15h"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "ca. kl. 15"
+             , "cirka kl. 15"
+             , "omkring klockan 15"
+             ]
+  , examples (datetime (2013, 2, 13, 17, 0, 0) Hour)
+             [ "imorgon klockan 17 exakt"
+             , "imorgon kl. 17 precis"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 15, 0) Minute)
+             [ "kvart över 15"
+             , "15:15"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 20, 0) Minute)
+             [ "kl. 20 över 15"
+             , "klockan 20 över 15"
+             , "tjugo över 15"
+             , "kl. 15:20"
+             , "15:20"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 30, 0) Minute)
+             [ "15:30"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 23, 24) Second)
+             [ "15:23:24"
+             ]
+  , examples (datetime (2013, 2, 12, 11, 45, 0) Minute)
+             [ "kvart i 12"
+             , "kvart i tolv"
+             , "11:45"
+             ]
+  , examples (datetime (2013, 2, 16, 9, 0, 0) Hour)
+             [ "klockan 9 på lördag"
+             ]
+  , examples (datetime (2014, 7, 18, 19, 0, 0) Minute)
+             [ "Fre, Jul 18, 2014 19:00"
+             ]
+  , examples (datetime (2014, 7, 18, 0, 0, 0) Day)
+             [ "Fre, Jul 18"
+             , "Jul 18, Fre"
+             ]
+  , examples (datetime (2014, 9, 20, 19, 30, 0) Minute)
+             [ "kl. 19:30, Lör, 20 sep"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 30, 1) Second)
+             [ "om 1 sekund"
+             , "om en sekund"
+             , "en sekund från nu"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 31, 0) Second)
+             [ "om 1 minut"
+             , "om en minut"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 32, 0) Second)
+             [ "om 2 minuter"
+             , "om två minuter"
+             , "om 2 minuter mer"
+             , "om två minuter mer"
+             --, "2 minuter från nu" t14892978
+             , "två minuter från nu"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Second)
+             [ "om 60 minuter"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 0, 0) Second)
+             [ "om en halv timme"
+             ]
+  , examples (datetime (2013, 2, 12, 7, 0, 0) Second)
+             [ "om 2,5 timme"
+             , "om 2 och en halv timme"
+             , "om två och en halv timme"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 30, 0) Minute)
+             [ "om en timme"
+             , "om 1 timme"
+             , "om 1t"
+             ]
+  , examples (datetime (2013, 2, 12, 6, 30, 0) Minute)
+             [ "om ett par timmar"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 30, 0) Minute)
+             [ "om 24 timmar"
+             ]
+  , examples (datetime (2013, 2, 13, 4, 0, 0) Hour)
+             [ "om en dag"
+             ]
+  , examples (datetime (2016, 2, 0, 0, 0, 0) Month)
+             [ -- "3 år från idag" t14892978
+             ]
+  , examples (datetime (2013, 2, 19, 4, 0, 0) Hour)
+             [ "om 7 dagar"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "om en vecka"
+             ]
+  , examples (datetime (2013, 2, 12, 5, 0, 0) Second)
+             [ "om ca. en halv timme"
+             , "om cirka en halv timme"
+             ]
+  , examples (datetime (2013, 2, 5, 4, 0, 0) Hour)
+             [ "7 dagar sedan"
+             , "sju dagar sedan"
+             ]
+  , examples (datetime (2013, 1, 29, 4, 0, 0) Hour)
+             [ "14 dagar sedan"
+             , "fjorton dagar sedan"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "en vecka sedan"
+             , "1 vecka sedan"
+             ]
+  , examples (datetime (2013, 1, 22, 0, 0, 0) Day)
+             [ "3 veckor sedan"
+             , "tre veckor sedan"
+             ]
+  , examples (datetime (2012, 11, 12, 0, 0, 0) Day)
+             [ "3 månader sedan"
+             , "tre månader sedan"
+             ]
+  , examples (datetime (2011, 2, 0, 0, 0, 0) Month)
+             [ "två år sedan"
+             , "2 år sedan"
+             ]
+  , examples (datetime (1954, 0, 0, 0, 0, 0) Year)
+             [ "1954"
+             ]
+  , examples (datetimeInterval ((2013, 6, 21, 0, 0, 0), (2013, 9, 24, 0, 0, 0)) Day)
+             [ "denna sommaren"
+             , "den här sommaren"
+             ]
+  , examples (datetimeInterval ((2012, 12, 21, 0, 0, 0), (2013, 3, 21, 0, 0, 0)) Day)
+             [ "denna vintern"
+             , "den här vintern"
+             ]
+  , examples (datetime (2013, 12, 25, 0, 0, 0) Day)
+             [ "juldagen"
+             ]
+  , examples (datetime (2013, 12, 31, 0, 0, 0) Day)
+             [ "nyårsafton"
+             ]
+  , examples (datetime (2014, 1, 1, 0, 0, 0) Day)
+             [ "nyårsdagen"
+             , "nyårsdag"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 18, 0, 0), (2013, 2, 13, 0, 0, 0)) Hour)
+             [ "ikväll"
+             ]
+  , examples (datetimeInterval ((2013, 2, 8, 18, 0, 0), (2013, 2, 11, 0, 0, 0)) Hour)
+             [ "förra helg"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 18, 0, 0), (2013, 2, 14, 0, 0, 0)) Hour)
+             [ "imorgon kväll"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 12, 0, 0), (2013, 2, 13, 14, 0, 0)) Hour)
+             [ "imorgon lunch"
+             ]
+  , examples (datetimeInterval ((2013, 2, 11, 18, 0, 0), (2013, 2, 12, 0, 0, 0)) Hour)
+             [ "igår kväll"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 18, 0, 0), (2013, 2, 18, 0, 0, 0)) Hour)
+             [ "denna helgen"
+             , "denna helg"
+             , "i helgen"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 4, 0, 0), (2013, 2, 18, 12, 0, 0)) Hour)
+             [ "måndag morgon"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 29, 58), (2013, 2, 12, 4, 30, 0)) Second)
+             [ "senaste 2 sekunder"
+             , "senaste två sekunderna"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 1), (2013, 2, 12, 4, 30, 4)) Second)
+             [ "nästa 3 sekunder"
+             , "nästa tre sekunder"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 28, 0), (2013, 2, 12, 4, 30, 0)) Minute)
+             [ "senaste 2 minuter"
+             , "senaste två minuter"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 31, 0), (2013, 2, 12, 4, 34, 0)) Minute)
+             [ "nästa 3 minuter"
+             , "nästa tre minuter"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 3, 0, 0), (2013, 2, 12, 4, 0, 0)) Hour)
+             [ "senaste 1 timme"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 5, 0, 0), (2013, 2, 12, 8, 0, 0)) Hour)
+             [ "nästa 3 timmar"
+             , "nästa tre timmar"
+             ]
+  , examples (datetimeInterval ((2013, 2, 10, 0, 0, 0), (2013, 2, 12, 0, 0, 0)) Day)
+             [ "senaste 2 dagar"
+             , "senaste två dagar"
+             , "senaste 2 dagar"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
+             [ "nästa 3 dagar"
+             , "nästa tre dagar"
+             ]
+  , examples (datetimeInterval ((2013, 1, 28, 0, 0, 0), (2013, 2, 11, 0, 0, 0)) Week)
+             [ "senaste 2 veckor"
+             , "senaste två veckorna"
+             , "senaste två veckor"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 0, 0, 0), (2013, 3, 11, 0, 0, 0)) Week)
+             [ "nästa 3 veckor"
+             , "nästa tre veckorna"
+             ]
+  , examples (datetimeInterval ((2012, 12, 0, 0, 0, 0), (2013, 2, 0, 0, 0, 0)) Month)
+             [ "senaste 2 månader"
+             , "senaste två månader"
+             , "senaste två månader"
+             ]
+  , examples (datetimeInterval ((2013, 3, 0, 0, 0, 0), (2013, 6, 0, 0, 0, 0)) Month)
+             [ "nästa 3 månader"
+             , "nästa tre månader"
+             ]
+  , examples (datetimeInterval ((2011, 0, 0, 0, 0, 0), (2013, 0, 0, 0, 0, 0)) Year)
+             [ "senaste 2 år"
+             , "senaste två år"
+             , "senaste 2 år"
+             ]
+  , examples (datetimeInterval ((2014, 0, 0, 0, 0, 0), (2017, 0, 0, 0, 0, 0)) Year)
+             [ "nästa 3 år"
+             , "nästa tre år"
+             ]
+  , examples (datetimeInterval ((2013, 7, 13, 0, 0, 0), (2013, 7, 16, 0, 0, 0)) Day)
+             [ "13-15 juli"
+             , "13-15 Juli"
+             , "13 till 15 Juli"
+             , "13 juli till 15 juli"
+             ]
+  , examples (datetimeInterval ((2013, 8, 8, 0, 0, 0), (2013, 8, 13, 0, 0, 0)) Day)
+             [ "8 Aug - 12 Aug"
+             , "8 Aug - 12 aug"
+             , "8 aug - 12 aug"
+             , "8 augusti - 12 augusti"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 9, 30, 0), (2013, 2, 12, 11, 1, 0)) Minute)
+             [ "9:30 - 11:00"
+             , "9:30 till 11:00"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 30, 0), (2013, 2, 14, 11, 1, 0)) Minute)
+             [ "från 9:30 - 11:00 på torsdag"
+             , "från 9:30 till 11:00 på torsdag"
+             , "mellan 9:30 och 11:00 på torsdag"
+             , "9:30 - 11:00 på torsdag"
+             , "9:30 till 11:00 på torsdag"
+             , "efter 9:30 men före 11:00 på torsdag"
+             , "torsdag från 9:30 till 11:00"
+             , "torsdag mellan 9:30 och 11:00"
+             , "från 9:30 till 11:00 på torsdag"
+             ]
+  , examples (datetimeInterval ((2013, 2, 14, 9, 0, 0), (2013, 2, 14, 12, 0, 0)) Hour)
+             [ "torsdag från 9 till 11"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 11, 30, 0), (2013, 2, 12, 13, 31, 0)) Minute)
+             [ "11:30-13:30"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 26, 0, 0, 0)) Second)
+             [ "inom 2 veckor"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 14, 0, 0) Hour)
+             [ "innan kl. 14"
+             , "innan klockan 14"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 0, 0) Minute)
+             [ "@ 16 CET"
+             , "kl. 16 CET"
+             , "klockan 16 CET"
+             ]
+  , examples (datetime (2013, 2, 14, 6, 0, 0) Minute)
+             [ "torsdag kl. 8:00 GMT"
+             , "torsdag klockan 8:00 GMT"
+             , "torsdag 08:00 GMT"
+             ]
+  , examples (datetime (2013, 2, 12, 14, 0, 0) Hour)
+             [ "idag kl. 14"
+             , "idag klockan 14"
+             , "kl. 14"
+             , "klockan 14"
+             ]
+  , examples (datetime (2013, 4, 25, 16, 0, 0) Minute)
+             [ "25/4 kl. 16:00"
+             , "25/4 klockan 16:00"
+             , "25-04 klockan 16:00"
+             , "25-4 kl. 16:00"
+             ]
+  , examples (datetime (2013, 2, 13, 15, 0, 0) Minute)
+             [ "15:00 imorgon"
+             , "kl. 15:00 imorgon"
+             , "klockan 15:00 imorgon"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 12, 14, 0, 0) Hour)
+             [ "efter kl. 14"
+             , "efter klockan 14"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 17, 4, 0, 0) Hour)
+             [ "efter 5 dagar"
+             , "efter fem dagar"
+             ]
+  , examples (datetime (2013, 2, 17, 4, 0, 0) Hour)
+             [ "om 5 dagar"
+             , "om fem dagar"
+             ]
+  , examples (datetimeOpenInterval After (2013, 2, 13, 14, 0, 0) Hour)
+             [ "efter imorgon kl. 14"
+             , "efter imorgon klockan 14"
+             , "imorgon efter kl. 14"
+             , "imorgon efter klockan 14"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 12, 11, 0, 0) Hour)
+             [ "före kl. 11"
+             , "före klockan 11"
+             ]
+  , examples (datetimeOpenInterval Before (2013, 2, 13, 11, 0, 0) Hour)
+             [ "imorgon före kl. 11"
+             , "imorgon före klockan 11"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 12, 0, 0), (2013, 2, 12, 19, 0, 0)) Hour)
+             [ "under eftermiddagen"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 30, 0) Minute)
+             [ "kl. 13:30"
+             , "klockan 13:30"
+             ]
+  , examples (datetime (2013, 2, 12, 4, 45, 0) Second)
+             [ "om 15 minuter"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 13, 0, 0), (2013, 2, 12, 17, 0, 0)) Hour)
+             [ "efter lunch"
+             ]
+  , examples (datetime (2013, 2, 12, 10, 30, 0) Minute)
+             [ "10:30"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "nästa måndag"
+             ]
+  ]
diff --git a/Duckling/Time/SV/Rules.hs b/Duckling/Time/SV/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/SV/Rules.hs
@@ -0,0 +1,1923 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.SV.Rules
+  ( rules ) where
+
+import Control.Monad (liftM2)
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Ordinal.Types (OrdinalData (..))
+import qualified Duckling.Ordinal.Types as TOrdinal
+import Duckling.Regex.Types
+import Duckling.Time.Helpers
+import Duckling.Time.Types (TimeData (..))
+import qualified Duckling.Time.Types as TTime
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleNamedday :: Rule
+ruleNamedday = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "m\x00e5ndag(en)?|m\x00e5n\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 1
+  }
+
+ruleTheDayAfterTomorrow :: Rule
+ruleTheDayAfterTomorrow = Rule
+  { name = "the day after tomorrow"
+  , pattern =
+    [ regex "i \x00f6verimorgon"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 2
+  }
+
+ruleNamedmonth12 :: Rule
+ruleNamedmonth12 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "december|dec\\.?"
+    ]
+  , prod = \_ -> tt $ month 12
+  }
+
+ruleRelativeMinutesTotillbeforeIntegerHourofday :: Rule
+ruleRelativeMinutesTotillbeforeIntegerHourofday = Rule
+  { name = "relative minutes to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "i"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleRelativeMinutesAfterpastIntegerHourofday :: Rule
+ruleRelativeMinutesAfterpastIntegerHourofday = Rule
+  { name = "relative minutes after|past <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 59
+    , regex "\x00f6ver"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (token:
+       _:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute True hours n
+      _ -> Nothing
+  }
+
+ruleHourofdayIntegerAsRelativeMinutes :: Rule
+ruleHourofdayIntegerAsRelativeMinutes = Rule
+  { name = "<hour-of-day> <integer> (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute True hours n
+      _ -> Nothing
+  }
+
+ruleQuarterTotillbeforeIntegerHourofday :: Rule
+ruleQuarterTotillbeforeIntegerHourofday = Rule
+  { name = "quarter to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ regex "(en)? ?(kvart)(er)?"
+    , regex "i"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:_:Token Time td:_) -> Token Time <$> minutesBefore 15 td
+      _ -> Nothing
+  }
+
+ruleQuarterAfterpastIntegerHourofday :: Rule
+ruleQuarterAfterpastIntegerHourofday = Rule
+  { name = "quarter after|past <integer> (hour-of-day)"
+  , pattern =
+    [ regex "(en)? ?(kvart)(er)?"
+    , regex "\x00f6ver"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       _:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> tt $ hourMinute True hours 15
+      _ -> Nothing
+  }
+
+ruleHourofdayQuarter :: Rule
+ruleHourofdayQuarter = Rule
+  { name = "<hour-of-day> quarter (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex  "(en)? ?(kvart)(er)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> tt $ hourMinute True hours 15
+      _ -> Nothing
+  }
+
+ruleHalfTotillbeforeIntegerHourofday :: Rule
+ruleHalfTotillbeforeIntegerHourofday = Rule
+  { name = "half to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ regex "halvtimme"
+    , regex "i"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:_:Token Time td:_) -> Token Time <$> minutesBefore 30 td
+      _ -> Nothing
+  }
+
+ruleHalfAfterpastIntegerHourofday :: Rule
+ruleHalfAfterpastIntegerHourofday = Rule
+  { name = "half after|past <integer> (hour-of-day)"
+  , pattern =
+    [ regex "halvtimme"
+    , regex "\x00f6ver"
+    , Predicate isAnHourOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       _:
+       Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> tt $ hourMinute True hours 30
+      _ -> Nothing
+  }
+
+ruleHourofdayHalf :: Rule
+ruleHourofdayHalf = Rule
+  { name = "<hour-of-day> half (as relative minutes)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "halvtimme"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> tt $ hourMinute True hours 30
+      _ -> Nothing
+  }
+
+ruleNamedday2 :: Rule
+ruleNamedday2 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "tisdag(en)?|tis?\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 2
+  }
+
+ruleTheOrdinalCycleOfTime :: Rule
+ruleTheOrdinalCycleOfTime = Rule
+  { name = "the <ordinal> <cycle> of <time>"
+  , pattern =
+    [ regex "den"
+    , dimension Ordinal
+    , dimension TimeGrain
+    , regex "av|i|fr\x00e5n"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleNthTimeOfTime2 :: Rule
+ruleNthTimeOfTime2 = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ regex "den"
+    , dimension Ordinal
+    , dimension Time
+    , regex "av|i"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       Token Time td1:
+       _:
+       Token Time td2:
+       _) -> Token Time . predNth (v - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNewYearsDay :: Rule
+ruleNewYearsDay = Rule
+  { name = "new year's day"
+  , pattern =
+    [ regex "ny\x00e5rsdag(en)?"
+    ]
+  , prod = \_ -> tt $ monthDay 1 1
+  }
+
+ruleLastTime :: Rule
+ruleLastTime = Rule
+  { name = "last <time>"
+  , pattern =
+    [ regex "(sista|f\x00f6rra|senaste)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleNamedday6 :: Rule
+ruleNamedday6 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "l\x00f6rdag(en)?|l\x00f6r\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 6
+  }
+
+ruleDatetimeDatetimeInterval :: Rule
+ruleDatetimeDatetimeInterval = Rule
+  { name = "<datetime> - <datetime> (interval)"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "\\-|till|till och med|tom"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth7 :: Rule
+ruleNamedmonth7 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "juli|jul\\.?"
+    ]
+  , prod = \_ -> tt $ month 7
+  }
+
+ruleEvening :: Rule
+ruleEvening = Rule
+  { name = "evening"
+  , pattern =
+    [ regex "kv\x00e4ll(en)?"
+    ]
+  , prod = \_ ->
+      let from = hour False 18
+          to = hour False 0
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleTheDayofmonthNonOrdinal :: Rule
+ruleTheDayofmonthNonOrdinal = Rule
+  { name = "the <day-of-month> (non ordinal)"
+  , pattern =
+    [ regex "den"
+    , Predicate isDOMInteger
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleCycleAfterTime :: Rule
+ruleCycleAfterTime = Rule
+  { name = "<cycle> after <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "efter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain 1 td
+      _ -> Nothing
+  }
+
+ruleInDuration :: Rule
+ruleInDuration = Rule
+  { name = "in <duration>"
+  , pattern =
+    [ regex "om"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleNow :: Rule
+ruleNow = Rule
+  { name = "now"
+  , pattern =
+    [ regex "just nu|nu|(i )?detta \x00f6gonblick"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Second 0
+  }
+
+ruleLastCycleOfTime :: Rule
+ruleLastCycleOfTime = Rule
+  { name = "last <cycle> of <time>"
+  , pattern =
+    [ regex "sista"
+    , dimension TimeGrain
+    , regex "av|i"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleLastOf grain td
+      _ -> Nothing
+  }
+
+ruleFromDatetimeDatetimeInterval :: Rule
+ruleFromDatetimeDatetimeInterval = Rule
+  { name = "from <datetime> - <datetime> (interval)"
+  , pattern =
+    [ regex "fr\x00e5n"
+    , dimension Time
+    , regex "\\-|till|till och med|tom"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleMonthDdddInterval :: Rule
+ruleMonthDdddInterval = Rule
+  { name = "<month> dd-dd (interval)"
+  , pattern =
+    [ regex "([012]?\\d|30|31)(er|\\.)?"
+    , regex "\\-|till"
+    , regex "([012]?\\d|30|31)(er|\\.)?"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:_)):
+       _:
+       Token RegexMatch (GroupMatch (m2:_)):
+       Token Time td:
+       _) -> do
+        v1 <- parseInt m1
+        v2 <- parseInt m2
+        from <- intersect (dayOfMonth v1) td
+        to <- intersect (dayOfMonth v2) td
+        tt $ interval TTime.Closed (from, to)
+      _ -> Nothing
+  }
+
+ruleNamedday4 :: Rule
+ruleNamedday4 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "torsdag(en)?|tors?\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 4
+  }
+
+ruleTheCycleAfterTime :: Rule
+ruleTheCycleAfterTime = Rule
+  { name = "the <cycle> after <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "(en|na|et)? efter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain 1 td
+      _ -> Nothing
+  }
+
+ruleTheCycleBeforeTime :: Rule
+ruleTheCycleBeforeTime = Rule
+  { name = "the <cycle> before <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "(en|na|et)? f\x00f6re"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain (-1) td
+      _ -> Nothing
+  }
+
+ruleYearLatent2 :: Rule
+ruleYearLatent2 = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 2101 10000
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ year v
+      _ -> Nothing
+  }
+
+ruleTimeAfterNext :: Rule
+ruleTimeAfterNext = Rule
+  { name = "<time> after next"
+  , pattern =
+    [ regex "n\x00e4sta"
+    , dimension Time
+    , regex "igen"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 1 True td
+      _ -> Nothing
+  }
+
+ruleTheIdesOfNamedmonth :: Rule
+ruleTheIdesOfNamedmonth = Rule
+  { name = "the ides of <named-month>"
+  , pattern =
+    [ regex "mitten av"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td@TimeData {TTime.form = Just (TTime.Month m)}:_) ->
+        Token Time <$>
+          intersect (dayOfMonth $ if elem m [3, 5, 7, 10] then 15 else 13) td
+      _ -> Nothing
+  }
+
+ruleNoon :: Rule
+ruleNoon = Rule
+  { name = "noon"
+  , pattern =
+    [ regex "middag|(kl(\\.|ockan)?)? tolv"
+    ]
+  , prod = \_ -> tt $ hour False 12
+  }
+
+ruleToday :: Rule
+ruleToday = Rule
+  { name = "today"
+  , pattern =
+    [ regex "i dag|idag"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 0
+  }
+
+ruleThisnextDayofweek :: Rule
+ruleThisnextDayofweek = Rule
+  { name = "this|next <day-of-week>"
+  , pattern =
+    [ regex "(kommande|n\x00e4sta)"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleTheDayBeforeYesterday :: Rule
+ruleTheDayBeforeYesterday = Rule
+  { name = "the day before yesterday"
+  , pattern =
+    [ regex "i f\x00f6rrg\x00e5r"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 2
+  }
+
+ruleBetweenTimeofdayAndTimeofdayInterval :: Rule
+ruleBetweenTimeofdayAndTimeofdayInterval = Rule
+  { name = "between <time-of-day> and <time-of-day> (interval)"
+  , pattern =
+    [ regex "mellan"
+    , Predicate isATimeOfDay
+    , regex "och"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNextCycle :: Rule
+ruleNextCycle = Rule
+  { name = "next <cycle>"
+  , pattern =
+    [ regex "n\x00e4sta|kommande"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleNamedmonth :: Rule
+ruleNamedmonth = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "januari|jan\\.?"
+    ]
+  , prod = \_ -> tt $ month 1
+  }
+
+ruleTheCycleOfTime :: Rule
+ruleTheCycleOfTime = Rule
+  { name = "the <cycle> of <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "(en|na|et)? av"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain 0 td
+      _ -> Nothing
+  }
+
+ruleTimeofdayApproximately :: Rule
+ruleTimeofdayApproximately = Rule
+  { name = "<time-of-day> approximately"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(cirka|ca\\.|-?ish)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleOnDate :: Rule
+ruleOnDate = Rule
+  { name = "on <date>"
+  , pattern =
+    [ regex "den|p\x00e5|under"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth3 :: Rule
+ruleNamedmonth3 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "mars|mar\\.?"
+    ]
+  , prod = \_ -> tt $ month 3
+  }
+
+ruleDurationFromNow :: Rule
+ruleDurationFromNow = Rule
+  { name = "<duration> from now"
+  , pattern =
+    [ dimension Duration
+    , regex "fr\x00e5n (idag|nu)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleLunch :: Rule
+ruleLunch = Rule
+  { name = "lunch"
+  , pattern =
+    [ regex "lunch(en)?"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 14
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleLastCycle :: Rule
+ruleLastCycle = Rule
+  { name = "last <cycle>"
+  , pattern =
+    [ regex "sista|senaste|f\x00f6rra"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleDdmm :: Rule
+ruleDdmm = Rule
+  { name = "dd/mm"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[\\/-](0?[1-9]|1[0-2])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
+        d <- parseInt m1
+        m <- parseInt m2
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleAfternoon :: Rule
+ruleAfternoon = Rule
+  { name = "afternoon"
+  , pattern =
+    [ regex "eftermiddag(en)?"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 19
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleNamedmonth4 :: Rule
+ruleNamedmonth4 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "april|apr\\.?"
+    ]
+  , prod = \_ -> tt $ month 4
+  }
+
+ruleTimeBeforeLast :: Rule
+ruleTimeBeforeLast = Rule
+  { name = "<time> before last"
+  , pattern =
+    [ regex "sista"
+    , dimension Time
+    , regex "igen"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth (-2) False td
+      _ -> Nothing
+  }
+
+ruleNamedmonthDayofmonthOrdinal :: Rule
+ruleNamedmonthDayofmonthOrdinal = Rule
+  { name = "<named-month> <day-of-month> (ordinal)"
+  , pattern =
+    [ Predicate isAMonth
+    , Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleChristmasEve :: Rule
+ruleChristmasEve = Rule
+  { name = "christmas eve"
+  , pattern =
+    [ regex "julafton?"
+    ]
+  , prod = \_ -> tt $ monthDay 12 24
+  }
+
+ruleInduringThePartofday :: Rule
+ruleInduringThePartofday = Rule
+  { name = "in|during the <part-of-day>"
+  , pattern =
+    [ regex "om|i"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedday5 :: Rule
+ruleNamedday5 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "fredag(en)?|fre\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 5
+  }
+
+ruleDayofmonthordinalNamedmonth :: Rule
+ruleDayofmonthordinalNamedmonth = Rule
+  { name = "<day-of-month>(ordinal) <named-month>"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleIntersectBy :: Rule
+ruleIntersectBy = Rule
+  { name = "intersect by \",\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex ","
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNthTimeAfterTime :: Rule
+ruleNthTimeAfterTime = Rule
+  { name = "nth <time> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "efter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       Token Time td1:
+       _:
+       Token Time td2:
+       _) -> tt $ predNthAfter (v - 1) td1 td2
+      _ -> Nothing
+  }
+
+ruleAfterDuration :: Rule
+ruleAfterDuration = Rule
+  { name = "after <duration>"
+  , pattern =
+    [ regex "efter"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt . withDirection TTime.After $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleTimeofdayLatent :: Rule
+ruleTimeofdayLatent = Rule
+  { name = "time-of-day (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 0 23
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ hour False n
+      _ -> Nothing
+  }
+
+ruleDayofmonthOrdinalOfNamedmonth :: Rule
+ruleDayofmonthOrdinalOfNamedmonth = Rule
+  { name = "<day-of-month> (ordinal) of <named-month>"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    , regex "av|i"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleFromTimeofdayTimeofdayInterval :: Rule
+ruleFromTimeofdayTimeofdayInterval = Rule
+  { name = "from <time-of-day> - <time-of-day> (interval)"
+  , pattern =
+    [ regex "(efter|fr\x00e5n)"
+    , Predicate isATimeOfDay
+    , regex "((men )?f\x00f6re)|\\-|till|till och med|tom"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth2 :: Rule
+ruleNamedmonth2 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "februari|feb\\.?"
+    ]
+  , prod = \_ -> tt $ month 2
+  }
+
+ruleExactlyTimeofday :: Rule
+ruleExactlyTimeofday = Rule
+  { name = "exactly <time-of-day>"
+  , pattern =
+    [ regex "(precis|exakt)( kl.| klockan)?"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleInduringThePartofday2 :: Rule
+ruleInduringThePartofday2 = Rule
+  { name = "in|during the <part-of-day>"
+  , pattern =
+    [ regex "om|i"
+    , Predicate isAPartOfDay
+    , regex "en|ten"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleSeason :: Rule
+ruleSeason = Rule
+  { name = "season"
+  , pattern =
+    [ regex "sommar(en)"
+    ]
+  , prod = \_ ->
+      let from = monthDay 6 21
+          to = monthDay 9 23
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleBetweenDatetimeAndDatetimeInterval :: Rule
+ruleBetweenDatetimeAndDatetimeInterval = Rule
+  { name = "between <datetime> and <datetime> (interval)"
+  , pattern =
+    [ regex "mellan"
+    , dimension Time
+    , regex "och"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNewYearsEve :: Rule
+ruleNewYearsEve = Rule
+  { name = "new year's eve"
+  , pattern =
+    [ regex "ny\x00e5rsafton?"
+    ]
+  , prod = \_ -> tt $ monthDay 12 31
+  }
+
+ruleDurationAgo :: Rule
+ruleDurationAgo = Rule
+  { name = "<duration> ago"
+  , pattern =
+    [ dimension Duration
+    , regex "sedan"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt $ durationAgo dd
+      _ -> Nothing
+  }
+
+ruleByTheEndOfTime :: Rule
+ruleByTheEndOfTime = Rule
+  { name = "by the end of <time>"
+  , pattern =
+    [ regex "i slutet av"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ interval TTime.Closed (cycleNth TG.Second 0, td)
+      _ -> Nothing
+  }
+
+ruleAfterWork :: Rule
+ruleAfterWork = Rule
+  { name = "after work"
+  , pattern =
+    [ regex "efter jobbet"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 17, hour False 21)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleLastNCycle :: Rule
+ruleLastNCycle = Rule
+  { name = "last n <cycle>"
+  , pattern =
+    [ regex "sista|senaste|f\x00f6rra"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain (- v)
+      _ -> Nothing
+  }
+
+ruleTimeofdaySharp :: Rule
+ruleTimeofdaySharp = Rule
+  { name = "<time-of-day> sharp"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(sharp|precis|exakt)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleWithinDuration :: Rule
+ruleWithinDuration = Rule
+  { name = "within <duration>"
+  , pattern =
+    [ regex "(innanf\x00f6r|inom)"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        let from = cycleNth TG.Second 0
+            to = inDuration dd
+        in tt $ interval TTime.Open (from, to)
+      _ -> Nothing
+  }
+
+ruleMidnighteodendOfDay :: Rule
+ruleMidnighteodendOfDay = Rule
+  { name = "midnight|EOD|end of day"
+  , pattern =
+    [ regex "midnatt|EOD"
+    ]
+  , prod = \_ -> tt $ hour False 0
+  }
+
+ruleDayofmonthNonOrdinalNamedmonth :: Rule
+ruleDayofmonthNonOrdinalNamedmonth = Rule
+  { name = "<day-of-month> (non ordinal) <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ Predicate isNotLatent
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleAboutTimeofday :: Rule
+ruleAboutTimeofday = Rule
+  { name = "about <time-of-day>"
+  , pattern =
+    [ regex "(omkring|cirka|runt|ca\\.)( kl\\.| klockan)?"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleUntilTimeofday :: Rule
+ruleUntilTimeofday = Rule
+  { name = "until <time-of-day>"
+  , pattern =
+    [ regex "innan|f\x00f6re|intill"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ withDirection TTime.Before td
+      _ -> Nothing
+  }
+
+ruleAtTimeofday :: Rule
+ruleAtTimeofday = Rule
+  { name = "at <time-of-day>"
+  , pattern =
+    [ regex "klockan|kl.|kl|@"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth6 :: Rule
+ruleNamedmonth6 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "juni|jun\\.?"
+    ]
+  , prod = \_ -> tt $ month 6
+  }
+
+ruleNthTimeOfTime :: Rule
+ruleNthTimeOfTime = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension Time
+    , regex "av|i"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       Token Time td1:
+       _:
+       Token Time td2:
+       _) -> Token Time . predNth (v - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNamedmonth8 :: Rule
+ruleNamedmonth8 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "augusti|aug\\.?"
+    ]
+  , prod = \_ -> tt $ month 8
+  }
+
+ruleTimePartofday :: Rule
+ruleTimePartofday = Rule
+  { name = "<time> <part-of-day>"
+  , pattern =
+    [ dimension Time
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleWeekend :: Rule
+ruleWeekend = Rule
+  { name = "week-end"
+  , pattern =
+    [ regex "((week(\\s|-)?end)|helg)(en)?"
+    ]
+  , prod = \_ -> do
+      from <- intersect (dayOfWeek 5) (hour False 18)
+      to <- intersect (dayOfWeek 1) (hour False 0)
+      tt $ interval TTime.Open (from, to)
+  }
+
+ruleEomendOfMonth :: Rule
+ruleEomendOfMonth = Rule
+  { name = "EOM|End of month"
+  , pattern =
+    [ regex "EOM"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Month 1
+  }
+
+ruleLastYear :: Rule
+ruleLastYear = Rule
+  { name = "Last year"
+  , pattern =
+    [ regex "i fjol|ifjol"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Year $ - 1
+  }
+
+ruleNthTimeAfterTime2 :: Rule
+ruleNthTimeAfterTime2 = Rule
+  { name = "nth <time> after <time>"
+  , pattern =
+    [ regex "den"
+    , dimension Ordinal
+    , dimension Time
+    , regex "efter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       Token Time td1:
+       _:
+       Token Time td2:
+       _) -> tt $ predNthAfter (v - 1) td1 td2
+      _ -> Nothing
+  }
+
+ruleNextTime :: Rule
+ruleNextTime = Rule
+  { name = "next <time>"
+  , pattern =
+    [ regex "n\x00e4sta|kommande"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleOrdinalQuarterYear :: Rule
+ruleOrdinalQuarterYear = Rule
+  { name = "<ordinal> quarter <year>"
+  , pattern =
+    [ dimension Ordinal
+    , Predicate $ isGrain TG.Quarter
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:_:Token Time td:_) ->
+        tt $ cycleNthAfter False TG.Quarter (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleYyyymmdd :: Rule
+ruleYyyymmdd = Rule
+  { name = "yyyy-mm-dd"
+  , pattern =
+    [ regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
+        y <- parseInt m1
+        m <- parseInt m2
+        d <- parseInt m3
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleTheOrdinalCycleAfterTime :: Rule
+ruleTheOrdinalCycleAfterTime = Rule
+  { name = "the <ordinal> <cycle> after <time>"
+  , pattern =
+    [ regex "den"
+    , dimension Ordinal
+    , dimension TimeGrain
+    , regex "efter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleIntersectByOfFromS :: Rule
+ruleIntersectByOfFromS = Rule
+  { name = "intersect by \"of\", \"from\", \"'s\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "i"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNextNCycle :: Rule
+ruleNextNCycle = Rule
+  { name = "next n <cycle>"
+  , pattern =
+    [ regex "n\x00e4sta"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleADuration :: Rule
+ruleADuration = Rule
+  { name = "a <duration>"
+  , pattern =
+    [ regex "(om )?en|ett"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleMorning :: Rule
+ruleMorning = Rule
+  { name = "morning"
+  , pattern =
+    [ regex "morgon(en)?"
+    ]
+  , prod = \_ ->
+      let from = hour False 4
+          to = hour False 12
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleThisPartofday :: Rule
+ruleThisPartofday = Rule
+  { name = "this <part-of-day>"
+  , pattern =
+    [ regex "i|denna"
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        Token Time . partOfDay <$> intersect (cycleNth TG.Day 0) td
+      _ -> Nothing
+  }
+
+ruleThisCycle :: Rule
+ruleThisCycle = Rule
+  { name = "this <cycle>"
+  , pattern =
+    [ regex "denna|detta|i|nuvarande"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 0
+      _ -> Nothing
+  }
+
+ruleThisTime :: Rule
+ruleThisTime = Rule
+  { name = "this <time>"
+  , pattern =
+    [ regex "(denna|detta|i|den h\x00e4r)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleDayofmonthNonOrdinalOfNamedmonth :: Rule
+ruleDayofmonthNonOrdinalOfNamedmonth = Rule
+  { name = "<day-of-month> (non ordinal) of <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , regex "av|i"
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleAfterLunch :: Rule
+ruleAfterLunch = Rule
+  { name = "after lunch"
+  , pattern =
+    [ regex "efter (lunch)"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 13, hour False 17)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleOnANamedday :: Rule
+ruleOnANamedday = Rule
+  { name = "on a named-day"
+  , pattern =
+    [ regex "p\x00e5 en"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleYearLatent :: Rule
+ruleYearLatent = Rule
+  { name = "year (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween (- 10000) 999
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ year v
+      _ -> Nothing
+  }
+
+ruleYesterday :: Rule
+ruleYesterday = Rule
+  { name = "yesterday"
+  , pattern =
+    [ regex "i g\x00e5r|ig\x00e5r"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 1
+  }
+
+ruleSeason2 :: Rule
+ruleSeason2 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "vinter(n)"
+    ]
+  , prod = \_ ->
+      let from = monthDay 12 21
+          to = monthDay 3 20
+      in tt $ interval TTime.Open (from, to)
+  }
+
+ruleAfterTimeofday :: Rule
+ruleAfterTimeofday = Rule
+  { name = "after <time-of-day>"
+  , pattern =
+    [ regex "efter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt . notLatent $ withDirection TTime.After td
+      _ -> Nothing
+  }
+
+ruleChristmas :: Rule
+ruleChristmas = Rule
+  { name = "christmas"
+  , pattern =
+    [ regex "juldag(en)?"
+    ]
+  , prod = \_ -> tt $ monthDay 12 25
+  }
+
+ruleNight :: Rule
+ruleNight = Rule
+  { name = "night"
+  , pattern =
+    [ regex "natt(en)?"
+    ]
+  , prod = \_ ->
+      let from = hour False 0
+          to = hour False 4
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleDayofmonthOrdinal :: Rule
+ruleDayofmonthOrdinal = Rule
+  { name = "<day-of-month> (ordinal)"
+  , pattern =
+    [ Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):_) ->
+        tt . mkLatent $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleOrdinalCycleAfterTime :: Rule
+ruleOrdinalCycleAfterTime = Rule
+  { name = "<ordinal> <cycle> after <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "efter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleOrdinalCycleOfTime :: Rule
+ruleOrdinalCycleOfTime = Rule
+  { name = "<ordinal> <cycle> of <time>"
+  , pattern =
+    [ dimension Ordinal
+    , dimension TimeGrain
+    , regex "av|i|fr\x00e5n"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleNamedmonth5 :: Rule
+ruleNamedmonth5 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "maj"
+    ]
+  , prod = \_ -> tt $ month 5
+  }
+
+ruleNamedday7 :: Rule
+ruleNamedday7 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "s\x00f6ndag(en)?|s\x00f6n\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 7
+  }
+
+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 (m1:m2:_)):_) -> do
+        h <- parseInt m1
+        m <- parseInt m2
+        tt $ hourMinute False h m
+      _ -> Nothing
+  }
+
+ruleTonight :: Rule
+ruleTonight = Rule
+  { name = "tonight"
+  , pattern =
+    [ regex "ikv\x00e4ll"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 18, hour False 0)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleYear :: Rule
+ruleYear = Rule
+  { name = "year"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1000 2100
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt $ year v
+      _ -> Nothing
+  }
+
+ruleNamedmonth10 :: Rule
+ruleNamedmonth10 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "oktober|okt\\.?"
+    ]
+  , prod = \_ -> tt $ month 10
+  }
+
+ruleNamedmonthDayofmonthNonOrdinal :: Rule
+ruleNamedmonthDayofmonthNonOrdinal = 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
+  }
+
+ruleAbsorptionOfAfterNamedDay :: Rule
+ruleAbsorptionOfAfterNamedDay = Rule
+  { name = "absorption of , after named day"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex ","
+    ]
+  , prod = \tokens -> case tokens of
+      (x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleLastDayofweekOfTime :: Rule
+ruleLastDayofweekOfTime = Rule
+  { name = "last <day-of-week> of <time>"
+  , pattern =
+    [ regex "sista"
+    , Predicate isADayOfWeek
+    , regex "av|i"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td1:_:Token Time td2:_) ->
+        tt $ predLastOf td1 td2
+      _ -> Nothing
+  }
+
+ruleCycleBeforeTime :: Rule
+ruleCycleBeforeTime = Rule
+  { name = "<cycle> before <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "f\x00f6re"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain (-1) td
+      _ -> Nothing
+  }
+
+ruleDdmmyyyy :: Rule
+ruleDdmmyyyy = Rule
+  { name = "dd/mm/yyyy"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[\\/-](0?[1-9]|1[0-2])[\\/-](\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
+        y <- parseInt m3
+        m <- parseInt m2
+        d <- parseInt m1
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleTimeofdayTimeofdayInterval :: Rule
+ruleTimeofdayTimeofdayInterval = Rule
+  { name = "<time-of-day> - <time-of-day> (interval)"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\-|till|till och med|tom"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        tt $ interval TTime.Closed (td1, td2)
+      _ -> Nothing
+  }
+
+ruleNamedmonth11 :: Rule
+ruleNamedmonth11 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "november|nov\\.?"
+    ]
+  , prod = \_ -> tt $ month 11
+  }
+
+ruleDurationAfterTime :: Rule
+ruleDurationAfterTime = Rule
+  { name = "<duration> after <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "efter"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_:Token Time td:_) ->
+        tt $ durationAfter dd td
+      _ -> Nothing
+  }
+
+ruleOrdinalQuarter :: Rule
+ruleOrdinalQuarter = Rule
+  { name = "<ordinal> quarter"
+  , pattern =
+    [ dimension Ordinal
+    , Predicate $ isGrain TG.Quarter
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Ordinal (OrdinalData {TOrdinal.value = v}):_) -> tt .
+        cycleNthAfter False TG.Quarter (v - 1) $ cycleNth TG.Year 0
+      _ -> Nothing
+  }
+
+ruleNamedday3 :: Rule
+ruleNamedday3 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "onsdag(en)?|ons\\.?"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 3
+  }
+
+ruleTheDayofmonthOrdinal :: Rule
+ruleTheDayofmonthOrdinal = Rule
+  { name = "the <day-of-month> (ordinal)"
+  , pattern =
+    [ regex "den"
+    , Predicate isDOMOrdinal
+    ]
+  , prod = \tokens -> case tokens of
+      (_:
+       Token Ordinal (OrdinalData {TOrdinal.value = v}):
+       _) -> tt $ dayOfMonth v
+      _ -> Nothing
+  }
+
+ruleDurationBeforeTime :: Rule
+ruleDurationBeforeTime = Rule
+  { name = "<duration> before <time>"
+  , pattern =
+    [ dimension Duration
+    , regex "f\x00f6re"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_:Token Time td:_) ->
+        tt $ durationBefore dd td
+      _ -> Nothing
+  }
+
+rulePartofdayOfTime :: Rule
+rulePartofdayOfTime = Rule
+  { name = "<part-of-day> of <time>"
+  , pattern =
+    [ Predicate isAPartOfDay
+    , regex "(en |ten )?den"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleEoyendOfYear :: Rule
+ruleEoyendOfYear = Rule
+  { name = "EOY|End of year"
+  , pattern =
+    [ regex "EOY"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Year 1
+  }
+
+ruleTomorrow :: Rule
+ruleTomorrow = Rule
+  { name = "tomorrow"
+  , pattern =
+    [ regex "i morgon|imorgon"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 1
+  }
+
+ruleTimeofdayOclock :: Rule
+ruleTimeofdayOclock = Rule
+  { name = "<time-of-day> o'clock"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "( )?h"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth9 :: Rule
+ruleNamedmonth9 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "september|sept?\\.?"
+    ]
+  , prod = \_ -> tt $ month 9
+  }
+
+ruleDayofmonthordinalNamedmonthYear :: Rule
+ruleDayofmonthordinalNamedmonthYear = 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
+         y <- parseInt match
+         dom <- intersectDOM td token
+         Token Time <$> intersect dom (year y)
+      _ -> 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 False h m s
+      _ -> Nothing
+  }
+
+ruleTimezone :: Rule
+ruleTimezone = Rule
+  { name = "<time> timezone"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\b(YEKT|YEKST|YAKT|YAKST|WITA|WIT|WIB|WGT|WGST|WFT|WET|WEST|WAT|WAST|VUT|VLAT|VLAST|VET|UZT|UYT|UYST|UTC|ULAT|TVT|TMT|TLT|TKT|TJT|TFT|TAHT|SST|SRT|SGT|SCT|SBT|SAST|SAMT|RET|PYT|PYST|PWT|PST|PONT|PMST|PMDT|PKT|PHT|PHOT|PGT|PETT|PETST|PET|PDT|OMST|OMSST|NZST|NZDT|NUT|NST|NPT|NOVT|NOVST|NFT|NDT|NCT|MYT|MVT|MUT|MST|MSK|MSD|MMT|MHT|MDT|MAWT|MART|MAGT|MAGST|LINT|LHST|LHDT|KUYT|KST|KRAT|KRAST|KGT|JST|IST|IRST|IRKT|IRKST|IRDT|IOT|IDT|ICT|HOVT|HKT|GYT|GST|GMT|GILT|GFT|GET|GAMT|GALT|FNT|FKT|FKST|FJT|FJST|EST|EGT|EGST|EET|EEST|EDT|ECT|EAT|EAST|EASST|DAVT|ChST|CXT|CVT|CST|COT|CLT|CLST|CKT|CHAST|CHADT|CET|CEST|CDT|CCT|CAT|CAST|BTT|BST|BRT|BRST|BOT|BNT|AZT|AZST|AZOT|AZOST|AWST|AWDT|AST|ART|AQTT|ANAT|ANAST|AMT|AMST|ALMT|AKST|AKDT|AFT|AEST|AEDT|ADT|ACST|ACDT)\\b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       Token RegexMatch (GroupMatch (tz:_)):
+       _) -> Token Time <$> inTimezone tz td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleADuration
+  , ruleAboutTimeofday
+  , ruleAbsorptionOfAfterNamedDay
+  , ruleAfterDuration
+  , ruleAfterLunch
+  , ruleAfterTimeofday
+  , ruleAfterWork
+  , ruleAfternoon
+  , ruleAtTimeofday
+  , ruleBetweenDatetimeAndDatetimeInterval
+  , ruleBetweenTimeofdayAndTimeofdayInterval
+  , ruleByTheEndOfTime
+  , ruleChristmas
+  , ruleChristmasEve
+  , ruleCycleAfterTime
+  , ruleCycleBeforeTime
+  , ruleDatetimeDatetimeInterval
+  , ruleDayofmonthNonOrdinalNamedmonth
+  , ruleDayofmonthNonOrdinalOfNamedmonth
+  , ruleDayofmonthOrdinal
+  , ruleDayofmonthOrdinalOfNamedmonth
+  , ruleDayofmonthordinalNamedmonth
+  , ruleDayofmonthordinalNamedmonthYear
+  , ruleDdmm
+  , ruleDdmmyyyy
+  , ruleDurationAfterTime
+  , ruleDurationAgo
+  , ruleDurationBeforeTime
+  , ruleDurationFromNow
+  , ruleEomendOfMonth
+  , ruleEoyendOfYear
+  , ruleEvening
+  , ruleExactlyTimeofday
+  , ruleFromDatetimeDatetimeInterval
+  , ruleFromTimeofdayTimeofdayInterval
+  , ruleHhmm
+  , ruleHhmmss
+  , ruleHourofdayIntegerAsRelativeMinutes
+  , ruleHourofdayQuarter
+  , ruleHourofdayHalf
+  , ruleInDuration
+  , ruleInduringThePartofday
+  , ruleInduringThePartofday2
+  , ruleIntersect
+  , ruleIntersectBy
+  , ruleIntersectByOfFromS
+  , ruleLastCycle
+  , ruleLastCycleOfTime
+  , ruleLastDayofweekOfTime
+  , ruleLastNCycle
+  , ruleLastTime
+  , ruleLastYear
+  , ruleLunch
+  , ruleMidnighteodendOfDay
+  , ruleMonthDdddInterval
+  , ruleMorning
+  , ruleNamedday
+  , ruleNamedday2
+  , ruleNamedday3
+  , ruleNamedday4
+  , ruleNamedday5
+  , ruleNamedday6
+  , ruleNamedday7
+  , ruleNamedmonth
+  , ruleNamedmonth10
+  , ruleNamedmonth11
+  , ruleNamedmonth12
+  , ruleNamedmonth2
+  , ruleNamedmonth3
+  , ruleNamedmonth4
+  , ruleNamedmonth5
+  , ruleNamedmonth6
+  , ruleNamedmonth7
+  , ruleNamedmonth8
+  , ruleNamedmonth9
+  , ruleNamedmonthDayofmonthNonOrdinal
+  , ruleNamedmonthDayofmonthOrdinal
+  , ruleNewYearsDay
+  , ruleNewYearsEve
+  , ruleNextCycle
+  , ruleNextNCycle
+  , ruleNextTime
+  , ruleNight
+  , ruleNoon
+  , ruleNow
+  , ruleNthTimeAfterTime
+  , ruleNthTimeAfterTime2
+  , ruleNthTimeOfTime
+  , ruleNthTimeOfTime2
+  , ruleOnANamedday
+  , ruleOnDate
+  , ruleOrdinalCycleAfterTime
+  , ruleOrdinalCycleOfTime
+  , ruleOrdinalQuarter
+  , ruleOrdinalQuarterYear
+  , rulePartofdayOfTime
+  , ruleRelativeMinutesAfterpastIntegerHourofday
+  , ruleQuarterAfterpastIntegerHourofday
+  , ruleHalfAfterpastIntegerHourofday
+  , ruleRelativeMinutesTotillbeforeIntegerHourofday
+  , ruleQuarterTotillbeforeIntegerHourofday
+  , ruleHalfTotillbeforeIntegerHourofday
+  , ruleSeason
+  , ruleSeason2
+  , ruleTheCycleAfterTime
+  , ruleTheCycleBeforeTime
+  , ruleTheCycleOfTime
+  , ruleTheDayAfterTomorrow
+  , ruleTheDayBeforeYesterday
+  , ruleTheDayofmonthNonOrdinal
+  , ruleTheDayofmonthOrdinal
+  , ruleTheIdesOfNamedmonth
+  , ruleTheOrdinalCycleAfterTime
+  , ruleTheOrdinalCycleOfTime
+  , ruleThisCycle
+  , ruleThisPartofday
+  , ruleThisTime
+  , ruleThisnextDayofweek
+  , ruleTimeAfterNext
+  , ruleTimeBeforeLast
+  , ruleTimePartofday
+  , ruleTimeofdayApproximately
+  , ruleTimeofdayLatent
+  , ruleTimeofdayOclock
+  , ruleTimeofdaySharp
+  , ruleTimeofdayTimeofdayInterval
+  , ruleToday
+  , ruleTomorrow
+  , ruleTonight
+  , ruleUntilTimeofday
+  , ruleWeekend
+  , ruleWithinDuration
+  , ruleYear
+  , ruleYearLatent
+  , ruleYearLatent2
+  , ruleYesterday
+  , ruleYyyymmdd
+  , ruleTimezone
+  ]
diff --git a/Duckling/Time/TimeZone/Parse.hs b/Duckling/Time/TimeZone/Parse.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/TimeZone/Parse.hs
@@ -0,0 +1,194 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.TimeZone.Parse
+  ( parseTimezone
+  ) where
+
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.String
+import Data.Text (Text)
+import Data.Time (TimeZone(..))
+import Prelude
+
+-- `TimeZone` reads anything but only accepts timezones known
+-- from the provided locale.
+parseTimezone :: Text -> Maybe TimeZone
+parseTimezone x = HashMap.lookup x tzs
+
+tzs :: HashMap Text TimeZone
+tzs = HashMap.fromList
+  [ ( "YEKT", TimeZone 300 False "YEKT" )
+  , ( "YEKST", TimeZone 360 True "YEKST" )
+  , ( "YAKT", TimeZone 540 False "YAKT" )
+  , ( "YAKST", TimeZone 600 True "YAKST" )
+  , ( "WITA", TimeZone 480 False "WITA" )
+  , ( "WIT", TimeZone 420 False "WIT" )
+  , ( "WIB", TimeZone 420 False "WIB" )
+  , ( "WGT", TimeZone (-180) False "WGT" )
+  , ( "WGST", TimeZone (-120) True "WGST" )
+  , ( "WFT", TimeZone 720 False "WFT" )
+  , ( "WET", TimeZone 0 False "WET" )
+  , ( "WEST", TimeZone 60 True "WEST" )
+  , ( "WAT", TimeZone 60 False "WAT" )
+  , ( "WAST", TimeZone 120 True "WAST" )
+  , ( "VUT", TimeZone 660 False "VUT" )
+  , ( "VLAT", TimeZone 660 False "VLAT" )
+  , ( "VLAST", TimeZone 660 True "VLAST" )
+  , ( "VET", TimeZone (-270) False "VET" )
+  , ( "UZT", TimeZone 300 False "UZT" )
+  , ( "UYT", TimeZone (-180) False "UYT" )
+  , ( "UYST", TimeZone (-120) True "UYST" )
+  , ( "UTC", TimeZone 0 False "UTC" )
+  , ( "ULAT", TimeZone 480 False "ULAT" )
+  , ( "TVT", TimeZone 720 False "TVT" )
+  , ( "TMT", TimeZone 300 False "TMT" )
+  , ( "TLT", TimeZone 540 False "TLT" )
+  , ( "TKT", TimeZone 780 False "TKT" )
+  , ( "TJT", TimeZone 300 False "TJT" )
+  , ( "TFT", TimeZone 300 False "TFT" )
+  , ( "TAHT", TimeZone (-600) False "TAHT" )
+  , ( "SST", TimeZone (-660) False "SST" )
+  , ( "SRT", TimeZone (-180) False "SRT" )
+  , ( "SGT", TimeZone 480 False "SGT" )
+  , ( "SCT", TimeZone 240 False "SCT" )
+  , ( "SBT", TimeZone 660 False "SBT" )
+  , ( "SAST", TimeZone 120 False "SAST" )
+  , ( "SAMT", TimeZone 300 False "SAMT" )
+  , ( "RET", TimeZone 240 False "RET" )
+  , ( "PYT", TimeZone (-240) False "PYT" )
+  , ( "PYST", TimeZone (-180) True "PYST" )
+  , ( "PWT", TimeZone (-420) True "PWT" )
+  , ( "PST", TimeZone (-480) False "PST" )
+  , ( "PONT", TimeZone 660 False "PONT" )
+  , ( "PMST", TimeZone (-180) False "PMST" )
+  , ( "PMDT", TimeZone (-120) True "PMDT" )
+  , ( "PKT", TimeZone 300 False "PKT" )
+  , ( "PHT", TimeZone 480 False "PHT" )
+  , ( "PHOT", TimeZone 780 False "PHOT" )
+  , ( "PGT", TimeZone 600 False "PGT" )
+  , ( "PETT", TimeZone 720 False "PETT" )
+  , ( "PETST", TimeZone 720 True "PETST" )
+  , ( "PET", TimeZone (-300) False "PET" )
+  , ( "PDT", TimeZone (-420) False "PDT" )
+  , ( "OMST", TimeZone 360 False "OMST" )
+  , ( "OMSST", TimeZone 420 True "OMSST" )
+  , ( "NZST", TimeZone 720 False "NZST" )
+  , ( "NZDT", TimeZone 780 False "NZDT" )
+  , ( "NUT", TimeZone (-660) False "NUT" )
+  , ( "NST", TimeZone (-660) False "NST" )
+  , ( "NPT", TimeZone (-600) True "NPT" )
+  , ( "NOVT", TimeZone 360 False "NOVT" )
+  , ( "NOVST", TimeZone 420 True "NOVST" )
+  , ( "NFT", TimeZone 690 False "NFT" )
+  , ( "NDT", TimeZone (-150) True "NDT" )
+  , ( "NCT", TimeZone 660 False "NCT" )
+  , ( "MYT", TimeZone 480 False "MYT" )
+  , ( "MVT", TimeZone 300 False "MVT" )
+  , ( "MUT", TimeZone 240 False "MUT" )
+  , ( "MST", TimeZone (-420) False "MST" )
+  , ( "MSK", TimeZone 180 False "MSK" )
+  , ( "MSD", TimeZone 240 True "MSD" )
+  , ( "MMT", TimeZone 390 False "MMT" )
+  , ( "MHT", TimeZone 720 False "MHT" )
+  , ( "MDT", TimeZone (-360) True "MDT" )
+  , ( "MAWT", TimeZone 300 False "MAWT" )
+  , ( "MART", TimeZone (-570) False "MART" )
+  , ( "MAGT", TimeZone 600 False "MAGT" )
+  , ( "MAGST", TimeZone 720 True "MAGST" )
+  , ( "LINT", TimeZone 840 False "LINT" )
+  , ( "LHST", TimeZone 630 False "LHST" )
+  , ( "LHDT", TimeZone 660 True "LHDT" )
+  , ( "KUYT", TimeZone 180 False "KUYT" )
+  , ( "KST", TimeZone 540 False "KST" )
+  , ( "KRAT", TimeZone 420 False "KRAT" )
+  , ( "KRAST", TimeZone 480 True "KRAST" )
+  , ( "KGT", TimeZone 360 False "KGT" )
+  , ( "JST", TimeZone 540 False "JST" )
+  , ( "IST", TimeZone 120 False "IST" )
+  , ( "IRST", TimeZone 210 False "IRST" )
+  , ( "IRKT", TimeZone 480 False "IRKT" )
+  , ( "IRKST", TimeZone 540 True "IRKST" )
+  , ( "IRDT", TimeZone 270 True "IRDT" )
+  , ( "IOT", TimeZone 360 False "IOT" )
+  , ( "IDT", TimeZone 180 True "IDT" )
+  , ( "ICT", TimeZone 420 False "ICT" )
+  , ( "HOVT", TimeZone 420 False "HOVT" )
+  , ( "HKT", TimeZone 480 False "HKT" )
+  , ( "GYT", TimeZone (-240) False "GYT" )
+  , ( "GST", TimeZone 240 False "GST" )
+  , ( "GMT", TimeZone 0 False "GMT" )
+  , ( "GILT", TimeZone 720 False "GILT" )
+  , ( "GFT", TimeZone (-180) False "GFT" )
+  , ( "GET", TimeZone 240 False "GET" )
+  , ( "GAMT", TimeZone (-540) False "GAMT" )
+  , ( "GALT", TimeZone (-360) False "GALT" )
+  , ( "FNT", TimeZone (-120) False "FNT" )
+  , ( "FKT", TimeZone (-240) False "FKT" )
+  , ( "FKST", TimeZone (-180) False "FKST" )
+  , ( "FJT", TimeZone 720 False "FJT" )
+  , ( "FJST", TimeZone 780 True "FJST" )
+  , ( "EST", TimeZone (-300) False "EST" )
+  , ( "EGT", TimeZone (-60) False "EGT" )
+  , ( "EGST", TimeZone 0 True "EGST" )
+  , ( "EET", TimeZone 120 False "EET" )
+  , ( "EEST", TimeZone 180 True "EEST" )
+  , ( "EDT", TimeZone (-240) True "EDT" )
+  , ( "ECT", TimeZone (-300) False "ECT" )
+  , ( "EAT", TimeZone 180 False "EAT" )
+  , ( "EAST", TimeZone (-300) False "EAST" )
+  , ( "EASST", TimeZone (-300) True "EASST" )
+  , ( "DAVT", TimeZone 420 False "DAVT" )
+  , ( "ChST", TimeZone 600 False "ChST" )
+  , ( "CXT", TimeZone 420 False "CXT" )
+  , ( "CVT", TimeZone (-60) False "CVT" )
+  , ( "CST", TimeZone (-360) False "CST" )
+  , ( "COT", TimeZone (-300) False "COT" )
+  , ( "CLT", TimeZone (-180) False "CLT" )
+  , ( "CLST", TimeZone (-180) True "CLST" )
+  , ( "CKT", TimeZone (-600) False "CKT" )
+  , ( "CHAST", TimeZone 765 False "CHAST" )
+  , ( "CHADT", TimeZone 825 True "CHADT" )
+  , ( "CET", TimeZone 60 False "CET" )
+  , ( "CEST", TimeZone 120 True "CEST" )
+  , ( "CDT", TimeZone 540 True "CDT" )
+  , ( "CCT", TimeZone 390 False "CCT" )
+  , ( "CAT", TimeZone 120 False "CAT" )
+  , ( "CAST", TimeZone 180 True "CAST" )
+  , ( "BTT", TimeZone 360 False "BTT" )
+  , ( "BST", TimeZone (-660) False "BST" )
+  , ( "BRT", TimeZone (-180) False "BRT" )
+  , ( "BRST", TimeZone (-120) True "BRST" )
+  , ( "BOT", TimeZone (-240) False "BOT" )
+  , ( "BNT", TimeZone 480 False "BNT" )
+  , ( "AZT", TimeZone 240 False "AZT" )
+  , ( "AZST", TimeZone 300 True "AZST" )
+  , ( "AZOT", TimeZone (-60) False "AZOT" )
+  , ( "AZOST", TimeZone 0 True "AZOST" )
+  , ( "AWST", TimeZone 480 False "AWST" )
+  , ( "AWDT", TimeZone 540 True "AWDT" )
+  , ( "AST", TimeZone (-240) False "AST" )
+  , ( "ART", TimeZone (-180) False "ART" )
+  , ( "AQTT", TimeZone 300 False "AQTT" )
+  , ( "ANAT", TimeZone 720 False "ANAT" )
+  , ( "ANAST", TimeZone 720 True "ANAST" )
+  , ( "AMT", TimeZone 240 False "AMT" )
+  , ( "AMST", TimeZone 300 True "AMST" )
+  , ( "ALMT", TimeZone 360 False "ALMT" )
+  , ( "AKST", TimeZone (-540) False "AKST" )
+  , ( "AKDT", TimeZone (-480) True "AKDT" )
+  , ( "AFT", TimeZone 270 False "AFT" )
+  , ( "AEST", TimeZone 600 False "AEST" )
+  , ( "AEDT", TimeZone 660 True "AEDT" )
+  , ( "ADT", TimeZone (-180) True "ADT" )
+  , ( "ACST", TimeZone 570 False "ACST" )
+  , ( "ACDT", TimeZone 630 True "ACDT" )
+  ]
diff --git a/Duckling/Time/Types.hs b/Duckling/Time/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/Types.hs
@@ -0,0 +1,698 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Duckling.Time.Types where
+
+import Control.Arrow ((***))
+import Control.DeepSeq
+import Control.Monad (join)
+import Control.Applicative ((<|>))
+import Data.Aeson
+import Data.Hashable
+import qualified Data.HashMap.Strict as H
+import Data.Maybe
+import Data.Monoid
+import Data.Text (Text)
+import qualified Data.Text as Text
+import qualified Data.Time as Time
+import qualified Data.Time.Calendar.WeekDate as Time
+import qualified Data.Time.LocalTime.TimeZone.Series as Series
+import GHC.Generics
+import TextShow (showt)
+import Prelude
+
+import Duckling.Resolve
+import Duckling.TimeGrain.Types (Grain)
+import qualified Duckling.TimeGrain.Types as TG
+
+data TimeObject = TimeObject
+  { start :: Time.UTCTime
+  , grain :: Grain
+  , end :: Maybe Time.UTCTime
+  } deriving (Eq, Show)
+
+data Form = DayOfWeek
+  | TimeOfDay
+    { hours :: Maybe Int
+    , is12H :: Bool
+    }
+  | Month { month :: Int }
+  | PartOfDay
+  deriving (Eq, Generic, Hashable, Show, Ord, NFData)
+
+data IntervalDirection = Before | After
+  deriving (Eq, Generic, Hashable, Ord, Show, NFData)
+
+-- Grain needed here for intersect
+data TimeData = TimeData
+  { timePred :: Predicate
+  , latent :: Bool
+  , timeGrain :: Grain
+  , notImmediate :: Bool
+  , form :: Maybe Form
+  , direction :: Maybe IntervalDirection
+  }
+
+instance Eq TimeData where
+  (==) (TimeData _ l1 g1 n1 f1 d1) (TimeData _ l2 g2 n2 f2 d2) =
+    l1 == l2 && g1 == g2 && n1 == n2 && f1 == f2 && d1 == d2
+
+instance Hashable TimeData where
+  hashWithSalt s (TimeData _ latent grain imm form dir) = hashWithSalt s
+    (0::Int, (latent, grain, imm, form, dir))
+
+instance Ord TimeData where
+  compare (TimeData _ l1 g1 n1 f1 d1) (TimeData _ l2 g2 n2 f2 d2) =
+    case compare g1 g2 of
+      EQ -> case compare f1 f2 of
+        EQ -> case compare l1 l2 of
+          EQ -> case compare n1 n2 of
+            EQ -> compare d1 d2
+            z -> z
+          z -> z
+        z -> z
+      z -> z
+
+instance Show TimeData where
+  show (TimeData _ latent grain _ form dir) =
+    "TimeData{" ++
+    "latent=" ++ show latent ++
+    ", grain=" ++ show grain ++
+    ", form=" ++ show form ++
+    ", direction=" ++ show dir ++
+    "}"
+
+instance NFData TimeData where
+  rnf TimeData{..} = rnf (latent, timeGrain, notImmediate, form, direction)
+
+instance Resolve TimeData where
+  type ResolvedValue TimeData = TimeValue
+  resolve _ TimeData {latent = True} = Nothing
+  resolve context TimeData {timePred, notImmediate, direction} = do
+    t <- case ts of
+      (behind, []) -> listToMaybe behind
+      (_, ahead:nextAhead:_)
+        | notImmediate && isJust (timeIntersect ahead refTime) -> Just nextAhead
+      (_, ahead:_) -> Just ahead
+    Just $ case direction of
+      Nothing -> TimeValue (timeValue tzSeries t) .
+        map (timeValue tzSeries) $ take 3 future
+      Just d -> TimeValue (openInterval tzSeries d t) .
+        map (openInterval tzSeries d) $ take 3 future
+    where
+      DucklingTime (Series.ZoneSeriesTime utcTime tzSeries) = referenceTime context
+      refTime = TimeObject
+        { start = utcTime
+        , grain = TG.Second
+        , end = Nothing
+        }
+      tc = TimeContext
+        { refTime = refTime
+        , tzSeries = tzSeries
+        , maxTime = timePlus refTime TG.Year 2000
+        , minTime = timePlus refTime TG.Year $ - 2000
+        }
+      ts@(_, future) = runPredicate timePred refTime tc
+
+timedata' :: TimeData
+timedata' = TimeData
+  { timePred = EmptyPredicate
+  , latent = False
+  , timeGrain = TG.Second
+  , notImmediate = False
+  , form = Nothing
+  , direction = Nothing
+  }
+
+data TimeContext = TimeContext
+  { refTime  :: TimeObject
+  , tzSeries :: Series.TimeZoneSeries
+  , maxTime  :: TimeObject
+  , minTime  :: TimeObject
+  }
+
+data InstantValue = InstantValue
+  { vValue :: Time.ZonedTime
+  , vGrain :: Grain
+  }
+  deriving (Show)
+
+instance Eq InstantValue where
+  (==) (InstantValue (Time.ZonedTime lt1 tz1) g1)
+       (InstantValue (Time.ZonedTime lt2 tz2) g2) =
+    g1 == g2 && lt1 == lt2 && tz1 == tz2
+
+data SingleTimeValue
+  = SimpleValue InstantValue
+  | IntervalValue (InstantValue, InstantValue)
+  | OpenIntervalValue (InstantValue, IntervalDirection)
+  deriving (Show, Eq)
+
+data TimeValue = TimeValue SingleTimeValue [SingleTimeValue]
+  deriving (Show, Eq)
+
+instance ToJSON InstantValue where
+  toJSON (InstantValue value grain) = object
+    [ "value" .= toRFC3339 value
+    , "grain" .= grain
+    ]
+
+instance ToJSON SingleTimeValue where
+  toJSON (SimpleValue value) = case toJSON value of
+    Object o -> Object $ H.insert "type" (toJSON ("value" :: Text)) o
+    _ -> Object H.empty
+  toJSON (IntervalValue (from, to)) = object
+    [ "type" .= ("interval" :: Text)
+    , "from" .= toJSON from
+    , "to" .= toJSON to
+    ]
+  toJSON (OpenIntervalValue (instant, Before)) = object
+    [ "type" .= ("interval" :: Text)
+    , "to" .= toJSON instant
+    ]
+  toJSON (OpenIntervalValue (instant, After)) = object
+    [ "type" .= ("interval" :: Text)
+    , "from" .= toJSON instant
+    ]
+
+instance ToJSON TimeValue where
+  toJSON (TimeValue value values) = case toJSON value of
+    Object o -> Object $ H.insert "values" (toJSON values) o
+    _ -> Object H.empty
+
+-- | Return a tuple of (past, future) elements
+type SeriesPredicate = TimeObject -> TimeContext -> ([TimeObject], [TimeObject])
+
+data AMPM = AM | PM
+  deriving Eq
+
+data Predicate
+  = SeriesPredicate SeriesPredicate
+  | EmptyPredicate
+  | TimeDatePredicate -- invariant: at least one of them is Just
+    { tdSecond :: Maybe Int
+    , tdMinute :: Maybe Int
+    , tdHour :: Maybe (Bool, Int)
+    , tdAMPM :: Maybe AMPM -- only used if we have an hour
+    , tdDayOfTheWeek :: Maybe Int
+    , tdDayOfTheMonth :: Maybe Int
+    , tdMonth :: Maybe Int
+    , tdYear :: Maybe Int
+    }
+  | IntersectPredicate Predicate Predicate
+  | TimeIntervalsPredicate TimeIntervalType Predicate Predicate
+
+{-# ANN runPredicate ("HLint: ignore Use foldr1OrError" :: String) #-}
+runPredicate :: Predicate -> SeriesPredicate
+runPredicate EmptyPredicate = \_ _ -> ([], [])
+runPredicate (SeriesPredicate p) = p
+runPredicate TimeDatePredicate{..}
+  -- This should not happen by construction, but if it does then
+  -- empty time series should be ok
+  | isNothing tdHour && isJust tdAMPM = \_ _ -> ([], [])
+runPredicate TimeDatePredicate{..} =
+  foldr1 runCompose toCompose
+  where
+  -- runComposePredicate performs best when the first predicate is of
+  -- smaller grain, that's why we order by grain here
+  toCompose = catMaybes
+    [ runSecondPredicate <$> tdSecond
+    , runMinutePredicate <$> tdMinute
+    , uncurry (runHourPredicate tdAMPM) <$> tdHour
+    , runDayOfTheWeekPredicate <$> tdDayOfTheWeek
+    , runDayOfTheMonthPredicate <$> tdDayOfTheMonth
+    , runMonthPredicate <$> tdMonth
+    , runYearPredicate <$> tdYear
+    ]
+runPredicate (IntersectPredicate pred1 pred2) =
+  runIntersectPredicate pred1 pred2
+runPredicate (TimeIntervalsPredicate ty pred1 pred2) =
+  runTimeIntervalsPredicate ty pred1 pred2
+
+-- Don't use outside this module, use a smart constructor
+emptyTimeDatePredicate :: Predicate
+emptyTimeDatePredicate =
+  TimeDatePredicate Nothing Nothing Nothing Nothing Nothing Nothing Nothing
+    Nothing
+
+-- Predicate smart constructors
+
+mkSeriesPredicate :: SeriesPredicate -> Predicate
+mkSeriesPredicate = SeriesPredicate
+
+mkSecondPredicate :: Int -> Predicate
+mkSecondPredicate n = emptyTimeDatePredicate { tdSecond = Just n }
+
+mkMinutePredicate :: Int -> Predicate
+mkMinutePredicate n = emptyTimeDatePredicate { tdMinute = Just n }
+
+mkHourPredicate :: Bool -> Int -> Predicate
+mkHourPredicate is12H h = emptyTimeDatePredicate { tdHour = Just (is12H, h) }
+
+mkAMPMPredicate :: AMPM -> Predicate
+mkAMPMPredicate ampm = emptyTimeDatePredicate { tdAMPM = Just ampm }
+
+mkDayOfTheWeekPredicate :: Int -> Predicate
+mkDayOfTheWeekPredicate n = emptyTimeDatePredicate { tdDayOfTheWeek = Just n }
+
+mkDayOfTheMonthPredicate :: Int -> Predicate
+mkDayOfTheMonthPredicate n = emptyTimeDatePredicate { tdDayOfTheMonth = Just n }
+
+mkMonthPredicate :: Int -> Predicate
+mkMonthPredicate n = emptyTimeDatePredicate { tdMonth = Just n }
+
+mkYearPredicate :: Int -> Predicate
+mkYearPredicate n = emptyTimeDatePredicate { tdYear = Just n }
+
+mkIntersectPredicate :: Predicate -> Predicate -> Predicate
+mkIntersectPredicate EmptyPredicate _ = EmptyPredicate
+mkIntersectPredicate _ EmptyPredicate = EmptyPredicate
+mkIntersectPredicate
+  (TimeDatePredicate a1 b1 c1 d1 e1 f1 g1 h1)
+  (TimeDatePredicate a2 b2 c2 d2 e2 f2 g2 h2)
+  = fromMaybe EmptyPredicate
+      (TimeDatePredicate <$>
+        unify a1 a2 <*>
+        unify b1 b2 <*>
+        unify c1 c2 <*>
+        unify d1 d2 <*>
+        unify e1 e2 <*>
+        unify f1 f2 <*>
+        unify g1 g2 <*>
+        unify h1 h2)
+  where
+  unify Nothing a = Just a
+  unify a Nothing = Just a
+  unify ma@(Just a) (Just b)
+    | a == b = Just ma
+    | otherwise = Nothing
+mkIntersectPredicate pred1 pred2 = IntersectPredicate pred1 pred2
+
+mkTimeIntervalsPredicate
+  :: TimeIntervalType -> Predicate -> Predicate -> Predicate
+mkTimeIntervalsPredicate _ EmptyPredicate _ = EmptyPredicate
+mkTimeIntervalsPredicate _ _ EmptyPredicate = EmptyPredicate
+-- `from (from a to b) to c` and `from c to (from a to b)` don't
+-- really have a good interpretation, so abort early
+mkTimeIntervalsPredicate _ TimeIntervalsPredicate{} _ = EmptyPredicate
+mkTimeIntervalsPredicate _ _ TimeIntervalsPredicate{} = EmptyPredicate
+mkTimeIntervalsPredicate t a b = TimeIntervalsPredicate t a b
+
+isEmptyPredicate :: Predicate -> Bool
+isEmptyPredicate EmptyPredicate = True
+isEmptyPredicate _ = False
+
+-- Predicate runners
+
+runSecondPredicate :: Int -> SeriesPredicate
+runSecondPredicate n = series
+  where
+  series t _ = timeSequence TG.Minute 1 anchor
+    where
+      Time.UTCTime _ diffTime = start t
+      Time.TimeOfDay _ _ s = Time.timeToTimeOfDay diffTime
+      anchor = timePlus (timeRound t TG.Second) TG.Second
+        $ mod (toInteger n - floor s :: Integer) 60
+
+runMinutePredicate :: Int -> SeriesPredicate
+runMinutePredicate n = series
+  where
+  series t _ = timeSequence TG.Hour 1 anchor
+    where
+      Time.UTCTime _ diffTime = start t
+      Time.TimeOfDay _ m _ = Time.timeToTimeOfDay diffTime
+      rounded = timeRound t TG.Minute
+      anchor = timePlus rounded TG.Minute . toInteger $ mod (n - m) 60
+
+runHourPredicate :: Maybe AMPM -> Bool -> Int -> SeriesPredicate
+runHourPredicate ampm is12H n = series
+  where
+  series t _ =
+    ( drop 1 $
+        iterate (\t -> timePlus t TG.Hour . toInteger $ - step) anchor
+    , iterate (\t -> timePlus t TG.Hour $ toInteger step) anchor
+    )
+    where
+      Time.UTCTime _ diffTime = start t
+      Time.TimeOfDay h _ _ = Time.timeToTimeOfDay diffTime
+      step :: Int
+      step = if is12H && n <= 12 && isNothing ampm then 12 else 24
+      n' = case ampm of
+            Just AM -> n `mod` 12
+            Just PM -> (n `mod` 12) + 12
+            Nothing -> n
+      rounded = timeRound t TG.Hour
+      anchor = timePlus rounded TG.Hour . toInteger $ mod (n' - h) step
+
+runAMPMPredicate :: AMPM -> SeriesPredicate
+runAMPMPredicate ampm = series
+  where
+  series t _ = (past, future)
+    where
+    past = maybeShrinkFirst $
+      iterate (\t -> timePlusEnd t TG.Hour . toInteger $ - step) anchor
+    future = maybeShrinkFirst $
+      iterate (\t -> timePlusEnd t TG.Hour $ toInteger step) anchor
+    -- to produce time in the future/past we need to adjust
+    -- the start/end of the first interval
+    maybeShrinkFirst (a:as) =
+      case timeIntersect (t { grain = TG.Day }) a of
+        Nothing -> as
+        Just ii -> ii:as
+    maybeShrinkFirst a = a
+    step :: Int
+    step = 24
+    n = case ampm of
+          AM -> 0
+          PM -> 12
+    rounded = timeRound t TG.Day
+    anchorStart = timePlus rounded TG.Hour n
+    anchorEnd = timePlus anchorStart TG.Hour 12
+    -- an interval of length 12h starting either at 12am or 12pm,
+    -- the same day as input time
+    anchor = timeInterval Open anchorStart anchorEnd
+
+runDayOfTheWeekPredicate :: Int -> SeriesPredicate
+runDayOfTheWeekPredicate n = series
+  where
+  series t _ = timeSequence TG.Day 7 anchor
+    where
+      Time.UTCTime day _ = start t
+      (_, _, dayOfWeek) = Time.toWeekDate day
+      daysUntilNextWeek = toInteger $ mod (n - dayOfWeek) 7
+      anchor =
+        timePlus (timeRound t TG.Day) TG.Day daysUntilNextWeek
+
+runDayOfTheMonthPredicate :: Int -> SeriesPredicate
+runDayOfTheMonthPredicate n = series
+  where
+  series t _ =
+    ( map addDays . filter enoughDays . iterate (addMonth $ - 1) $
+        addMonth (- 1) anchor
+    , map addDays . filter enoughDays $ iterate (addMonth 1) anchor
+    )
+    where
+      enoughDays :: TimeObject -> Bool
+      enoughDays t = let Time.UTCTime day _ = start t
+                         (year, month, _) = Time.toGregorian day
+                     in n <= Time.gregorianMonthLength year month
+      addDays :: TimeObject -> TimeObject
+      addDays t = timePlus t TG.Day . toInteger $ n - 1
+      addMonth :: Int -> TimeObject -> TimeObject
+      addMonth i t = timePlus t TG.Month $ toInteger i
+      roundMonth :: TimeObject -> TimeObject
+      roundMonth t = timeRound t TG.Month
+      rounded = roundMonth t
+      Time.UTCTime day _ = start t
+      (_, _, dayOfMonth) = Time.toGregorian day
+      anchor = if dayOfMonth <= n then rounded else addMonth 1 rounded
+
+runMonthPredicate :: Int -> SeriesPredicate
+runMonthPredicate n = series
+  where
+  series t _ = timeSequence TG.Year 1 anchor
+    where
+      rounded =
+        timePlus (timeRound t TG.Year) TG.Month . toInteger $ n - 1
+      anchor = if timeStartsBeforeTheEndOf t rounded
+        then rounded
+        else timePlus rounded TG.Year 1
+
+-- | Converts 2-digits to a year between 1950 and 2050
+runYearPredicate :: Int -> SeriesPredicate
+runYearPredicate n = series
+  where
+  series t _ =
+    if tyear <= year
+      then ([], [y])
+      else ([y], [])
+    where
+      Time.UTCTime day _ = start t
+      (tyear, _, _) = Time.toGregorian day
+      year = toInteger $ if n <= 99 then mod (n + 50) 100 + 2000 - 50 else n
+      y = timePlus (timeRound t TG.Year) TG.Year $ year - tyear
+
+-- Limits how deep into lists of segments to look
+safeMax :: Int
+safeMax = 10
+
+runIntersectPredicate :: Predicate -> Predicate -> SeriesPredicate
+runIntersectPredicate pred1 pred2 =
+  runCompose (runPredicate pred1) (runPredicate pred2)
+
+-- Performs best when pred1 is smaller grain than pred2
+runCompose :: SeriesPredicate -> SeriesPredicate -> SeriesPredicate
+runCompose pred1 pred2 = series
+  where
+  series nowTime context = (backward, forward)
+    where
+    (past, future) = pred2 nowTime context
+    computeSerie tokens =
+      [t | time1 <- take safeMax tokens
+         , t <- mapMaybe (timeIntersect time1) .
+                takeWhile (startsBefore time1) .
+                snd . pred1 time1 $ fixedRange time1
+      ]
+
+    startsBefore t1 this = timeStartsBeforeTheEndOf this t1
+    fixedRange t1 = context {minTime = t1, maxTime = t1}
+
+    backward = computeSerie $ takeWhile (\t ->
+      timeStartsBeforeTheEndOf (minTime context) t) past
+    forward = computeSerie $ takeWhile (\t ->
+      timeStartsBeforeTheEndOf t (maxTime context)) future
+
+runTimeIntervalsPredicate
+  :: TimeIntervalType -> Predicate
+  -> Predicate -> SeriesPredicate
+runTimeIntervalsPredicate intervalType pred1 pred2 = timeSeqMap True f pred1
+  where
+    -- Pick the first interval *after* the given time segment
+    f thisSegment ctx = case runPredicate pred2 thisSegment ctx of
+      (_, firstFuture:_) -> Just $
+        timeInterval intervalType thisSegment firstFuture
+      _ -> Nothing
+
+-- Limits how deep into lists of segments to look
+safeMaxInterval :: Int
+safeMaxInterval = 12
+
+-- | Applies `f` to each interval yielded by `g`.
+-- | Intervals including "now" are in the future.
+timeSeqMap
+  :: Bool
+     -- Given an interval and range, compute a single new interval
+  -> (TimeObject -> TimeContext -> Maybe TimeObject)
+     -- First-layer series generator
+  -> Predicate
+     -- Series generator for values that come from `f`
+  -> SeriesPredicate
+timeSeqMap dontReverse f g = series
+  where
+  series nowTime context = (past, future)
+    where
+    -- computes a single interval from `f` based on each interval in the series
+    applyF series = mapMaybe (\x -> f x context) $ take safeMaxInterval series
+
+    (firstPast, firstFuture) = runPredicate g nowTime context
+    (past1, future1) = (applyF firstPast, applyF firstFuture)
+
+    -- Separate what's before and after now from the past's series
+    (newFuture, stillPast) =
+      span (timeStartsBeforeTheEndOf nowTime) past1
+    -- A series that ends at the earliest time
+    oldPast = takeWhile
+      (timeStartsBeforeTheEndOf $ minTime context)
+      stillPast
+
+    -- Separate what's before and after now from the future's series
+    (newPast, stillFuture) =
+      break (timeStartsBeforeTheEndOf nowTime) future1
+    -- A series that ends at the furthest future time
+    oldFuture = takeWhile
+      (\x -> timeStartsBeforeTheEndOf x $ maxTime context)
+      stillFuture
+
+    -- Reverse the list if needed?
+    applyRev series = if dontReverse then series else reverse series
+    (sortedPast, sortedFuture) = (applyRev newPast, applyRev newFuture)
+
+    -- Past is the past from the future's series with the
+    -- past from the past's series tacked on
+    past = sortedPast ++ oldPast
+
+    -- Future is the future from the past's series with the
+    -- future from the future's series tacked on
+    future = sortedFuture ++ oldFuture
+
+
+timeSequence
+  :: TG.Grain
+  -> Int
+  -> TimeObject
+  -> ([TimeObject], [TimeObject])
+timeSequence grain step anchor =
+  ( drop 1 $ iterate (f $ - step) anchor
+  , iterate (f step) anchor
+  )
+  where
+    f :: Int -> TimeObject -> TimeObject
+    f n t = timePlus t grain $ toInteger n
+
+-- | Zero-pad `x` to reach length `n`.
+pad :: Int -> Int -> Text
+pad n x
+  | x <= magnitude = Text.replicate (n - Text.length s) "0" <> s
+  | otherwise      = s
+  where
+    magnitude = round ((10 :: Float) ** fromIntegral (n - 1) :: Float)
+    s = showt x
+
+-- | Return the timezone offset portion of the RFC3339 format, e.g. "-02:00".
+timezoneOffset :: Time.TimeZone -> Text
+timezoneOffset (Time.TimeZone t _ _) = Text.concat [sign, hh, ":", mm]
+  where
+    (sign, t') = if t < 0 then ("-", negate t) else ("+", t)
+    (hh, mm) = join (***) (pad 2) $ divMod t' 60
+
+-- | Return a RFC3339 formatted time, e.g. "2013-02-12T04:30:00.000-02:00".
+-- | Backward-compatible with Duckling: fraction of second is milli and padded.
+toRFC3339 :: Time.ZonedTime -> Text
+toRFC3339 (Time.ZonedTime (Time.LocalTime day (Time.TimeOfDay h m s)) tz) =
+  Text.concat
+    [ Text.pack $ Time.showGregorian day
+    , "T"
+    , pad 2 h
+    , ":"
+    , pad 2 m
+    , ":"
+    , pad 2 $ floor s
+    , "."
+    , pad 3 . round $ (s - realToFrac (floor s :: Integer)) * 1000
+    , timezoneOffset tz
+    ]
+
+instantValue :: Series.TimeZoneSeries -> Time.UTCTime -> Grain -> InstantValue
+instantValue tzSeries t g = InstantValue
+  { vValue = fromUTC t $ Series.timeZoneFromSeries tzSeries t
+  , vGrain = g
+  }
+
+timeValue :: Series.TimeZoneSeries -> TimeObject -> SingleTimeValue
+timeValue tzSeries (TimeObject s g Nothing) =
+  SimpleValue $ instantValue tzSeries s g
+timeValue tzSeries (TimeObject s g (Just e)) = IntervalValue
+  ( instantValue tzSeries s g
+  , instantValue tzSeries e g
+  )
+
+openInterval
+  :: Series.TimeZoneSeries -> IntervalDirection -> TimeObject -> SingleTimeValue
+openInterval tzSeries direction (TimeObject s g _) = OpenIntervalValue
+  ( instantValue tzSeries s g
+  , direction
+  )
+
+-- -----------------------------------------------------------------
+-- Time object helpers
+
+timeRound :: TimeObject -> TG.Grain -> TimeObject
+timeRound t TG.Week = TimeObject {start = s, grain = TG.Week, end = Nothing}
+  where
+    Time.UTCTime day diffTime = start $ timeRound t TG.Day
+    (year, week, _) = Time.toWeekDate day
+    newDay = Time.fromWeekDate year week 1
+    s = Time.UTCTime newDay diffTime
+timeRound t TG.Quarter = newTime {grain = TG.Quarter}
+  where
+    monthTime = timeRound t TG.Month
+    Time.UTCTime day _ = start monthTime
+    (_, month, _) = Time.toGregorian day
+    newTime = timePlus monthTime TG.Month . toInteger $ - (mod (month - 1) 3)
+timeRound t grain = TimeObject {start = s, grain = grain, end = Nothing}
+  where
+    Time.UTCTime day diffTime = start t
+    timeOfDay = Time.timeToTimeOfDay diffTime
+    (year, month, dayOfMonth) = Time.toGregorian day
+    Time.TimeOfDay hours mins secs = timeOfDay
+    newMonth = if grain > TG.Month then 1 else month
+    newDayOfMonth = if grain > TG.Day then 1 else dayOfMonth
+    newDay = Time.fromGregorian year newMonth newDayOfMonth
+    newHours = if grain > TG.Hour then 0 else hours
+    newMins = if grain > TG.Minute then 0 else mins
+    newSecs = if grain > TG.Second then 0 else secs
+    newDiffTime = Time.timeOfDayToTime $ Time.TimeOfDay newHours newMins newSecs
+    s = Time.UTCTime newDay newDiffTime
+
+timePlus :: TimeObject -> TG.Grain -> Integer -> TimeObject
+timePlus (TimeObject start grain _) theGrain n = TimeObject
+  { start = TG.add start theGrain n
+  , grain = min grain theGrain
+  , end = Nothing
+  }
+
+-- | Shifts the whole interval by n units of theGrain
+-- Returned interval has the same length as the input one
+timePlusEnd :: TimeObject -> TG.Grain -> Integer -> TimeObject
+timePlusEnd (TimeObject start grain end) theGrain n = TimeObject
+  { start = TG.add start theGrain n
+  , grain = min grain theGrain
+  , end = TG.add <$> end <*> return theGrain <*> return n
+  }
+
+timeEnd :: TimeObject -> Time.UTCTime
+timeEnd (TimeObject start grain end) = fromMaybe (TG.add start grain 1) end
+
+timeStartingAtTheEndOf :: TimeObject -> TimeObject
+timeStartingAtTheEndOf t = TimeObject
+  {start = timeEnd t, end = Nothing, grain = grain t}
+
+-- | Closed if the interval between A and B should include B
+-- Open if the interval should end right before B
+data TimeIntervalType = Open | Closed
+  deriving (Eq, Show)
+
+timeInterval :: TimeIntervalType -> TimeObject -> TimeObject -> TimeObject
+timeInterval intervalType t1 t2 = TimeObject
+  { start = start t1
+  , grain = min (grain t1) (grain t2)
+  , end = Just $ case intervalType of
+                   Open -> start t2
+                   Closed -> timeEnd t2
+  }
+
+timeStartsBeforeTheEndOf :: TimeObject -> TimeObject -> Bool
+timeStartsBeforeTheEndOf t1 t2 = start t1 < timeEnd t2
+
+timeBefore :: TimeObject -> TimeObject -> Bool
+timeBefore t1 t2 = start t1 < start t2
+
+-- | Intersection between two `TimeObject`.
+-- The resulting grain and end fields are the smallest.
+-- Prefers intervals when the range is equal.
+timeIntersect :: TimeObject -> TimeObject -> Maybe TimeObject
+timeIntersect t1 t2
+  | s1 > s2 = timeIntersect t2 t1
+  | e1 <= s2 = Nothing
+  | e1 < e2 || s1 == s2 && e1 == e2 && isJust end1 = Just TimeObject
+    {start = s2, end = end1, grain = g'}
+  | otherwise = Just t2 {grain = g'}
+  where
+    TimeObject s1 g1 end1 = t1
+    TimeObject s2 g2 _    = t2
+    e1 = timeEnd t1
+    e2 = timeEnd t2
+    g' = min g1 g2
diff --git a/Duckling/Time/VI/Corpus.hs b/Duckling/Time/VI/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/VI/Corpus.hs
@@ -0,0 +1,370 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.VI.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Time.Corpus
+import Duckling.Time.Types hiding (Month, refTime)
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Testing.Types hiding (examples)
+
+corpus :: Corpus
+corpus = (context, allExamples)
+  where
+    context = testContext
+      { lang = VI
+      , referenceTime = refTime (2017, 2, 2, 3, 55, 0) (-2)
+      }
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (datetime (2017, 2, 2, 3, 55, 0) Second)
+             [ "bây giờ"
+             , "ngay bây giờ"
+             , "ngay lúc này"
+             ]
+  , examples (datetime (2017, 2, 2, 0, 0, 0) Day)
+             [ "hôm nay"
+             , "ngày hôm nay"
+             , "bữa nay"
+             ]
+  , examples (datetime (2017, 2, 1, 0, 0, 0) Day)
+             [ "hôm qua"
+             , "ngày hôm qua"
+             ]
+  , examples (datetime (2017, 2, 3, 0, 0, 0) Day)
+             [ "ngày mai"
+             ]
+  , examples (datetime (2017, 1, 31, 0, 0, 0) Day)
+             [ "hôm kia"
+             , "ngày hôm kia"
+             ]
+  , examples (datetime (2017, 2, 6, 0, 0, 0) Day)
+             [ "thứ 2"
+             , "thứ hai"
+             ]
+  , examples (datetime (2017, 2, 6, 0, 0, 0) Day)
+             [ "thứ 2 ngày 6 tháng 2"
+             , "thứ 2 mồng 6 tháng 2"
+             , "thứ hai ngày 6 tháng 2"
+             ]
+  , examples (datetime (2017, 2, 7, 0, 0, 0) Day)
+             [ "thứ 3"
+             , "thứ ba"
+             ]
+  , examples (datetime (2017, 2, 5, 0, 0, 0) Day)
+             [ "chủ nhật"
+             ]
+  , examples (datetime (2017, 6, 0, 0, 0, 0) Month)
+             [ "tháng 6"
+             , "tháng sáu"
+             ]
+  , examples (datetime (2017, 3, 1, 0, 0, 0) Day)
+             [ "ngày đầu tiên của tháng ba"
+             , "ngày đầu tiên của tháng 3"
+             ]
+  , examples (datetime (2017, 3, 3, 0, 0, 0) Day)
+             [ "mồng 3 tháng ba"
+             , "mồng 3 tháng 3"
+             ]
+  , examples (datetime (2017, 3, 3, 0, 0, 0) Day)
+             [ "ngày mồng 3 tháng 3 năm 2017"
+             , "ngày 3 tháng 3 năm 2017"
+             , "3/3/2017"
+             , "3/3/17"
+             , "03/03/2017"
+             ]
+  , examples (datetime (2017, 3, 7, 0, 0, 0) Day)
+             [ "ngày mồng 7 tháng 3"
+             , "ngày 7 tháng ba"
+             , "7/3"
+             , "07/03"
+             ]
+  , examples (datetime (2017, 10, 0, 0, 0, 0) Month)
+             [ "tháng 10 năm 2017"
+             , "tháng mười năm 2017"
+             ]
+  , examples (datetime (1991, 9, 3, 0, 0, 0) Day)
+             [ "03/09/1991"
+             , "3/9/91"
+             , "3/9/1991"
+             ]
+  , examples (datetime (2017, 10, 12, 0, 0, 0) Day)
+             [ "12 tháng 10 năm 2017"
+             , "ngày 12 tháng 10 năm 2017"
+             ]
+  , examples (datetime (2017, 2, 9, 0, 0, 0) Day)
+             [ "thứ năm tuần tới"
+             , "thứ 5 tuần sau"
+             ]
+  , examples (datetime (2017, 3, 0, 0, 0, 0) Month)
+             [ "tháng 3 tới"
+             ]
+  , examples (datetime (2017, 4, 9, 0, 0, 0) Day)
+             [ "chủ nhật ngày mồng 9 tháng 4"
+             , "chủ nhật ngày 9 tháng 4"
+             ]
+  , examples (datetime (2017, 2, 6, 0, 0, 0) Day)
+             [ "thứ 2 ngày 6 tháng 2"
+             , "thứ 2 ngày mồng 6 tháng 2"
+             , "thứ hai ngày mồng 6 tháng 2"
+             ]
+  , examples (datetime (2018, 4, 3, 0, 0, 0) Day)
+             [ "thứ 3 ngày 3 tháng 4 năm 2018"
+             ]
+  , examples (datetime (2017, 1, 30, 0, 0, 0) Week)
+             [ "tuần này"
+             ]
+  , examples (datetime (2017, 1, 23, 0, 0, 0) Week)
+             [ "tuần trước"
+             ]
+  , examples (datetime (2017, 2, 6, 0, 0, 0) Week)
+             [ "tuần sau"
+             ]
+  , examples (datetime (2017, 1, 0, 0, 0, 0) Month)
+             [ "tháng trước"
+             ]
+  , examples (datetime (2017, 3, 0, 0, 0, 0) Month)
+             [ "tháng sau"
+             ]
+  , examples (datetime (2017, 1, 1, 0, 0, 0) Quarter)
+             [ "quý này"
+             ]
+  , examples (datetime (2017, 4, 1, 0, 0, 0) Quarter)
+             [ "quý sau"
+             ]
+  , examples (datetime (2017, 7, 1, 0, 0, 0) Quarter)
+             [ "quý 3"
+             , "quý ba"
+             ]
+  , examples (datetime (2018, 10, 1, 0, 0, 0) Quarter)
+             [ "quý 4 năm 2018"
+             ]
+  , examples (datetime (2016, 0, 0, 0, 0, 0) Year)
+             [ "năm trước"
+             , "năm ngoái"
+             ]
+  , examples (datetime (2017, 0, 0, 0, 0, 0) Year)
+             [ "năm nay"
+             ]
+  , examples (datetime (2018, 0, 0, 0, 0, 0) Year)
+             [ "năm sau"
+             ]
+  , examples (datetime (2017, 1, 1, 0, 0, 0) Quarter)
+             [ "quý này"
+             , "quý nay"
+             , "quý hiện tại"
+             ]
+  , examples (datetime (2017, 4, 1, 0, 0, 0) Quarter)
+             [ "quý tới"
+             , "quý tiếp"
+             ]
+  , examples (datetime (2017, 7, 1, 0, 0, 0) Quarter)
+             [ "quý ba"
+             , "quý 3"
+             ]
+  , examples (datetime (2018, 10, 1, 0, 0, 0) Quarter)
+             [ "quý 4 của năm 2018"
+             ]
+  , examples (datetime (2016, 0, 0, 0, 0, 0) Year)
+             [ "năm ngoái"
+             , "năm trước"
+             ]
+  , examples (datetime (2017, 0, 0, 0, 0, 0) Year)
+             [ "năm nay"
+             ]
+  , examples (datetime (2018, 0, 0, 0, 0, 0) Year)
+             [ "năm tiếp theo"
+             , "năm kế tiếp"
+             , "năm tới"
+             ]
+  , examples (datetime (2017, 1, 31, 0, 0, 0) Day)
+             [ "thứ ba vừa rồi"
+             ]
+  , examples (datetime (2017, 2, 7, 0, 0, 0) Day)
+             [ "thứ ba tới"
+             ]
+  , examples (datetime (2017, 2, 3, 0, 0, 0) Day)
+             [ "thứ sáu tới"
+             ]
+  , examples (datetime (2017, 2, 8, 0, 0, 0) Day)
+             [ "thứ tư tuần tới"
+             , "thứ tư của tuần tới"
+             ]
+  , examples (datetime (2017, 2, 3, 0, 0, 0) Day)
+             [ "thứ sáu tuần này"
+             , "thứ 6 tuần này"
+             , "thứ 6 của tuần này"
+             ]
+  , examples (datetime (2017, 2, 2, 0, 0, 0) Day)
+             [ "thứ năm tuần này"
+             , "thứ 5 của tuần này"
+             ]
+  , examples (datetime (2017, 9, 4, 0, 0, 0) Week)
+             [ "tuần đầu tiên của tháng 9 năm 2017"
+             ]
+  , examples (datetime (2017, 2, 3, 2, 0, 0) Hour)
+             [ "vào lúc 2 giờ sáng"
+             , "lúc 2 giờ sáng"
+             ]
+  , examples (datetime (2017, 2, 3, 1, 18, 0) Minute)
+             [ "1:18 sáng"
+             ]
+  , examples (datetime (2017, 2, 2, 15, 0, 0) Hour)
+             [ "lúc 3 giờ tối"
+             , "vào lúc 3 giờ chiều"
+             , "vào đúng 3 giờ chiều"
+             ]
+  , examples (datetime (2017, 2, 2, 15, 0, 0) Hour)
+             [ "vào khoảng 3 giờ chiều"
+             , "khoảng 3 giờ chiều"
+             ]
+  , examples (datetime (2017, 2, 2, 15, 30, 0) Minute)
+             [ "3 giờ rưỡi chiều"
+             , "3:30 chiều"
+             , "ba giờ rưỡi chiều"
+             ]
+  , examples (datetime (2017, 2, 2, 14, 30, 0) Minute)
+             [ "2:30"
+             , "hai giờ rưỡi"
+             ]
+  , examples (datetime (2017, 2, 2, 15, 23, 24) Second)
+             [ "15:23:24"
+             ]
+  , examples (datetime (2017, 2, 2, 10, 45, 0) Minute)
+             [ "11 giờ kém 15"
+             , "10 giờ 45 phút"
+             , "10:45"
+             , "10 giờ 45"
+             , "10h45"
+             , "10g45"
+             ]
+  , examples (datetime (2017, 2, 2, 20, 0, 0) Hour)
+             [ "8 giờ tối nay"
+             ]
+  , examples (datetime (2017, 4, 20, 19, 30, 0) Minute)
+             [ "vào lúc 7:30 chiều ngày 20 tháng 4 năm 2017"
+             , "7:30 chiều ngày 20/4/2017"
+             ]
+  , examples (datetimeInterval ((2017, 6, 21, 0, 0, 0), (2017, 9, 24, 0, 0, 0)) Day)
+             [ "mùa hè này"
+             , "mùa hè năm nay"
+             ]
+  , examples (datetimeInterval ((2016, 12, 21, 0, 0, 0), (2017, 3, 21, 0, 0, 0)) Day)
+             [ "mùa đông này"
+             ]
+  , examples (datetimeInterval ((2017, 2, 2, 18, 0, 0), (2017, 2, 3, 0, 0, 0)) Hour)
+             [ "tối nay"
+             , "tối hôm nay"
+             ]
+  , examples (datetimeInterval ((2017, 2, 3, 18, 0, 0), (2017, 2, 4, 0, 0, 0)) Hour)
+             [ "tối mai"
+             , "tối ngày mai"
+             ]
+  , examples (datetimeInterval ((2017, 2, 3, 12, 0, 0), (2017, 2, 3, 14, 0, 0)) Hour)
+             [ "trưa mai"
+             , "trưa ngày mai"
+             ]
+  , examples (datetimeInterval ((2017, 2, 1, 18, 0, 0), (2017, 2, 2, 0, 0, 0)) Hour)
+             [ "tối qua"
+             , "tối hôm qua"
+             ]
+  , examples (datetimeInterval ((2017, 2, 5, 4, 0, 0), (2017, 2, 5, 12, 0, 0)) Hour)
+             [ "sáng chủ nhật"
+             ]
+  , examples (datetimeInterval ((2017, 2, 2, 3, 54, 58), (2017, 2, 2, 3, 55, 0)) Second)
+             [ "2 giây vừa rồi"
+             ]
+  , examples (datetimeInterval ((2017, 2, 2, 3, 55, 1), (2017, 2, 2, 3, 55, 4)) Second)
+             [ "3 giây tới"
+             , "3 giây tiếp theo"
+             , "3 s tiếp theo"
+             ]
+  , examples (datetimeInterval ((2017, 2, 2, 3, 53, 0), (2017, 2, 2, 3, 55, 0)) Minute)
+             [ "2 phút vừa rồi"
+             ]
+  , examples (datetimeInterval ((2017, 2, 2, 3, 56, 0), (2017, 2, 2, 3, 59, 0)) Minute)
+             [ "3 phút tới"
+             , "3 phút tiếp theo"
+             ]
+  , examples (datetimeInterval ((2017, 2, 2, 2, 0, 0), (2017, 2, 2, 3, 0, 0)) Hour)
+             [ "một tiếng vừa rồi"
+             , "1 giờ vừa qua"
+             ]
+  , examples (datetimeInterval ((2017, 2, 2, 4, 0, 0), (2017, 2, 2, 7, 0, 0)) Hour)
+             [ "3 tiếng tiếp theo"
+             , "3 giờ tới"
+             ]
+  , examples (datetimeInterval ((2017, 1, 31, 0, 0, 0), (2017, 2, 2, 0, 0, 0)) Day)
+             [ "2 ngày vừa rồi"
+             , "2 ngày vừa qua"
+             ]
+  , examples (datetimeInterval ((2017, 2, 3, 0, 0, 0), (2017, 2, 6, 0, 0, 0)) Day)
+             [ "3 ngày tới"
+             , "3 ngày tiếp theo"
+             ]
+  , examples (datetimeInterval ((2016, 12, 0, 0, 0, 0), (2017, 2, 0, 0, 0, 0)) Month)
+             [ "2 tháng vừa rồi"
+             , "2 tháng qua"
+             ]
+  , examples (datetimeInterval ((2017, 3, 0, 0, 0, 0), (2017, 6, 0, 0, 0, 0)) Month)
+             [ "3 tháng tới"
+             , "ba tháng tiếp theo"
+             ]
+  , examples (datetimeInterval ((2015, 0, 0, 0, 0, 0), (2017, 0, 0, 0, 0, 0)) Year)
+             [ "2 năm vừa rồi"
+             ]
+  , examples (datetimeInterval ((2018, 0, 0, 0, 0, 0), (2021, 0, 0, 0, 0, 0)) Year)
+             [ "3 năm tới"
+             , "3 năm tiếp theo"
+             ]
+  , examples (datetime (2017, 2, 2, 13, 0, 0) Minute)
+             [ "4pm CET"
+             ]
+  , examples (datetime (2017, 2, 2, 14, 0, 0) Hour)
+             [ "hôm nay lúc 2 giờ chiều"
+             , "lúc 2 giờ chiều"
+             ]
+  , examples (datetime (2017, 4, 23, 16, 0, 0) Minute)
+             [ "lúc 4:00 chiều ngày 23/4"
+             ]
+  , examples (datetime (2017, 4, 23, 16, 0, 0) Hour)
+             [ "lúc 4 giờ chiều ngày 23 tháng 4"
+             ]
+  , examples (datetime (2017, 2, 3, 15, 0, 0) Hour)
+             [ "3 giờ chiều ngày mai"
+             ]
+  , examples (datetime (2017, 2, 2, 13, 30, 0) Minute)
+             [ "lúc 1:30 chiều"
+             , "lúc 1 giờ 30 chiều"
+             ]
+  , examples (datetimeInterval ((2017, 2, 2, 13, 0, 0), (2017, 2, 2, 17, 0, 0)) Hour)
+             [ "sau bữa trưa"
+             ]
+  , examples (datetime (2017, 2, 2, 10, 30, 0) Minute)
+             [ "10:30"
+             ]
+  , examples (datetimeInterval ((2017, 2, 2, 4, 0, 0), (2017, 2, 2, 12, 0, 0)) Hour)
+             [ "buổi sáng nay"
+             ]
+  , examples (datetime (2017, 2, 6, 0, 0, 0) Day)
+             [ "thứ hai tới"
+             , "thứ 2 tới"
+             ]
+  , examples (datetime (2017, 4, 0, 0, 0, 0) Month)
+             [ "tháng 4"
+             , "tháng tư"
+             ]
+  ]
diff --git a/Duckling/Time/VI/Rules.hs b/Duckling/Time/VI/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/VI/Rules.hs
@@ -0,0 +1,1211 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.VI.Rules
+  ( rules ) where
+
+import Control.Monad (liftM2)
+import Prelude
+import qualified Data.Text as Text
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import Duckling.Numeral.Types (NumeralData(..))
+import Duckling.Ordinal.Types (OrdinalData(..))
+import Duckling.Regex.Types
+import Duckling.Time.Helpers
+import Duckling.Time.Types (TimeData (..))
+import Duckling.Types
+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
+
+ruleTtDng :: Rule
+ruleTtDng = Rule
+  { name = "tết dương"
+  , pattern =
+    [ regex "(t(\x1ebf)t d(\x01b0)(\x01a1)ng)(l(\x1ecb)ch)?"
+    ]
+  , prod = \_ -> tt $ monthDay 1 1
+  }
+
+ruleNamedday :: Rule
+ruleNamedday = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "th(\x1ee9) (2|hai)"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 1
+  }
+
+ruleDayofmonthNamedmonth :: Rule
+ruleDayofmonthNamedmonth = Rule
+  { name = "<day-of-month> (numeric with day symbol) <named-month>"
+  , pattern =
+    [ regex "(ng(\x00e0)y|m(\x1ed3)ng)( m(\x1ed3)ng)?"
+    , Predicate isDOMInteger
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleDayofweekCuiCngCaTime :: Rule
+ruleDayofweekCuiCngCaTime = Rule
+  { name = "<day-of-week> cuối cùng của <time>"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex "cu(\x1ed1)i c(\x00f9)ng|cu(\x1ed1)i"
+    , regex "c(\x1ee7)a|trong|v(\x00e0)o"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:_:Token Time td2:_) -> tt $ predLastOf td1 td2
+      _ -> Nothing
+  }
+
+ruleTimeTi :: Rule
+ruleTimeTi = Rule
+  { name = "<time> tới"
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "t(\x1edb)i"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleCycleCuiCngCaTime :: Rule
+ruleCycleCuiCngCaTime = Rule
+  { name = "<cycle> cuối cùng của <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "cu(\x1ed1)i c(\x00f9)ng|cu(\x1ed1)i"
+    , regex "c(\x1ee7)a|trong|v(\x00e0)o"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_:_:Token Time td:_) ->
+        tt $ cycleLastOf grain td
+      _ -> Nothing
+  }
+
+ruleNamedmonth12 :: Rule
+ruleNamedmonth12 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "th(\x00e1)ng m(\x01b0)(\x1edd)i hai"
+    ]
+  , prod = \_ -> tt $ month 12
+  }
+
+ruleTimeofdayLatent2 :: Rule
+ruleTimeofdayLatent2 = Rule
+  { name = "time-of-day (latent)"
+  , pattern =
+    [ regex "(l(\x00fa)c|v(\x00e0)o)( l(\x00fa)c)?"
+    , Predicate $ isIntegerBetween 0 23
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ hour False v
+      _ -> Nothing
+  }
+
+ruleCchMngThng :: Rule
+ruleCchMngThng = Rule
+  { name = "cách mạng tháng 8"
+  , pattern =
+    [ regex "c(\x00e1)ch m(\x1ea1)ng th(\x00e1)ng (8|t(\x00e1)m)"
+    ]
+  , prod = \_ -> tt $ monthDay 8 19
+  }
+
+ruleNamedday2 :: Rule
+ruleNamedday2 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "th(\x1ee9) (3|ba)"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 2
+  }
+
+ruleTimeofdayGiNg :: Rule
+ruleTimeofdayGiNg = Rule
+  { name = "<time-of-day> giờ đúng"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "gi(\x1edd) (\x0111)(\x00fa)ng"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedday6 :: Rule
+ruleNamedday6 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "th(\x1ee9) (7|b((\x1ea3)|(\x1ea9))y)"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 6
+  }
+
+rulePartofdayHmNay :: Rule
+rulePartofdayHmNay = Rule
+  { name = "<part-of-day> (hôm )?nay"
+  , pattern =
+    [ Predicate isAPartOfDay
+    , regex "(h(\x00f4)m )?nay"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        Token Time . partOfDay <$> intersect (cycleNth TG.Day 0) td
+      _ -> Nothing
+  }
+
+ruleNamedmonth7 :: Rule
+ruleNamedmonth7 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "th(\x00e1)ng b(\x1ea3)y"
+    ]
+  , prod = \_ -> tt $ month 7
+  }
+
+ruleNgyDdmmyyyy :: Rule
+ruleNgyDdmmyyyy = Rule
+  { name = "ngày dd/mm/yyyy"
+  , pattern =
+    [ regex "ng(\x00e0)y"
+    , regex "(3[01]|[12]\\d|0?[1-9])[-/](0?[1-9]|1[0-2])[/-](\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
+        y <- parseInt m3
+        m <- parseInt m2
+        d <- parseInt m1
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleNamedday4 :: Rule
+ruleNamedday4 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "th(\x1ee9) (5|n(\x0103)m)"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 4
+  }
+
+rulePartofdayTime :: Rule
+rulePartofdayTime = Rule
+  { name = "<part-of-day> <time>"
+  , pattern =
+    [ Predicate isAPartOfDay
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) -> Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleQuarterNumber :: Rule
+ruleQuarterNumber = Rule
+  { name = "quarter <number>"
+  , pattern =
+    [ Predicate $ isGrain TG.Quarter
+    , dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        n <- getIntValue token
+        tt . cycleNthAfter True TG.Quarter (n - 1) $
+          cycleNth TG.Year 0
+      _ -> Nothing
+  }
+
+ruleSeason4 :: Rule
+ruleSeason4 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "m(\x00f9)a? xu(\x00e2)n"
+    ]
+  , prod = \_ -> tt $ interval TTime.Open (monthDay 3 20, monthDay 6 21)
+  }
+
+ruleNoon :: Rule
+ruleNoon = Rule
+  { name = "noon"
+  , pattern =
+    [ regex "(bu(\x1ed5)i )?(t(\x1ed1)i|(\x0111)(\x00ea)m)"
+    ]
+  , prod = \_ -> tt $ hour False 12
+  }
+
+ruleQucTLaoNg :: Rule
+ruleQucTLaoNg = Rule
+  { name = "quốc tế lao động"
+  , pattern =
+    [ regex "(ng(\x00e0)y )?qu(\x1ed1)c t(\x1ebf) lao (\x0111)(\x00f4)ng"
+    ]
+  , prod = \_ -> tt $ monthDay 5 1
+  }
+
+ruleNextCycle :: Rule
+ruleNextCycle = Rule
+  { name = "next <cycle>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "(t(\x1edb)i|k(\x1ebf)|sau|ti(\x1ebf)p)( theo)?( ti(\x1ebf)p)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_) -> tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleNamedmonth :: Rule
+ruleNamedmonth = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "th(\x00e1)ng (gi(\x00ea)ng|m(\x1ed9)t)"
+    ]
+  , prod = \_ -> tt $ month 1
+  }
+
+ruleTimeofdayApproximately :: Rule
+ruleTimeofdayApproximately = Rule
+  { name = "<time-of-day> approximately"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "g(\x1ea7)n"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleQuarterNumberOfYear :: Rule
+ruleQuarterNumberOfYear = Rule
+  { name = "quarter <number> of <year>"
+  , pattern =
+    [ Predicate $ isGrain TG.Quarter
+    , dimension Numeral
+    , regex "c(\x1ee7)a|trong"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_:Token Time td:_) -> do
+        n <- getIntValue token
+        tt $ cycleNthAfter False TG.Quarter (n - 1) td
+      _ -> Nothing
+  }
+
+ruleNamedmonth3 :: Rule
+ruleNamedmonth3 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "th(\x00e1)ng ba"
+    ]
+  , prod = \_ -> tt $ month 3
+  }
+
+ruleLunch :: Rule
+ruleLunch = Rule
+  { name = "lunch"
+  , pattern =
+    [ regex "(bu(\x1ed5)i )?tr(\x01b0)a"
+    ]
+  , prod = \_ -> tt . partOfDay . mkLatent $
+      interval TTime.Open (hour False 12, hour False 14)
+  }
+
+ruleLastCycle :: Rule
+ruleLastCycle = Rule
+  { name = "last <cycle>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "tr(\x01b0)(\x1edb)c|qua|v(\x1eeb)a r(\x1ed3)i|ngo(\x00e1)i"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_) -> tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleDdmm :: Rule
+ruleDdmm = Rule
+  { name = "dd/mm"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])/(0?[1-9]|1[0-2])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (dd:mm:_)):_) -> do
+        d <- parseInt dd
+        m <- parseInt mm
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleAfternoon :: Rule
+ruleAfternoon = Rule
+  { name = "afternoon"
+  , pattern =
+    [ regex "(bu(\x1ed5)i )?chi(\x1ec1)u"
+    ]
+  , prod = \_ -> tt . partOfDay . mkLatent $
+      interval TTime.Open (hour False 12, hour False 19)
+  }
+
+ruleNamedmonth4 :: Rule
+ruleNamedmonth4 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "th(\x00e1)ng t(\x01b0)|th(\x00e1)ng b(\x1ed1)n"
+    ]
+  , prod = \_ -> tt $ month 4
+  }
+
+ruleHmNay :: Rule
+ruleHmNay = Rule
+  { name = "hôm nay"
+  , pattern =
+    [ regex "((ngay )?h(\x00f4)m|b(\x1eef)a) nay"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 0
+  }
+
+ruleAtHhmm :: Rule
+ruleAtHhmm = Rule
+  { name = "at hh:mm"
+  , pattern =
+    [ regex "(l(\x00fa)c|v(\x00e0)o)( l(\x00fa)c)?"
+    , regex "((?:[01]?\\d)|(?:2[0-3]))[:.hg]([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt $ hourMinute True h m
+      _ -> Nothing
+  }
+
+ruleHourofdayInteger :: Rule
+ruleHourofdayInteger = Rule
+  { name = "<hour-of-day> <integer>"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleHourofdayQuarter :: Rule
+ruleHourofdayQuarter = Rule
+  { name = "(hour-of-day) quarter"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "k(\x00e9)m"
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_:token:_) -> do
+        n <- getIntValue token
+        Token Time <$> minutesBefore n td
+      _ -> Nothing
+  }
+
+ruleHourofdayHalf :: Rule
+ruleHourofdayHalf = Rule
+  { name = "(hour-of-day) half"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "r(\x01b0)(\x1ee1)i"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:_) ->
+        tt $ hourMinute is12H hours 30
+      _ -> Nothing
+  }
+
+ruleHourofdayIntegerPht :: Rule
+ruleHourofdayIntegerPht = Rule
+  { name = "<hour-of-day> <integer> phút"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , Predicate $ isIntegerBetween 1 59
+    , regex "ph(\x00fa)t"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) is12H)}:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute is12H hours n
+      _ -> Nothing
+  }
+
+ruleNamedday5 :: Rule
+ruleNamedday5 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "th(\x1ee9) 6|th(\x1ee9) s(\x00e1)u"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 5
+  }
+
+ruleCuiNm :: Rule
+ruleCuiNm = Rule
+  { name = "cuối năm"
+  , pattern =
+    [ regex "cu(\x1ed1)i n(\x0103)m"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Year 1
+  }
+
+ruleQuarterNumberYear :: Rule
+ruleQuarterNumberYear = Rule
+  { name = "quarter <number> <year>"
+  , pattern =
+    [ Predicate $ isGrain TG.Quarter
+    , dimension Numeral
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token Time td:_) -> do
+        v <- getIntValue token
+        tt $ cycleNthAfter False TG.Quarter (v - 1) td
+      _ -> Nothing
+  }
+
+ruleTimeofdayLatent :: Rule
+ruleTimeofdayLatent = Rule
+  { name = "time-of-day (latent)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 0 23
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        n <- getIntValue token
+        tt . mkLatent $ hour True n
+      _ -> Nothing
+  }
+
+ruleNamedmonth2 :: Rule
+ruleNamedmonth2 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "th(\x00e1)ng hai"
+    ]
+  , prod = \_ -> tt $ month 2
+  }
+
+ruleExactlyTimeofday :: Rule
+ruleExactlyTimeofday = Rule
+  { name = "exactly <time-of-day>"
+  , pattern =
+    [ regex "(v(\x00e0)o )?(\x0111)(\x00fa)ng"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNgyHmQua :: Rule
+ruleNgyHmQua = Rule
+  { name = "ngày hôm qua"
+  , pattern =
+    [ regex "(ng(\x00e0)y )?(h(\x00f4)m )?qua"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 1
+  }
+
+ruleSeason3 :: Rule
+ruleSeason3 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "m(\x00f9)a? (\x0111)(\x00f4)ng"
+    ]
+  , prod = \_ -> tt $ interval TTime.Open (monthDay 12 21, monthDay 3 20)
+  }
+
+ruleSeason :: Rule
+ruleSeason = Rule
+  { name = "season"
+  , pattern =
+    [ regex "m(\x00f9)a? (h(\x00e8)|h(\x1ea1))"
+    ]
+  , prod = \_ -> tt $ interval TTime.Open (monthDay 6 21, monthDay 9 23)
+  }
+
+ruleDayofmonthNamedmonth2 :: Rule
+ruleDayofmonthNamedmonth2 = Rule
+  { name = "<day-of-month> <named-month>"
+  , pattern =
+    [ Predicate isDOMInteger
+    , Predicate isAMonth
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleYearNumericWithYearSymbol :: Rule
+ruleYearNumericWithYearSymbol = Rule
+  { name = "year (numeric with year symbol)"
+  , pattern =
+    [ regex "n(\x0103)m"
+    , Predicate $ isIntegerBetween 1000 9999
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        n <- getIntValue token
+        tt $ year n
+      _ -> Nothing
+  }
+
+ruleAfterWork :: Rule
+ruleAfterWork = Rule
+  { name = "after work"
+  , pattern =
+    [ regex "(sau gi(\x1edd) l(\x00e0)m|sau gi(\x1edd) tan t(\x1ea7)m|l(\x00fa)c tan t(\x1ea7)m)"
+    ]
+  , prod = \_ -> Token Time . partOfDay . notLatent <$>
+      intersect (cycleNth TG.Day 0)
+      (interval TTime.Open (hour False 17, hour False 21))
+  }
+
+ruleLastNCycle :: Rule
+ruleLastNCycle = Rule
+  { name = "last n <cycle>"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    , regex "tr(\x01b0)(\x1edb)c|qua|v(\x1eeb)a r(\x1ed3)i|ngo(\x00e1)i|v(\x1eeb)a qua"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token TimeGrain grain:_) -> do
+        n <- getIntValue token
+        tt . cycleN True grain $ - n
+      _ -> Nothing
+  }
+
+ruleTimeofdaySharp :: Rule
+ruleTimeofdaySharp = Rule
+  { name = "<time-of-day> sharp"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(\x0111)(\x00fa)ng"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ Predicate isNotLatent
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) -> Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleAboutTimeofday :: Rule
+ruleAboutTimeofday = Rule
+  { name = "about <time-of-day>"
+  , pattern =
+    [ regex "(v(\x00e0)o )?kho(\x1ea3)ng"
+    , Predicate isATimeOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleLTnhNhn :: Rule
+ruleLTnhNhn = Rule
+  { name = "lễ tình nhân"
+  , pattern =
+    [ regex "(ng(\x00e0)y )?(l(\x1ec5))? t(\x00ec)nh nh(\x00e2)n"
+    ]
+  , prod = \_ -> tt $ monthDay 2 14
+  }
+
+ruleNamedmonth6 :: Rule
+ruleNamedmonth6 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "th(\x00e1)ng s(\x00e1)u"
+    ]
+  , prod = \_ -> tt $ month 6
+  }
+
+ruleNamedmonth8 :: Rule
+ruleNamedmonth8 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "th(\x00e1)ng t(\x00e1)m"
+    ]
+  , prod = \_ -> tt $ month 8
+  }
+
+ruleCuiThng :: Rule
+ruleCuiThng = Rule
+  { name = "cuối tháng"
+  , pattern =
+    [ regex "cu(\x1ed1)i th(\x00e1)ng"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Month 1
+  }
+
+ruleWeekend :: Rule
+ruleWeekend = Rule
+  { name = "week-end"
+  , pattern =
+    [ regex "(cu(\x1ed1)i|h(\x1ebf)t) tu(\x1ea7)n"
+    ]
+  , prod = \_ -> do
+      fri <- intersect (dayOfWeek 5) (hour False 18)
+      mon <- intersect (dayOfWeek 1) (hour False 0)
+      tt $ interval TTime.Open (fri, mon)
+  }
+
+ruleTimeofdaySngchiuti :: Rule
+ruleTimeofdaySngchiuti = Rule
+  { name = "<time-of-day> sáng|chiều|tối"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(s(\x00e1)ng|chi(\x1ec1)u|t(\x1ed1)i)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:Token RegexMatch (GroupMatch (ap:_)):_) ->
+        tt . timeOfDayAMPM td $ Text.toLower ap == "s\225ng"
+      _ -> Nothing
+  }
+
+ruleTimeTrc :: Rule
+ruleTimeTrc = Rule
+  { name = "<time> trước"
+  , pattern =
+    [ dimension Time
+    , regex "tr(\x01b0)(\x1edb)c|v(\x1eeb)a r(\x1ed3)i"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleTimeofdayGi :: Rule
+ruleTimeofdayGi = Rule
+  { name = "time-of-day giờ"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "gi(\x1edd)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleQucKhnh :: Rule
+ruleQucKhnh = Rule
+  { name = "quốc khánh"
+  , pattern =
+    [ regex "qu(\x1ed1)c kh(\x00e1)nh"
+    ]
+  , prod = \_ -> tt $ monthDay 9 3
+  }
+
+ruleYyyymmdd :: Rule
+ruleYyyymmdd = Rule
+  { name = "yyyy-mm-dd"
+  , pattern =
+    [ regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (yy:mm:dd:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleIntersectByOfFromS :: Rule
+ruleIntersectByOfFromS = Rule
+  { name = "intersect by \"of\", \"from\", \"'s\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex "c(\x1ee7)a|trong"
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) -> Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNextNCycle :: Rule
+ruleNextNCycle = Rule
+  { name = "next n <cycle>"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    , regex "(t(\x1edb)i|k(\x1ebf)|sau|ti(\x1ebf)p)( theo)?( ti(\x1ebf)p)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleMorning :: Rule
+ruleMorning = Rule
+  { name = "morning"
+  , pattern =
+    [ regex "(bu(\x1ed5)i )?s(\x00e1)ng"
+    ]
+  , prod = \_ -> tt . partOfDay . mkLatent $
+      interval TTime.Open (hour False 4, hour False 12)
+  }
+
+ruleThisCycle :: Rule
+ruleThisCycle = Rule
+  { name = "this <cycle>"
+  , pattern =
+    [ dimension TimeGrain
+    , regex "nay|n(\x00e0)y|hi(\x1ec7)n t(\x1ea1)i|h(\x00f4)m nay|n(\x0103)m nay"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 0
+      _ -> Nothing
+  }
+
+ruleNoon2 :: Rule
+ruleNoon2 = Rule
+  { name = "noon"
+  , pattern =
+    [ regex "(bu(\x1ed5)i )?(t(\x1ed1)i|(\x0111)(\x00ea)m)"
+    ]
+  , prod = \_ -> tt . partOfDay . mkLatent $
+      interval TTime.Open (hour False 18, hour False 0)
+  }
+
+ruleAfterLunch :: Rule
+ruleAfterLunch = Rule
+  { name = "after lunch"
+  , pattern =
+    [ regex "(sau|qua) (bu(\x1ed5)i |b(\x1eef)a )?tr(\x01b0)a"
+    ]
+  , prod = \_ -> Token Time . partOfDay . notLatent <$>
+      intersect (cycleNth TG.Day 0)
+      (interval TTime.Open (hour False 13, hour False 17))
+  }
+
+ruleSeason2 :: Rule
+ruleSeason2 = Rule
+  { name = "season"
+  , pattern =
+    [ regex "m(\x00f9)a? thu"
+    ]
+  , prod = \_ ->
+      tt $ interval TTime.Open (monthDay 9 23, monthDay 12 21)
+  }
+
+ruleTimeNy :: Rule
+ruleTimeNy = Rule
+  { name = "<time> này"
+  , pattern =
+    [ dimension Time
+    , regex "n(\x00e0)y|nay"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleTimeofdayAmpm :: Rule
+ruleTimeofdayAmpm = Rule
+  { name = "<time-of-day> am|pm"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "(in the )?([ap])(\\s|\\.)?m?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:Token RegexMatch (GroupMatch (_:ap:_)):_) ->
+        tt . timeOfDayAMPM td $ Text.toLower ap == "a"
+      _ -> Nothing
+  }
+
+ruleNgyMai :: Rule
+ruleNgyMai = Rule
+  { name = "ngày mai"
+  , pattern =
+    [ regex "(ng(\x00e0)y )?mai"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 1
+  }
+
+ruleOrdinalCycleOfTime :: Rule
+ruleOrdinalCycleOfTime = Rule
+  { name = "<ordinal> <cycle> of <time>"
+  , pattern =
+    [ dimension TimeGrain
+    , dimension Ordinal
+    , regex "c(\x1ee7)a|trong"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token TimeGrain grain:Token Ordinal od:_:Token Time td:_) ->
+        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
+      _ -> Nothing
+  }
+
+ruleNamedmonth5 :: Rule
+ruleNamedmonth5 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "th(\x00e1)ng n(\x0103)m"
+    ]
+  , prod = \_ -> tt $ month 5
+  }
+
+ruleNamedday7 :: Rule
+ruleNamedday7 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "ch(\x1ee7) nh(\x1ead)t"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 7
+  }
+
+ruleMonthNumericWithMonthSymbol :: Rule
+ruleMonthNumericWithMonthSymbol = Rule
+  { name = "month (numeric with month symbol)"
+  , pattern =
+    [ regex "th(\x00e1)ng"
+    , Predicate $ isIntegerBetween 1 12
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:_) -> do
+        v <- getIntValue token
+        tt $ month v
+      _ -> Nothing
+  }
+
+ruleHhmm :: Rule
+ruleHhmm = Rule
+  { name = "hh:mm"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))[:.hg]([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt $ hourMinute True h m
+      _ -> Nothing
+  }
+
+ruleTonight :: Rule
+ruleTonight = Rule
+  { name = "tonight"
+  , pattern =
+    [ regex "(t(\x1ed1)i|(\x0111)(\x00ea)m)( h(\x00f4)m)? nay"
+    ]
+  , prod = \_ ->
+      let today = cycleNth TG.Day 0
+          evening = interval TTime.Open (hour False 18, hour False 0) in
+        Token Time . partOfDay . notLatent <$> intersect today evening
+  }
+
+ruleTimezone :: Rule
+ruleTimezone = Rule
+  { name = "<time> timezone"
+  , pattern =
+    [ Predicate $ liftM2 (&&) (isGrainFinerThan TG.Day) isNotLatent
+    , regex "\\b(YEKT|YEKST|YAKT|YAKST|WITA|WIT|WIB|WGT|WGST|WFT|WET|WEST|WAT|WAST|VUT|VLAT|VLAST|VET|UZT|UYT|UYST|UTC|ULAT|TVT|TMT|TLT|TKT|TJT|TFT|TAHT|SST|SRT|SGT|SCT|SBT|SAST|SAMT|RET|PYT|PYST|PWT|PST|PONT|PMST|PMDT|PKT|PHT|PHOT|PGT|PETT|PETST|PET|PDT|OMST|OMSST|NZST|NZDT|NUT|NST|NPT|NOVT|NOVST|NFT|NDT|NCT|MYT|MVT|MUT|MST|MSK|MSD|MMT|MHT|MDT|MAWT|MART|MAGT|MAGST|LINT|LHST|LHDT|KUYT|KST|KRAT|KRAST|KGT|JST|IST|IRST|IRKT|IRKST|IRDT|IOT|IDT|ICT|HOVT|HKT|GYT|GST|GMT|GILT|GFT|GET|GAMT|GALT|FNT|FKT|FKST|FJT|FJST|EST|EGT|EGST|EET|EEST|EDT|ECT|EAT|EAST|EASST|DAVT|ChST|CXT|CVT|CST|COT|CLT|CLST|CKT|CHAST|CHADT|CET|CEST|CDT|CCT|CAT|CAST|BTT|BST|BRT|BRST|BOT|BNT|AZT|AZST|AZOT|AZOST|AWST|AWDT|AST|ART|AQTT|ANAT|ANAST|AMT|AMST|ALMT|AKST|AKDT|AFT|AEST|AEDT|ADT|ACST|ACDT)\\b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       Token RegexMatch (GroupMatch (tz:_)):
+       _) -> Token Time <$> inTimezone tz td
+      _ -> Nothing
+  }
+
+ruleNamedmonth10 :: Rule
+ruleNamedmonth10 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "th(\x00e1)ng m(\x01b0)(\x1edd)i"
+    ]
+  , prod = \_ -> tt $ month 10
+  }
+
+ruleByGi :: Rule
+ruleByGi = Rule
+  { name = "bây giờ"
+  , pattern =
+    [ regex "(ngay )?(b(\x00e2)y gi(\x1edd)|l(\x00fa)c n(\x00e0)y)"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Second 0
+  }
+
+ruleNgyDdmm :: Rule
+ruleNgyDdmm = Rule
+  { name = "ngày dd/mm"
+  , pattern =
+    [ regex "ng(\x00e0)y"
+    , regex "(3[01]|[12]\\d|0?[1-9])/(0?[1-9]|1[0-2])"
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token RegexMatch (GroupMatch (dd:mm:_)):_) -> do
+        d <- parseInt dd
+        m <- parseInt mm
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleHhmmMilitaryAmpm :: Rule
+ruleHhmmMilitaryAmpm = Rule
+  { name = "hhmm (military) am|pm"
+  , pattern =
+    [ regex "((?:1[012]|0?\\d))([0-5]\\d)"
+    , regex "([ap])\\.?m?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):
+       Token RegexMatch (GroupMatch (ap:_)):
+       _) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt . timeOfDayAMPM (hourMinute True h m) $
+          Text.toLower ap == "a"
+      _ -> Nothing
+  }
+
+ruleHhmmMilitarySngchiuti :: Rule
+ruleHhmmMilitarySngchiuti = Rule
+  { name = "hhmm (military) sáng|chiều|tối"
+  , pattern =
+    [ regex "((?:1[012]|0?\\d))([0-5]\\d)"
+    , regex "(s(\x00e1)ng|chi(\x1ec1)u|t(\x1ed1)i)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):
+       Token RegexMatch (GroupMatch (ap:_)):
+       _) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt . timeOfDayAMPM (hourMinute True h m) $
+          Text.toLower ap == "s\225ng"
+      _ -> Nothing
+  }
+
+ruleDdmmyyyy :: Rule
+ruleDdmmyyyy = Rule
+  { name = "dd/mm/yyyy"
+  , pattern =
+    [ regex "(3[01]|[12]\\d|0?[1-9])[-/](0?[1-9]|1[0-2])[/-](\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (dd:mm:yy:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleNamedmonth11 :: Rule
+ruleNamedmonth11 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "th(\x00e1)ng m(\x01b0)(\x1edd)i m(\x1ed9)t"
+    ]
+  , prod = \_ -> tt $ month 11
+  }
+
+ruleGingSinh :: Rule
+ruleGingSinh = Rule
+  { name = "giáng sinh"
+  , pattern =
+    [ regex "(ng(\x00e0)y )(xmas|christmas|gi(\x00e1)ng sinh)?"
+    ]
+  , prod = \_ -> tt $ monthDay 12 25
+  }
+
+ruleNamedday3 :: Rule
+ruleNamedday3 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "th(\x1ee9) 4|th(\x1ee9) b(\x1ed1)n|th(\x1ee9) t(\x01b0)"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 3
+  }
+
+ruleNgyHmKia :: Rule
+ruleNgyHmKia = Rule
+  { name = "ngày hôm kia"
+  , pattern =
+    [ regex "(ng(\x00e0)y )?h(\x00f4)m kia"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 2
+  }
+
+ruleDayofweekTi :: Rule
+ruleDayofweekTi = Rule
+  { name = "<day-of-week> tới"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex "t(\x1edb)i"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleNamedmonth9 :: Rule
+ruleNamedmonth9 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "th(\x00e1)ng ch(\x00ed)n"
+    ]
+  , prod = \_ -> tt $ month 9
+  }
+
+ruleHhmmss :: Rule
+ruleHhmmss = Rule
+  { name = "hh:mm:ss"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))[:.]([0-5]\\d)[:.]([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:ss:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        s <- parseInt ss
+        tt $ hourMinuteSecond True h m s
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAboutTimeofday
+  , ruleAfterLunch
+  , ruleAfterWork
+  , ruleAfternoon
+  , ruleAtHhmm
+  , ruleByGi
+  , ruleCchMngThng
+  , ruleCuiNm
+  , ruleCuiThng
+  , ruleCycleCuiCngCaTime
+  , ruleDayofmonthNamedmonth
+  , ruleDayofmonthNamedmonth2
+  , ruleDayofweekCuiCngCaTime
+  , ruleDayofweekTi
+  , ruleDdmm
+  , ruleDdmmyyyy
+  , ruleExactlyTimeofday
+  , ruleGingSinh
+  , ruleHhmm
+  , ruleHhmmMilitaryAmpm
+  , ruleHhmmMilitarySngchiuti
+  , ruleHhmmss
+  , ruleHmNay
+  , ruleHourofdayHalf
+  , ruleHourofdayInteger
+  , ruleHourofdayIntegerPht
+  , ruleHourofdayQuarter
+  , ruleIntersect
+  , ruleIntersectByOfFromS
+  , ruleLTnhNhn
+  , ruleLastCycle
+  , ruleLastNCycle
+  , ruleLunch
+  , ruleMonthNumericWithMonthSymbol
+  , ruleMorning
+  , ruleNamedday
+  , ruleNamedday2
+  , ruleNamedday3
+  , ruleNamedday4
+  , ruleNamedday5
+  , ruleNamedday6
+  , ruleNamedday7
+  , ruleNamedmonth
+  , ruleNamedmonth10
+  , ruleNamedmonth11
+  , ruleNamedmonth12
+  , ruleNamedmonth2
+  , ruleNamedmonth3
+  , ruleNamedmonth4
+  , ruleNamedmonth5
+  , ruleNamedmonth6
+  , ruleNamedmonth7
+  , ruleNamedmonth8
+  , ruleNamedmonth9
+  , ruleNextCycle
+  , ruleNextNCycle
+  , ruleNgyDdmm
+  , ruleNgyDdmmyyyy
+  , ruleNgyHmKia
+  , ruleNgyHmQua
+  , ruleNgyMai
+  , ruleNoon
+  , ruleNoon2
+  , ruleOrdinalCycleOfTime
+  , rulePartofdayHmNay
+  , rulePartofdayTime
+  , ruleQuarterNumber
+  , ruleQuarterNumberOfYear
+  , ruleQuarterNumberYear
+  , ruleQucKhnh
+  , ruleQucTLaoNg
+  , ruleSeason
+  , ruleSeason2
+  , ruleSeason3
+  , ruleSeason4
+  , ruleThisCycle
+  , ruleTimeNy
+  , ruleTimeTi
+  , ruleTimeTrc
+  , ruleTimeofdayAmpm
+  , ruleTimeofdayApproximately
+  , ruleTimeofdayGi
+  , ruleTimeofdayGiNg
+  , ruleTimeofdayLatent
+  , ruleTimeofdayLatent2
+  , ruleTimeofdaySharp
+  , ruleTimeofdaySngchiuti
+  , ruleTimezone
+  , ruleTonight
+  , ruleTtDng
+  , ruleWeekend
+  , ruleYearNumericWithYearSymbol
+  , ruleYyyymmdd
+  ]
diff --git a/Duckling/Time/ZH/Corpus.hs b/Duckling/Time/ZH/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/ZH/Corpus.hs
@@ -0,0 +1,418 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.ZH.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Time.Corpus
+import Duckling.TimeGrain.Types hiding (add)
+import Duckling.Testing.Types hiding (examples)
+
+corpus :: Corpus
+corpus = (testContext {lang = ZH}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (datetime (2013, 2, 12, 4, 30, 0) Second)
+             [ "现在"
+             , "此时"
+             , "此刻"
+             , "当前"
+             , "現在"
+             , "此時"
+             , "當前"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "今天"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
+             [ "昨天"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "明天"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "后天"
+             , "後天"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "前天"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "星期一"
+             , "礼拜一"
+             , "周一"
+             , "禮拜一"
+             , "週一"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "星期二"
+             , "礼拜二"
+             , "周二"
+             , "禮拜二"
+             , "週二"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "星期三"
+             , "礼拜三"
+             , "周三"
+             , "禮拜三"
+             , "週三"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "星期四"
+             , "礼拜四"
+             , "周四"
+             , "禮拜四"
+             , "週四"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "星期五"
+             , "礼拜五"
+             , "周五"
+             , "禮拜五"
+             , "週五"
+             ]
+  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
+             [ "星期六"
+             , "礼拜六"
+             , "周六"
+             , "禮拜六"
+             , "週六"
+             ]
+  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
+             [ "星期日"
+             , "星期天"
+             , "礼拜天"
+             , "周日"
+             , "禮拜天"
+             , "週日"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 18, 0, 0), (2013, 2, 18, 0, 0, 0)) Hour)
+             [ "这周末"
+             , "這週末"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 4, 0, 0), (2013, 2, 18, 12, 0, 0)) Hour)
+             [ "周一早上"
+             , "周一早晨"
+             , "礼拜一早上"
+             , "礼拜一早晨"
+             , "週一早上"
+             , "週一早晨"
+             , "禮拜一早上"
+             , "禮拜一早晨"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "上周日"
+             , "上星期天"
+             , "上礼拜天"
+             , "上週日"
+             , "上星期天"
+             , "上禮拜天"
+             ]
+  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
+             [ "周日, 二月十号"
+             , "星期天, 二月十号"
+             , "礼拜天, 二月十号"
+             , "週日, 二月十號"
+             , "星期天, 二月十號"
+             , "禮拜天, 二月十號"
+             ]
+  , examples (datetime (2013, 10, 7, 0, 0, 0) Day)
+             [ "十月第一个星期一"
+             , "十月的第一个星期一"
+             ]
+  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
+             [ "上周二"
+             , "上礼拜二"
+             , "上週二"
+             , "上禮拜二"
+             ]
+  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
+             [ "三月一号"
+             , "三月一日"
+             , "三月一號"
+             ]
+  , examples (datetime (2015, 3, 3, 0, 0, 0) Day)
+             [ "2015年3月3号"
+             , "2015年3月三号"
+             , "2015年三月3号"
+             , "2015年三月三号"
+             , "2015年3月3號"
+             , "2015年3月三號"
+             , "2015年三月3號"
+             , "2015年三月三號"
+             , "3/3/2015"
+             , "3/3/15"
+             , "2015-3-3"
+             , "2015-03-03"
+             ]
+  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
+             [ "2013年2月15号"
+             , "2013年二月十五号"
+             , "2月15号"
+             , "二月十五号"
+             , "2013年2月15號"
+             , "2013年二月十五號"
+             , "2月15號"
+             , "二月十五號"
+             , "2/15"
+             ]
+  , examples (datetime (1974, 10, 31, 0, 0, 0) Day)
+             [ "10/31/1974"
+             , "10/31/74"
+             ]
+  , examples (datetimeInterval ((2013, 2, 15, 4, 0, 0), (2013, 2, 15, 12, 0, 0)) Hour)
+             [ "二月十五号早上"
+             , "二月十五号早晨"
+             , "2月15号早上"
+             , "2月15号早晨"
+             , "二月十五號早上"
+             , "二月十五號早晨"
+             , "2月15號早上"
+             , "2月15號早晨"
+             ]
+  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
+             [ "下周二"
+             , "下週二"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "这周三"
+             , "这礼拜三"
+             , "這週三"
+             , "這禮拜三"
+             ]
+  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
+             [ "下周三"
+             , "下礼拜三"
+             , "下週三"
+             , "下禮拜三"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
+             [ "这周一"
+             , "这礼拜一"
+             , "這週一"
+             , "這禮拜一"
+             ]
+  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
+             [ "这周二"
+             , "这礼拜二"
+             , "這週二"
+             , "這禮拜二"
+             ]
+  , examples (datetime (2013, 2, 11, 0, 0, 0) Week)
+             [ "这周"
+             , "这一周"
+             , "这礼拜"
+             , "这一礼拜"
+             , "這週"
+             , "這一周"
+             , "這禮拜"
+             , "這一禮拜"
+             ]
+  , examples (datetime (2013, 2, 4, 0, 0, 0) Week)
+             [ "上周"
+             , "上週"
+             ]
+  , examples (datetime (2013, 2, 18, 0, 0, 0) Week)
+             [ "下周"
+             , "下週"
+             ]
+  , examples (datetime (2013, 1, 0, 0, 0, 0) Month)
+             [ "上月"
+             , "上个月"
+             , "上個月"
+             ]
+  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
+             [ "下月"
+             , "下个月"
+             , "下個月"
+             ]
+  , examples (datetime (2012, 0, 0, 0, 0, 0) Year)
+             [ "去年"
+             ]
+  , examples (datetime (2013, 0, 0, 0, 0, 0) Year)
+             [ "今年"
+             , "这一年"
+             , "這一年"
+             ]
+  , examples (datetime (2014, 0, 0, 0, 0, 0) Year)
+             [ "明年"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 29, 58), (2013, 2, 12, 4, 30, 0)) Second)
+             [ "上两秒"
+             , "上二秒"
+             , "前两秒"
+             , "前二秒"
+             , "上兩秒"
+             , "前兩秒"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 1), (2013, 2, 12, 4, 30, 4)) Second)
+             [ "下三秒"
+             , "后三秒"
+             , "後三秒"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 28, 0), (2013, 2, 12, 4, 30, 0)) Minute)
+             [ "上两分钟"
+             , "上二分钟"
+             , "前两分钟"
+             , "前二分钟"
+             , "上兩分鐘"
+             , "上二分鐘"
+             , "前兩分鐘"
+             , "前二分鐘"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 4, 31, 0), (2013, 2, 12, 4, 34, 0)) Minute)
+             [ "下三分钟"
+             , "后三分钟"
+             , "下三分鐘"
+             , "後三分鐘"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 2, 0, 0), (2013, 2, 12, 4, 0, 0)) Hour)
+             [ "上两小时"
+             , "上二小时"
+             , "前两小时"
+             , "前二小时"
+             , "上兩小時"
+             , "上二小時"
+             , "前兩小時"
+             , "前二小時"
+             ]
+  , examples (datetimeInterval ((2013, 2, 12, 5, 0, 0), (2013, 2, 12, 8, 0, 0)) Hour)
+             [ "下三小时"
+             , "后三小时"
+             , "下三小時"
+             , "後三小時"
+             ]
+  , examples (datetimeInterval ((2013, 2, 10, 0, 0, 0), (2013, 2, 12, 0, 0, 0)) Day)
+             [ "上两天"
+             , "前两天"
+             , "上兩天"
+             , "前兩天"
+             ]
+  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
+             [ "下三天"
+             , "后三天"
+             , "後三天"
+             ]
+  , examples (datetimeInterval ((2013, 1, 28, 0, 0, 0), (2013, 2, 11, 0, 0, 0)) Week)
+             [ "上两周"
+             , "上二周"
+             , "上兩週"
+             , "上二週"
+             ]
+  , examples (datetimeInterval ((2013, 2, 18, 0, 0, 0), (2013, 3, 11, 0, 0, 0)) Week)
+             [ "下三周"
+             , "下三个周"
+             , "下三週"
+             , "下三個週"
+             ]
+  , examples (datetimeInterval ((2012, 12, 0, 0, 0, 0), (2013, 2, 0, 0, 0, 0)) Month)
+             [ "上两个月"
+             , "上二个月"
+             , "上二月"
+             , "前两个月"
+             , "前二个月"
+             , "前两月"
+             , "上兩個月"
+             , "上二個月"
+             , "前兩個月"
+             , "前二個月"
+             , "前兩月"
+             ]
+  , examples (datetimeInterval ((2013, 3, 0, 0, 0, 0), (2013, 6, 0, 0, 0, 0)) Month)
+             [ "下三个月"
+             , "后三个月"
+             , "後三個月"
+             ]
+  , examples (datetimeInterval ((2011, 0, 0, 0, 0, 0), (2013, 0, 0, 0, 0, 0)) Year)
+             [ "前两年"
+             , "前兩年"
+             ]
+  , examples (datetimeInterval ((2014, 0, 0, 0, 0, 0), (2017, 0, 0, 0, 0, 0)) Year)
+             [ "下三年"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
+             [ "三点"
+             , "三點"
+             , "3pm"
+             ]
+  , examples (datetime (2013, 2, 12, 15, 15, 0) Minute)
+             [ "下午三点十五"
+             , "下午3:15"
+             , "15:15"
+             , "3:15pm"
+             , "3:15PM"
+             , "3:15p"
+             , "下午三點十五"
+             ]
+  , examples (datetime (2013, 2, 12, 13, 0, 0) Minute)
+             [ "4pm CET"
+             ]
+  , examples (datetime (2015, 4, 14, 0, 0, 0) Day)
+             [ "2015年4月14号"
+             ]
+  , examples (datetime (2013, 2, 12, 20, 0, 0) Hour)
+             [ "今晚8点"
+             , "今晚八点"
+             , "今晚8點"
+             , "今晚八點"
+             ]
+  , examples (datetime (2014, 1, 1, 0, 0, 0) Day)
+             [ "元旦"
+             , "元旦节"
+             , "元旦節"
+             ]
+  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
+             [ "情人节"
+             , "情人節"
+             ]
+  , examples (datetime (2013, 3, 8, 0, 0, 0) Day)
+             [ "妇女节"
+             , "婦女節"
+             ]
+  , examples (datetime (2013, 5, 1, 0, 0, 0) Day)
+             [ "劳动节"
+             , "勞動節"
+             ]
+  , examples (datetime (2013, 6, 1, 0, 0, 0) Day)
+             [ "儿童节"
+             , "兒童節"
+             ]
+  , examples (datetime (2013, 8, 1, 0, 0, 0) Day)
+             [ "建军节"
+             , "建軍節"
+             ]
+  , examples (datetime (2013, 10, 1, 0, 0, 0) Day)
+             [ "国庆"
+             , "國慶"
+             , "国庆节"
+             , "國慶節"
+             ]
+  , examples (datetime (2013, 12, 25, 0, 0, 0) Day)
+             [ "圣诞"
+             , "聖誕"
+             , "圣诞节"
+             , "聖誕節"
+             ]
+  , examples (datetimeInterval ((2013, 10, 1, 18, 0, 0), (2013, 10, 2, 0, 0, 0)) Hour)
+             [ "国庆节晚上"
+             , "國慶節晚上"
+             ]
+  , examples (datetime (2013, 6, 1, 15, 15, 0) Minute)
+             [ "儿童节下午三点十五"
+             , "兒童節下午三點十五"
+             ]
+  ]
diff --git a/Duckling/Time/ZH/Rules.hs b/Duckling/Time/ZH/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Time/ZH/Rules.hs
@@ -0,0 +1,1213 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.ZH.Rules
+  ( rules ) where
+
+import Control.Monad (liftM2)
+import qualified Data.Text as Text
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Helpers (parseInt)
+import qualified Duckling.Ordinal.Types as TOrdinal
+import Duckling.Regex.Types
+import Duckling.Time.Helpers
+import Duckling.Time.Types (TimeData (..))
+import qualified Duckling.Time.Types as TTime
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+ruleNamedday :: Rule
+ruleNamedday = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "\x661f\x671f\x4e00|\x5468\x4e00|\x793c\x62dc\x4e00|\x79ae\x62dc\x4e00|\x9031\x4e00"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 1
+  }
+
+ruleTheDayAfterTomorrow :: Rule
+ruleTheDayAfterTomorrow = Rule
+  { name = "the day after tomorrow"
+  , pattern =
+    [ regex "\x540e\x5929|\x5f8c\x5929"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 2
+  }
+
+ruleNamedmonth12 :: Rule
+ruleNamedmonth12 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x5341\x4e8c\x6708(\x4efd)?"
+    ]
+  , prod = \_ -> tt $ month 12
+  }
+
+ruleRelativeMinutesTotillbeforeIntegerHourofday :: Rule
+ruleRelativeMinutesTotillbeforeIntegerHourofday = Rule
+  { name = "relative minutes to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "(\x70b9|\x9ede)\x5dee"
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_:token:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleRelativeMinutesTotillbeforeNoonmidnight :: Rule
+ruleRelativeMinutesTotillbeforeNoonmidnight = Rule
+  { name = "relative minutes to|till|before noon|midnight"
+  , pattern =
+    [ Predicate isMidnightOrNoon
+    , regex "\x5dee"
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_:token:_) -> do
+        n <- getIntValue token
+        t <- minutesBefore n td
+        Just $ Token Time t
+      _ -> Nothing
+  }
+
+ruleRelativeMinutesAfterpastIntegerHourofday :: Rule
+ruleRelativeMinutesAfterpastIntegerHourofday = Rule
+  { name = "relative minutes after|past <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "\x70b9|\x9ede"
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute True hours n
+      _ -> Nothing
+  }
+
+ruleRelativeMinutesAfterpastNoonmidnight :: Rule
+ruleRelativeMinutesAfterpastNoonmidnight = Rule
+  { name = "relative minutes after|past noon|midnight"
+  , pattern =
+    [ Predicate isMidnightOrNoon
+    , regex "\x8fc7"
+    , Predicate $ isIntegerBetween 1 59
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _:
+       token:
+       _) -> do
+        n <- getIntValue token
+        tt $ hourMinute True hours n
+      _ -> Nothing
+  }
+
+ruleQuarterTotillbeforeIntegerHourofday :: Rule
+ruleQuarterTotillbeforeIntegerHourofday = Rule
+  { name = "quarter to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "(\x70b9|\x9ede)\x5dee"
+    , regex "\x4e00\x523b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> Token Time <$> minutesBefore 15 td
+      _ -> Nothing
+  }
+ruleQuarterTotillbeforeNoonmidnight :: Rule
+ruleQuarterTotillbeforeNoonmidnight = Rule
+  { name = "quarter to|till|before noon|midnight"
+  , pattern =
+    [ Predicate isMidnightOrNoon
+    , regex "\x5dee"
+    , regex "\x4e00\x523b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> Token Time <$> minutesBefore 15 td
+      _ -> Nothing
+  }
+ruleQuarterAfterpastIntegerHourofday :: Rule
+ruleQuarterAfterpastIntegerHourofday = Rule
+  { name = "quarter after|past <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "\x70b9|\x9ede"
+    , regex "\x4e00\x523b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> tt $ hourMinute True hours 15
+      _ -> Nothing
+  }
+ruleQuarterAfterpastNoonmidnight :: Rule
+ruleQuarterAfterpastNoonmidnight = Rule
+  { name = "quarter after|past noon|midnight"
+  , pattern =
+    [ Predicate isMidnightOrNoon
+    , regex "\x8fc7"
+    , regex "\x4e00\x523b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> tt $ hourMinute True hours 15
+      _ -> Nothing
+  }
+
+ruleHalfTotillbeforeIntegerHourofday :: Rule
+ruleHalfTotillbeforeIntegerHourofday = Rule
+  { name = "half to|till|before <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "(\x70b9|\x9ede)\x5dee"
+    , regex "\x534a"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> Token Time <$> minutesBefore 30 td
+      _ -> Nothing
+  }
+ruleHalfTotillbeforeNoonmidnight :: Rule
+ruleHalfTotillbeforeNoonmidnight = Rule
+  { name = "half to|till|before noon|midnight"
+  , pattern =
+    [ Predicate isMidnightOrNoon
+    , regex "\x5dee"
+    , regex "\x534a"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) -> Token Time <$> minutesBefore 30 td
+      _ -> Nothing
+  }
+ruleHalfAfterpastIntegerHourofday :: Rule
+ruleHalfAfterpastIntegerHourofday = Rule
+  { name = "half after|past <integer> (hour-of-day)"
+  , pattern =
+    [ Predicate isAnHourOfDay
+    , regex "\x70b9|\x9ede"
+    , regex "\x534a"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> tt $ hourMinute True hours 30
+      _ -> Nothing
+  }
+ruleHalfAfterpastNoonmidnight :: Rule
+ruleHalfAfterpastNoonmidnight = Rule
+  { name = "half after|past noon|midnight"
+  , pattern =
+    [ Predicate isMidnightOrNoon
+    , regex "\x8fc7"
+    , regex "\x534a"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time TimeData {TTime.form = Just (TTime.TimeOfDay (Just hours) _)}:
+       _) -> tt $ hourMinute True hours 30
+      _ -> Nothing
+  }
+
+ruleNamedday2 :: Rule
+ruleNamedday2 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "\x661f\x671f\x4e8c|\x5468\x4e8c|\x793c\x62dc\x4e8c|\x79ae\x62dc\x4e8c|\x9031\x4e8c"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 2
+  }
+
+ruleValentinesDay :: Rule
+ruleValentinesDay = Rule
+  { name = "valentine's day"
+  , pattern =
+    [ regex "\x60c5\x4eba(\x8282|\x7bc0)"
+    ]
+  , prod = \_ -> tt $ monthDay 2 14
+  }
+
+ruleHhmmTimeofday :: Rule
+ruleHhmmTimeofday = Rule
+  { name = "hh:mm (time-of-day)"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3])):([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt $ hourMinute True h m
+      _ -> Nothing
+  }
+
+ruleThisDayofweek :: Rule
+ruleThisDayofweek = Rule
+  { name = "this <day-of-week>"
+  , pattern =
+    [ regex "\x8fd9|\x9019"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleNthTimeOfTime2 :: Rule
+ruleNthTimeOfTime2 = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ dimension Time
+    , regex "\x7684"
+    , dimension Ordinal
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Ordinal od:Token Time td2:_) -> Token Time .
+        predNth (TOrdinal.value od - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNewYearsDay :: Rule
+ruleNewYearsDay = Rule
+  { name = "new year's day"
+  , pattern =
+    [ regex "\x5143\x65e6(\x8282|\x7bc0)?"
+    ]
+  , prod = \_ -> tt $ monthDay 1 1
+  }
+
+ruleLastTime :: Rule
+ruleLastTime = Rule
+  { name = "last <time>"
+  , pattern =
+    [ regex "\x53bb"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleNamedday6 :: Rule
+ruleNamedday6 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "\x661f\x671f\x516d|\x5468\x516d|\x793c\x62dc\x516d|\x79ae\x62dc\x516d|\x9031\x516d"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 6
+  }
+
+ruleNamedmonth7 :: Rule
+ruleNamedmonth7 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x4e03\x6708(\x4efd)?"
+    ]
+  , prod = \_ -> tt $ month 7
+  }
+
+ruleInDuration :: Rule
+ruleInDuration = Rule
+  { name = "in <duration>"
+  , pattern =
+    [ regex "\x518d"
+    , dimension Duration
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleNow :: Rule
+ruleNow = Rule
+  { name = "now"
+  , pattern =
+    [ regex "\x73b0\x5728|\x6b64\x65f6|\x6b64\x523b|\x5f53\x524d|\x73fe\x5728|\x6b64\x6642|\x7576\x524d"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Second 0
+  }
+
+ruleNationalDay :: Rule
+ruleNationalDay = Rule
+  { name = "national day"
+  , pattern =
+    [ regex "(\x56fd\x5e86|\x570b\x6176)(\x8282|\x7bc0)?"
+    ]
+  , prod = \_ -> tt $ monthDay 10 1
+  }
+
+ruleNamedday4 :: Rule
+ruleNamedday4 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "\x661f\x671f\x56db|\x5468\x56db|\x793c\x62dc\x56db|\x79ae\x62dc\x56db|\x9031\x56db"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 4
+  }
+
+ruleTheCycleAfterTime :: Rule
+ruleTheCycleAfterTime = Rule
+  { name = "the <cycle> after <time>"
+  , pattern =
+    [ regex "\x90a3"
+    , dimension TimeGrain
+    , regex "(\x4e4b)?(\x540e|\x5f8c)"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain 1 td
+      _ -> Nothing
+  }
+
+ruleTheCycleBeforeTime :: Rule
+ruleTheCycleBeforeTime = Rule
+  { name = "the <cycle> before <time>"
+  , pattern =
+    [ regex "\x90a3"
+    , dimension TimeGrain
+    , regex "(\x4e4b)?\x524d"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_:Token Time td:_) ->
+        tt $ cycleNthAfter False grain (-1) td
+      _ -> Nothing
+  }
+
+ruleNoon :: Rule
+ruleNoon = Rule
+  { name = "noon"
+  , pattern =
+    [ regex "\x4e2d\x5348"
+    ]
+  , prod = \_ -> tt $ hour False 12
+  }
+
+ruleToday :: Rule
+ruleToday = Rule
+  { name = "today"
+  , pattern =
+    [ regex "\x4eca\x5929"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 0
+  }
+
+ruleThisnextDayofweek :: Rule
+ruleThisnextDayofweek = Rule
+  { name = "this|next <day-of-week>"
+  , pattern =
+    [ regex "\x4eca|\x660e|\x4e0b(\x4e2a|\x500b)?"
+    , Predicate isADayOfWeek
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 True td
+      _ -> Nothing
+  }
+
+ruleTheDayBeforeYesterday :: Rule
+ruleTheDayBeforeYesterday = Rule
+  { name = "the day before yesterday"
+  , pattern =
+    [ regex "\x524d\x5929"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 2
+  }
+
+ruleLaborDay :: Rule
+ruleLaborDay = Rule
+  { name = "labor day"
+  , pattern =
+    [ regex "\x52b3\x52a8\x8282|\x52de\x52d5\x7bc0"
+    ]
+  , prod = \_ -> tt $ monthDay 5 1
+  }
+
+ruleNextCycle :: Rule
+ruleNextCycle = Rule
+  { name = "next <cycle>"
+  , pattern =
+    [ regex "\x4e0b(\x4e2a|\x500b)?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 1
+      _ -> Nothing
+  }
+
+ruleNamedmonth :: Rule
+ruleNamedmonth = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x4e00\x6708(\x4efd)?"
+    ]
+  , prod = \_ -> tt $ month 1
+  }
+
+ruleNamedmonth3 :: Rule
+ruleNamedmonth3 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x4e09\x6708(\x4efd)?"
+    ]
+  , prod = \_ -> tt $ month 3
+  }
+
+ruleDurationFromNow :: Rule
+ruleDurationFromNow = Rule
+  { name = "<duration> from now"
+  , pattern =
+    [ dimension Duration
+    , regex "\x540e|\x5f8c"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt $ inDuration dd
+      _ -> Nothing
+  }
+
+ruleLastCycle :: Rule
+ruleLastCycle = Rule
+  { name = "last <cycle>"
+  , pattern =
+    [ regex "\x4e0a(\x4e2a|\x500b)?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt . cycleNth grain $ - 1
+      _ -> Nothing
+  }
+
+ruleAfternoon :: Rule
+ruleAfternoon = Rule
+  { name = "afternoon"
+  , pattern =
+    [ regex "\x4e0b\x5348|\x4e2d\x5348"
+    ]
+  , prod = \_ ->
+      let from = hour False 12
+          to = hour False 19
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleNamedmonth4 :: Rule
+ruleNamedmonth4 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x56db\x6708(\x4efd)?"
+    ]
+  , prod = \_ -> tt $ month 4
+  }
+
+ruleMidnight :: Rule
+ruleMidnight = Rule
+  { name = "midnight"
+  , pattern =
+    [ regex "\x5348\x591c|\x51cc\x6668"
+    ]
+  , prod = \_ -> tt $ hour False 0
+  }
+
+ruleInduringThePartofday :: Rule
+ruleInduringThePartofday = Rule
+  { name = "in|during the <part-of-day>"
+  , pattern =
+    [ Predicate isAPartOfDay
+    , regex "\x70b9|\x9ede"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedday5 :: Rule
+ruleNamedday5 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "\x661f\x671f\x4e94|\x5468\x4e94|\x793c\x62dc\x4e94|\x79ae\x62dc\x4e94|\x9031\x4e94"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 5
+  }
+
+ruleIntersectBy :: Rule
+ruleIntersectBy = Rule
+  { name = "intersect by \",\""
+  , pattern =
+    [ Predicate isNotLatent
+    , regex ","
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:_:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleMmdd :: Rule
+ruleMmdd = Rule
+  { name = "mm/dd"
+  , pattern =
+    [ regex "(0?[1-9]|1[0-2])/(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (mm:dd:_)):_) -> do
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ monthDay m d
+      _ -> Nothing
+  }
+
+ruleNamedmonth2 :: Rule
+ruleNamedmonth2 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x4e8c\x6708(\x4efd)?"
+    ]
+  , prod = \_ -> tt $ month 2
+  }
+
+ruleIntegerLatentTimeofday :: Rule
+ruleIntegerLatentTimeofday = Rule
+  { name = "<integer> (latent time-of-day)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 0 23
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ hour True v
+      _ -> Nothing
+  }
+
+ruleYearNumericWithYearSymbol :: Rule
+ruleYearNumericWithYearSymbol = Rule
+  { name = "year (numeric with year symbol)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1000 9999
+    , regex "\x5e74"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt $ year v
+      _ -> Nothing
+  }
+
+ruleDurationAgo :: Rule
+ruleDurationAgo = Rule
+  { name = "<duration> ago"
+  , pattern =
+    [ dimension Duration
+    , regex "\x524d"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Duration dd:_) ->
+        tt $ durationAgo dd
+      _ -> Nothing
+  }
+
+ruleHhmmMilitaryTimeofday :: Rule
+ruleHhmmMilitaryTimeofday = Rule
+  { name = "hhmm (military time-of-day)"
+  , pattern =
+    [ regex "((?:[01]?\\d)|(?:2[0-3]))([0-5]\\d)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (hh:mm:_)):_) -> do
+        h <- parseInt hh
+        m <- parseInt mm
+        tt . mkLatent $ hourMinute False h m
+      _ -> Nothing
+  }
+
+ruleLastNCycle :: Rule
+ruleLastNCycle = Rule
+  { name = "last n <cycle>"
+  , pattern =
+    [ regex "\x4e0a|\x524d"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain (- v)
+      _ -> Nothing
+  }
+
+ruleIntersect :: Rule
+ruleIntersect = Rule
+  { name = "intersect"
+  , pattern =
+    [ Predicate isNotLatent
+    , Predicate isNotLatent
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNamedmonth6 :: Rule
+ruleNamedmonth6 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x516d\x6708(\x4efd)?"
+    ]
+  , prod = \_ -> tt $ month 6
+  }
+
+ruleNthTimeOfTime :: Rule
+ruleNthTimeOfTime = Rule
+  { name = "nth <time> of <time>"
+  , pattern =
+    [ dimension Time
+    , dimension Ordinal
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Ordinal od:Token Time td2:_) -> Token Time .
+        predNth (TOrdinal.value od - 1) False <$> intersect td2 td1
+      _ -> Nothing
+  }
+
+ruleNamedmonth8 :: Rule
+ruleNamedmonth8 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x516b\x6708(\x4efd)?"
+    ]
+  , prod = \_ -> tt $ month 8
+  }
+
+ruleWeekend :: Rule
+ruleWeekend = Rule
+  { name = "week-end"
+  , pattern =
+    [ regex "\x5468\x672b|\x9031\x672b"
+    ]
+  , prod = \_ -> do
+      from <- intersect (dayOfWeek 5) (hour False 18)
+      to <- intersect (dayOfWeek 1) (hour False 0)
+      tt $ interval TTime.Open (from, to)
+  }
+
+ruleLastYear :: Rule
+ruleLastYear = Rule
+  { name = "last year"
+  , pattern =
+    [ regex "\x53bb\x5e74"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Year $ - 1
+  }
+
+ruleDimTimePartofday :: Rule
+ruleDimTimePartofday = Rule
+  { name = "<dim time> <part-of-day>"
+  , pattern =
+    [ dimension Time
+    , Predicate isAPartOfDay
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleNextTime :: Rule
+ruleNextTime = Rule
+  { name = "next <time>"
+  , pattern =
+    [ regex "\x660e|\x4e0b(\x4e2a|\x500b)?"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 1 False td
+      _ -> Nothing
+  }
+
+ruleYyyymmdd :: Rule
+ruleYyyymmdd = Rule
+  { name = "yyyy-mm-dd"
+  , pattern =
+    [ regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (yy:mm:dd:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleNextNCycle :: Rule
+ruleNextNCycle = Rule
+  { name = "next n <cycle>"
+  , pattern =
+    [ regex "\x4e0b|\x540e|\x5f8c"
+    , Predicate $ isIntegerBetween 1 9999
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:token:Token TimeGrain grain:_) -> do
+        v <- getIntValue token
+        tt $ cycleN True grain v
+      _ -> Nothing
+  }
+
+ruleMorning :: Rule
+ruleMorning = Rule
+  { name = "morning"
+  , pattern =
+    [ regex "\x65e9\x4e0a|\x65e9\x6668"
+    ]
+  , prod = \_ ->
+      let from = hour False 4
+          to = hour False 12
+      in tt . mkLatent . partOfDay $
+           interval TTime.Open (from, to)
+  }
+
+ruleNextYear :: Rule
+ruleNextYear = Rule
+  { name = "next year"
+  , pattern =
+    [ regex "\x660e\x5e74"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Year 1
+  }
+
+ruleThisCycle :: Rule
+ruleThisCycle = Rule
+  { name = "this <cycle>"
+  , pattern =
+    [ regex "(\x8fd9|\x9019)(\x4e00)?"
+    , dimension TimeGrain
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token TimeGrain grain:_) ->
+        tt $ cycleNth grain 0
+      _ -> Nothing
+  }
+
+ruleThisTime :: Rule
+ruleThisTime = Rule
+  { name = "this <time>"
+  , pattern =
+    [ regex "\x4eca|\x8fd9|\x9019"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth 0 False td
+      _ -> Nothing
+  }
+
+ruleYesterday :: Rule
+ruleYesterday = Rule
+  { name = "yesterday"
+  , pattern =
+    [ regex "\x6628\x5929"
+    ]
+  , prod = \_ -> tt . cycleNth TG.Day $ - 1
+  }
+
+ruleChristmas :: Rule
+ruleChristmas = Rule
+  { name = "christmas"
+  , pattern =
+    [ regex "(\x5723\x8bde|\x8056\x8a95)(\x8282|\x7bc0)?"
+    ]
+  , prod = \_ -> tt $ monthDay 12 25
+  }
+
+ruleLastNight :: Rule
+ruleLastNight = Rule
+  { name = "last night"
+  , pattern =
+    [ regex "\x6628\x665a|\x6628\x5929\x665a\x4e0a"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day $ - 1
+          td2 = interval TTime.Open (hour False 18, hour False 0)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleTimeofdayAmpm :: Rule
+ruleTimeofdayAmpm = Rule
+  { name = "<time-of-day> am|pm"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "([ap])(\\s|\\.)?m?\\.?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:Token RegexMatch (GroupMatch (ap:_)):_) ->
+        tt . timeOfDayAMPM td $ Text.toLower ap == "a"
+      _ -> Nothing
+  }
+
+ruleArmysDay :: Rule
+ruleArmysDay = Rule
+  { name = "army's day"
+  , pattern =
+    [ regex "\x5efa(\x519b\x8282|\x8ecd\x7bc0)"
+    ]
+  , prod = \_ -> tt $ monthDay 8 1
+  }
+
+ruleNamedmonthDayofmonth :: Rule
+ruleNamedmonthDayofmonth = Rule
+  { name = "<named-month> <day-of-month>"
+  , pattern =
+    [ Predicate isAMonth
+    , dimension Numeral
+    , regex "\x53f7|\x865f|\x65e5"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:token:_) -> Token Time <$> intersectDOM td token
+      _ -> Nothing
+  }
+
+ruleLastTuesdayLastJuly :: Rule
+ruleLastTuesdayLastJuly = Rule
+  { name = "last tuesday, last july"
+  , pattern =
+    [ regex "\x4e0a"
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (_:Token Time td:_) ->
+        tt $ predNth (-1) False td
+      _ -> Nothing
+  }
+
+ruleNamedmonth5 :: Rule
+ruleNamedmonth5 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x4e94\x6708(\x4efd)?"
+    ]
+  , prod = \_ -> tt $ month 5
+  }
+
+ruleNamedday7 :: Rule
+ruleNamedday7 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "\x661f\x671f\x65e5|\x661f\x671f\x5929|\x793c\x62dc\x5929|\x5468\x65e5|\x79ae\x62dc\x5929|\x9031\x65e5"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 7
+  }
+
+rulePartofdayDimTime :: Rule
+rulePartofdayDimTime = Rule
+  { name = "<part-of-day> <dim time>"
+  , pattern =
+    [ Predicate isAPartOfDay
+    , dimension Time
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td1:Token Time td2:_) ->
+        Token Time <$> intersect td1 td2
+      _ -> Nothing
+  }
+
+ruleMonthNumericWithMonthSymbol :: Rule
+ruleMonthNumericWithMonthSymbol = Rule
+  { name = "month (numeric with month symbol)"
+  , pattern =
+    [ Predicate $ isIntegerBetween 1 12
+    , regex "\x6708"
+    ]
+  , prod = \tokens -> case tokens of
+      (token:_) -> do
+        v <- getIntValue token
+        tt . mkLatent $ month v
+      _ -> Nothing
+  }
+
+ruleTonight :: Rule
+ruleTonight = Rule
+  { name = "tonight"
+  , pattern =
+    [ regex "\x4eca\x665a|\x4eca\x5929\x665a\x4e0a"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 0
+          td2 = interval TTime.Open (hour False 18, hour False 0)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleTomorrowNight :: Rule
+ruleTomorrowNight = Rule
+  { name = "tomorrow night"
+  , pattern =
+    [ regex "\x660e\x665a|\x660e\x5929\x665a\x4e0a"
+    ]
+  , prod = \_ ->
+      let td1 = cycleNth TG.Day 1
+          td2 = interval TTime.Open (hour False 18, hour False 0)
+      in Token Time . partOfDay <$> intersect td1 td2
+  }
+
+ruleNamedmonth10 :: Rule
+ruleNamedmonth10 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x5341\x6708(\x4efd)?"
+    ]
+  , prod = \_ -> tt $ month 10
+  }
+
+ruleChildrensDay :: Rule
+ruleChildrensDay = Rule
+  { name = "children's day"
+  , pattern =
+    [ regex "(\x513f|\x5152)\x7ae5(\x8282|\x7bc0)"
+    ]
+  , prod = \_ -> tt $ monthDay 6 1
+  }
+
+ruleThisYear :: Rule
+ruleThisYear = Rule
+  { name = "this year"
+  , pattern =
+    [ regex "\x4eca\x5e74"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Year 0
+  }
+
+ruleAbsorptionOfAfterNamedDay :: Rule
+ruleAbsorptionOfAfterNamedDay = Rule
+  { name = "absorption of , after named day"
+  , pattern =
+    [ Predicate isADayOfWeek
+    , regex ","
+    ]
+  , prod = \tokens -> case tokens of
+      (x:_) -> Just x
+      _ -> Nothing
+  }
+
+ruleNamedmonth11 :: Rule
+ruleNamedmonth11 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x5341\x4e00\x6708(\x4efd)?"
+    ]
+  , prod = \_ -> tt $ month 11
+  }
+
+ruleWomensDay :: Rule
+ruleWomensDay = Rule
+  { name = "women's day"
+  , pattern =
+    [ regex "(\x5987|\x5a66)\x5973(\x8282|\x7bc0)"
+    ]
+  , prod = \_ -> tt $ monthDay 3 8
+  }
+
+ruleEveningnight :: Rule
+ruleEveningnight = Rule
+  { name = "evening|night"
+  , pattern =
+    [ regex "\x665a\x4e0a|\x665a\x95f4"
+    ]
+  , prod = \_ ->
+      let from = hour False 18
+          to = hour False 0
+      in tt . partOfDay . mkLatent $
+           interval TTime.Open (from, to)
+  }
+
+ruleNamedday3 :: Rule
+ruleNamedday3 = Rule
+  { name = "named-day"
+  , pattern =
+    [ regex "\x661f\x671f\x4e09|\x5468\x4e09|\x793c\x62dc\x4e09|\x79ae\x62dc\x4e09|\x9031\x4e09"
+    ]
+  , prod = \_ -> tt $ dayOfWeek 3
+  }
+
+ruleMmddyyyy :: Rule
+ruleMmddyyyy = Rule
+  { name = "mm/dd/yyyy"
+  , pattern =
+    [ regex "(0?[1-9]|1[0-2])/(3[01]|[12]\\d|0?[1-9])/(\\d{2,4})"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (mm:dd:yy:_)):_) -> do
+        y <- parseInt yy
+        m <- parseInt mm
+        d <- parseInt dd
+        tt $ yearMonthDay y m d
+      _ -> Nothing
+  }
+
+ruleTomorrow :: Rule
+ruleTomorrow = Rule
+  { name = "tomorrow"
+  , pattern =
+    [ regex "\x660e\x5929"
+    ]
+  , prod = \_ -> tt $ cycleNth TG.Day 1
+  }
+
+ruleTimeofdayOclock :: Rule
+ruleTimeofdayOclock = Rule
+  { name = "<time-of-day> o'clock"
+  , pattern =
+    [ Predicate isATimeOfDay
+    , regex "\x9ede|\x70b9"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:_) ->
+        tt $ notLatent td
+      _ -> Nothing
+  }
+
+ruleNamedmonth9 :: Rule
+ruleNamedmonth9 = Rule
+  { name = "named-month"
+  , pattern =
+    [ regex "\x4e5d\x6708(\x4efd)?"
+    ]
+  , prod = \_ -> tt $ month 9
+  }
+
+ruleTimezone :: Rule
+ruleTimezone = Rule
+  { name = "<time> timezone"
+  , pattern =
+    [ Predicate $ liftM2 (&&) isATimeOfDay isNotLatent
+    , regex "\\b(YEKT|YEKST|YAKT|YAKST|WITA|WIT|WIB|WGT|WGST|WFT|WET|WEST|WAT|WAST|VUT|VLAT|VLAST|VET|UZT|UYT|UYST|UTC|ULAT|TVT|TMT|TLT|TKT|TJT|TFT|TAHT|SST|SRT|SGT|SCT|SBT|SAST|SAMT|RET|PYT|PYST|PWT|PST|PONT|PMST|PMDT|PKT|PHT|PHOT|PGT|PETT|PETST|PET|PDT|OMST|OMSST|NZST|NZDT|NUT|NST|NPT|NOVT|NOVST|NFT|NDT|NCT|MYT|MVT|MUT|MST|MSK|MSD|MMT|MHT|MDT|MAWT|MART|MAGT|MAGST|LINT|LHST|LHDT|KUYT|KST|KRAT|KRAST|KGT|JST|IST|IRST|IRKT|IRKST|IRDT|IOT|IDT|ICT|HOVT|HKT|GYT|GST|GMT|GILT|GFT|GET|GAMT|GALT|FNT|FKT|FKST|FJT|FJST|EST|EGT|EGST|EET|EEST|EDT|ECT|EAT|EAST|EASST|DAVT|ChST|CXT|CVT|CST|COT|CLT|CLST|CKT|CHAST|CHADT|CET|CEST|CDT|CCT|CAT|CAST|BTT|BST|BRT|BRST|BOT|BNT|AZT|AZST|AZOT|AZOST|AWST|AWDT|AST|ART|AQTT|ANAT|ANAST|AMT|AMST|ALMT|AKST|AKDT|AFT|AEST|AEDT|ADT|ACST|ACDT)\\b"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Time td:
+       Token RegexMatch (GroupMatch (tz:_)):
+       _) -> Token Time <$> inTimezone tz td
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleAbsorptionOfAfterNamedDay
+  , ruleAfternoon
+  , ruleArmysDay
+  , ruleChildrensDay
+  , ruleChristmas
+  , ruleDimTimePartofday
+  , ruleDurationAgo
+  , ruleDurationFromNow
+  , ruleEveningnight
+  , ruleHhmmMilitaryTimeofday
+  , ruleHhmmTimeofday
+  , ruleInDuration
+  , ruleInduringThePartofday
+  , ruleIntegerLatentTimeofday
+  , ruleIntersect
+  , ruleIntersectBy
+  , ruleLaborDay
+  , ruleLastCycle
+  , ruleLastNCycle
+  , ruleLastNight
+  , ruleLastTime
+  , ruleLastTuesdayLastJuly
+  , ruleLastYear
+  , ruleMidnight
+  , ruleMmdd
+  , ruleMmddyyyy
+  , ruleMonthNumericWithMonthSymbol
+  , ruleMorning
+  , ruleNamedday
+  , ruleNamedday2
+  , ruleNamedday3
+  , ruleNamedday4
+  , ruleNamedday5
+  , ruleNamedday6
+  , ruleNamedday7
+  , ruleNamedmonth
+  , ruleNamedmonth10
+  , ruleNamedmonth11
+  , ruleNamedmonth12
+  , ruleNamedmonth2
+  , ruleNamedmonth3
+  , ruleNamedmonth4
+  , ruleNamedmonth5
+  , ruleNamedmonth6
+  , ruleNamedmonth7
+  , ruleNamedmonth8
+  , ruleNamedmonth9
+  , ruleNamedmonthDayofmonth
+  , ruleNationalDay
+  , ruleNewYearsDay
+  , ruleNextCycle
+  , ruleNextNCycle
+  , ruleNextTime
+  , ruleNextYear
+  , ruleNoon
+  , ruleNow
+  , ruleNthTimeOfTime
+  , ruleNthTimeOfTime2
+  , rulePartofdayDimTime
+  , ruleRelativeMinutesAfterpastIntegerHourofday
+  , ruleRelativeMinutesAfterpastNoonmidnight
+  , ruleRelativeMinutesTotillbeforeIntegerHourofday
+  , ruleRelativeMinutesTotillbeforeNoonmidnight
+  , ruleQuarterAfterpastIntegerHourofday
+  , ruleQuarterAfterpastNoonmidnight
+  , ruleQuarterTotillbeforeIntegerHourofday
+  , ruleQuarterTotillbeforeNoonmidnight
+  , ruleHalfAfterpastIntegerHourofday
+  , ruleHalfAfterpastNoonmidnight
+  , ruleHalfTotillbeforeIntegerHourofday
+  , ruleHalfTotillbeforeNoonmidnight
+  , ruleTheCycleAfterTime
+  , ruleTheCycleBeforeTime
+  , ruleTheDayAfterTomorrow
+  , ruleTheDayBeforeYesterday
+  , ruleThisCycle
+  , ruleThisDayofweek
+  , ruleThisTime
+  , ruleThisYear
+  , ruleThisnextDayofweek
+  , ruleTimeofdayAmpm
+  , ruleTimeofdayOclock
+  , ruleToday
+  , ruleTomorrow
+  , ruleTomorrowNight
+  , ruleTonight
+  , ruleValentinesDay
+  , ruleWeekend
+  , ruleWomensDay
+  , ruleYearNumericWithYearSymbol
+  , ruleYesterday
+  , ruleYyyymmdd
+  , ruleTimezone
+  ]
diff --git a/Duckling/TimeGrain/DA/Rules.hs b/Duckling/TimeGrain/DA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/DA/Rules.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.DA.Rules
+  ( rules ) where
+
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("second (grain)", "sekund(er)?", TG.Second)
+         , ("minute (grain)", "minut(ter)?", TG.Minute)
+         , ("hour (grain)", "t(imer?)?", TG.Hour)
+         , ("day (grain)", "dag(e)?", TG.Day)
+         , ("week (grain)", "uger?", TG.Week)
+         , ("month (grain)", "m\x00e5ned(er)?", TG.Month)
+         , ("quarter (grain)", "kvartal(er)?", TG.Quarter)
+         , ("year (grain)", "\x00e5r", TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/TimeGrain/DE/Rules.hs b/Duckling/TimeGrain/DE/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/DE/Rules.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.DE.Rules
+  ( rules ) where
+
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("second (grain)", "sekunden?", TG.Second)
+         , ("minute (grain)", "minuten?", TG.Minute)
+         , ("hour (grain)", "stunden?", TG.Hour)
+         , ("day (grain)", "tage?n?", TG.Day)
+         , ("week (grain)", "wochen?", TG.Week)
+         , ("month (grain)", "monate?n?", TG.Month)
+         , ("quarter (grain)", "quartale?", TG.Quarter)
+         , ("year (grain)", "jahre?n?", TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/TimeGrain/EN/Rules.hs b/Duckling/TimeGrain/EN/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/EN/Rules.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.EN.Rules
+  ( rules ) where
+
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("second (grain) ", "sec(ond)?s?",      TG.Second)
+         , ("minute (grain)" , "min(ute)?s?",      TG.Minute)
+         , ("hour (grain)"   , "h(((ou)?rs?)|r)?", TG.Hour)
+         , ("day (grain)"    , "days?",            TG.Day)
+         , ("week (grain)"   , "weeks?",           TG.Week)
+         , ("month (grain)"  , "months?",          TG.Month)
+         , ("quarter (grain)", "(quarter|qtr)s?",        TG.Quarter)
+         , ("year (grain)"   , "y(ea)?rs?",           TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/TimeGrain/ES/Rules.hs b/Duckling/TimeGrain/ES/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/ES/Rules.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.ES.Rules
+  ( rules ) where
+
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("segundo (grain)", "seg(undo)?s?", TG.Second)
+         , ("minutos (grain)", "min(uto)?s?", TG.Minute)
+         , ("hora (grain)", "h(ora)?s?", TG.Hour)
+         , ("dia (grain)", "d(\x00ed|i)as?", TG.Day)
+         , ("semana (grain)", "semanas?", TG.Week)
+         , ("mes (grain)", "mes(es)?", TG.Month)
+         , ("trimestre (grain)", "trimestres?", TG.Quarter)
+         , ("año (grain)", "a(n|\x00f1)os?", TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/TimeGrain/FR/Rules.hs b/Duckling/TimeGrain/FR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/FR/Rules.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.FR.Rules
+  ( rules ) where
+
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("seconde (grain)", "sec(onde)?s?", TG.Second)
+         , ("minute (grain)", "min(ute)?s?", TG.Minute)
+         , ("heure (grain)", "heures?", TG.Hour)
+         , ("jour (grain)", "jour(n(e|\x00e9)e?)?s?", TG.Day)
+         , ("semaine (grain)", "semaines?", TG.Week)
+         , ("mois (grain)", "mois", TG.Month)
+         , ("trimestre (grain)", "trimestres?", TG.Quarter)
+         , ("année (grain)", "an(n(e|\x00e9)e?)?s?", TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/TimeGrain/GA/Rules.hs b/Duckling/TimeGrain/GA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/GA/Rules.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.GA.Rules
+  ( rules ) where
+
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("soicind (grain)", "t?sh?oicind(\x00ed|i)?", TG.Second)
+         , ("nóiméad (grain)", "n[\x00f3o]im(\x00e9|e)[ai]da?", TG.Minute)
+         , ("uair (grain)", "([thn]-?)?uair(e|eanta)?", TG.Hour)
+         , ("lá (grain)", "l(ae(thanta)?|(\x00e1|a))", TG.Day)
+         , ("seachtain (grain)", "t?sh?eachtain(e|(\x00ed|i))?", TG.Week)
+         , ("mí (grain)", "mh?(\x00ed|i)(sa|nna)", TG.Month)
+         , ("ráithe (grain)", "r(\x00e1|a)ith(e|(\x00ed|i))", TG.Quarter)
+         , ("bliain (grain)", "m?bh?lia(in|na|nta)", TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/TimeGrain/HE/Rules.hs b/Duckling/TimeGrain/HE/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/HE/Rules.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.HE.Rules
+  ( rules ) where
+
+import Data.String
+import Data.Text (Text)
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Types
+import qualified Duckling.TimeGrain.Types as TG
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("second (grain)", "\x05e9\x05e0\x05d9\x05d5\x05ea|\x05e9\x05e0\x05d9\x05d9\x05d4", TG.Second)
+         , ("minute (grain)", "\x05d3\x05e7\x05d4|\x05d3\x05e7\x05d5\x05ea", TG.Minute)
+         , ("hour (grain)", "\x05e9\x05e2\x05d5\x05ea|\x05e9\x05e2\x05d4", TG.Hour)
+         , ("day (grain)", "\x05d9\x05de\x05d9\x05dd|\x05d9\x05d5\x05dd", TG.Day)
+         , ("week (grain)", "\x05e9\x05d1\x05d5\x05e2|\x05e9\x05d1\x05d5\x05e2\x05d5\x05ea", TG.Week)
+         , ("month (grain)", "\x05d7\x05d5\x05d3\x05e9|\x05d7\x05d5\x05d3\x05e9\x05d9\x05dd", TG.Month)
+         , ("quarter (grain)", "\x05e8\x05d1\x05e2", TG.Quarter)
+         , ("year (grain)", "\x05e9\x05e0\x05d4", TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/TimeGrain/HR/Rules.hs b/Duckling/TimeGrain/HR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/HR/Rules.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.HR.Rules
+  ( rules ) where
+
+import Data.String
+import Data.Text (Text)
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Types
+import qualified Duckling.TimeGrain.Types as TG
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("second (grain)", "sek(und)?(a|e|u)?", TG.Second)
+         , ("minute (grain)", "min(ut)?(a|e|u)?", TG.Minute)
+         , ("hour (grain)", "h|sat(i|a|e)?", TG.Hour)
+         , ("day (grain)", "dan(i|a|e)?", TG.Day)
+         , ("week (grain)", "tjeda?n(a|e|u|i)?", TG.Week)
+         , ("month (grain)", "mjesec(a|e|u|i)?", TG.Month)
+         , ("quarter (grain)", "kvartalu?|tromjese(c|\x010d)j(e|u)", TG.Quarter)
+         , ("year (grain)", "godin(a|e|u)", TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/TimeGrain/IT/Rules.hs b/Duckling/TimeGrain/IT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/IT/Rules.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.IT.Rules
+  ( rules ) where
+
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("seconde (grain)", "second[oi]", TG.Second)
+         , ("minute (grain)", "minut[oi]", TG.Minute)
+         , ("heure (grain)", "or[ae]", TG.Hour)
+         , ("jour (grain)", "giorn[oi]", TG.Day)
+         , ("semaine (grain)", "settiman[ae]", TG.Week)
+         , ("mois (grain)", "mes[ei]", TG.Month)
+         , ("trimestre (grain)", "trimestr[ei]", TG.Quarter)
+         , ("année (grain)", "ann?[oi]", TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/TimeGrain/JA/Rules.hs b/Duckling/TimeGrain/JA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/JA/Rules.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.JA.Rules
+  ( rules ) where
+
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("second (grain)", "\x79d2(\x6bce|\x9593)?", TG.Second)
+         , ("minute (grain)", "\x5206(\x6bce|\x9593)?", TG.Minute)
+         , ("hour (grain)", "\x6642(\x6bce|\x9593)?", TG.Hour)
+         , ("day (grain)", "\x65e5(\x6bce|\x9593)?", TG.Day)
+         , ("week (grain)", "\x9031(\x6bce|\x9593)?", TG.Week)
+         , ("month (grain)", "\x6708(\x6bce|\x9593)?", TG.Month)
+         , ("year (grain)", "\x5e74(\x6bce|\x9593)?", TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/TimeGrain/KO/Rules.hs b/Duckling/TimeGrain/KO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/KO/Rules.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.KO.Rules
+  ( rules ) where
+
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("second (grain)", "\xcd08", TG.Second)
+         , ("minute (grain)", "\xbd84", TG.Minute)
+         , ("hour (grain)", "\xc2dc(\xac04)?", TG.Hour)
+         , ("day (grain)", "\xb0a0|\xc77c(\xac04|\xb3d9\xc548)?", TG.Day)
+         , ("week (grain)", "\xc8fc(\xac04|\xb3d9\xc548|\xc77c)?", TG.Week)
+         , ("month (grain)", "(\xb2ec)(\xac04|\xb3d9\xc548)?", TG.Month)
+         , ("quarter (grain)", "\xbd84\xae30(\xac04|\xb3d9\xc548)?", TG.Quarter)
+         , ("year (grain)", "\xd574|\xc5f0\xac04|\xb144(\xac04|\xb3d9\xc548)?", TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/TimeGrain/NB/Rules.hs b/Duckling/TimeGrain/NB/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/NB/Rules.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.NB.Rules
+  ( rules ) where
+
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("second (grain)", "sek(und(er)?)?", TG.Second)
+         , ("minute (grain)", "min(utt(er)?)?", TG.Minute)
+         , ("hour (grain)", "t(ime(r)?)?", TG.Hour)
+         , ("day (grain)", "dag(er)?", TG.Day)
+         , ("week (grain)", "uke(r|n)?", TG.Week)
+         , ("month (grain)", "m\x00e5ned(er)?", TG.Month)
+         , ("quarter (grain)", "kvart(al|er)(et)?", TG.Quarter)
+         , ("year (grain)", "\x00e5r", TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/TimeGrain/PL/Rules.hs b/Duckling/TimeGrain/PL/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/PL/Rules.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.PL.Rules
+  ( rules ) where
+
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("second (grain)", "sekund(y|zie|(e|\x0119)|om|ami|ach|o|a)?|s", TG.Second)
+         , ("minute (grain)", "minut(y|cie|(e|\x0119)|om|o|ami|ach|(a|\x0105))?|m", TG.Minute)
+         , ("hour (grain)", "h|godzin(y|(e|\x0119)|ie|om|o|ami|ach|(a|\x0105))?", TG.Hour)
+         , ("day (grain)", "dzie(n|\x0144|ni(a|\x0105))|dni(owi|ach|a|\x0105)?", TG.Day)
+         , ("week (grain)", "tydzie(n|\x0144|)|tygod(ni(owi|u|a|em))|tygodn(iach|iami|iom|ie|i)|tyg\\.?", TG.Week)
+         , ("month (grain)", "miesi(a|\x0105)c(owi|em|u|e|om|ami|ach|a)?", TG.Month)
+         , ("quarter (grain)", "kwarta(l|\x0142)(u|owi|em|e|(o|\x00f3)w|om|ach|ami|y)?", TG.Quarter)
+         , ("year (grain)", "rok(u|owi|iem)?|lat(ami|ach|a|om)?", TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/TimeGrain/PT/Rules.hs b/Duckling/TimeGrain/PT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/PT/Rules.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.PT.Rules
+  ( rules ) where
+
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("segundo (grain)", "seg(undo)?s?", TG.Second)
+         , ("minutos (grain)", "min(uto)?s?", TG.Minute)
+         , ("hora (grain)", "h(ora)?s?", TG.Hour)
+         , ("dia (grain)", "d(\x00ed|i)as?", TG.Day)
+         , ("semana (grain)", "semanas?", TG.Week)
+         , ("mes (grain)", "m(e|\x00ea)s(es)?", TG.Month)
+         , ("ano (grain)", "anos?", TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/TimeGrain/RO/Rules.hs b/Duckling/TimeGrain/RO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/RO/Rules.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.RO.Rules
+  ( rules ) where
+
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("secunde (grain)", "sec(und(a|e|\x0103))?", TG.Second)
+         , ("minute (grain)", "min(ut(e|ul)?)?", TG.Minute)
+         , ("ore (grain)", "h|or(a|e(le)?|\x0103)", TG.Hour)
+         , ("zile (grain)", "zi(le(le)?|u(a|\x0103))?", TG.Day)
+         , ("saptamani (grain)", "sapt(a|\x0103)m(a|\x00e2)n(ile|a|\x0103|i)", TG.Week)
+         , ("luni (grain)", "lun(i(le)?|a|\x0103)", TG.Month)
+         , ("trimestru (grain)", "trimestr(e(le)?|ul?)", TG.Quarter)
+         , ("ani (grain)", "an(ul|ii?)?", TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/TimeGrain/SV/Rules.hs b/Duckling/TimeGrain/SV/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/SV/Rules.hs
@@ -0,0 +1,41 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.SV.Rules
+  ( rules ) where
+
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("second (grain)", "sek(und(er(na)?)?)?", TG.Second)
+         , ("minute (grain)", "min(ut(er(na)?)?)?", TG.Minute)
+         , ("hour (grain)", "t(imm(e(n)?|ar(na)?)?)?", TG.Hour)
+         , ("day (grain)", "dag(en|ar(na)?)?", TG.Day)
+         , ("week (grain)", "veck(or(na)?|a(n)?)?", TG.Week)
+         , ("month (grain)", "m\x00e5nad(er(na)?)?", TG.Month)
+         , ("quarter (grain)", "kvart(al)(et)?", TG.Quarter)
+         , ("year (grain)", "\x00e5r(en)?", TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/TimeGrain/Types.hs b/Duckling/TimeGrain/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/Types.hs
@@ -0,0 +1,69 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Duckling.TimeGrain.Types
+  ( Grain(..)
+  , add
+  , inSeconds
+  )
+  where
+
+import Control.DeepSeq
+import Data.Aeson
+import Data.Hashable
+import qualified Data.Text as Text
+import Data.Text.Lazy.Builder (fromText)
+import qualified Data.Time as Time
+import GHC.Generics
+import TextShow
+
+import Prelude
+
+import Duckling.Resolve (Resolve(..))
+
+data Grain = Second | Minute | Hour | Day | Week | Month | Quarter | Year
+  deriving (Eq, Generic, Hashable, Ord, Bounded, Enum, Show, NFData)
+
+instance Resolve Grain where
+  type ResolvedValue Grain = Grain
+  resolve _ _ = Nothing
+
+instance TextShow Grain where
+  showb = fromText . Text.toLower . Text.pack . show
+
+instance ToJSON Grain where
+  toJSON = String . showt
+
+updateUTCDay :: Time.UTCTime -> (Time.Day -> Time.Day) -> Time.UTCTime
+updateUTCDay (Time.UTCTime day diffTime) f = Time.UTCTime (f day) diffTime
+
+add :: Time.UTCTime -> Grain -> Integer -> Time.UTCTime
+add utcTime Second n = Time.addUTCTime (realToFrac n) utcTime
+add utcTime Minute n = Time.addUTCTime (realToFrac $ 60 * n) utcTime
+add utcTime Hour n = Time.addUTCTime (realToFrac $ 3600 * n) utcTime
+add utcTime Day n = updateUTCDay utcTime $ Time.addDays n
+add utcTime Week n = updateUTCDay utcTime . Time.addDays $ 7 * n
+add utcTime Month n = updateUTCDay utcTime $ Time.addGregorianMonthsClip n
+add utcTime Quarter n =
+  updateUTCDay utcTime . Time.addGregorianMonthsClip $ 3 * n
+add utcTime Year n = updateUTCDay utcTime $ Time.addGregorianYearsClip n
+
+inSeconds :: Grain -> Int -> Int
+inSeconds Second  n = n
+inSeconds Minute  n = n * 60
+inSeconds Hour    n = n * inSeconds Minute 60
+inSeconds Day     n = n * inSeconds Hour 24
+inSeconds Week    n = n * inSeconds Day 7
+inSeconds Month   n = n * inSeconds Day 30
+inSeconds Quarter n = n * inSeconds Month 3
+inSeconds Year    n = n * inSeconds Day 365
diff --git a/Duckling/TimeGrain/VI/Rules.hs b/Duckling/TimeGrain/VI/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/VI/Rules.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.VI.Rules
+  ( rules ) where
+
+import Data.String
+import Data.Text (Text)
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Types
+import qualified Duckling.TimeGrain.Types as TG
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("second (grain)", "(gi\x00e2y|s|sec)", TG.Second)
+         , ("minute (grain)", "(ph\x00fat|m|min)", TG.Minute)
+         , ("hour (grain)", "(gi\x1edd|h|ti\x1ebfng)", TG.Hour)
+         , ("day (grain)", "ng\x00e0y", TG.Day)
+         , ("week (grain)", "tu\x1ea7n", TG.Week)
+         , ("month (grain)", "th\x00e1ng", TG.Month)
+         , ("quarter (grain)", "qu\x00fd", TG.Quarter)
+         , ("year (grain)", "n\x0103m", TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/TimeGrain/ZH/Rules.hs b/Duckling/TimeGrain/ZH/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/TimeGrain/ZH/Rules.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.TimeGrain.ZH.Rules
+  ( rules ) where
+
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import qualified Duckling.TimeGrain.Types as TG
+import Duckling.Types
+
+grains :: [(Text, String, TG.Grain)]
+grains = [ ("second (grain)", "\x79d2(\x949f|\x9418)?", TG.Second)
+         , ("minute (grain)", "\x5206(\x949f|\x9418)?", TG.Minute)
+         , ("hour (grain)", "\x5c0f\x65f6|\x5c0f\x6642", TG.Hour)
+         , ("day (grain)", "\x5929", TG.Day)
+         , ("week (grain)", "\x5468|\x9031|\x793c\x62dc|\x79ae\x62dc", TG.Week)
+         , ("month (grain)", "\x6708", TG.Month)
+         , ("year (grain)", "\x5e74", TG.Year)
+         ]
+
+rules :: [Rule]
+rules = map go grains
+  where
+    go (name, regexPattern, grain) = Rule
+      { name = name
+      , pattern = [regex regexPattern]
+      , prod = \_ -> Just $ Token TimeGrain grain
+      }
diff --git a/Duckling/Types.hs b/Duckling/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Types.hs
@@ -0,0 +1,153 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE ExistentialQuantification #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE StandaloneDeriving #-}
+
+module Duckling.Types where
+
+import Control.DeepSeq
+import Data.Aeson
+import qualified Data.ByteString.Lazy as LB
+import Data.GADT.Compare
+import Data.Hashable
+import Data.Maybe
+import Data.String
+import Data.Text (Text)
+import qualified Data.Text.Encoding as Text
+import Data.Typeable ((:~:)(Refl), Typeable)
+import GHC.Generics
+import Prelude
+import qualified Text.Regex.Base as R
+import qualified Text.Regex.PCRE as PCRE
+
+import Duckling.Dimensions.Types
+import Duckling.Resolve
+
+-- -----------------------------------------------------------------
+-- Token
+
+data Token = forall a . (Resolve a, Eq a, Hashable a, Show a, NFData a) =>
+  Token (Dimension a) a
+
+deriving instance Show Token
+instance Eq Token where
+  Token d1 v1 == Token d2 v2 = case geq d1 d2 of
+    Just Refl -> v1 == v2
+    Nothing   -> False
+
+instance Hashable Token where
+  hashWithSalt s (Token dim v) = hashWithSalt s (dim, v)
+
+instance NFData Token where
+  rnf (Token _ v) = rnf v
+
+isDimension :: Dimension a -> Token -> Bool
+isDimension dim (Token dim' _) = isJust $ geq dim dim'
+
+data Node = Node
+  { nodeRange :: Range
+  , token     :: Token
+  , children  :: [Node]
+  , rule      :: Maybe Text
+  } deriving (Eq, Generic, Hashable, Show, NFData)
+
+data ResolvedToken = Resolved
+  { range :: Range
+  , node :: Node
+  , jsonValue :: Value
+  } deriving (Eq, Show)
+
+instance Ord ResolvedToken where
+  compare (Resolved range1 _ json1) (Resolved range2 _ json2) =
+    case compare range1 range2 of
+      EQ -> compare (toJText json1) (toJText json2)
+      z  -> z
+
+data Candidate = Candidate ResolvedToken Double Bool
+  deriving (Eq, Show)
+
+instance Ord Candidate where
+  compare (Candidate (Resolved{range = Range s1 e1, node = Node{token = Token d1 _}}) score1 t1)
+          (Candidate (Resolved{range = Range s2 e2, node = Node{token = tok2}}) score2 t2)
+    | isDimension d1 tok2 = case starts of
+        EQ -> case ends of
+          EQ -> compare score1 score2
+          z -> z
+        LT -> case ends of
+          LT -> EQ
+          _ -> GT
+        GT -> case ends of
+          GT -> EQ
+          _ -> LT
+    | t1 == t2 = compRange
+    | t1 && compRange == GT = GT
+    | t2 && compRange == LT = LT
+    | otherwise = EQ
+      where
+        starts = compare s1 s2
+        ends = compare e1 e2
+        -- a > b if a recovers b
+        compRange = case starts of
+          EQ -> ends
+          LT -> case ends of
+            LT -> EQ
+            _  -> GT
+          GT -> case ends of
+            GT -> EQ
+            _  -> LT
+
+data Range = Range Int Int
+  deriving (Eq, Ord, Generic, Hashable, Show, NFData)
+
+type Production = [Token] -> Maybe Token
+type Predicate = Token -> Bool
+data PatternItem = Regex PCRE.Regex | Predicate Predicate
+
+type Pattern = [PatternItem]
+
+data Rule = Rule
+  { name :: Text
+  , pattern :: Pattern
+  , prod :: Production
+  }
+
+instance Show Rule where
+  show (Rule name _ _) = show name
+
+data Entity = Entity
+  { dim   :: Text
+  , body  :: Text
+  , value :: Text
+  , start :: Int
+  , end   :: Int
+  } deriving (Eq, Generic, Show, NFData)
+
+instance ToJSON Entity where
+  toEncoding = genericToEncoding defaultOptions
+
+toJText :: ToJSON x => x -> Text
+toJText j = Text.decodeUtf8 $ LB.toStrict $ encode j
+
+-- -----------------------------------------------------------------
+-- Predicates helpers
+
+regex :: String -> PatternItem
+regex = Regex . R.makeRegexOpts compOpts execOpts
+  where
+    compOpts = PCRE.defaultCompOpt + PCRE.compCaseless
+    execOpts = PCRE.defaultExecOpt
+
+dimension :: Typeable a => Dimension a -> PatternItem
+dimension value = Predicate $ isDimension value
diff --git a/Duckling/Types/Document.hs b/Duckling/Types/Document.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Types/Document.hs
@@ -0,0 +1,199 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Duckling.Types.Document
+  ( Document -- abstract
+  , fromText
+  , (!)
+  , length
+  , byteStringFromPos
+  , isAdjacent
+  , isRangeValid
+  ) where
+
+import qualified Data.Array.Unboxed as Array
+import Data.Array.Unboxed (UArray)
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as BS
+import qualified Data.Char as Char
+import Data.List (scanl', foldl', foldr)
+import Data.String
+import Data.Text (Text)
+import qualified Data.Text.Unsafe as UText
+import qualified Data.Text.Encoding as Text
+import qualified Data.Text as Text
+import qualified Data.Text.Internal.Unsafe.Char as UText
+import Prelude hiding (length)
+
+
+data Document = Document
+  { rawInput :: !Text
+  , utf8Encoded :: ByteString
+  , indexable :: UArray Int Char -- for O(1) indexing pos -> Char
+  , firstNonAdjacent :: UArray Int Int
+    -- for a given index 'i' it keeps a first index 'j' greater or equal 'i'
+    -- such that isAdjacentSeparator (indexable ! j) == False
+    -- eg. " a document " :: Document
+    --     firstNonAdjacent = [1,1,3,3,4,5,6,7,8,9,10,12]
+    -- Note that in this case 12 is the length of the vector, hence not a
+    -- valid index inside the array, this is intentional.
+  , tDropToBSDrop :: UArray Int Int
+    -- how many bytes to BS.drop from a utf8 encoded ByteString to
+    -- reach the same position as Text.drop would
+  , bsDropToTDrop :: UArray Int Int
+    -- the inverse of tDropToBSDrop, rounds down for bytes that are
+    -- not on character boundary
+    -- for "żółty" :: Document
+    --   tDropToBSDrop = [0,2,4,6,7,8]
+    --   bsDropToTDrop = [0,1,1,2,2,3,3,4,5]
+    --   tDropToUtf16Drop = [0,1,2,3,4,5]
+  , tDropToUtf16Drop :: UArray Int Int
+    -- translate Text.drop to Data.Text.Unsafe.dropWord16
+  } deriving (Show)
+
+{-
+Note [Regular expressions and Text]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Text is UTF-16 encoded internally and PCRE operates on UTF-8 encoded
+ByteStrings. Because we do a lot of regexp matching on the same Text,
+it pays off to cache UTF-8 the encoded ByteString. That's the utf8Ecoded
+field in Document.
+
+Moreover we do regexp matching with capture, where the captured groups
+are returned as ByteString and we want them as Text. But all of the
+captured groups are just a substrings of the original Text.
+Fortunately PCRE has an API that returns a MatchArray - a structure with
+just the ByteString indices and ByteString lengths of the matched fragments.
+If we play with indices right we can translate them to offsets into the
+original Text, share the underlying Text buffer and avoid all of
+UTF-8 and UTF-16 encoding and new ByteString and Text allocation.
+-}
+
+instance IsString Document where
+  fromString = fromText . fromString
+
+fromText :: Text -> Document
+fromText rawInput = Document{..}
+  where
+  utf8Encoded = Text.encodeUtf8 rawInput
+  rawInputLength = Text.length rawInput
+  unpacked = Text.unpack rawInput
+  indexable = Array.listArray (0, rawInputLength - 1) unpacked
+  firstNonAdjacent = Array.listArray (0, rawInputLength - 1) $ snd $
+   foldr gen (rawInputLength, []) $ zip [0..] unpacked
+  -- go from the end keeping track of the first nonAdjacent (best)
+  gen (ix, elem) (best, !acc)
+    | isAdjacentSeparator elem = (best, best:acc)
+    | otherwise = (ix, ix:acc)
+  tDropToBSDropList = scanl' (\acc a -> acc + utf8CharWidth a) 0 unpacked
+  tDropToBSDrop = Array.listArray (0, rawInputLength) tDropToBSDropList
+  tDropToUtf16Drop = Array.listArray (0, rawInputLength) $
+    scanl' (\acc a -> acc + utf16CharWidth a) 0 unpacked
+  bsDropToTDrop = Array.listArray (0, BS.length utf8Encoded) $
+    reverse $ snd $ foldl' fun (-1, []) $ zip [0..] tDropToBSDropList
+  fun (lastPos, !acc) (ix, elem) = (elem, replicate (elem - lastPos) ix ++ acc)
+  utf8CharWidth c
+    | w <= 0x7F = 1
+    | w <= 0x7FF = 2
+    | w <= 0xFFFF = 3
+    | otherwise = 4
+    where
+    w = UText.ord c
+  utf16CharWidth c
+    | w < 0x10000 = 1
+    | otherwise = 2
+    where
+    w = UText.ord c
+
+-- As regexes are matched without whitespace delimitator, we need to check
+-- the reasonability of the match to actually be a word.
+isRangeValid :: Document -> Int -> Int -> Bool
+isRangeValid doc start end =
+  (start == 0 ||
+      isDifferent (doc ! (start - 1)) (doc ! start)) &&
+  (end == length doc ||
+      isDifferent (doc ! (end - 1)) (doc ! end))
+  where
+    charClass :: Char -> Char
+    charClass c
+      | Char.isLower c = 'l'
+      | Char.isUpper c = 'u'
+      | Char.isDigit c = 'd'
+      | otherwise = c
+    isDifferent :: Char -> Char -> Bool
+    isDifferent a b = charClass a /= charClass b
+
+-- True iff a is followed by whitespaces and b.
+isAdjacent :: Document -> Int -> Int -> Bool
+isAdjacent Document{..} a b =
+  b >= a && (firstNonAdjacent Array.! a >= b)
+
+isAdjacentSeparator :: Char -> Bool
+isAdjacentSeparator c = elem c [' ', '\t', '-']
+
+(!) :: Document -> Int -> Char
+(!) Document { indexable = s } ix = s Array.! ix
+
+length :: Document -> Int
+length Document { indexable = s } = Array.rangeSize $ Array.bounds s
+
+-- | Given a document and an offset (think Text.drop offset),
+-- returns a utf8 encoded substring of Document at that offset
+-- and 2 translation functions:
+--   rangeToText - given a range in the returned ByteString, gives
+--     a corresponding subrange of the Document as Text
+--   translateRange - given a start and a length of a range in the returned
+--     ByteString, gives a corresponding subrange in the Document as pair
+--     of (start, end) of Text.drop offsets
+{-# INLINE byteStringFromPos #-}
+-- if we don't inline we seem to pay for the tuple, there might be
+-- an easier way
+byteStringFromPos
+  :: Document
+  -> Int
+  -> ( ByteString
+     , (Int, Int) -> Text
+     , Int -> Int -> (Int, Int)
+     )
+byteStringFromPos
+  Document { rawInput = rawInput
+           , utf8Encoded = utf8Encoded
+           , tDropToBSDrop = tDropToBSDrop
+           , bsDropToTDrop = bsDropToTDrop
+           , tDropToUtf16Drop = tDropToUtf16Drop
+           }
+  position = (substring, rangeToText, translateRange)
+  where
+  -- See Note [Regular expressions and Text] to understand what's going
+  -- on here
+  utf8Position = tDropToBSDrop Array.! position
+  substring :: ByteString
+  substring = BS.drop utf8Position utf8Encoded
+  -- get a subrange of Text reusing the underlying buffer using
+  -- utf16 start and end positions
+  rangeToText :: (Int, Int) -> Text
+  rangeToText (-1, _) = ""
+  -- this is what regexec from Text.Regex.PCRE.ByteString does
+  rangeToText r = UText.takeWord16 (end16Pos - start16Pos) $
+    UText.dropWord16 start16Pos rawInput
+    where
+    start16Pos = tDropToUtf16Drop Array.! startPos
+    end16Pos = tDropToUtf16Drop Array.! endPos
+    (startPos, endPos) = uncurry translateRange r
+  -- from utf8 offset and length to Text character start and end position
+  translateRange :: Int -> Int -> (Int, Int)
+  translateRange !bsStart !bsLen = startPos `seq` endPos `seq` res
+    where
+    res = (startPos, endPos)
+    realBsStart = utf8Position + bsStart
+    realBsEnd = realBsStart + bsLen
+    startPos = bsDropToTDrop Array.! realBsStart
+    endPos = bsDropToTDrop Array.! realBsEnd
diff --git a/Duckling/Types/Stash.hs b/Duckling/Types/Stash.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Types/Stash.hs
@@ -0,0 +1,66 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+
+module Duckling.Types.Stash where
+
+import qualified Data.IntMap.Strict as IntMap
+import Data.IntMap.Strict (IntMap)
+import qualified Data.HashSet as HashSet
+import Data.HashSet (HashSet)
+import Data.Maybe
+import Prelude
+
+import Duckling.Types
+
+newtype Stash = Stash { getSet :: IntMap (HashSet Node) }
+
+filter :: (Node -> Bool) -> Stash -> Stash
+filter p Stash{..} = Stash (IntMap.map (HashSet.filter p) getSet)
+
+toPosOrderedList:: Stash -> [Node]
+toPosOrderedList Stash{..} = concatMap HashSet.toList $ IntMap.elems getSet
+
+toPosOrderedListFrom :: Stash -> Int -> [Node]
+toPosOrderedListFrom Stash{..} pos =
+  concatMap HashSet.toList $ maybeToList equal ++ IntMap.elems bigger
+  where
+  (_smaller, equal, bigger) = IntMap.splitLookup pos getSet
+  -- this is where we take advantage of the order
+
+empty :: Stash
+empty = Stash IntMap.empty
+
+fromList :: [Node] -> Stash
+fromList ns = Stash (IntMap.fromListWith HashSet.union $ map mkKV ns)
+  where
+  mkKV n@(Node { nodeRange = Range start _ }) = (start, HashSet.singleton n)
+
+union :: Stash -> Stash -> Stash
+union (Stash set1) (Stash set2) =
+  Stash (IntMap.unionWith HashSet.union set1 set2)
+
+-- Checks if two stashes have equal amount of Nodes on each position.
+-- Used to detect a fixpoint, because the Stashes are only growing.
+--
+-- Not proud of this, but the algorithm shouldn't use it as the termination
+-- condition, it should know when it stopped adding tokens
+sizeEqual :: Stash -> Stash -> Bool
+sizeEqual (Stash set1) (Stash set2) =
+  go (IntMap.toAscList set1) (IntMap.toAscList set2)
+  where
+  go [] [] = True
+  go [] (_:_) = False
+  go (_:_) [] = False
+  go ((k1, h1):rest1) ((k2, h2):rest2) =
+    k1 == k2 && HashSet.size h1 == HashSet.size h2 && go rest1 rest2
+
+null :: Stash -> Bool
+null (Stash set) = IntMap.null set
diff --git a/Duckling/Url/Corpus.hs b/Duckling/Url/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Url/Corpus.hs
@@ -0,0 +1,73 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Url.Corpus
+  ( corpus
+  , negativeCorpus
+  ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Testing.Types
+import Duckling.Url.Types
+
+corpus :: Corpus
+corpus = (testContext, allExamples)
+
+negativeCorpus :: NegativeCorpus
+negativeCorpus = (testContext, examples)
+  where
+    examples =
+      [ "foo"
+      , "MYHOST"
+      , "hey:42"
+      , "25"
+      ]
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (UrlData "http://www.bla.com" "bla.com")
+             [ "http://www.bla.com"
+             ]
+  , examples (UrlData "www.bla.com:8080/path" "bla.com")
+             [ "www.bla.com:8080/path"
+             ]
+  , examples (UrlData "https://myserver?foo=bar" "myserver")
+             [ "https://myserver?foo=bar"
+             ]
+  , examples (UrlData "cnn.com/info" "cnn.com")
+             [ "cnn.com/info"
+             ]
+  , examples (UrlData "bla.com/path/path?ext=%23&foo=bla" "bla.com")
+             [ "bla.com/path/path?ext=%23&foo=bla"
+             ]
+  , examples (UrlData "localhost" "localhost")
+             [ "localhost"
+             ]
+  , examples (UrlData "localhost:8000" "localhost")
+             [ "localhost:8000"
+             ]
+  , examples (UrlData "http://kimchi" "kimchi")
+             [ "http://kimchi"
+             ]
+  , examples (UrlData "https://500px.com:443/about" "500px.com")
+             [ "https://500px.com:443/about"
+             ]
+  , examples (UrlData "www2.foo-bar.net?foo=bar" "foo-bar.net")
+             [ "www2.foo-bar.net?foo=bar"
+             ]
+  , examples (UrlData "https://api.wit.ai/message?q=hi" "api.wit.ai")
+             [ "https://api.wit.ai/message?q=hi"
+             ]
+  , examples (UrlData "aMaZon.co.uk/?page=home" "amazon.co.uk")
+             [ "aMaZon.co.uk/?page=home"
+             ]
+  ]
diff --git a/Duckling/Url/Helpers.hs b/Duckling/Url/Helpers.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Url/Helpers.hs
@@ -0,0 +1,31 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Url.Helpers
+  ( url
+  ) where
+
+import Data.String
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+
+import qualified Duckling.Url.Types as TUrl
+import Duckling.Url.Types (UrlData(..))
+
+-- -----------------------------------------------------------------
+-- Patterns
+
+-- -----------------------------------------------------------------
+-- Production
+
+url :: Text -> Text -> UrlData
+url value domain = UrlData
+  {TUrl.value = value, TUrl.domain = Text.toLower domain}
diff --git a/Duckling/Url/Rules.hs b/Duckling/Url/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Url/Rules.hs
@@ -0,0 +1,64 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Url.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Regex.Types
+import Duckling.Types
+import Duckling.Url.Helpers
+
+ruleURL :: Rule
+ruleURL = Rule
+  { name = "url"
+  , pattern =
+    [ regex "((([a-zA-Z]+)://)?(w{2,3}[0-9]*\\.)?(([\\w_-]+\\.)+[a-z]{2,4})(:(\\d+))?(/[^?\\s#]*)?(\\?[^\\s#]+)?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m:_:_protocol:_:domain:_:_:_port:_path:_query:_)):
+       _) -> Just . Token Url $ url m domain
+      _ -> Nothing
+  }
+
+ruleLocalhost :: Rule
+ruleLocalhost = Rule
+  { name = "localhost"
+  , pattern =
+    [ regex "((([a-zA-Z]+)://)?localhost(:(\\d+))?(/[^?\\s#]*)?(\\?[^\\s#]+)?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m:_:_protocol:_:_port:_path:_query:_)):_) ->
+        Just . Token Url $ url m "localhost"
+      _ -> Nothing
+  }
+
+ruleLocalURL :: Rule
+ruleLocalURL = Rule
+  { name = "local url"
+  , pattern =
+    [ regex "(([a-zA-Z]+)://([\\w_-]+)(:(\\d+))?(/[^?\\s#]*)?(\\?[^\\s#]+)?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token RegexMatch (GroupMatch (m:_protocol:domain:_:_port:_path:_query:_)):
+       _) -> Just . Token Url $ url m domain
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleURL
+  , ruleLocalhost
+  , ruleLocalURL
+  ]
diff --git a/Duckling/Url/Types.hs b/Duckling/Url/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Url/Types.hs
@@ -0,0 +1,43 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Duckling.Url.Types where
+
+import Control.DeepSeq
+import Data.Aeson
+import Data.Hashable
+import Data.Maybe
+import Data.Monoid
+import Data.Text (Text)
+import GHC.Generics
+import Prelude
+
+import Duckling.Resolve (Resolve(..))
+
+data UrlData = UrlData
+  { value :: Text
+  , domain :: Text
+  }
+  deriving (Eq, Generic, Hashable, Ord, Show, NFData)
+
+instance Resolve UrlData where
+  type ResolvedValue UrlData = UrlData
+  resolve _ x = Just x
+
+instance ToJSON UrlData where
+  toJSON (UrlData value domain) = object
+    [ "value"  .= value
+    , "domain" .= domain
+    ]
diff --git a/Duckling/Volume/EN/Corpus.hs b/Duckling/Volume/EN/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/EN/Corpus.hs
@@ -0,0 +1,42 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.EN.Corpus
+  ( corpus ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Testing.Types
+import Duckling.Volume.Types
+
+corpus :: Corpus
+corpus = (testContext, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (VolumeValue Millilitre 250)
+             [ "250 milliliters"
+             , "250ml"
+             , "250 ml"
+             ]
+  , examples (VolumeValue Litre 2)
+             [ "2 liters" ]
+  , examples (VolumeValue Gallon 3)
+             [ "3 gallons"
+             , "3 gal"
+             ]
+  , examples (VolumeValue Hectolitre 3)
+             [ "3 hectoliters"
+             ]
+  , examples (VolumeValue Litre 0.5)
+             [ "half liter"
+             ]
+  ]
diff --git a/Duckling/Volume/EN/Rules.hs b/Duckling/Volume/EN/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/EN/Rules.hs
@@ -0,0 +1,51 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.EN.Rules
+  ( rules ) where
+
+import Data.Text (Text)
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Types
+import Duckling.Volume.Helpers
+import qualified Duckling.Volume.Types as TVolume
+
+ruleHalfLiter :: Rule
+ruleHalfLiter = Rule
+  { name = "half liter"
+  , pattern = [ regex "half lit(er|re)" ]
+  , prod = \_ -> Just . Token Volume . withUnit TVolume.Litre $ volume 0.5
+  }
+
+volumes :: [(Text, String, TVolume.Unit)]
+volumes = [ ("<latent vol> ml"    , "m(l|illilit(er|re)s?)", TVolume.Millilitre)
+          , ("<vol> hectoliters"  , "hectolit(er|re)s?"    , TVolume.Hectolitre)
+          , ("<vol> liters"       , "l(it(er|re)s?)?"      , TVolume.Litre)
+          , ("<latent vol> gallon", "gal(l?ons?)?"         , TVolume.Gallon)
+          ]
+
+ruleVolumes :: [Rule]
+ruleVolumes = map go volumes
+  where
+    go :: (Text, String, TVolume.Unit) -> Rule
+    go (name, regexPattern, u) = Rule
+      { name = name
+      , pattern = [ dimension Volume, regex regexPattern ]
+      , prod = \tokens -> case tokens of
+          (Token Volume vd:_) -> Just . Token Volume $ withUnit u vd
+          _ -> Nothing
+      }
+
+rules :: [Rule]
+rules = ruleHalfLiter:ruleVolumes
diff --git a/Duckling/Volume/ES/Corpus.hs b/Duckling/Volume/ES/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/ES/Corpus.hs
@@ -0,0 +1,44 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.ES.Corpus
+  ( corpus ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Volume.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = ES}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (VolumeValue Millilitre 250)
+             [ "250 mililitros"
+             , "250ml"
+             , "250 ml"
+             ]
+  , examples (VolumeValue Litre 2)
+             [ "2 litros"
+             ]
+  , examples (VolumeValue Gallon 3)
+             [ "3 galón"
+             ]
+  , examples (VolumeValue Hectolitre 3)
+             [ "3 hectolitros"
+             ]
+  , examples (VolumeValue Litre 0.5)
+             [ "medio litro"
+             ]
+  ]
diff --git a/Duckling/Volume/ES/Rules.hs b/Duckling/Volume/ES/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/ES/Rules.hs
@@ -0,0 +1,91 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.ES.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Types
+import Duckling.Volume.Helpers
+import qualified Duckling.Volume.Types as TVolume
+
+ruleLatentVolMl :: Rule
+ruleLatentVolMl = Rule
+  { name = "<latent vol> ml"
+  , pattern =
+    [ dimension Volume
+    , regex "m(l|ililitros?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Millilitre vd
+      _ -> Nothing
+  }
+
+ruleVolHectoliters :: Rule
+ruleVolHectoliters = Rule
+  { name = "<vol> hectoliters"
+  , pattern =
+    [ dimension Volume
+    , regex "hectolitros?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Hectolitre vd
+      _ -> Nothing
+  }
+
+ruleVolLiters :: Rule
+ruleVolLiters = Rule
+  { name = "<vol> liters"
+  , pattern =
+    [ dimension Volume
+    , regex "l(itros?)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Litre vd
+      _ -> Nothing
+  }
+
+ruleHalfLiter :: Rule
+ruleHalfLiter = Rule
+  { name = "half liter"
+  , pattern =
+    [ regex "medio litros?"
+    ]
+  , prod = \_ -> Just . Token Volume . withUnit TVolume.Litre $ volume 0.5
+  }
+
+ruleLatentVolGallon :: Rule
+ruleLatentVolGallon = Rule
+  { name = "<latent vol> gallon"
+  , pattern =
+    [ dimension Volume
+    , regex "gal(o|\x00f3)ne?s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Gallon vd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleHalfLiter
+  , ruleLatentVolGallon
+  , ruleLatentVolMl
+  , ruleVolHectoliters
+  , ruleVolLiters
+  ]
diff --git a/Duckling/Volume/FR/Corpus.hs b/Duckling/Volume/FR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/FR/Corpus.hs
@@ -0,0 +1,46 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.FR.Corpus
+  ( corpus ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Volume.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = FR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (VolumeValue Millilitre 250)
+             [ "250 millilitres"
+             , "250ml"
+             , "250 ml"
+             ]
+  , examples (VolumeValue Litre 2)
+             [ "2 litres"
+             ]
+  , examples (VolumeValue Gallon 3)
+             [ "3 gallons"
+             , "3 gal"
+             ]
+  , examples (VolumeValue Hectolitre 3)
+             [ "3 hectolitres"
+             ]
+  , examples (VolumeValue Litre 0.5)
+             [ "demi-litre"
+             , "demi litre"
+             ]
+  ]
diff --git a/Duckling/Volume/FR/Rules.hs b/Duckling/Volume/FR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/FR/Rules.hs
@@ -0,0 +1,91 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.FR.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Types
+import Duckling.Volume.Helpers
+import qualified Duckling.Volume.Types as TVolume
+
+ruleLatentVolMl :: Rule
+ruleLatentVolMl = Rule
+  { name = "<latent vol> ml"
+  , pattern =
+    [ dimension Volume
+    , regex "m(l|illilitres?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Millilitre vd
+      _ -> Nothing
+  }
+
+ruleVolHectoliters :: Rule
+ruleVolHectoliters = Rule
+  { name = "<vol> hectoliters"
+  , pattern =
+    [ dimension Volume
+    , regex "hectolitres?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Hectolitre vd
+      _ -> Nothing
+  }
+
+ruleVolLiters :: Rule
+ruleVolLiters = Rule
+  { name = "<vol> liters"
+  , pattern =
+    [ dimension Volume
+    , regex "l(itres?)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Litre vd
+      _ -> Nothing
+  }
+
+ruleHalfLiter :: Rule
+ruleHalfLiter = Rule
+  { name = "half liter"
+  , pattern =
+    [ regex "demi( |-)?litre"
+    ]
+  , prod = \_ -> Just . Token Volume . withUnit TVolume.Litre $ volume 0.5
+  }
+
+ruleLatentVolGallon :: Rule
+ruleLatentVolGallon = Rule
+  { name = "<latent vol> gallon"
+  , pattern =
+    [ dimension Volume
+    , regex "gal(l?ons?)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Gallon vd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleHalfLiter
+  , ruleLatentVolGallon
+  , ruleLatentVolMl
+  , ruleVolHectoliters
+  , ruleVolLiters
+  ]
diff --git a/Duckling/Volume/GA/Corpus.hs b/Duckling/Volume/GA/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/GA/Corpus.hs
@@ -0,0 +1,39 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.GA.Corpus
+  ( corpus ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Volume.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = GA}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (VolumeValue Millilitre 250)
+             [ "250 millilítir"
+             , "250 millilitir"
+             , "250ml"
+             , "250 ml"
+             ]
+  , examples (VolumeValue Litre 2)
+             [ "2 lítir"
+             ]
+  , examples (VolumeValue Gallon 5)
+             [ "5 galúin"
+             ]
+  ]
diff --git a/Duckling/Volume/GA/Rules.hs b/Duckling/Volume/GA/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/GA/Rules.hs
@@ -0,0 +1,95 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.GA.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Types
+import Duckling.Volume.Helpers
+import qualified Duckling.Volume.Types as TVolume
+
+ruleLatentVolMl :: Rule
+ruleLatentVolMl = Rule
+  { name = "<latent vol> ml"
+  , pattern =
+    [ dimension Volume
+    , regex "m(l\\.?|h?illil(\x00ed|i)t(ea|i)r)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Millilitre vd
+      _ -> Nothing
+  }
+
+ruleLatentVolKl :: Rule
+ruleLatentVolKl = Rule
+  { name = "<latent vol> kl"
+  , pattern =
+    [ dimension Volume
+    , regex "(kl\\.?|g?ch?illil(\x00ed|i)t(ea|i)r)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Millilitre vd
+      _ -> Nothing
+  }
+
+ruleVolHeictiltir :: Rule
+ruleVolHeictiltir = Rule
+  { name = "<vol> heictilítir"
+  , pattern =
+    [ dimension Volume
+    , regex "heictil(\x00ed|i)t(ea|i)r"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Hectolitre vd
+      _ -> Nothing
+  }
+
+ruleVolLtear :: Rule
+ruleVolLtear = Rule
+  { name = "<vol> lítear"
+  , pattern =
+    [ dimension Volume
+    , regex "(l(\x00ed|i)t(ea|i)r|l\\.?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Litre vd
+      _ -> Nothing
+  }
+
+ruleLatentVolGaln :: Rule
+ruleLatentVolGaln = Rule
+  { name = "<latent vol> galún"
+  , pattern =
+    [ dimension Volume
+    , regex "n?gh?al(\x00fa|u)i?n"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Gallon vd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleLatentVolGaln
+  , ruleLatentVolKl
+  , ruleLatentVolMl
+  , ruleVolHeictiltir
+  , ruleVolLtear
+  ]
diff --git a/Duckling/Volume/HR/Corpus.hs b/Duckling/Volume/HR/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/HR/Corpus.hs
@@ -0,0 +1,44 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.HR.Corpus
+  ( corpus ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Types
+import Duckling.Volume.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = HR}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (VolumeValue Millilitre 250)
+             [ "250 mililitara"
+             , "250ml"
+             , "250 ml"
+             ]
+  , examples (VolumeValue Litre 2)
+             [ "2 litre"
+             ]
+  , examples (VolumeValue Gallon 3)
+             [ "3 galona"
+             , "3 gal"
+             ]
+  , examples (VolumeValue Hectolitre 3)
+             [ "3 hektolitra"
+             ]
+  , examples (VolumeValue Litre 0.5)
+             [ "pola litre"
+             ]
+  ]
diff --git a/Duckling/Volume/HR/Rules.hs b/Duckling/Volume/HR/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/HR/Rules.hs
@@ -0,0 +1,90 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.HR.Rules
+  ( rules ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Types
+import Duckling.Volume.Helpers
+import qualified Duckling.Volume.Types as TVolume
+
+ruleLatentVolMl :: Rule
+ruleLatentVolMl = Rule
+  { name = "<latent vol> ml"
+  , pattern =
+    [ dimension Volume
+    , regex "m(l|ililita?ra?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Millilitre vd
+      _ -> Nothing
+  }
+
+ruleVolHektolitar :: Rule
+ruleVolHektolitar = Rule
+  { name = "<vol> hektolitar"
+  , pattern =
+    [ dimension Volume
+    , regex "hektolita?ra?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Hectolitre vd
+      _ -> Nothing
+  }
+
+ruleVolLitra :: Rule
+ruleVolLitra = Rule
+  { name = "<vol> litra"
+  , pattern =
+    [ dimension Volume
+    , regex "l(it(a)?r(a|e)?)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Litre vd
+      _ -> Nothing
+  }
+
+rulePolaLitre :: Rule
+rulePolaLitre = Rule
+  { name = "pola litre"
+  , pattern =
+    [ regex "pola litre"
+    ]
+  , prod = \_ -> Just . Token Volume . withUnit TVolume.Litre $ volume 0.5
+  }
+
+ruleLatentVolGalon :: Rule
+ruleLatentVolGalon = Rule
+  { name = "<latent vol> galon"
+  , pattern =
+    [ dimension Volume
+    , regex "gal(ona?)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Gallon vd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleLatentVolGalon
+  , ruleLatentVolMl
+  , rulePolaLitre
+  , ruleVolHektolitar
+  , ruleVolLitra
+  ]
diff --git a/Duckling/Volume/Helpers.hs b/Duckling/Volume/Helpers.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/Helpers.hs
@@ -0,0 +1,29 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Volume.Helpers
+  ( volume
+  , withUnit
+  ) where
+
+import Prelude
+
+import Duckling.Volume.Types (VolumeData(..))
+import qualified Duckling.Volume.Types as TVolume
+
+-- -----------------------------------------------------------------
+-- Patterns
+
+-- -----------------------------------------------------------------
+-- Production
+
+volume :: Double -> VolumeData
+volume x = VolumeData {TVolume.value = x, TVolume.unit = Nothing}
+
+withUnit :: TVolume.Unit -> VolumeData -> VolumeData
+withUnit value vd = vd {TVolume.unit = Just value}
diff --git a/Duckling/Volume/IT/Corpus.hs b/Duckling/Volume/IT/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/IT/Corpus.hs
@@ -0,0 +1,46 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.IT.Corpus
+  ( corpus ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Volume.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = IT}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (VolumeValue Millilitre 250)
+             [ "250 millilitri"
+             , "250ml"
+             , "250 ml"
+             ]
+  , examples (VolumeValue Litre 2)
+             [ "2 litri"
+             , "2l"
+             ]
+  , examples (VolumeValue Gallon 3)
+             [ "3 galloni"
+             , "3 gal"
+             ]
+  , examples (VolumeValue Hectolitre 3)
+             [ "3 ettolitri"
+             ]
+  , examples (VolumeValue Litre 0.5)
+             [ "mezzo litro"
+             ]
+  ]
diff --git a/Duckling/Volume/IT/Rules.hs b/Duckling/Volume/IT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/IT/Rules.hs
@@ -0,0 +1,89 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.IT.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Types
+import Duckling.Volume.Helpers
+import qualified Duckling.Volume.Types as TVolume
+
+ruleLatentVolMl :: Rule
+ruleLatentVolMl = Rule
+  { name = "<latent vol> ml"
+  , pattern =
+    [ dimension Volume
+    , regex "m(l|illilitr(i|o))"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Millilitre vd
+      _ -> Nothing
+  }
+
+ruleVolHectoliters :: Rule
+ruleVolHectoliters = Rule
+  { name = "<vol> hectoliters"
+  , pattern =
+    [ dimension Volume
+    , regex "ettolitr(i|o)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Hectolitre vd
+      _ -> Nothing
+  }
+
+ruleVolLiters :: Rule
+ruleVolLiters = Rule
+  { name = "<vol> liters"
+  , pattern =
+    [ dimension Volume
+    , regex "l(itr(i|o))?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) -> Just . Token Volume $ withUnit TVolume.Litre vd
+      _ -> Nothing
+  }
+
+ruleHalfLiter :: Rule
+ruleHalfLiter = Rule
+  { name = "half liter"
+  , pattern =
+    [ regex "mezzo litro"
+    ]
+  , prod = \_ -> Just . Token Volume . withUnit TVolume.Litre $ volume 0.5
+  }
+
+ruleLatentVolGallon :: Rule
+ruleLatentVolGallon = Rule
+  { name = "<latent vol> gallon"
+  , pattern =
+    [ dimension Volume
+    , regex "gal(lon(e|i))?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) -> Just . Token Volume $ withUnit TVolume.Gallon vd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleHalfLiter
+  , ruleLatentVolGallon
+  , ruleLatentVolMl
+  , ruleVolHectoliters
+  , ruleVolLiters
+  ]
diff --git a/Duckling/Volume/KO/Corpus.hs b/Duckling/Volume/KO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/KO/Corpus.hs
@@ -0,0 +1,49 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.KO.Corpus
+  ( corpus ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Volume.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = KO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (VolumeValue Millilitre 250)
+             [ "250 밀리리터"
+             , "250 미리리터"
+             , "이백오십미리리터"
+             , "250ml"
+             , "250 ml"
+             ]
+  , examples (VolumeValue Litre 2)
+             [ "2 리터"
+             , "이리터"
+             ]
+  , examples (VolumeValue Gallon 3)
+             [ "3 갤론"
+             , "삼 갤론"
+             ]
+  , examples (VolumeValue Hectolitre 3)
+             [ "3 헥토리터"
+             , "삼 헥토리터"
+             ]
+  , examples (VolumeValue Litre 0.5)
+             [ "반 리터"
+             ]
+  ]
diff --git a/Duckling/Volume/KO/Rules.hs b/Duckling/Volume/KO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/KO/Rules.hs
@@ -0,0 +1,81 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.KO.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Types
+import Duckling.Volume.Helpers
+import qualified Duckling.Volume.Types as TVolume
+
+ruleLatentVolMl :: Rule
+ruleLatentVolMl = Rule
+  { name = "<latent vol> ml"
+  , pattern =
+    [ dimension Volume
+    , regex "ml|(\xbc00|\xbbf8)\xb9ac\xb9ac\xd130"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Millilitre vd
+      _ -> Nothing
+  }
+
+ruleVolHectoliters :: Rule
+ruleVolHectoliters = Rule
+  { name = "<vol> hectoliters"
+  , pattern =
+    [ dimension Volume
+    , regex "(\xd575|\xd5e5)\xd1a0\xb9ac\xd130"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Hectolitre vd
+      _ -> Nothing
+  }
+
+ruleVolLiters :: Rule
+ruleVolLiters = Rule
+  { name = "<vol> liters"
+  , pattern =
+    [ dimension Volume
+    , regex "l|\xb9ac\xd130"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Litre vd
+      _ -> Nothing
+  }
+
+ruleLatentVolGallon :: Rule
+ruleLatentVolGallon = Rule
+  { name = "<latent vol> gallon"
+  , pattern =
+    [ dimension Volume
+    , regex "gal(l?ons?)?|\xac24(\xb7f0|\xb860)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Gallon vd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleLatentVolGallon
+  , ruleLatentVolMl
+  , ruleVolHectoliters
+  , ruleVolLiters
+  ]
diff --git a/Duckling/Volume/NL/Corpus.hs b/Duckling/Volume/NL/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/NL/Corpus.hs
@@ -0,0 +1,46 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.NL.Corpus
+  ( corpus ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Volume.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = NL}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (VolumeValue Millilitre 250)
+             [ "250 mililiter"
+             , "250ml"
+             , "250 ml"
+             ]
+  , examples (VolumeValue Litre 2)
+             [ "2 liter"
+             , "2l"
+             , "2 l"
+             ]
+  , examples (VolumeValue Gallon 3)
+             [ "3 gallon"
+             ]
+  , examples (VolumeValue Hectolitre 3)
+             [ "3 hectoliter"
+             ]
+  , examples (VolumeValue Litre 0.5)
+             [ "halve liter"
+             ]
+  ]
diff --git a/Duckling/Volume/NL/Rules.hs b/Duckling/Volume/NL/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/NL/Rules.hs
@@ -0,0 +1,91 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.NL.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Types
+import Duckling.Volume.Helpers
+import qualified Duckling.Volume.Types as TVolume
+
+ruleLatentVolMl :: Rule
+ruleLatentVolMl = Rule
+  { name = "<latent vol> ml"
+  , pattern =
+    [ dimension Volume
+    , regex "m(ili)?l(iter)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Millilitre vd
+      _ -> Nothing
+  }
+
+ruleVolHectoliters :: Rule
+ruleVolHectoliters = Rule
+  { name = "<vol> hectoliters"
+  , pattern =
+    [ dimension Volume
+    , regex "hectoliter?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Hectolitre vd
+      _ -> Nothing
+  }
+
+ruleVolLiters :: Rule
+ruleVolLiters = Rule
+  { name = "<vol> liters"
+  , pattern =
+    [ dimension Volume
+    , regex "l(iter)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Litre vd
+      _ -> Nothing
+  }
+
+ruleHalfLiter :: Rule
+ruleHalfLiter = Rule
+  { name = "half liter"
+  , pattern =
+    [ regex "halve liter?"
+    ]
+  , prod = \_ -> Just . Token Volume . withUnit TVolume.Litre $ volume 0.5
+  }
+
+ruleLatentVolGallon :: Rule
+ruleLatentVolGallon = Rule
+  { name = "<latent vol> gallon"
+  , pattern =
+    [ dimension Volume
+    , regex "gallon?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Gallon vd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleHalfLiter
+  , ruleLatentVolGallon
+  , ruleLatentVolMl
+  , ruleVolHectoliters
+  , ruleVolLiters
+  ]
diff --git a/Duckling/Volume/PT/Corpus.hs b/Duckling/Volume/PT/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/PT/Corpus.hs
@@ -0,0 +1,47 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.PT.Corpus
+  ( corpus ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Volume.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = PT}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (VolumeValue Millilitre 250)
+             [ "250 mililitros"
+             , "250ml"
+             , "250 ml"
+             ]
+  , examples (VolumeValue Litre 2)
+             [ "2 litros"
+             ]
+  , examples (VolumeValue Gallon 1)
+             [ "1 galão"
+             ]
+  , examples (VolumeValue Gallon 3)
+             [ "3 galões"
+             ]
+  , examples (VolumeValue Hectolitre 3)
+             [ "3 hectolitros"
+             ]
+  , examples (VolumeValue Litre 0.5)
+             [ "meio litro"
+             ]
+  ]
diff --git a/Duckling/Volume/PT/Rules.hs b/Duckling/Volume/PT/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/PT/Rules.hs
@@ -0,0 +1,91 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.PT.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Types
+import Duckling.Volume.Helpers
+import qualified Duckling.Volume.Types as TVolume
+
+ruleLatentVolMl :: Rule
+ruleLatentVolMl = Rule
+  { name = "<latent vol> ml"
+  , pattern =
+    [ dimension Volume
+    , regex "m(l|ililitros?)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Millilitre vd
+      _ -> Nothing
+  }
+
+ruleVolHectoliters :: Rule
+ruleVolHectoliters = Rule
+  { name = "<vol> hectoliters"
+  , pattern =
+    [ dimension Volume
+    , regex "hectolitros?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Hectolitre vd
+      _ -> Nothing
+  }
+
+ruleVolLiters :: Rule
+ruleVolLiters = Rule
+  { name = "<vol> liters"
+  , pattern =
+    [ dimension Volume
+    , regex "l(itros?)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Litre vd
+      _ -> Nothing
+  }
+
+ruleHalfLiter :: Rule
+ruleHalfLiter = Rule
+  { name = "half liter"
+  , pattern =
+    [ regex "meio litros?"
+    ]
+  , prod = \_ -> Just . Token Volume . withUnit TVolume.Litre $ volume 0.5
+  }
+
+ruleLatentVolGallon :: Rule
+ruleLatentVolGallon = Rule
+  { name = "<latent vol> gallon"
+  , pattern =
+    [ dimension Volume
+    , regex "gal(a|\x00e3|o|\x00f5)o?e?s?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Gallon vd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleHalfLiter
+  , ruleLatentVolGallon
+  , ruleLatentVolMl
+  , ruleVolHectoliters
+  , ruleVolLiters
+  ]
diff --git a/Duckling/Volume/RO/Corpus.hs b/Duckling/Volume/RO/Corpus.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/RO/Corpus.hs
@@ -0,0 +1,48 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.RO.Corpus
+  ( corpus ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Volume.Types
+import Duckling.Testing.Types
+
+corpus :: Corpus
+corpus = (testContext {lang = RO}, allExamples)
+
+allExamples :: [Example]
+allExamples = concat
+  [ examples (VolumeValue Millilitre 250)
+             [ "250 mililitri"
+             , "250ml"
+             , "250 ml"
+             ]
+  , examples (VolumeValue Litre 2)
+             [ "2 litri"
+             , "2 l"
+             , "2l"
+             ]
+  , examples (VolumeValue Gallon 3)
+             [ "3 galoane"
+             , "3 gal"
+             ]
+  , examples (VolumeValue Hectolitre 3)
+             [ "3 hectolitri"
+             ]
+  , examples (VolumeValue Litre 0.5)
+             [ "jumatate de litru"
+             , "jumătate de litru"
+             ]
+  ]
diff --git a/Duckling/Volume/RO/Rules.hs b/Duckling/Volume/RO/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/RO/Rules.hs
@@ -0,0 +1,91 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.RO.Rules
+  ( rules ) where
+
+import Prelude
+import Data.String
+
+import Duckling.Dimensions.Types
+import Duckling.Types
+import Duckling.Volume.Helpers
+import qualified Duckling.Volume.Types as TVolume
+
+ruleLatentVolMl :: Rule
+ruleLatentVolMl = Rule
+  { name = "<latent vol> ml"
+  , pattern =
+    [ dimension Volume
+    , regex "m(ililitr[ui]|l)"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Millilitre vd
+      _ -> Nothing
+  }
+
+ruleVolHectoliters :: Rule
+ruleVolHectoliters = Rule
+  { name = "<vol> hectoliters"
+  , pattern =
+    [ dimension Volume
+    , regex "hectolitr[ui]"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Hectolitre vd
+      _ -> Nothing
+  }
+
+ruleVolLiters :: Rule
+ruleVolLiters = Rule
+  { name = "<vol> liters"
+  , pattern =
+    [ dimension Volume
+    , regex "l(itr[ui])?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Litre vd
+      _ -> Nothing
+  }
+
+ruleHalfLiter :: Rule
+ruleHalfLiter = Rule
+  { name = "half liter"
+  , pattern =
+    [ regex "jum(a|\x0103)tate de litr[ui]"
+    ]
+  , prod = \_ -> Just . Token Volume . withUnit TVolume.Litre $ volume 0.5
+  }
+
+ruleLatentVolGalon :: Rule
+ruleLatentVolGalon = Rule
+  { name = "<latent vol> galon"
+  , pattern =
+    [ dimension Volume
+    , regex "gal(oane|on)?"
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Volume vd:_) ->
+        Just . Token Volume $ withUnit TVolume.Gallon vd
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleHalfLiter
+  , ruleLatentVolGalon
+  , ruleLatentVolMl
+  , ruleVolHectoliters
+  , ruleVolLiters
+  ]
diff --git a/Duckling/Volume/Rules.hs b/Duckling/Volume/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/Rules.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Volume.Rules
+  ( rules
+  ) where
+
+import Data.String
+import Prelude
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.Types (NumeralData(..))
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Types
+import Duckling.Volume.Helpers
+
+ruleNumeralAsVolume :: Rule
+ruleNumeralAsVolume = Rule
+  { name = "number as volume"
+  , pattern =
+    [ dimension Numeral
+    ]
+  , prod = \tokens -> case tokens of
+      (Token Numeral NumeralData {TNumeral.value = v}:_) ->
+        Just . Token Volume $ volume v
+      _ -> Nothing
+  }
+
+rules :: [Rule]
+rules =
+  [ ruleNumeralAsVolume
+  ]
diff --git a/Duckling/Volume/Types.hs b/Duckling/Volume/Types.hs
new file mode 100644
--- /dev/null
+++ b/Duckling/Volume/Types.hs
@@ -0,0 +1,61 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Duckling.Volume.Types where
+
+import Control.DeepSeq
+import Data.Aeson
+import Data.Hashable
+import Data.Text (Text)
+import qualified Data.Text as Text
+import GHC.Generics
+import Prelude
+
+import Duckling.Resolve (Resolve (..))
+
+data Unit
+  = Gallon
+  | Hectolitre
+  | Litre
+  | Millilitre
+  deriving (Eq, Generic, Hashable, Ord, Show, NFData)
+
+instance ToJSON Unit where
+  toJSON = String . Text.toLower . Text.pack . show
+
+data VolumeData = VolumeData
+  { unit :: Maybe Unit
+  , value :: Double
+  }
+  deriving (Eq, Generic, Hashable, Ord, Show, NFData)
+
+instance Resolve VolumeData where
+  type ResolvedValue VolumeData = VolumeValue
+  resolve _ VolumeData {unit = Nothing} = Nothing
+  resolve _ VolumeData {unit = Just unit, value} = Just VolumeValue
+    {vValue = value, vUnit = unit}
+
+data VolumeValue = VolumeValue
+  { vUnit :: Unit
+  , vValue :: Double
+  }
+  deriving (Eq, Ord, Show)
+
+instance ToJSON VolumeValue where
+  toJSON (VolumeValue unit value) = object
+    [ "type" .= ("value" :: Text)
+    , "value" .= value
+    , "unit" .= unit
+    ]
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+BSD License
+
+For Duckling software
+
+Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+ * Neither the name Facebook nor the names of its contributors may be used to
+   endorse or promote products derived from this software without specific
+   prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/PATENTS b/PATENTS
new file mode 100644
--- /dev/null
+++ b/PATENTS
@@ -0,0 +1,33 @@
+Additional Grant of Patent Rights Version 2
+
+"Software" means the Duckling software contributed by Facebook, Inc.
+
+Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software
+("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable
+(subject to the termination provision below) license under any Necessary
+Claims, to make, have made, use, sell, offer to sell, import, and otherwise
+transfer the Software. For avoidance of doubt, no license is granted under
+Facebook’s rights in any patent claims that are infringed by (i) modifications
+to the Software made by you or any third party or (ii) the Software in
+combination with any software or other technology.
+
+The license granted hereunder will terminate, automatically and without notice,
+if you (or any of your subsidiaries, corporate affiliates or agents) initiate
+directly or indirectly, or take a direct financial interest in, any Patent
+Assertion: (i) against Facebook or any of its subsidiaries or corporate
+affiliates, (ii) against any party if such Patent Assertion arises in whole or
+in part from any software, technology, product or service of Facebook or any of
+its subsidiaries or corporate affiliates, or (iii) against any party relating
+to the Software. Notwithstanding the foregoing, if Facebook or any of its
+subsidiaries or corporate affiliates files a lawsuit alleging patent
+infringement against you in the first instance, and you respond by filing a
+patent infringement counterclaim in that lawsuit against that party that is
+unrelated to the Software, the license granted hereunder will not terminate
+under section (i) of this paragraph due to such counterclaim.
+
+A "Necessary Claim" is a claim of a patent owned by Facebook that is
+necessarily infringed by the Software standing alone.
+
+A "Patent Assertion" is any lawsuit or other action alleging direct, indirect,
+or contributory infringement or inducement to infringe any patent, including a
+cross-claim or counterclaim.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,95 @@
+![Duckling Logo](https://github.com/facebookincubator/duckling/raw/master/logo.png)
+
+# Duckling [![Build Status](https://travis-ci.org/facebookincubator/duckling.svg?branch=master)](https://travis-ci.org/facebookincubator/duckling)
+Duckling is a Haskell library that parses text into structured data.
+
+```
+"the first Tuesday of October"
+=> {"value":"2017-10-03T00:00:00.000-07:00","grain":"day"}
+```
+
+## Requirements
+A Haskell environment is required. We recommend using
+[stack](https://haskell-lang.org/get-started).
+
+On macOS you'll need to install PCRE development headers.
+The easiest way to do that is with [Homebrew](https://brew.sh/):
+```
+brew install pcre
+```
+
+## Quickstart
+To compile and run the binary:
+```
+$ stack build
+$ stack exec duckling-example-exe
+```
+The first time you run it, it will download all required packages.
+
+This runs a basic HTTP server. Example request:
+```
+$ curl -XPOST http://0.0.0.0:8000/parse --data 'text=tomorrow at eight'
+```
+
+See `exe/ExampleMain.hs` for an example on how to integrate Duckling in your
+project.
+
+## Supported dimensions
+Duckling supports many languages, but most don't support all dimensions yet
+(we need your help!).
+
+| Dimension | Example input | Example value output
+| --------- | ------------- | --------------------
+| `AmountOfMoney` | "42€" | `{"value":42,"type":"value","unit":"EUR"}`
+| `Distance` | "6 miles" | `{"value":6,"type":"value","unit":"mile"}`
+| `Duration` | "3 mins" | `{"value":3,"minute":3,"unit":"minute","normalized":{"value":180,"unit":"second"}}`
+| `Email` | "duckling-team@fb.com" | `{"value":"duckling-team@fb.com"}`
+| `Numeral` | "eighty eight" | `{"value":88,"type":"value"}`
+| `Ordinal` | "33rd" | `{"value":33,"type":"value"}`
+| `PhoneNumber` | "+1 (650) 123-4567" | `{"value":"(+1) 6501234567"}`
+| `Quantity` | "3 cups of sugar" | `{"value":3,"type":"value","product":"sugar","unit":"cup"}`
+| `Temperature` | "80F" | `{"value":80,"type":"value","unit":"fahrenheit"}`
+| `Time` | "today at 9am" | `{"values":[{"value":"2016-12-14T09:00:00.000-08:00","grain":"hour","type":"value"}],"value":"2016-12-14T09:00:00.000-08:00","grain":"hour","type":"value"}`
+| `Url` | "https://api.wit.ai/message?q=hi" | `{"value":"https://api.wit.ai/message?q=hi","domain":"api.wit.ai"}`
+| `Volume` | "4 gallons" | `{"value":4,"type":"value","unit":"gallon"}`
+
+## Extending Duckling
+To regenerate the classifiers and run the test suite:
+```
+$ stack build :duckling-regen-exe && stack exec duckling-regen-exe && stack test
+```
+
+It's important to regenerate the classifiers after updating the code and before
+running the test suite.
+
+To extend Duckling's support for a dimension in a given language, typically 2
+files need to be updated:
+* `Duckling/<dimension>/<language>/Rules.hs`
+* `Duckling/<dimension>/<language>/Corpus.hs`
+
+Rules have a name, a pattern and a production.
+Patterns are used to perform character-level matching (regexes on input) and
+concept-level matching (predicates on tokens).
+Productions are arbitrary functions that take a list of tokens and return a new
+token.
+
+The corpus (resp. negative corpus) is a list of examples that should (resp.
+shouldn't) parse. The reference time for the corpus is Tuesday Feb 12, 2013 at
+4:30am.
+
+`Duckling.Debug` provides a few debugging tools:
+```
+> :l Duckling.Debug
+> debug EN "in two minutes" [This Time]
+in|within|after <duration> (in two minutes)
+-- regex (in)
+-- <integer> <unit-of-duration> (two minutes)
+-- -- integer (0..19) (two)
+-- -- -- regex (two)
+-- -- minute (grain) (minutes)
+-- -- -- regex (minutes)
+[Entity {dim = "time", body = "in two minutes", value = "{\"values\":[{\"value\":\"2013-02-12T04:32:00.000-02:00\",\"grain\":\"second\",\"type\":\"value\"}],\"value\":\"2013-02-12T04:32:00.000-02:00\",\"grain\":\"second\",\"type\":\"value\"}", start = 0, end = 14}]
+```
+
+## License
+Duckling is BSD-licensed. We also provide an additional patent grant.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/duckling.cabal b/duckling.cabal
new file mode 100644
--- /dev/null
+++ b/duckling.cabal
@@ -0,0 +1,732 @@
+name:                duckling
+version:             0.1.0.0
+synopsis:            A Haskell library for parsing text into structured data.
+description:
+  Duckling is a library for parsing text into structured data.
+homepage:            https://github.com/facebookincubator/duckling#readme
+bug-reports:         https://github.com/facebookincubator/duckling/issues
+license:             OtherLicense
+license-files:       LICENSE,PATENTS
+author:              Facebook, Inc.
+maintainer:          duckling-team@fb.com
+copyright:           Copyright (c) 2014-present, Facebook, Inc.
+category:            Systems
+build-type:          Simple
+stability:           alpha
+cabal-version:       >=1.10
+tested-with:
+  GHC==8.0.2
+
+extra-source-files:    README.md
+                     , PATENTS
+
+library
+  exposed-modules:     Duckling.Core
+                     , Duckling.Debug
+                     , Duckling.Testing.Types
+  -- ------------------------------------------------------------------
+  -- Core
+                     , Duckling.Api
+                     , Duckling.Engine
+                     , Duckling.Engine.Regex
+                     , Duckling.Lang
+                     , Duckling.Resolve
+                     , Duckling.Types
+                     , Duckling.Types.Document
+                     , Duckling.Types.Stash
+
+  -- ------------------------------------------------------------------
+  -- Rules
+                     , Duckling.Rules
+                     , Duckling.Rules.Common
+                     , Duckling.Rules.AR
+                     , Duckling.Rules.DA
+                     , Duckling.Rules.DE
+                     , Duckling.Rules.EN
+                     , Duckling.Rules.ES
+                     , Duckling.Rules.ET
+                     , Duckling.Rules.FR
+                     , Duckling.Rules.GA
+                     , Duckling.Rules.HE
+                     , Duckling.Rules.HR
+                     , Duckling.Rules.ID
+                     , Duckling.Rules.IT
+                     , Duckling.Rules.JA
+                     , Duckling.Rules.KO
+                     , Duckling.Rules.MY
+                     , Duckling.Rules.NB
+                     , Duckling.Rules.NL
+                     , Duckling.Rules.PL
+                     , Duckling.Rules.PT
+                     , Duckling.Rules.RO
+                     , Duckling.Rules.RU
+                     , Duckling.Rules.SV
+                     , Duckling.Rules.TR
+                     , Duckling.Rules.UK
+                     , Duckling.Rules.VI
+                     , Duckling.Rules.ZH
+
+  -- ------------------------------------------------------------------
+  -- Ranking
+                     , Duckling.Ranking.Types
+                     , Duckling.Ranking.Extraction
+                     , Duckling.Ranking.Rank
+                     , Duckling.Ranking.Classifiers
+                     , Duckling.Ranking.Classifiers.DA
+                     , Duckling.Ranking.Classifiers.DE
+                     , Duckling.Ranking.Classifiers.EN
+                     , Duckling.Ranking.Classifiers.ES
+                     , Duckling.Ranking.Classifiers.FR
+                     , Duckling.Ranking.Classifiers.GA
+                     , Duckling.Ranking.Classifiers.HE
+                     , Duckling.Ranking.Classifiers.HR
+                     , Duckling.Ranking.Classifiers.IT
+                     , Duckling.Ranking.Classifiers.KO
+                     , Duckling.Ranking.Classifiers.NB
+                     , Duckling.Ranking.Classifiers.PL
+                     , Duckling.Ranking.Classifiers.PT
+                     , Duckling.Ranking.Classifiers.RO
+                     , Duckling.Ranking.Classifiers.SV
+                     , Duckling.Ranking.Classifiers.ZH
+                     , Duckling.Ranking.Classifiers.AR
+                     , Duckling.Ranking.Classifiers.ET
+                     , Duckling.Ranking.Classifiers.ID
+                     , Duckling.Ranking.Classifiers.JA
+                     , Duckling.Ranking.Classifiers.MY
+                     , Duckling.Ranking.Classifiers.NL
+                     , Duckling.Ranking.Classifiers.RU
+                     , Duckling.Ranking.Classifiers.TR
+                     , Duckling.Ranking.Classifiers.UK
+                     , Duckling.Ranking.Classifiers.VI
+
+  -- ------------------------------------------------------------------
+  -- Dimensions
+                     , Duckling.Dimensions
+                     , Duckling.Dimensions.Common
+                     , Duckling.Dimensions.Types
+                     , Duckling.Dimensions.AR
+                     , Duckling.Dimensions.DA
+                     , Duckling.Dimensions.DE
+                     , Duckling.Dimensions.EN
+                     , Duckling.Dimensions.ES
+                     , Duckling.Dimensions.ET
+                     , Duckling.Dimensions.FR
+                     , Duckling.Dimensions.GA
+                     , Duckling.Dimensions.HE
+                     , Duckling.Dimensions.HR
+                     , Duckling.Dimensions.ID
+                     , Duckling.Dimensions.IT
+                     , Duckling.Dimensions.JA
+                     , Duckling.Dimensions.KO
+                     , Duckling.Dimensions.MY
+                     , Duckling.Dimensions.NB
+                     , Duckling.Dimensions.NL
+                     , Duckling.Dimensions.PL
+                     , Duckling.Dimensions.PT
+                     , Duckling.Dimensions.RO
+                     , Duckling.Dimensions.RU
+                     , Duckling.Dimensions.SV
+                     , Duckling.Dimensions.TR
+                     , Duckling.Dimensions.UK
+                     , Duckling.Dimensions.VI
+                     , Duckling.Dimensions.ZH
+
+                     -- AmountOfMoney
+                     , Duckling.AmountOfMoney.EN.Corpus
+                     , Duckling.AmountOfMoney.EN.Rules
+                     , Duckling.AmountOfMoney.ES.Corpus
+                     , Duckling.AmountOfMoney.ES.Rules
+                     , Duckling.AmountOfMoney.FR.Corpus
+                     , Duckling.AmountOfMoney.FR.Rules
+                     , Duckling.AmountOfMoney.GA.Corpus
+                     , Duckling.AmountOfMoney.GA.Rules
+                     , Duckling.AmountOfMoney.HR.Corpus
+                     , Duckling.AmountOfMoney.HR.Rules
+                     , Duckling.AmountOfMoney.ID.Corpus
+                     , Duckling.AmountOfMoney.ID.Rules
+                     , Duckling.AmountOfMoney.KO.Corpus
+                     , Duckling.AmountOfMoney.KO.Rules
+                     , Duckling.AmountOfMoney.NB.Corpus
+                     , Duckling.AmountOfMoney.NB.Rules
+                     , Duckling.AmountOfMoney.PT.Corpus
+                     , Duckling.AmountOfMoney.PT.Rules
+                     , Duckling.AmountOfMoney.RO.Corpus
+                     , Duckling.AmountOfMoney.RO.Rules
+                     , Duckling.AmountOfMoney.SV.Corpus
+                     , Duckling.AmountOfMoney.SV.Rules
+                     , Duckling.AmountOfMoney.VI.Corpus
+                     , Duckling.AmountOfMoney.VI.Rules
+                     , Duckling.AmountOfMoney.Helpers
+                     , Duckling.AmountOfMoney.Rules
+                     , Duckling.AmountOfMoney.Types
+
+                     -- Distance
+                     , Duckling.Distance.EN.Corpus
+                     , Duckling.Distance.EN.Rules
+                     , Duckling.Distance.ES.Corpus
+                     , Duckling.Distance.ES.Rules
+                     , Duckling.Distance.FR.Corpus
+                     , Duckling.Distance.FR.Rules
+                     , Duckling.Distance.GA.Corpus
+                     , Duckling.Distance.GA.Rules
+                     , Duckling.Distance.HR.Corpus
+                     , Duckling.Distance.HR.Rules
+                     , Duckling.Distance.KO.Corpus
+                     , Duckling.Distance.KO.Rules
+                     , Duckling.Distance.PT.Corpus
+                     , Duckling.Distance.PT.Rules
+                     , Duckling.Distance.NL.Corpus
+                     , Duckling.Distance.NL.Rules
+                     , Duckling.Distance.RO.Corpus
+                     , Duckling.Distance.RO.Rules
+                     , Duckling.Distance.Helpers
+                     , Duckling.Distance.Rules
+                     , Duckling.Distance.Types
+
+                     -- Duration
+                     , Duckling.Duration.DA.Rules
+                     , Duckling.Duration.DE.Rules
+                     , Duckling.Duration.EN.Corpus
+                     , Duckling.Duration.EN.Rules
+                     , Duckling.Duration.FR.Corpus
+                     , Duckling.Duration.FR.Rules
+                     , Duckling.Duration.GA.Corpus
+                     , Duckling.Duration.GA.Rules
+                     , Duckling.Duration.HE.Rules
+                     , Duckling.Duration.HR.Rules
+                     , Duckling.Duration.IT.Rules
+                     , Duckling.Duration.JA.Corpus
+                     , Duckling.Duration.KO.Corpus
+                     , Duckling.Duration.KO.Rules
+                     , Duckling.Duration.NB.Corpus
+                     , Duckling.Duration.NB.Rules
+                     , Duckling.Duration.PL.Corpus
+                     , Duckling.Duration.PL.Rules
+                     , Duckling.Duration.PT.Corpus
+                     , Duckling.Duration.SV.Corpus
+                     , Duckling.Duration.SV.Rules
+                     , Duckling.Duration.ZH.Corpus
+                     , Duckling.Duration.RO.Corpus
+                     , Duckling.Duration.RO.Rules
+                     , Duckling.Duration.Helpers
+                     , Duckling.Duration.Rules
+                     , Duckling.Duration.Types
+
+                     -- Email
+                     , Duckling.Email.EN.Corpus
+                     , Duckling.Email.EN.Rules
+                     , Duckling.Email.FR.Corpus
+                     , Duckling.Email.FR.Rules
+                     , Duckling.Email.IT.Corpus
+                     , Duckling.Email.IT.Rules
+                     , Duckling.Email.Corpus
+                     , Duckling.Email.Rules
+                     , Duckling.Email.Types
+
+                     -- Numeral
+                     , Duckling.Numeral.AR.Corpus
+                     , Duckling.Numeral.AR.Rules
+                     , Duckling.Numeral.DA.Corpus
+                     , Duckling.Numeral.DA.Rules
+                     , Duckling.Numeral.DE.Corpus
+                     , Duckling.Numeral.DE.Rules
+                     , Duckling.Numeral.EN.Corpus
+                     , Duckling.Numeral.EN.Rules
+                     , Duckling.Numeral.ES.Corpus
+                     , Duckling.Numeral.ES.Rules
+                     , Duckling.Numeral.ET.Corpus
+                     , Duckling.Numeral.ET.Rules
+                     , Duckling.Numeral.FR.Corpus
+                     , Duckling.Numeral.FR.Rules
+                     , Duckling.Numeral.GA.Corpus
+                     , Duckling.Numeral.GA.Rules
+                     , Duckling.Numeral.HE.Corpus
+                     , Duckling.Numeral.HE.Rules
+                     , Duckling.Numeral.HR.Corpus
+                     , Duckling.Numeral.HR.Rules
+                     , Duckling.Numeral.ID.Corpus
+                     , Duckling.Numeral.ID.Rules
+                     , Duckling.Numeral.IT.Corpus
+                     , Duckling.Numeral.IT.Rules
+                     , Duckling.Numeral.JA.Corpus
+                     , Duckling.Numeral.JA.Rules
+                     , Duckling.Numeral.KO.Corpus
+                     , Duckling.Numeral.KO.Rules
+                     , Duckling.Numeral.MY.Corpus
+                     , Duckling.Numeral.MY.Rules
+                     , Duckling.Numeral.NB.Corpus
+                     , Duckling.Numeral.NB.Rules
+                     , Duckling.Numeral.PL.Corpus
+                     , Duckling.Numeral.PL.Rules
+                     , Duckling.Numeral.PT.Corpus
+                     , Duckling.Numeral.PT.Rules
+                     , Duckling.Numeral.RU.Corpus
+                     , Duckling.Numeral.RU.Rules
+                     , Duckling.Numeral.SV.Corpus
+                     , Duckling.Numeral.SV.Rules
+                     , Duckling.Numeral.TR.Corpus
+                     , Duckling.Numeral.TR.Rules
+                     , Duckling.Numeral.UK.Corpus
+                     , Duckling.Numeral.UK.Rules
+                     , Duckling.Numeral.VI.Corpus
+                     , Duckling.Numeral.VI.Rules
+                     , Duckling.Numeral.ZH.Corpus
+                     , Duckling.Numeral.ZH.Rules
+                     , Duckling.Numeral.NL.Corpus
+                     , Duckling.Numeral.NL.Rules
+                     , Duckling.Numeral.RO.Corpus
+                     , Duckling.Numeral.RO.Rules
+                     , Duckling.Numeral.Helpers
+                     , Duckling.Numeral.Types
+
+                     -- Ordinal
+                     , Duckling.Ordinal.AR.Corpus
+                     , Duckling.Ordinal.AR.Rules
+                     , Duckling.Ordinal.DA.Corpus
+                     , Duckling.Ordinal.DA.Rules
+                     , Duckling.Ordinal.DE.Corpus
+                     , Duckling.Ordinal.DE.Rules
+                     , Duckling.Ordinal.EN.Corpus
+                     , Duckling.Ordinal.EN.Rules
+                     , Duckling.Ordinal.ES.Rules
+                     , Duckling.Ordinal.ET.Corpus
+                     , Duckling.Ordinal.ET.Rules
+                     , Duckling.Ordinal.FR.Corpus
+                     , Duckling.Ordinal.FR.Rules
+                     , Duckling.Ordinal.GA.Corpus
+                     , Duckling.Ordinal.GA.Rules
+                     , Duckling.Ordinal.HE.Corpus
+                     , Duckling.Ordinal.HE.Rules
+                     , Duckling.Ordinal.HR.Corpus
+                     , Duckling.Ordinal.HR.Rules
+                     , Duckling.Ordinal.ID.Corpus
+                     , Duckling.Ordinal.ID.Rules
+                     , Duckling.Ordinal.IT.Corpus
+                     , Duckling.Ordinal.IT.Rules
+                     , Duckling.Ordinal.JA.Corpus
+                     , Duckling.Ordinal.JA.Rules
+                     , Duckling.Ordinal.KO.Corpus
+                     , Duckling.Ordinal.KO.Rules
+                     , Duckling.Ordinal.NB.Corpus
+                     , Duckling.Ordinal.NB.Rules
+                     , Duckling.Ordinal.NL.Corpus
+                     , Duckling.Ordinal.NL.Rules
+                     , Duckling.Ordinal.PL.Corpus
+                     , Duckling.Ordinal.PL.Rules
+                     , Duckling.Ordinal.PT.Corpus
+                     , Duckling.Ordinal.PT.Rules
+                     , Duckling.Ordinal.RO.Corpus
+                     , Duckling.Ordinal.RO.Rules
+                     , Duckling.Ordinal.RU.Corpus
+                     , Duckling.Ordinal.RU.Rules
+                     , Duckling.Ordinal.SV.Corpus
+                     , Duckling.Ordinal.SV.Rules
+                     , Duckling.Ordinal.TR.Corpus
+                     , Duckling.Ordinal.TR.Rules
+                     , Duckling.Ordinal.UK.Corpus
+                     , Duckling.Ordinal.UK.Rules
+                     , Duckling.Ordinal.VI.Corpus
+                     , Duckling.Ordinal.VI.Rules
+                     , Duckling.Ordinal.ZH.Corpus
+                     , Duckling.Ordinal.ZH.Rules
+                     , Duckling.Ordinal.Helpers
+                     , Duckling.Ordinal.Types
+
+                     -- PhoneNumber
+                     , Duckling.PhoneNumber.PT.Corpus
+                     , Duckling.PhoneNumber.PT.Rules
+                     , Duckling.PhoneNumber.Corpus
+                     , Duckling.PhoneNumber.Rules
+                     , Duckling.PhoneNumber.Types
+
+                     -- Quantity
+                     , Duckling.Quantity.EN.Corpus
+                     , Duckling.Quantity.EN.Rules
+                     , Duckling.Quantity.FR.Corpus
+                     , Duckling.Quantity.FR.Rules
+                     , Duckling.Quantity.HR.Corpus
+                     , Duckling.Quantity.HR.Rules
+                     , Duckling.Quantity.KO.Corpus
+                     , Duckling.Quantity.KO.Rules
+                     , Duckling.Quantity.PT.Corpus
+                     , Duckling.Quantity.PT.Rules
+                     , Duckling.Quantity.RO.Corpus
+                     , Duckling.Quantity.RO.Rules
+                     , Duckling.Quantity.Helpers
+                     , Duckling.Quantity.Types
+
+                     -- Regex
+                     , Duckling.Regex.Types
+
+                     -- Temperature
+                     , Duckling.Temperature.EN.Corpus
+                     , Duckling.Temperature.EN.Rules
+                     , Duckling.Temperature.ES.Corpus
+                     , Duckling.Temperature.ES.Rules
+                     , Duckling.Temperature.FR.Corpus
+                     , Duckling.Temperature.FR.Rules
+                     , Duckling.Temperature.GA.Corpus
+                     , Duckling.Temperature.GA.Rules
+                     , Duckling.Temperature.HR.Corpus
+                     , Duckling.Temperature.HR.Rules
+                     , Duckling.Temperature.IT.Corpus
+                     , Duckling.Temperature.IT.Rules
+                     , Duckling.Temperature.JA.Corpus
+                     , Duckling.Temperature.JA.Rules
+                     , Duckling.Temperature.KO.Corpus
+                     , Duckling.Temperature.KO.Rules
+                     , Duckling.Temperature.PT.Corpus
+                     , Duckling.Temperature.PT.Rules
+                     , Duckling.Temperature.RO.Corpus
+                     , Duckling.Temperature.RO.Rules
+                     , Duckling.Temperature.ZH.Corpus
+                     , Duckling.Temperature.ZH.Rules
+                     , Duckling.Temperature.Helpers
+                     , Duckling.Temperature.Rules
+                     , Duckling.Temperature.Types
+
+                     -- Time
+                     , Duckling.Time.DA.Corpus
+                     , Duckling.Time.DA.Rules
+                     , Duckling.Time.DE.Corpus
+                     , Duckling.Time.DE.Rules
+                     , Duckling.Time.EN.Corpus
+                     , Duckling.Time.EN.Rules
+                     , Duckling.Time.ES.Corpus
+                     , Duckling.Time.ES.Rules
+                     , Duckling.Time.FR.Corpus
+                     , Duckling.Time.FR.Rules
+                     , Duckling.Time.GA.Corpus
+                     , Duckling.Time.GA.Rules
+                     , Duckling.Time.HR.Corpus
+                     , Duckling.Time.HR.Rules
+                     , Duckling.Time.HE.Corpus
+                     , Duckling.Time.HE.Rules
+                     , Duckling.Time.IT.Corpus
+                     , Duckling.Time.IT.Rules
+                     , Duckling.Time.KO.Corpus
+                     , Duckling.Time.KO.Rules
+                     , Duckling.Time.NB.Corpus
+                     , Duckling.Time.NB.Rules
+                     , Duckling.Time.PL.Corpus
+                     , Duckling.Time.PL.Rules
+                     , Duckling.Time.PT.Corpus
+                     , Duckling.Time.PT.Rules
+                     , Duckling.Time.RO.Corpus
+                     , Duckling.Time.RO.Rules
+                     , Duckling.Time.SV.Corpus
+                     , Duckling.Time.SV.Rules
+                     , Duckling.Time.VI.Corpus
+                     , Duckling.Time.VI.Rules
+                     , Duckling.Time.ZH.Corpus
+                     , Duckling.Time.ZH.Rules
+                     , Duckling.Time.Corpus
+                     , Duckling.Time.Helpers
+                     , Duckling.Time.Types
+                     -- REMOVE ME
+                     , Duckling.Time.TimeZone.Parse
+
+                     -- TimeGrain
+                     , Duckling.TimeGrain.DA.Rules
+                     , Duckling.TimeGrain.DE.Rules
+                     , Duckling.TimeGrain.EN.Rules
+                     , Duckling.TimeGrain.ES.Rules
+                     , Duckling.TimeGrain.FR.Rules
+                     , Duckling.TimeGrain.GA.Rules
+                     , Duckling.TimeGrain.HE.Rules
+                     , Duckling.TimeGrain.HR.Rules
+                     , Duckling.TimeGrain.IT.Rules
+                     , Duckling.TimeGrain.JA.Rules
+                     , Duckling.TimeGrain.KO.Rules
+                     , Duckling.TimeGrain.NB.Rules
+                     , Duckling.TimeGrain.PL.Rules
+                     , Duckling.TimeGrain.PT.Rules
+                     , Duckling.TimeGrain.RO.Rules
+                     , Duckling.TimeGrain.SV.Rules
+                     , Duckling.TimeGrain.VI.Rules
+                     , Duckling.TimeGrain.ZH.Rules
+                     , Duckling.TimeGrain.Types
+
+                     -- Url
+                     , Duckling.Url.Corpus
+                     , Duckling.Url.Helpers
+                     , Duckling.Url.Rules
+                     , Duckling.Url.Types
+
+                     -- Volume
+                     , Duckling.Volume.EN.Corpus
+                     , Duckling.Volume.EN.Rules
+                     , Duckling.Volume.ES.Corpus
+                     , Duckling.Volume.ES.Rules
+                     , Duckling.Volume.FR.Corpus
+                     , Duckling.Volume.FR.Rules
+                     , Duckling.Volume.GA.Corpus
+                     , Duckling.Volume.GA.Rules
+                     , Duckling.Volume.HR.Corpus
+                     , Duckling.Volume.HR.Rules
+                     , Duckling.Volume.IT.Corpus
+                     , Duckling.Volume.IT.Rules
+                     , Duckling.Volume.KO.Corpus
+                     , Duckling.Volume.KO.Rules
+                     , Duckling.Volume.PT.Corpus
+                     , Duckling.Volume.PT.Rules
+                     , Duckling.Volume.NL.Corpus
+                     , Duckling.Volume.NL.Rules
+                     , Duckling.Volume.RO.Corpus
+                     , Duckling.Volume.RO.Rules
+                     , Duckling.Volume.Helpers
+                     , Duckling.Volume.Rules
+                     , Duckling.Volume.Types
+  build-depends:       base                  >= 4.8.2 && < 5.0
+                     , array                 >= 0.5.1.1 && < 0.6
+                     , attoparsec            >= 0.13.1.0 && < 0.14
+                     , aeson                 >= 0.11.3.0 && < 1.1
+                     , bytestring            >= 0.10.6.0 && < 0.11
+                     , containers            >= 0.5.6.2 && < 0.6
+                     , deepseq               >= 1.4.1.1 && < 1.5
+                     , dependent-sum         >= 0.3.2.2 && < 0.5
+                     , extra                 >= 1.4.10 && < 1.6
+                     , hashable              >= 1.2.4.0 && < 1.3
+                     , regex-base            >= 0.93.2 && < 0.94
+                     , regex-pcre            >= 0.94.4 && < 0.95
+                     , text                  >= 1.2.2.1 && < 1.3
+                     , text-show             >= 2.1.2 && < 3.5
+                     , time                  >= 1.5.0.1 && < 1.9
+                     , timezone-series       >= 0.1.5.1 && < 0.2
+                     , unordered-containers  >= 0.2.7.2 && < 0.3
+  default-language:    Haskell2010
+  default-extensions:  OverloadedStrings
+                     , RebindableSyntax
+
+test-suite duckling-test
+  type:                exitcode-stdio-1.0
+  main-is:             TestMain.hs
+  hs-source-dirs:      tests
+  build-depends:       duckling
+                     , base                  >= 4.8.2 && < 5.0
+                     , aeson                 >= 0.11.3.0 && < 1.1
+                     , text                  >= 1.2.2.1 && < 1.3
+                     , time                  >= 1.5.0.1 && < 1.9
+                     , unordered-containers  >= 0.2.7.2 && < 0.3
+                     , tasty                 >= 0.11.1 && < 0.12
+                     , tasty-hunit           >= 0.9.2 && < 1.0
+  other-modules:       Duckling.Tests
+                     , Duckling.Testing.Asserts
+
+                     , Duckling.Api.Tests
+                     , Duckling.Engine.Tests
+
+  -- ------------------------------------------------------------------
+  -- Dimensions
+                     , Duckling.Dimensions.Tests
+
+                     -- AmountOfMoney
+                     , Duckling.AmountOfMoney.EN.Tests
+                     , Duckling.AmountOfMoney.ES.Tests
+                     , Duckling.AmountOfMoney.FR.Tests
+                     , Duckling.AmountOfMoney.GA.Tests
+                     , Duckling.AmountOfMoney.HR.Tests
+                     , Duckling.AmountOfMoney.ID.Tests
+                     , Duckling.AmountOfMoney.KO.Tests
+                     , Duckling.AmountOfMoney.NB.Tests
+                     , Duckling.AmountOfMoney.PT.Tests
+                     , Duckling.AmountOfMoney.RO.Tests
+                     , Duckling.AmountOfMoney.SV.Tests
+                     , Duckling.AmountOfMoney.VI.Tests
+                     , Duckling.AmountOfMoney.Tests
+
+                     -- Distance
+                     , Duckling.Distance.EN.Tests
+                     , Duckling.Distance.ES.Tests
+                     , Duckling.Distance.FR.Tests
+                     , Duckling.Distance.GA.Tests
+                     , Duckling.Distance.HR.Tests
+                     , Duckling.Distance.KO.Tests
+                     , Duckling.Distance.NL.Tests
+                     , Duckling.Distance.PT.Tests
+                     , Duckling.Distance.RO.Tests
+                     , Duckling.Distance.Tests
+
+                     -- Duration
+                     , Duckling.Duration.EN.Tests
+                     , Duckling.Duration.FR.Tests
+                     , Duckling.Duration.GA.Tests
+                     , Duckling.Duration.JA.Tests
+                     , Duckling.Duration.KO.Tests
+                     , Duckling.Duration.NB.Tests
+                     , Duckling.Duration.PL.Tests
+                     , Duckling.Duration.PT.Tests
+                     , Duckling.Duration.RO.Tests
+                     , Duckling.Duration.SV.Tests
+                     , Duckling.Duration.ZH.Tests
+                     , Duckling.Duration.Tests
+
+                     -- Email
+                     , Duckling.Email.EN.Tests
+                     , Duckling.Email.FR.Tests
+                     , Duckling.Email.IT.Tests
+                     , Duckling.Email.Tests
+
+                     -- Numeral
+                     , Duckling.Numeral.AR.Tests
+                     , Duckling.Numeral.DA.Tests
+                     , Duckling.Numeral.DE.Tests
+                     , Duckling.Numeral.EN.Tests
+                     , Duckling.Numeral.ES.Tests
+                     , Duckling.Numeral.ET.Tests
+                     , Duckling.Numeral.FR.Tests
+                     , Duckling.Numeral.GA.Tests
+                     , Duckling.Numeral.HE.Tests
+                     , Duckling.Numeral.HR.Tests
+                     , Duckling.Numeral.ID.Tests
+                     , Duckling.Numeral.IT.Tests
+                     , Duckling.Numeral.JA.Tests
+                     , Duckling.Numeral.KO.Tests
+                     , Duckling.Numeral.MY.Tests
+                     , Duckling.Numeral.NB.Tests
+                     , Duckling.Numeral.NL.Tests
+                     , Duckling.Numeral.PL.Tests
+                     , Duckling.Numeral.PT.Tests
+                     , Duckling.Numeral.RO.Tests
+                     , Duckling.Numeral.RU.Tests
+                     , Duckling.Numeral.SV.Tests
+                     , Duckling.Numeral.TR.Tests
+                     , Duckling.Numeral.UK.Tests
+                     , Duckling.Numeral.VI.Tests
+                     , Duckling.Numeral.ZH.Tests
+                     , Duckling.Numeral.Tests
+
+                     -- Ordinal
+                     , Duckling.Ordinal.AR.Tests
+                     , Duckling.Ordinal.DA.Tests
+                     , Duckling.Ordinal.DE.Tests
+                     , Duckling.Ordinal.EN.Tests
+                     , Duckling.Ordinal.ET.Tests
+                     , Duckling.Ordinal.FR.Tests
+                     , Duckling.Ordinal.GA.Tests
+                     , Duckling.Ordinal.HE.Tests
+                     , Duckling.Ordinal.HR.Tests
+                     , Duckling.Ordinal.ID.Tests
+                     , Duckling.Ordinal.IT.Tests
+                     , Duckling.Ordinal.JA.Tests
+                     , Duckling.Ordinal.KO.Tests
+                     , Duckling.Ordinal.NB.Tests
+                     , Duckling.Ordinal.NL.Tests
+                     , Duckling.Ordinal.PL.Tests
+                     , Duckling.Ordinal.PT.Tests
+                     , Duckling.Ordinal.RO.Tests
+                     , Duckling.Ordinal.RU.Tests
+                     , Duckling.Ordinal.SV.Tests
+                     , Duckling.Ordinal.TR.Tests
+                     , Duckling.Ordinal.UK.Tests
+                     , Duckling.Ordinal.VI.Tests
+                     , Duckling.Ordinal.ZH.Tests
+                     , Duckling.Ordinal.Tests
+
+                     -- PhoneNumber
+                     , Duckling.PhoneNumber.PT.Tests
+                     , Duckling.PhoneNumber.Tests
+
+                     -- Quantity
+                     , Duckling.Quantity.EN.Tests
+                     , Duckling.Quantity.FR.Tests
+                     , Duckling.Quantity.HR.Tests
+                     , Duckling.Quantity.KO.Tests
+                     , Duckling.Quantity.PT.Tests
+                     , Duckling.Quantity.RO.Tests
+                     , Duckling.Quantity.Tests
+
+                     -- Temperature
+                     , Duckling.Temperature.EN.Tests
+                     , Duckling.Temperature.ES.Tests
+                     , Duckling.Temperature.FR.Tests
+                     , Duckling.Temperature.GA.Tests
+                     , Duckling.Temperature.HR.Tests
+                     , Duckling.Temperature.IT.Tests
+                     , Duckling.Temperature.JA.Tests
+                     , Duckling.Temperature.KO.Tests
+                     , Duckling.Temperature.PT.Tests
+                     , Duckling.Temperature.ZH.Tests
+                     , Duckling.Temperature.RO.Tests
+                     , Duckling.Temperature.Tests
+
+                     -- Time
+                     , Duckling.Time.DA.Tests
+                     , Duckling.Time.DE.Tests
+                     , Duckling.Time.EN.Tests
+                     , Duckling.Time.ES.Tests
+                     , Duckling.Time.FR.Tests
+                     , Duckling.Time.GA.Tests
+                     , Duckling.Time.HR.Tests
+                     , Duckling.Time.HE.Tests
+                     , Duckling.Time.IT.Tests
+                     , Duckling.Time.KO.Tests
+                     , Duckling.Time.NB.Tests
+                     , Duckling.Time.PL.Tests
+                     , Duckling.Time.PT.Tests
+                     , Duckling.Time.RO.Tests
+                     , Duckling.Time.SV.Tests
+                     , Duckling.Time.VI.Tests
+                     , Duckling.Time.ZH.Tests
+                     , Duckling.Time.Tests
+
+                     -- Url
+                     , Duckling.Url.Tests
+
+                     -- Volume
+                     , Duckling.Volume.EN.Tests
+                     , Duckling.Volume.ES.Tests
+                     , Duckling.Volume.FR.Tests
+                     , Duckling.Volume.GA.Tests
+                     , Duckling.Volume.HR.Tests
+                     , Duckling.Volume.IT.Tests
+                     , Duckling.Volume.KO.Tests
+                     , Duckling.Volume.PT.Tests
+                     , Duckling.Volume.NL.Tests
+                     , Duckling.Volume.RO.Tests
+                     , Duckling.Volume.Tests
+
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
+  default-language:    Haskell2010
+  default-extensions:  OverloadedStrings
+
+executable duckling-regen-exe
+  main-is:             RegenMain.hs
+  hs-source-dirs:      exe
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
+  other-modules:       Duckling.Ranking.Train
+                     , Duckling.Ranking.Generate
+  build-depends:       duckling
+                     , base                  >= 4.8.2 && < 5.0
+                     , haskell-src-exts      >= 1.18 && < 1.19
+                     , text                  >= 1.2.2.1 && < 1.3
+                     , unordered-containers  >= 0.2.7.2 && < 0.3
+  default-language:    Haskell2010
+  default-extensions:  OverloadedStrings
+
+executable duckling-example-exe
+  main-is:             ExampleMain.hs
+  hs-source-dirs:      exe
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
+  other-modules:       Duckling.Data.TimeZone
+  build-depends:       duckling
+                     , base                  >= 4.8.2 && < 5.0
+                     , aeson                 >= 0.11.3.0 && < 1.1
+                     , bytestring            >= 0.10.6.0 && < 0.11
+                     , directory             >= 1.2.2.0 && < 1.4
+                     , extra                 >= 1.4.10 && < 1.6
+                     , filepath              >= 1.4.0.0 && < 1.5
+                     , snap-core             >= 1.0.2.0 && < 1.1
+                     , snap-server           >= 1.0.1.1 && < 1.1
+                     , text                  >= 1.2.2.1 && < 1.3
+                     , text-show             >= 2.1.2 && < 3.5
+                     , time                  >= 1.5.0.1 && < 1.9
+                     , timezone-olson        >= 0.1.7 && < 0.2
+                     , timezone-series       >= 0.1.5.1 && < 0.2
+                     , unordered-containers  >= 0.2.7.2 && < 0.3
+  default-language:    Haskell2010
+  default-extensions:  OverloadedStrings
+
+source-repository head
+  type:     git
+  location: https://github.com/facebookincubator/duckling
diff --git a/exe/Duckling/Data/TimeZone.hs b/exe/Duckling/Data/TimeZone.hs
new file mode 100644
--- /dev/null
+++ b/exe/Duckling/Data/TimeZone.hs
@@ -0,0 +1,71 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Data.TimeZone
+  ( loadTimeZoneSeries
+  ) where
+
+import qualified Control.Exception as E
+import Control.Monad.Extra
+import Data.Either
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import qualified Data.HashSet as HashSet
+import Data.String
+import qualified Data.Text as Text
+import Data.Time (TimeZone(..))
+import Data.Text (Text)
+import Data.Time.LocalTime.TimeZone.Olson
+import Data.Time.LocalTime.TimeZone.Series
+import System.Directory
+import System.FilePath
+
+import Prelude
+
+-- | Reference implementation for pulling TimeZoneSeries data from local
+-- Olson files.
+-- Many linux distros have Olson data in "/usr/share/zoneinfo/"
+loadTimeZoneSeries :: FilePath -> IO (HashMap Text TimeZoneSeries)
+loadTimeZoneSeries base = do
+  files <- getFiles base
+  tzSeries <- mapM parseOlsonFile files
+  -- This data is large, will live a long time, and essentially be constant,
+  -- so it's a perfect candidate for compact regions
+  return $ HashMap.fromList $ rights tzSeries
+  where
+    -- Multiple versions of the data can exist. We intentionally ignore the
+    -- posix and right formats
+    ignored_dirs = HashSet.fromList $ map (base </>)
+      [ "posix", "right" ]
+
+    -- Recursively crawls a directory to list every file underneath it,
+    -- ignoring certain directories as needed
+    getFiles :: FilePath -> IO [FilePath]
+    getFiles dir = do
+      fsAll <- getDirectoryContents dir
+      let
+        fs = filter notDotFile fsAll
+        full_fs = map (dir </>) fs
+      (dirs, files) <- partitionM doesDirectoryExist full_fs
+
+      subdirs <- concatMapM getFiles
+        [ d | d <- dirs, not $ HashSet.member d ignored_dirs ]
+
+      return $ files ++ subdirs
+
+    -- Attempts to read a file in Olson format and returns its
+    -- canonical name (file path relative to the base) and the data
+    parseOlsonFile :: FilePath
+                   -> IO (Either E.ErrorCall (Text, TimeZoneSeries))
+    parseOlsonFile f = E.try $ do
+      r <- getTimeZoneSeriesFromOlsonFile f
+      return (Text.pack $ makeRelative base f, r)
+
+    notDotFile s = not $ elem s [".", ".."]
diff --git a/exe/Duckling/Ranking/Generate.hs b/exe/Duckling/Ranking/Generate.hs
new file mode 100644
--- /dev/null
+++ b/exe/Duckling/Ranking/Generate.hs
@@ -0,0 +1,238 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Duckling.Ranking.Generate
+  ( regenAllClassifiers
+  , regenClassifiers
+  ) where
+
+import qualified Data.HashMap.Strict as HashMap
+import qualified Data.HashSet as HashSet
+import qualified Data.Text as Text
+import Prelude
+import Language.Haskell.Exts as F
+
+import Duckling.Dimensions.Types
+import Duckling.Lang
+import Duckling.Ranking.Train
+import Duckling.Ranking.Types
+import Duckling.Rules
+import Duckling.Testing.Types
+import qualified Duckling.Time.DA.Corpus as DATime
+import qualified Duckling.Time.DE.Corpus as DETime
+import qualified Duckling.Time.EN.Corpus as ENTime
+import qualified Duckling.Time.ES.Corpus as ESTime
+import qualified Duckling.Time.FR.Corpus as FRTime
+import qualified Duckling.Time.GA.Corpus as GATime
+import qualified Duckling.Time.HR.Corpus as HRTime
+import qualified Duckling.Time.HE.Corpus as HETime
+import qualified Duckling.Time.IT.Corpus as ITTime
+import qualified Duckling.Time.KO.Corpus as KOTime
+import qualified Duckling.Time.NB.Corpus as NBTime
+import qualified Duckling.Time.PL.Corpus as PLTime
+import qualified Duckling.Time.PT.Corpus as PTTime
+import qualified Duckling.Time.RO.Corpus as ROTime
+import qualified Duckling.Time.SV.Corpus as SVTime
+import qualified Duckling.Time.VI.Corpus as VITime
+import qualified Duckling.Time.ZH.Corpus as ZHTime
+
+-- -----------------------------------------------------------------
+-- Main
+
+regenAllClassifiers :: IO ()
+regenAllClassifiers = mapM_ regenClassifiers [minBound .. maxBound]
+
+-- | Run this function to overwrite the file with Classifiers data
+regenClassifiers :: Lang -> IO ()
+regenClassifiers lang = do
+  putStrLn $ "Regenerating " ++ filepath ++ "..."
+  writeFile filepath $
+    (headerComment ++) $
+    prettyPrintWithMode baseMode $ (noLoc <$) m
+  putStrLn "Done!"
+  where
+    filepath = "Duckling/Ranking/Classifiers/" ++ show lang ++ ".hs"
+
+    rules = rulesFor lang . HashSet.singleton $ This Time
+
+    -- | The trained classifier to write out
+    classifiers = makeClassifiers rules trainSet
+
+    -- | The training set (corpus)
+    trainSet = case lang of
+      AR -> (testContext, [])
+      DA -> DATime.corpus
+      DE -> DETime.corpus
+      EN -> ENTime.corpus
+      ES -> ESTime.corpus
+      ET -> (testContext, [])
+      FR -> FRTime.corpus
+      GA -> GATime.corpus
+      HR -> HRTime.corpus
+      HE -> HETime.corpus
+      ID -> (testContext, [])
+      IT -> ITTime.corpus
+      JA -> (testContext, [])
+      KO -> KOTime.corpus
+      MY -> (testContext, [])
+      NB -> NBTime.corpus
+      NL -> (testContext, [])
+      PL -> PLTime.corpus
+      PT -> PTTime.corpus
+      RO -> ROTime.corpus
+      RU -> (testContext, [])
+      SV -> SVTime.corpus
+      TR -> (testContext, [])
+      UK -> (testContext, [])
+      VI -> VITime.corpus
+      ZH -> ZHTime.corpus
+
+    -- Data structure for the module
+    m = Module () (Just header) pragmas imports decls
+
+    -- Declares the top level options pragma
+    pragmas = [ LanguagePragma () [Ident () "OverloadedStrings"] ]
+
+    -- Declares the header for the module
+    -- "module Duckling.Ranking.Classifiers (classifiers) where"
+    header = ModuleHead ()
+      (ModuleName () $ "Duckling.Ranking.Classifiers." ++ show lang)
+      Nothing $
+      Just $ ExportSpecList ()
+       [ EVar () (unQual "classifiers")
+       ]
+
+    -- All imports the file will need
+    imports =
+      [ genImportModule "Prelude"
+      , genImportModule "Duckling.Ranking.Types"
+      , (genImportModule "Data.HashMap.Strict")
+        { importQualified = True
+        , importAs = Just (ModuleName () "HashMap")
+        }
+      , genImportModule "Data.String"
+      ]
+
+    -- The code body
+    decls =
+      [ -- Type Signature
+        TypeSig () [Ident () "classifiers"] (TyCon () (unQual "Classifiers"))
+        -- function body
+      , FunBind ()
+          [ Match () (Ident () "classifiers") []
+              (UnGuardedRhs () (genList classifiers)) Nothing
+          ]
+      ]
+
+    headerComment :: String
+    headerComment = "\
+\-- Copyright (c) 2016-present, Facebook, Inc.\n\
+\-- All rights reserved.\n\
+\--\n\
+\-- This source code is licensed under the BSD-style license found in the\n\
+\-- LICENSE file in the root directory of this source tree. An additional grant\n\
+\-- of patent rights can be found in the PATENTS file in the same directory.\n\n\
+\-----------------------------------------------------------------\n\
+\-- Auto-generated by regenClassifiers\n\
+\--\n\
+\-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n\
+\--  @" ++ "generated\n\
+\-----------------------------------------------------------------\n"
+
+-- -----------------------------------------------------------------
+-- Source generators
+
+-- | Generates a line for an import
+--
+-- `genImportModule "Foo.Bar"` spits out:
+-- "import Foo.Bar" in the code
+genImportModule :: String -> ImportDecl ()
+genImportModule name = ImportDecl
+  { importAnn = ()
+  , importModule = ModuleName () name
+  , importQualified = False
+  , importSrc = False
+  , importSafe = False
+  , importPkg = Nothing
+  , importAs = Nothing
+  , importSpecs = Nothing
+  }
+
+-- | Creates the expression to build the HashMap object
+genList :: Classifiers -> Exp ()
+genList cs = appFromList $ map genClassifier $ HashMap.toList cs
+  where
+    -- "fromList ..."
+    appFromList exprs = App ()
+      (Var () (Qual () (ModuleName () "HashMap") (Ident () "fromList")))
+      (List () exprs)
+
+    -- ("name", Classifier { okData ....
+    genClassifier (name, Classifier{..}) =
+      let uname = Text.unpack name in
+      Tuple () Boxed
+        [ Lit () $ F.String () uname uname
+        , RecConstr () (unQual "Classifier")
+            [ genClassData okData "okData"
+            , genClassData koData "koData"
+            ]
+        ]
+
+    -- ClassData { prior = -0.123, unseen = ...
+    genClassData ClassData{..} name = FieldUpdate () (unQual name) $
+      RecConstr () (unQual "ClassData")
+        [ FieldUpdate () (unQual "prior") $ floatSym prior
+        , FieldUpdate () (unQual "unseen") $ floatSym unseen
+        , FieldUpdate () (unQual "likelihoods") $
+            appFromList $ map genLikelihood $ HashMap.toList likelihoods
+        , FieldUpdate () (unQual "n") $
+            Lit () (Int () (fromIntegral n) (show n))
+        ]
+
+    -- ("feature", 0.0)
+    genLikelihood (f, d) =
+      let uf = Text.unpack f in
+      Tuple () Boxed
+        [ Lit () $ F.String () uf uf
+        , floatSym d
+        ]
+
+-- Helper to print out doubles
+floatSym :: Double -> Exp ()
+floatSym val
+  | isInfinite val = if val < 0
+      then NegApp () inf
+      else inf
+  | otherwise = Lit () (Frac () (realToFrac val) $ show val)
+  where
+    inf = Var () $ unQual "infinity"
+
+-- Helper for unqualified things
+unQual :: String -> QName ()
+unQual name = UnQual () (Ident () name)
+
+
+-- -----------------------------------------------------------------
+-- Printing helpers
+
+baseMode :: PPHsMode
+baseMode = PPHsMode
+  { classIndent   = 2
+  , doIndent      = 3
+  , multiIfIndent = 3
+  , caseIndent    = 2
+  , letIndent     = 2
+  , whereIndent   = 2
+  , onsideIndent  = 2
+  , spacing       = True
+  , layout        = PPOffsideRule
+  , linePragmas   = False
+  }
diff --git a/exe/Duckling/Ranking/Train.hs b/exe/Duckling/Ranking/Train.hs
new file mode 100644
--- /dev/null
+++ b/exe/Duckling/Ranking/Train.hs
@@ -0,0 +1,98 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE RecordWildCards #-}
+
+module Duckling.Ranking.Train
+  ( makeClassifiers
+  ) where
+
+import qualified Data.HashMap.Strict as HashMap
+import qualified Data.HashSet as HashSet
+import Data.HashSet (HashSet)
+
+import Prelude
+import qualified Data.List as List
+
+import Duckling.Engine
+import Duckling.Ranking.Extraction
+import Duckling.Ranking.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+import Duckling.Types
+
+-- -----------------------------------------------------------------
+-- Probabilistic layer
+-- Naive Bayes classifier with Laplace smoothing
+-- Train one classifier per rule, based on the test corpus.
+
+makeClassifiers :: [Rule] -> Corpus -> Classifiers
+makeClassifiers rules corpus = HashMap.map train $ makeDataset rules corpus
+
+-- | Train a classifier for a single rule
+train :: [Datum] -> Classifier
+train datums = Classifier {okData = okClass, koData = koClass}
+  where
+    total = List.length datums
+    (ok, ko) = List.partition snd datums
+    merge :: [BagOfFeatures] -> BagOfFeatures -> BagOfFeatures
+    merge xs m = List.foldl' (HashMap.unionWith (+)) m xs
+    okCounts = merge (map fst ok) HashMap.empty
+    koCounts = merge (map fst ko) HashMap.empty
+    vocSize = HashMap.size $ HashMap.union okCounts koCounts
+    okClass = makeClass okCounts total (List.length ok) vocSize
+    koClass = makeClass koCounts total (List.length ko) vocSize
+
+-- | Compute prior and likelihoods log-probabilities for one class.
+makeClass :: BagOfFeatures -> Int -> Int -> Int -> ClassData
+makeClass feats total classTotal vocSize = ClassData
+  { prior = prior
+  , unseen = unseen
+  , likelihoods = likelihoods
+  , n = classTotal
+  }
+  where
+    prior = log $ fromIntegral classTotal / fromIntegral total
+    denum = vocSize + sum (HashMap.elems feats)
+    unseen = log $ 1.0 / (fromIntegral denum + 1.0)
+    likelihoods = HashMap.map (\x ->
+      log $ (fromIntegral x + 1.0) / fromIntegral denum
+      ) feats
+
+
+-- | Augment the dataset with one example.
+-- | Add all the nodes contributing to the resolutions of the input sentence.
+-- | Classes:
+-- | 1) True (node contributed to a token passing test predicate)
+-- | 2) False (node didn't contribute to any passing tokens)
+makeDataset1 :: [Rule] -> Context -> Dataset -> Example -> Dataset
+makeDataset1 rules context dataset (sentence, predicate) = dataset'
+  where
+    tokens = parseAndResolve rules sentence context
+    (ok, ko) = List.partition (predicate context) tokens
+    subnodes :: Node -> HashSet Node
+    subnodes node@(Node{..}) = case children of
+      [] -> HashSet.empty
+      cs -> HashSet.unions $ HashSet.singleton node:map subnodes cs
+    nodesOK = HashSet.unions $ map (subnodes . node) ok
+    nodesKO = HashSet.difference
+      (HashSet.unions $ map (subnodes . node) ko) nodesOK
+    updateDataset :: Class -> HashSet Node -> Dataset -> Dataset
+    updateDataset klass nodes dataset =
+      HashSet.foldl' (\dataset node@Node {..} ->
+        case rule of
+          Just rule -> HashMap.insertWith (++) rule
+            [(extractFeatures node, klass)] dataset
+          Nothing -> dataset
+        ) dataset nodes
+    dataset' = updateDataset False nodesKO $ updateDataset True nodesOK dataset
+
+-- | Build a dataset (rule name -> datums)
+makeDataset :: [Rule] -> Corpus -> Dataset
+makeDataset rules (context, examples) =
+   List.foldl' (makeDataset1 rules context) HashMap.empty examples
diff --git a/exe/ExampleMain.hs b/exe/ExampleMain.hs
new file mode 100644
--- /dev/null
+++ b/exe/ExampleMain.hs
@@ -0,0 +1,90 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+import Prelude
+import Control.Applicative
+import Control.Arrow ((***))
+import Control.Monad.IO.Class
+import Data.Aeson
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Lazy as LBS
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
+import Data.Maybe
+import Data.Text (Text)
+import qualified Data.Text as Text
+import qualified Data.Text.Encoding as Text
+import Data.Time.LocalTime.TimeZone.Series
+import Data.String
+import TextShow
+import Text.Read (readMaybe)
+
+import Snap.Core
+import Snap.Http.Server
+
+import Duckling.Core
+import Duckling.Data.TimeZone
+
+main :: IO ()
+main = do
+  tzs <- loadTimeZoneSeries "/usr/share/zoneinfo/"
+  quickHttpServe $
+    ifTop (writeBS "quack!") <|>
+    route
+      [ ("targets", method GET targetsHandler)
+      , ("parse", method POST $ parseHandler tzs)
+      ]
+
+-- | Return which languages have which dimensions
+targetsHandler :: Snap ()
+targetsHandler = do
+  modifyResponse $ setHeader "Content-Type" "application/json"
+  writeLBS $ encode $
+    HashMap.fromList . map dimText $ HashMap.toList supportedDimensions
+  where
+    dimText :: (Lang, [Some Dimension]) -> (Text, [Text])
+    dimText = (Text.toLower . showt) *** map (\(This d) -> toName d)
+
+
+-- | Parse some text into the given dimensions
+parseHandler :: HashMap Text TimeZoneSeries -> Snap ()
+parseHandler tzs = do
+  modifyResponse $ setHeader "Content-Type" "application/json"
+  t <- getPostParam "text"
+  l <- getPostParam "lang"
+  ds <- getPostParam "dims"
+  tz <- getPostParam "tz"
+
+  case t of
+    Nothing -> do
+      modifyResponse $ setResponseStatus 422 "Bad Input"
+      writeBS "Need a 'text' parameter to parse"
+    Just tx -> do
+      refTime <- liftIO $ currentReftime tzs $
+                   fromMaybe defaultTimeZone $ Text.decodeUtf8 <$> tz
+      let
+        context = Context
+          { referenceTime = refTime
+          , lang = parseLang l
+          }
+
+        dimParse = fromMaybe [] $ decode $ LBS.fromStrict $ fromMaybe "" ds
+        dims = mapMaybe fromName dimParse
+
+        parsedResult = parse (Text.decodeUtf8 tx) context dims
+
+      writeLBS $ encode parsedResult
+  where
+    defaultLang = EN
+    defaultTimeZone = "America/Los_Angeles"
+
+    parseLang :: Maybe ByteString -> Lang
+    parseLang l = fromMaybe defaultLang $ l >>=
+      readMaybe . Text.unpack . Text.toUpper . Text.decodeUtf8
diff --git a/exe/RegenMain.hs b/exe/RegenMain.hs
new file mode 100644
--- /dev/null
+++ b/exe/RegenMain.hs
@@ -0,0 +1,13 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+import Prelude
+import Duckling.Ranking.Generate
+
+main :: IO ()
+main = regenAllClassifiers
diff --git a/tests/Duckling/AmountOfMoney/EN/Tests.hs b/tests/Duckling/AmountOfMoney/EN/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/AmountOfMoney/EN/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.AmountOfMoney.EN.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.AmountOfMoney.EN.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "EN Tests"
+  [ makeCorpusTest [This AmountOfMoney] corpus
+  ]
diff --git a/tests/Duckling/AmountOfMoney/ES/Tests.hs b/tests/Duckling/AmountOfMoney/ES/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/AmountOfMoney/ES/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.AmountOfMoney.ES.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.AmountOfMoney.ES.Corpus
+
+tests :: TestTree
+tests = testGroup "ES Tests"
+  [ makeCorpusTest [This AmountOfMoney] corpus
+  ]
diff --git a/tests/Duckling/AmountOfMoney/FR/Tests.hs b/tests/Duckling/AmountOfMoney/FR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/AmountOfMoney/FR/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.AmountOfMoney.FR.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.AmountOfMoney.FR.Corpus
+
+tests :: TestTree
+tests = testGroup "FR Tests"
+  [ makeCorpusTest [This AmountOfMoney] corpus
+  ]
diff --git a/tests/Duckling/AmountOfMoney/GA/Tests.hs b/tests/Duckling/AmountOfMoney/GA/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/AmountOfMoney/GA/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.AmountOfMoney.GA.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.AmountOfMoney.GA.Corpus
+
+tests :: TestTree
+tests = testGroup "GA Tests"
+  [ makeCorpusTest [This AmountOfMoney] corpus
+  ]
diff --git a/tests/Duckling/AmountOfMoney/HR/Tests.hs b/tests/Duckling/AmountOfMoney/HR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/AmountOfMoney/HR/Tests.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+module Duckling.AmountOfMoney.HR.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.AmountOfMoney.HR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "HR Tests"
+  [ makeCorpusTest [This AmountOfMoney] corpus
+  ]
diff --git a/tests/Duckling/AmountOfMoney/ID/Tests.hs b/tests/Duckling/AmountOfMoney/ID/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/AmountOfMoney/ID/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.AmountOfMoney.ID.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.AmountOfMoney.ID.Corpus
+
+tests :: TestTree
+tests = testGroup "ID Tests"
+  [ makeCorpusTest [This AmountOfMoney] corpus
+  ]
diff --git a/tests/Duckling/AmountOfMoney/KO/Tests.hs b/tests/Duckling/AmountOfMoney/KO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/AmountOfMoney/KO/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.AmountOfMoney.KO.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.AmountOfMoney.KO.Corpus
+
+tests :: TestTree
+tests = testGroup "KO Tests"
+  [ makeCorpusTest [This AmountOfMoney] corpus
+  ]
diff --git a/tests/Duckling/AmountOfMoney/NB/Tests.hs b/tests/Duckling/AmountOfMoney/NB/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/AmountOfMoney/NB/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.AmountOfMoney.NB.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.AmountOfMoney.NB.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "NB Tests"
+  [ makeCorpusTest [This AmountOfMoney] corpus
+  ]
diff --git a/tests/Duckling/AmountOfMoney/PT/Tests.hs b/tests/Duckling/AmountOfMoney/PT/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/AmountOfMoney/PT/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.AmountOfMoney.PT.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.AmountOfMoney.PT.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "PT Tests"
+  [ makeCorpusTest [This AmountOfMoney] corpus
+  ]
diff --git a/tests/Duckling/AmountOfMoney/RO/Tests.hs b/tests/Duckling/AmountOfMoney/RO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/AmountOfMoney/RO/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.AmountOfMoney.RO.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.AmountOfMoney.RO.Corpus
+
+tests :: TestTree
+tests = testGroup "RO Tests"
+  [ makeCorpusTest [This AmountOfMoney] corpus
+  ]
diff --git a/tests/Duckling/AmountOfMoney/SV/Tests.hs b/tests/Duckling/AmountOfMoney/SV/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/AmountOfMoney/SV/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.AmountOfMoney.SV.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.AmountOfMoney.SV.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "SV Tests"
+  [ makeCorpusTest [This AmountOfMoney] corpus
+  ]
diff --git a/tests/Duckling/AmountOfMoney/Tests.hs b/tests/Duckling/AmountOfMoney/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/AmountOfMoney/Tests.hs
@@ -0,0 +1,42 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.AmountOfMoney.Tests (tests) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import qualified Duckling.AmountOfMoney.EN.Tests as EN
+import qualified Duckling.AmountOfMoney.ES.Tests as ES
+import qualified Duckling.AmountOfMoney.FR.Tests as FR
+import qualified Duckling.AmountOfMoney.GA.Tests as GA
+import qualified Duckling.AmountOfMoney.HR.Tests as HR
+import qualified Duckling.AmountOfMoney.ID.Tests as ID
+import qualified Duckling.AmountOfMoney.KO.Tests as KO
+import qualified Duckling.AmountOfMoney.NB.Tests as NB
+import qualified Duckling.AmountOfMoney.PT.Tests as PT
+import qualified Duckling.AmountOfMoney.RO.Tests as RO
+import qualified Duckling.AmountOfMoney.SV.Tests as SV
+import qualified Duckling.AmountOfMoney.VI.Tests as VI
+
+tests :: TestTree
+tests = testGroup "AmountOfMoney Tests"
+  [ EN.tests
+  , ES.tests
+  , FR.tests
+  , GA.tests
+  , HR.tests
+  , ID.tests
+  , KO.tests
+  , NB.tests
+  , PT.tests
+  , RO.tests
+  , SV.tests
+  , VI.tests
+  ]
diff --git a/tests/Duckling/AmountOfMoney/VI/Tests.hs b/tests/Duckling/AmountOfMoney/VI/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/AmountOfMoney/VI/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.AmountOfMoney.VI.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.AmountOfMoney.VI.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "VI Tests"
+  [ makeCorpusTest [This AmountOfMoney] corpus
+  ]
diff --git a/tests/Duckling/Api/Tests.hs b/tests/Duckling/Api/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Api/Tests.hs
@@ -0,0 +1,137 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE NoRebindableSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections #-}
+
+module Duckling.Api.Tests (tests) where
+
+import qualified Data.HashMap.Strict as HashMap
+import qualified Data.HashSet as HashSet
+import Data.List (sortOn)
+import Data.Text (Text)
+import Prelude
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import Duckling.Api
+import Duckling.Dimensions.Types
+import Duckling.Lang
+import qualified Duckling.Numeral.Types as TNumeral
+import Duckling.Testing.Asserts
+import Duckling.Testing.Types
+import Duckling.Types
+
+tests :: TestTree
+tests = testGroup "API Tests"
+  [ parseTest
+  , rankTest
+  , rangeTest
+  , supportedDimensionsTest
+  ]
+
+parseTest :: TestTree
+parseTest = testCase "Parse Test" $
+  case parse sentence testContext [This Numeral] of
+    [] -> assertFailure "empty result"
+    (Entity dim body value start end:_) -> do
+      assertEqual "dim" "number" dim
+      assertEqual "body" "42" body
+      assertEqual "value" val value
+      assertEqual "start" 4 start
+      assertEqual "end" 6 end
+  where
+    sentence = "hey 42 there"
+    val = toJText TNumeral.NumeralValue {TNumeral.vValue = 42.0}
+
+rankTest :: TestTree
+rankTest = testGroup "Rank Tests"
+  [ rankFilterTest
+  , rankOrderTest
+  ]
+
+rankFilterTest :: TestTree
+rankFilterTest = testCase "Rank Filter Tests" $ do
+  mapM_ check
+    [ ( "in 2 minutes"
+      , [This Numeral, This Duration, This Time]
+      , [This Time]
+      )
+    , ( "in 2 minutes, about 42 degrees"
+      , [This Numeral, This Temperature, This Time]
+      , [This Time, This Temperature]
+      )
+    , ( "today works... and tomorrow at 9pm too"
+      , [This Numeral, This Time]
+      , [This Time, This Time]
+      )
+    , ( "between 9:30 and 11:00 on thursday or Saturday and Thanksgiving Day"
+      , [This Numeral, This Time]
+      , [This Time, This Time, This Time]
+      )
+    , ("the day after tomorrow 5pm", [This Time], [This Time])
+    , ("the day after tomorrow 5pm", [This Time, This Numeral], [This Time])
+    , ("the day after tomorrow 5pm", [], [This Time])
+    ]
+  where
+    check :: (Text, [Some Dimension], [Some Dimension]) -> IO ()
+    check (sentence, targets, expected) =
+      let go = analyze sentence testContext $ HashSet.fromList targets
+          actual = flip map go $
+                     \(Resolved{node=Node{token=Token d _}}) -> This d
+      in assertEqual ("wrong winners for " ++ show sentence) expected actual
+
+rankOrderTest :: TestTree
+rankOrderTest = testCase "Rank Order Tests" $ do
+  mapM_ check
+    [ ("tomorrow at 5PM or 8PM", [This Time])
+    , ("321 12 3456 ... 7", [This Numeral])
+    , ("42 today 23 tomorrow", [This Numeral, This Time])
+    ]
+  where
+    check (s, targets) =
+      let tokens = analyze s testContext $ HashSet.fromList targets
+        in assertEqual "wrong ordering" (sortOn range tokens) tokens
+
+rangeTest :: TestTree
+rangeTest = testCase "Range Tests" $ do
+  mapM_ (analyzedFirstTest testContext) xs
+  where
+    xs = map (\(input, targets, range) -> (input, targets, f range))
+             [ ( "order status 3233763377", [This PhoneNumber], Range 13 23 )
+             , ( "  3233763377  "         , [This PhoneNumber], Range  2 12 )
+             , ( " -3233763377"           , [This PhoneNumber], Range  2 12 )
+             , ( "  now"                  , [This Time]       , Range  2  5 )
+             , ( "   Monday  "            , [This Time]       , Range  3  9 )
+             , ( "  next   week "         , [This Time]       , Range  2 13 )
+             , ( "   42\n\n"              , [This Numeral]    , Range  3  5 )
+             ]
+    f :: Range -> TestPredicate
+    f expected _ (Resolved {range = actual}) = expected == actual
+
+supportedDimensionsTest :: TestTree
+supportedDimensionsTest = testCase "Supported Dimensions Test" $ do
+  mapM_ check
+    [ ( AR
+      , [ This Email, This AmountOfMoney, This PhoneNumber, This Url
+        , This Numeral, This Ordinal
+        ]
+      )
+    , ( PL
+      , [ This Email, This AmountOfMoney, This PhoneNumber, This Url
+        , This Duration, This Numeral, This Ordinal, This Time
+        ]
+      )
+    ]
+  where
+    check :: (Lang, [Some Dimension]) -> IO ()
+    check (l, expected) = case HashMap.lookup l supportedDimensions of
+      Nothing -> assertFailure $ "no dimensions for " ++ show l
+      Just actual ->
+        assertEqual ("wrong dimensions for " ++ show l) expected actual
diff --git a/tests/Duckling/Dimensions/Tests.hs b/tests/Duckling/Dimensions/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Dimensions/Tests.hs
@@ -0,0 +1,44 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Dimensions.Tests
+  ( tests
+  ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import qualified Duckling.AmountOfMoney.Tests as AmountOfMoney
+import qualified Duckling.Distance.Tests as Distance
+import qualified Duckling.Duration.Tests as Duration
+import qualified Duckling.Email.Tests as Email
+import qualified Duckling.Numeral.Tests as Numeral
+import qualified Duckling.Ordinal.Tests as Ordinal
+import qualified Duckling.PhoneNumber.Tests as PhoneNumber
+import qualified Duckling.Quantity.Tests as Quantity
+import qualified Duckling.Temperature.Tests as Temperature
+import qualified Duckling.Time.Tests as Time
+import qualified Duckling.Volume.Tests as Volume
+import qualified Duckling.Url.Tests as Url
+
+tests :: TestTree
+tests = testGroup "Dimensions Tests"
+  [ AmountOfMoney.tests
+  , Distance.tests
+  , Duration.tests
+  , Email.tests
+  , Numeral.tests
+  , Ordinal.tests
+  , PhoneNumber.tests
+  , Quantity.tests
+  , Temperature.tests
+  , Time.tests
+  , Volume.tests
+  , Url.tests
+  ]
diff --git a/tests/Duckling/Distance/EN/Tests.hs b/tests/Duckling/Distance/EN/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Distance/EN/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Distance.EN.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Distance.EN.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "EN Tests"
+  [ makeCorpusTest [This Distance] corpus
+  ]
diff --git a/tests/Duckling/Distance/ES/Tests.hs b/tests/Duckling/Distance/ES/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Distance/ES/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Distance.ES.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Distance.ES.Corpus
+
+tests :: TestTree
+tests = testGroup "ES Tests"
+  [ makeCorpusTest [This Distance] corpus
+  ]
diff --git a/tests/Duckling/Distance/FR/Tests.hs b/tests/Duckling/Distance/FR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Distance/FR/Tests.hs
@@ -0,0 +1,21 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Distance.FR.Tests
+  ( tests ) where
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Distance.FR.Corpus
+
+tests :: TestTree
+tests = testGroup "FR Tests"
+  [ makeCorpusTest [This Distance] corpus
+  ]
diff --git a/tests/Duckling/Distance/GA/Tests.hs b/tests/Duckling/Distance/GA/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Distance/GA/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Distance.GA.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Distance.GA.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "GA Tests"
+  [ makeCorpusTest [This Distance] corpus
+  ]
diff --git a/tests/Duckling/Distance/HR/Tests.hs b/tests/Duckling/Distance/HR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Distance/HR/Tests.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+module Duckling.Distance.HR.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Distance.HR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "HR Tests"
+  [ makeCorpusTest [This Distance] corpus
+  ]
diff --git a/tests/Duckling/Distance/KO/Tests.hs b/tests/Duckling/Distance/KO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Distance/KO/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Distance.KO.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Distance.KO.Corpus
+
+tests :: TestTree
+tests = testGroup "KO Tests"
+  [ makeCorpusTest [This Distance] corpus
+  ]
diff --git a/tests/Duckling/Distance/NL/Tests.hs b/tests/Duckling/Distance/NL/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Distance/NL/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Distance.NL.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Distance.NL.Corpus
+
+tests :: TestTree
+tests = testGroup "NL Tests"
+  [ makeCorpusTest [This Distance] corpus
+  ]
diff --git a/tests/Duckling/Distance/PT/Tests.hs b/tests/Duckling/Distance/PT/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Distance/PT/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Distance.PT.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Distance.PT.Corpus
+
+tests :: TestTree
+tests = testGroup "PT Tests"
+  [ makeCorpusTest [This Distance] corpus
+  ]
diff --git a/tests/Duckling/Distance/RO/Tests.hs b/tests/Duckling/Distance/RO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Distance/RO/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Distance.RO.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Distance.RO.Corpus
+
+tests :: TestTree
+tests = testGroup "RO Tests"
+  [ makeCorpusTest [This Distance] corpus
+  ]
diff --git a/tests/Duckling/Distance/Tests.hs b/tests/Duckling/Distance/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Distance/Tests.hs
@@ -0,0 +1,36 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Distance.Tests (tests) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import qualified Duckling.Distance.EN.Tests as EN
+import qualified Duckling.Distance.ES.Tests as ES
+import qualified Duckling.Distance.FR.Tests as FR
+import qualified Duckling.Distance.GA.Tests as GA
+import qualified Duckling.Distance.HR.Tests as HR
+import qualified Duckling.Distance.KO.Tests as KO
+import qualified Duckling.Distance.NL.Tests as NL
+import qualified Duckling.Distance.PT.Tests as PT
+import qualified Duckling.Distance.RO.Tests as RO
+
+tests :: TestTree
+tests = testGroup "Distance Tests"
+  [ EN.tests
+  , ES.tests
+  , FR.tests
+  , GA.tests
+  , HR.tests
+  , KO.tests
+  , NL.tests
+  , PT.tests
+  , RO.tests
+  ]
diff --git a/tests/Duckling/Duration/EN/Tests.hs b/tests/Duckling/Duration/EN/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Duration/EN/Tests.hs
@@ -0,0 +1,25 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Duration.EN.Tests
+  ( tests
+  ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.EN.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "EN Tests"
+  [ makeCorpusTest [This Duration] corpus
+  , makeNegativeCorpusTest [This Duration] negativeCorpus
+  ]
diff --git a/tests/Duckling/Duration/FR/Tests.hs b/tests/Duckling/Duration/FR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Duration/FR/Tests.hs
@@ -0,0 +1,25 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Duration.FR.Tests
+  ( tests
+  ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.FR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "FR Tests"
+  [ makeCorpusTest [This Duration] corpus
+  , makeNegativeCorpusTest [This Duration] negativeCorpus
+  ]
diff --git a/tests/Duckling/Duration/GA/Tests.hs b/tests/Duckling/Duration/GA/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Duration/GA/Tests.hs
@@ -0,0 +1,24 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Duration.GA.Tests
+  ( tests
+  ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.GA.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "GA Tests"
+  [ makeCorpusTest [This Duration] corpus
+  ]
diff --git a/tests/Duckling/Duration/JA/Tests.hs b/tests/Duckling/Duration/JA/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Duration/JA/Tests.hs
@@ -0,0 +1,24 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Duration.JA.Tests
+  ( tests
+  ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.JA.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "JA Tests"
+  [ makeCorpusTest [This Duration] corpus
+  ]
diff --git a/tests/Duckling/Duration/KO/Tests.hs b/tests/Duckling/Duration/KO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Duration/KO/Tests.hs
@@ -0,0 +1,24 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Duration.KO.Tests
+  ( tests
+  ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.KO.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "KO Tests"
+  [ makeCorpusTest [This Duration] corpus
+  ]
diff --git a/tests/Duckling/Duration/NB/Tests.hs b/tests/Duckling/Duration/NB/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Duration/NB/Tests.hs
@@ -0,0 +1,24 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Duration.NB.Tests
+  ( tests
+  ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.NB.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "NB Tests"
+  [ makeCorpusTest [This Duration] corpus
+  ]
diff --git a/tests/Duckling/Duration/PL/Tests.hs b/tests/Duckling/Duration/PL/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Duration/PL/Tests.hs
@@ -0,0 +1,24 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Duration.PL.Tests
+  ( tests
+  ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.PL.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "PL Tests"
+  [ makeCorpusTest [This Duration] corpus
+  ]
diff --git a/tests/Duckling/Duration/PT/Tests.hs b/tests/Duckling/Duration/PT/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Duration/PT/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Duration.PT.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.PT.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "PT Tests"
+  [ makeCorpusTest [This Duration] corpus
+  ]
diff --git a/tests/Duckling/Duration/RO/Tests.hs b/tests/Duckling/Duration/RO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Duration/RO/Tests.hs
@@ -0,0 +1,24 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Duration.RO.Tests
+  ( tests
+  ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.RO.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "RO Tests"
+  [ makeCorpusTest [This Duration] corpus
+  ]
diff --git a/tests/Duckling/Duration/SV/Tests.hs b/tests/Duckling/Duration/SV/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Duration/SV/Tests.hs
@@ -0,0 +1,24 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Duration.SV.Tests
+  ( tests
+  ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.SV.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "SV Tests"
+  [ makeCorpusTest [This Duration] corpus
+  ]
diff --git a/tests/Duckling/Duration/Tests.hs b/tests/Duckling/Duration/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Duration/Tests.hs
@@ -0,0 +1,42 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Duration.Tests
+  ( tests
+  ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import qualified Duckling.Duration.EN.Tests as EN
+import qualified Duckling.Duration.FR.Tests as FR
+import qualified Duckling.Duration.GA.Tests as GA
+import qualified Duckling.Duration.JA.Tests as JA
+import qualified Duckling.Duration.KO.Tests as KO
+import qualified Duckling.Duration.NB.Tests as NB
+import qualified Duckling.Duration.PL.Tests as PL
+import qualified Duckling.Duration.PT.Tests as PT
+import qualified Duckling.Duration.RO.Tests as RO
+import qualified Duckling.Duration.SV.Tests as SV
+import qualified Duckling.Duration.ZH.Tests as ZH
+
+tests :: TestTree
+tests = testGroup "Duration Tests"
+  [ EN.tests
+  , FR.tests
+  , GA.tests
+  , JA.tests
+  , KO.tests
+  , NB.tests
+  , PL.tests
+  , PT.tests
+  , RO.tests
+  , SV.tests
+  , ZH.tests
+  ]
diff --git a/tests/Duckling/Duration/ZH/Tests.hs b/tests/Duckling/Duration/ZH/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Duration/ZH/Tests.hs
@@ -0,0 +1,31 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Duration.ZH.Tests
+  ( tests
+  ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import Duckling.Dimensions.Types
+import Duckling.Duration.ZH.Corpus
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "ZH Tests"
+  [ testCase "Corpus Tests" $
+      mapM_ (analyzedFirstTest context {lang = ZH} . withTargets [This Duration])
+        xs
+  ]
+  where
+    (context, xs) = corpus
diff --git a/tests/Duckling/Email/EN/Tests.hs b/tests/Duckling/Email/EN/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Email/EN/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Email.EN.Tests
+  ( tests
+  ) where
+
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Email.EN.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "Email Tests"
+  [ makeCorpusTest [This Email] corpus
+  ]
diff --git a/tests/Duckling/Email/FR/Tests.hs b/tests/Duckling/Email/FR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Email/FR/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Email.FR.Tests
+  ( tests
+  ) where
+
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Email.FR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "Email Tests"
+  [ makeCorpusTest [This Email] corpus
+  ]
diff --git a/tests/Duckling/Email/IT/Tests.hs b/tests/Duckling/Email/IT/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Email/IT/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Email.IT.Tests
+  ( tests
+  ) where
+
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Email.IT.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "Email Tests"
+  [ makeCorpusTest [This Email] corpus
+  ]
diff --git a/tests/Duckling/Email/Tests.hs b/tests/Duckling/Email/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Email/Tests.hs
@@ -0,0 +1,29 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Email.Tests (tests) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Email.Corpus
+import qualified Duckling.Email.EN.Tests as EN
+import qualified Duckling.Email.FR.Tests as FR
+import qualified Duckling.Email.IT.Tests as IT
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "Email Tests"
+  [ makeCorpusTest [This Email] corpus
+  , makeNegativeCorpusTest [This Email] negativeCorpus
+  , EN.tests
+  , FR.tests
+  , IT.tests
+  ]
diff --git a/tests/Duckling/Engine/Tests.hs b/tests/Duckling/Engine/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Engine/Tests.hs
@@ -0,0 +1,53 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Engine.Tests
+  ( tests
+  ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import Duckling.Engine
+import Duckling.Types
+import Duckling.Dimensions.Types
+import Duckling.Regex.Types
+
+tests :: TestTree
+tests = testGroup "Engine Tests"
+  [ emptyRegexTest
+  , unicodeAndRegexTest
+  ]
+
+emptyRegexTest :: TestTree
+emptyRegexTest = testCase "Empty Regex Test" $
+  case regex "()" of
+    Regex regex -> assertEqual "empty result" [] $
+      runDuckling $ lookupRegexAnywhere "hey" regex
+    _ -> assertFailure "expected a regex"
+
+unicodeAndRegexTest :: TestTree
+unicodeAndRegexTest = testCase "Unicode and Regex Test" $
+  case regex "\\$([0-9]*)" of
+    Regex regex -> do --
+      assertEqual "" expected $
+        runDuckling $ lookupRegexAnywhere "\128526 $35" regex
+    _ -> assertFailure "expected a regex"
+  where
+  expected =
+    [ Node
+      { nodeRange = Range 2 5
+      , token = Token RegexMatch (GroupMatch ["35"])
+      , children = []
+      , rule = Nothing
+      }
+    ]
diff --git a/tests/Duckling/Numeral/AR/Tests.hs b/tests/Duckling/Numeral/AR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/AR/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.AR.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.AR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "AR Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/DA/Tests.hs b/tests/Duckling/Numeral/DA/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/DA/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.DA.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.DA.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "DA Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/DE/Tests.hs b/tests/Duckling/Numeral/DE/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/DE/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.DE.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.DE.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "DE Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/EN/Tests.hs b/tests/Duckling/Numeral/EN/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/EN/Tests.hs
@@ -0,0 +1,47 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Numeral.EN.Tests
+  ( tests
+  ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.EN.Corpus
+import Duckling.Numeral.Types
+import Duckling.Testing.Asserts
+import Duckling.Testing.Types
+
+tests :: TestTree
+tests = testGroup "EN Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  , surroundTests
+  ]
+
+surroundTests :: TestTree
+surroundTests = testCase "Surround Tests" $
+  mapM_ (analyzedFirstTest testContext . withTargets [This Numeral]) xs
+  where
+    xs = concat
+      [ examples (NumeralValue 3)
+                 [ "3km"
+                 ]
+      , examples (NumeralValue 100000)
+                 [ "100k€"
+                 , "100k\x20ac"
+                 ]
+      , examples (NumeralValue 10.99)
+                 [ "10.99$"
+                 ]
+      ]
diff --git a/tests/Duckling/Numeral/ES/Tests.hs b/tests/Duckling/Numeral/ES/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/ES/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.ES.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.ES.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "ES Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/ET/Tests.hs b/tests/Duckling/Numeral/ET/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/ET/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.ET.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.ET.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "ET Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/FR/Tests.hs b/tests/Duckling/Numeral/FR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/FR/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.FR.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.FR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "FR Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/GA/Tests.hs b/tests/Duckling/Numeral/GA/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/GA/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.GA.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.GA.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "GA Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/HE/Tests.hs b/tests/Duckling/Numeral/HE/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/HE/Tests.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+module Duckling.Numeral.HE.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.HE.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "HE Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/HR/Tests.hs b/tests/Duckling/Numeral/HR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/HR/Tests.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+module Duckling.Numeral.HR.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.HR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "HR Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/ID/Tests.hs b/tests/Duckling/Numeral/ID/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/ID/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.ID.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.ID.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "ID Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/IT/Tests.hs b/tests/Duckling/Numeral/IT/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/IT/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.IT.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.IT.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "IT Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/JA/Tests.hs b/tests/Duckling/Numeral/JA/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/JA/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.JA.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.JA.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "JA Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/KO/Tests.hs b/tests/Duckling/Numeral/KO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/KO/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.KO.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.KO.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "KO Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/MY/Tests.hs b/tests/Duckling/Numeral/MY/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/MY/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.MY.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.MY.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "MY Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/NB/Tests.hs b/tests/Duckling/Numeral/NB/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/NB/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.NB.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.NB.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "NB Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/NL/Tests.hs b/tests/Duckling/Numeral/NL/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/NL/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.NL.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.NL.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "NL Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/PL/Tests.hs b/tests/Duckling/Numeral/PL/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/PL/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.PL.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.PL.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "PL Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/PT/Tests.hs b/tests/Duckling/Numeral/PT/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/PT/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.PT.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.PT.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "PT Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/RO/Tests.hs b/tests/Duckling/Numeral/RO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/RO/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.RO.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.RO.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "RO Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/RU/Tests.hs b/tests/Duckling/Numeral/RU/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/RU/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.RU.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.RU.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "RU Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/SV/Tests.hs b/tests/Duckling/Numeral/SV/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/SV/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.SV.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.SV.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "SV Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/TR/Tests.hs b/tests/Duckling/Numeral/TR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/TR/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.TR.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.TR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "TR Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/Tests.hs b/tests/Duckling/Numeral/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/Tests.hs
@@ -0,0 +1,70 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.Tests (tests) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import qualified Duckling.Numeral.AR.Tests as AR
+import qualified Duckling.Numeral.DA.Tests as DA
+import qualified Duckling.Numeral.DE.Tests as DE
+import qualified Duckling.Numeral.EN.Tests as EN
+import qualified Duckling.Numeral.ES.Tests as ES
+import qualified Duckling.Numeral.ET.Tests as ET
+import qualified Duckling.Numeral.FR.Tests as FR
+import qualified Duckling.Numeral.GA.Tests as GA
+import qualified Duckling.Numeral.HE.Tests as HE
+import qualified Duckling.Numeral.HR.Tests as HR
+import qualified Duckling.Numeral.ID.Tests as ID
+import qualified Duckling.Numeral.IT.Tests as IT
+import qualified Duckling.Numeral.JA.Tests as JA
+import qualified Duckling.Numeral.KO.Tests as KO
+import qualified Duckling.Numeral.MY.Tests as MY
+import qualified Duckling.Numeral.NB.Tests as NB
+import qualified Duckling.Numeral.NL.Tests as NL
+import qualified Duckling.Numeral.PL.Tests as PL
+import qualified Duckling.Numeral.PT.Tests as PT
+import qualified Duckling.Numeral.RO.Tests as RO
+import qualified Duckling.Numeral.RU.Tests as RU
+import qualified Duckling.Numeral.SV.Tests as SV
+import qualified Duckling.Numeral.TR.Tests as TR
+import qualified Duckling.Numeral.UK.Tests as UK
+import qualified Duckling.Numeral.VI.Tests as VI
+import qualified Duckling.Numeral.ZH.Tests as ZH
+
+tests :: TestTree
+tests = testGroup "Numeral Tests"
+  [ AR.tests
+  , DA.tests
+  , DE.tests
+  , EN.tests
+  , ES.tests
+  , ET.tests
+  , FR.tests
+  , GA.tests
+  , HE.tests
+  , HR.tests
+  , ID.tests
+  , IT.tests
+  , JA.tests
+  , KO.tests
+  , MY.tests
+  , NB.tests
+  , NL.tests
+  , PL.tests
+  , PT.tests
+  , RO.tests
+  , RU.tests
+  , SV.tests
+  , TR.tests
+  , UK.tests
+  , VI.tests
+  , ZH.tests
+  ]
diff --git a/tests/Duckling/Numeral/UK/Tests.hs b/tests/Duckling/Numeral/UK/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/UK/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.UK.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.UK.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "UK Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/VI/Tests.hs b/tests/Duckling/Numeral/VI/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/VI/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.VI.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.VI.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "VI Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Numeral/ZH/Tests.hs b/tests/Duckling/Numeral/ZH/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Numeral/ZH/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Numeral.ZH.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Numeral.ZH.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "ZH Tests"
+  [ makeCorpusTest [This Numeral] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/AR/Tests.hs b/tests/Duckling/Ordinal/AR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/AR/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.AR.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.AR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "AR Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/DA/Tests.hs b/tests/Duckling/Ordinal/DA/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/DA/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.DA.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.DA.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "DA Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/DE/Tests.hs b/tests/Duckling/Ordinal/DE/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/DE/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.DE.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.DE.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "DE Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/EN/Tests.hs b/tests/Duckling/Ordinal/EN/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/EN/Tests.hs
@@ -0,0 +1,24 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.EN.Tests
+  ( tests
+  ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.EN.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "EN Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/ET/Tests.hs b/tests/Duckling/Ordinal/ET/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/ET/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.ET.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.ET.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "ET Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/FR/Tests.hs b/tests/Duckling/Ordinal/FR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/FR/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.FR.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.FR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "FR Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/GA/Tests.hs b/tests/Duckling/Ordinal/GA/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/GA/Tests.hs
@@ -0,0 +1,24 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.GA.Tests
+  ( tests
+  ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.GA.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "GA Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/HE/Tests.hs b/tests/Duckling/Ordinal/HE/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/HE/Tests.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+module Duckling.Ordinal.HE.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.HE.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "HE Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/HR/Tests.hs b/tests/Duckling/Ordinal/HR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/HR/Tests.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+module Duckling.Ordinal.HR.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.HR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "HR Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/ID/Tests.hs b/tests/Duckling/Ordinal/ID/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/ID/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.ID.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.ID.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "ID Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/IT/Tests.hs b/tests/Duckling/Ordinal/IT/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/IT/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.IT.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.IT.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "IT Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/JA/Tests.hs b/tests/Duckling/Ordinal/JA/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/JA/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.JA.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.JA.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "JA Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/KO/Tests.hs b/tests/Duckling/Ordinal/KO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/KO/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.KO.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.KO.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "KO Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/NB/Tests.hs b/tests/Duckling/Ordinal/NB/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/NB/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.NB.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.NB.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "NB Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/NL/Tests.hs b/tests/Duckling/Ordinal/NL/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/NL/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.NL.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.NL.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "NL Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/PL/Tests.hs b/tests/Duckling/Ordinal/PL/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/PL/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.PL.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.PL.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "PL Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/PT/Tests.hs b/tests/Duckling/Ordinal/PT/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/PT/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.PT.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.PT.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "PT Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/RO/Tests.hs b/tests/Duckling/Ordinal/RO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/RO/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.RO.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.RO.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "RO Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/RU/Tests.hs b/tests/Duckling/Ordinal/RU/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/RU/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.RU.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.RU.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "RU Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/SV/Tests.hs b/tests/Duckling/Ordinal/SV/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/SV/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.SV.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.SV.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "SV Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/TR/Tests.hs b/tests/Duckling/Ordinal/TR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/TR/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.TR.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.TR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "TR Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/Tests.hs b/tests/Duckling/Ordinal/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/Tests.hs
@@ -0,0 +1,66 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.Tests (tests) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import qualified Duckling.Ordinal.AR.Tests as AR
+import qualified Duckling.Ordinal.DA.Tests as DA
+import qualified Duckling.Ordinal.DE.Tests as DE
+import qualified Duckling.Ordinal.EN.Tests as EN
+import qualified Duckling.Ordinal.ET.Tests as ET
+import qualified Duckling.Ordinal.FR.Tests as FR
+import qualified Duckling.Ordinal.GA.Tests as GA
+import qualified Duckling.Ordinal.HE.Tests as HE
+import qualified Duckling.Ordinal.HR.Tests as HR
+import qualified Duckling.Ordinal.ID.Tests as ID
+import qualified Duckling.Ordinal.IT.Tests as IT
+import qualified Duckling.Ordinal.JA.Tests as JA
+import qualified Duckling.Ordinal.KO.Tests as KO
+import qualified Duckling.Ordinal.NB.Tests as NB
+import qualified Duckling.Ordinal.NL.Tests as NL
+import qualified Duckling.Ordinal.PL.Tests as PL
+import qualified Duckling.Ordinal.PT.Tests as PT
+import qualified Duckling.Ordinal.RO.Tests as RO
+import qualified Duckling.Ordinal.RU.Tests as RU
+import qualified Duckling.Ordinal.SV.Tests as SV
+import qualified Duckling.Ordinal.TR.Tests as TR
+import qualified Duckling.Ordinal.UK.Tests as UK
+import qualified Duckling.Ordinal.VI.Tests as VI
+import qualified Duckling.Ordinal.ZH.Tests as ZH
+
+tests :: TestTree
+tests = testGroup "Ordinal Tests"
+  [ AR.tests
+  , DA.tests
+  , DE.tests
+  , EN.tests
+  , ET.tests
+  , FR.tests
+  , GA.tests
+  , HE.tests
+  , HR.tests
+  , ID.tests
+  , IT.tests
+  , JA.tests
+  , KO.tests
+  , NB.tests
+  , NL.tests
+  , PL.tests
+  , PT.tests
+  , RO.tests
+  , RU.tests
+  , SV.tests
+  , TR.tests
+  , UK.tests
+  , VI.tests
+  , ZH.tests
+  ]
diff --git a/tests/Duckling/Ordinal/UK/Tests.hs b/tests/Duckling/Ordinal/UK/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/UK/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.UK.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.UK.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "UK Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/VI/Tests.hs b/tests/Duckling/Ordinal/VI/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/VI/Tests.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+module Duckling.Ordinal.VI.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.VI.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "VI Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/Ordinal/ZH/Tests.hs b/tests/Duckling/Ordinal/ZH/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Ordinal/ZH/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Ordinal.ZH.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Ordinal.ZH.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "ZH Tests"
+  [ makeCorpusTest [This Ordinal] corpus
+  ]
diff --git a/tests/Duckling/PhoneNumber/PT/Tests.hs b/tests/Duckling/PhoneNumber/PT/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/PhoneNumber/PT/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.PhoneNumber.PT.Tests
+  ( tests
+  ) where
+
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.PhoneNumber.PT.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "PhoneNumber Tests"
+  [ makeCorpusTest [This PhoneNumber] corpus
+  ]
diff --git a/tests/Duckling/PhoneNumber/Tests.hs b/tests/Duckling/PhoneNumber/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/PhoneNumber/Tests.hs
@@ -0,0 +1,46 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.PhoneNumber.Tests
+  ( tests
+  ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import Duckling.Dimensions.Types
+import Duckling.PhoneNumber.Corpus
+import Duckling.PhoneNumber.Types
+import Duckling.Testing.Asserts
+import Duckling.Testing.Types
+import qualified Duckling.PhoneNumber.PT.Tests as PT
+
+tests :: TestTree
+tests = testGroup "PhoneNumber Tests"
+  [ makeCorpusTest [This PhoneNumber] corpus
+  , makeNegativeCorpusTest [This PhoneNumber] negativeCorpus
+  , surroundTests
+  , PT.tests
+  ]
+
+surroundTests :: TestTree
+surroundTests = testCase "Surround Tests" $
+  mapM_ (analyzedFirstTest testContext . withTargets [This PhoneNumber]) xs
+  where
+    xs = examples (PhoneNumberValue "06354640807")
+                  [ "hey 06354640807"
+                  , "06354640807 hey"
+                  , "hey 06354640807 hey"
+                  ]
+         ++ examples (PhoneNumberValue "18998078030")
+                     [ "a 18998078030 b"
+                     ]
diff --git a/tests/Duckling/Quantity/EN/Tests.hs b/tests/Duckling/Quantity/EN/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Quantity/EN/Tests.hs
@@ -0,0 +1,24 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Quantity.EN.Tests
+  ( tests
+  ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Quantity.EN.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "EN Tests"
+  [ makeCorpusTest [This Quantity] corpus
+  ]
diff --git a/tests/Duckling/Quantity/FR/Tests.hs b/tests/Duckling/Quantity/FR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Quantity/FR/Tests.hs
@@ -0,0 +1,21 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Quantity.FR.Tests
+  ( tests ) where
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Quantity.FR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "FR Tests"
+  [ makeCorpusTest [This Quantity] corpus
+  ]
diff --git a/tests/Duckling/Quantity/HR/Tests.hs b/tests/Duckling/Quantity/HR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Quantity/HR/Tests.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+module Duckling.Quantity.HR.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Quantity.HR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "HR Tests"
+  [ makeCorpusTest [This Quantity] corpus
+  ]
diff --git a/tests/Duckling/Quantity/KO/Tests.hs b/tests/Duckling/Quantity/KO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Quantity/KO/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Quantity.KO.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Quantity.KO.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "KO Tests"
+  [ makeCorpusTest [This Quantity] corpus
+  ]
diff --git a/tests/Duckling/Quantity/PT/Tests.hs b/tests/Duckling/Quantity/PT/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Quantity/PT/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Quantity.PT.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Quantity.PT.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "PT Tests"
+  [ makeCorpusTest [This Quantity] corpus
+  ]
diff --git a/tests/Duckling/Quantity/RO/Tests.hs b/tests/Duckling/Quantity/RO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Quantity/RO/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Quantity.RO.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Quantity.RO.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "RO Tests"
+  [ makeCorpusTest [This Quantity] corpus
+  ]
diff --git a/tests/Duckling/Quantity/Tests.hs b/tests/Duckling/Quantity/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Quantity/Tests.hs
@@ -0,0 +1,30 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Quantity.Tests (tests) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import qualified Duckling.Quantity.EN.Tests as EN
+import qualified Duckling.Quantity.FR.Tests as FR
+import qualified Duckling.Quantity.HR.Tests as HR
+import qualified Duckling.Quantity.KO.Tests as KO
+import qualified Duckling.Quantity.PT.Tests as PT
+import qualified Duckling.Quantity.RO.Tests as RO
+
+tests :: TestTree
+tests = testGroup "Quantity Tests"
+  [ EN.tests
+  , FR.tests
+  , HR.tests
+  , KO.tests
+  , PT.tests
+  , RO.tests
+  ]
diff --git a/tests/Duckling/Temperature/EN/Tests.hs b/tests/Duckling/Temperature/EN/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Temperature/EN/Tests.hs
@@ -0,0 +1,24 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Temperature.EN.Tests
+  ( tests
+  ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.EN.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "EN Tests"
+  [ makeCorpusTest [This Temperature] corpus
+  ]
diff --git a/tests/Duckling/Temperature/ES/Tests.hs b/tests/Duckling/Temperature/ES/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Temperature/ES/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Temperature.ES.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.ES.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "ES Tests"
+  [ makeCorpusTest [This Temperature] corpus
+  ]
diff --git a/tests/Duckling/Temperature/FR/Tests.hs b/tests/Duckling/Temperature/FR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Temperature/FR/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Temperature.FR.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.FR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "FR Tests"
+  [ makeCorpusTest [This Temperature] corpus
+  ]
diff --git a/tests/Duckling/Temperature/GA/Tests.hs b/tests/Duckling/Temperature/GA/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Temperature/GA/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Temperature.GA.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.GA.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "GA Tests"
+  [ makeCorpusTest [This Temperature] corpus
+  ]
diff --git a/tests/Duckling/Temperature/HR/Tests.hs b/tests/Duckling/Temperature/HR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Temperature/HR/Tests.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+module Duckling.Temperature.HR.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.HR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "HR Tests"
+  [ makeCorpusTest [This Temperature] corpus
+  ]
diff --git a/tests/Duckling/Temperature/IT/Tests.hs b/tests/Duckling/Temperature/IT/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Temperature/IT/Tests.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+module Duckling.Temperature.IT.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.IT.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "IT Tests"
+  [ makeCorpusTest [This Temperature] corpus
+  ]
diff --git a/tests/Duckling/Temperature/JA/Tests.hs b/tests/Duckling/Temperature/JA/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Temperature/JA/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Temperature.JA.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.JA.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "JA Tests"
+  [ makeCorpusTest [This Temperature] corpus
+  ]
diff --git a/tests/Duckling/Temperature/KO/Tests.hs b/tests/Duckling/Temperature/KO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Temperature/KO/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Temperature.KO.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.KO.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "KO Tests"
+  [ makeCorpusTest [This Temperature] corpus
+  ]
diff --git a/tests/Duckling/Temperature/PT/Tests.hs b/tests/Duckling/Temperature/PT/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Temperature/PT/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Temperature.PT.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.PT.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "PT Tests"
+  [ makeCorpusTest [This Temperature] corpus
+  ]
diff --git a/tests/Duckling/Temperature/RO/Tests.hs b/tests/Duckling/Temperature/RO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Temperature/RO/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Temperature.RO.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.RO.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "RO Tests"
+  [ makeCorpusTest [This Temperature] corpus
+  ]
diff --git a/tests/Duckling/Temperature/Tests.hs b/tests/Duckling/Temperature/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Temperature/Tests.hs
@@ -0,0 +1,40 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Temperature.Tests (tests) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import qualified Duckling.Temperature.EN.Tests as EN
+import qualified Duckling.Temperature.ES.Tests as ES
+import qualified Duckling.Temperature.FR.Tests as FR
+import qualified Duckling.Temperature.GA.Tests as GA
+import qualified Duckling.Temperature.HR.Tests as HR
+import qualified Duckling.Temperature.IT.Tests as IT
+import qualified Duckling.Temperature.JA.Tests as JA
+import qualified Duckling.Temperature.KO.Tests as KO
+import qualified Duckling.Temperature.PT.Tests as PT
+import qualified Duckling.Temperature.RO.Tests as RO
+import qualified Duckling.Temperature.ZH.Tests as ZH
+
+tests :: TestTree
+tests = testGroup "Temperature Tests"
+  [ EN.tests
+  , ES.tests
+  , FR.tests
+  , GA.tests
+  , HR.tests
+  , IT.tests
+  , JA.tests
+  , KO.tests
+  , PT.tests
+  , RO.tests
+  , ZH.tests
+  ]
diff --git a/tests/Duckling/Temperature/ZH/Tests.hs b/tests/Duckling/Temperature/ZH/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Temperature/ZH/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Temperature.ZH.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Temperature.ZH.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "ZH Tests"
+  [ makeCorpusTest [This Temperature] corpus
+  ]
diff --git a/tests/Duckling/Testing/Asserts.hs b/tests/Duckling/Testing/Asserts.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Testing/Asserts.hs
@@ -0,0 +1,86 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE TupleSections #-}
+
+module Duckling.Testing.Asserts
+  ( analyzedTargetTest
+  , analyzedFirstTest
+  , analyzedNTest
+  , analyzedNothingTest
+  , makeCorpusTest
+  , makeNegativeCorpusTest
+  , withTargets
+  ) where
+
+import qualified Data.HashSet as HashSet
+import Data.String
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Prelude
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import Duckling.Api
+import Duckling.Dimensions.Types
+import Duckling.Resolve
+import Duckling.Testing.Types
+import Duckling.Types
+
+withTargets :: [Some Dimension] -> (Text, a) -> (Text, [Some Dimension], a)
+withTargets targets (input, expected) = (input, targets, expected)
+
+analyzedTargetTest :: Context -> (Text, Some Dimension) -> IO ()
+analyzedTargetTest context (input, target) =
+  assertBool msg $ all (== target) dimensions
+  where
+    msg = "analyze " ++ show (input, [target])
+          ++ "dimensions = " ++ show dimensions
+    dimensions = flip map (analyze input context $ HashSet.singleton target) $
+      \(Resolved{node=Node{token=Token dimension _}}) -> This dimension
+
+analyzedFirstTest :: Context -> (Text, [Some Dimension], TestPredicate) -> IO ()
+analyzedFirstTest context (input, targets, predicate) =
+  case tokens of
+    [] -> assertFailure ("empty result on " ++ show (input, targets))
+    (token:_) -> assertBool ("don't pass predicate on " ++ show input) $
+      predicate context token
+    where
+      tokens = analyze input context $ HashSet.fromList targets
+
+makeCorpusTest :: [Some Dimension] -> Corpus -> TestTree
+makeCorpusTest targets (context, xs) = testCase "Corpus Tests" $ mapM_ check xs
+  where
+    dims = HashSet.fromList targets
+    check :: Example -> IO ()
+    check (input, predicate) = let tokens = analyze input context dims in
+      case tokens of
+        [] -> assertFailure $ "empty result on " ++ show input
+        (_:_:_) -> assertFailure $
+          show (length tokens) ++ " tokens found for " ++ show input
+        (token:_) -> do
+          assertEqual ("don't fully match " ++ show input)
+            (Range 0 (Text.length input)) (range token)
+          assertBool ("don't pass predicate on " ++ show input) $
+            predicate context token
+
+makeNegativeCorpusTest :: [Some Dimension] -> NegativeCorpus -> TestTree
+makeNegativeCorpusTest targets (context, xs) = testCase "Negative Corpus Tests" $
+  mapM_ (analyzedNothingTest context . (, targets)) xs
+
+analyzedNothingTest :: Context -> (Text, [Some Dimension]) -> IO ()
+analyzedNothingTest context (input, targets) =
+  analyzedNTest context (input, targets, 0)
+
+analyzedNTest :: Context -> (Text, [Some Dimension], Int) -> IO ()
+analyzedNTest context (input, targets, n) =
+  assertBool msg . (== n) $ length tokens
+  where
+    msg = "analyze " ++ show (input, targets)
+          ++ "tokens= " ++ show tokens
+    tokens = analyze input context $ HashSet.fromList targets
diff --git a/tests/Duckling/Tests.hs b/tests/Duckling/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Tests.hs
@@ -0,0 +1,28 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE NoRebindableSyntax #-}
+
+module Duckling.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import qualified Duckling.Api.Tests as Api
+import qualified Duckling.Dimensions.Tests as Dimensions
+import qualified Duckling.Engine.Tests as Engine
+
+tests :: TestTree
+tests = testGroup "Duckling Tests"
+  [ Api.tests
+  , Dimensions.tests
+  , Engine.tests
+  ]
diff --git a/tests/Duckling/Time/DA/Tests.hs b/tests/Duckling/Time/DA/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/DA/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Time.DA.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Time.DA.Corpus
+
+tests :: TestTree
+tests = testGroup "DA Tests"
+  [ makeCorpusTest [This Time] corpus
+  ]
diff --git a/tests/Duckling/Time/DE/Tests.hs b/tests/Duckling/Time/DE/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/DE/Tests.hs
@@ -0,0 +1,24 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Time.DE.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Time.DE.Corpus
+
+tests :: TestTree
+tests = testGroup "DE Tests"
+  [ makeCorpusTest [This Time] corpus
+  , makeNegativeCorpusTest [This Time] negativeCorpus
+  ]
diff --git a/tests/Duckling/Time/EN/Tests.hs b/tests/Duckling/Time/EN/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/EN/Tests.hs
@@ -0,0 +1,80 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections #-}
+
+module Duckling.Time.EN.Tests
+  ( tests
+  ) where
+
+import Data.Aeson
+import Data.Aeson.Types ((.:), parseMaybe, withObject)
+import Data.String
+import Data.Text (Text)
+import Prelude
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import Duckling.Dimensions.Types
+import Duckling.Resolve
+import Duckling.Testing.Asserts
+import Duckling.Testing.Types hiding (examples)
+import Duckling.Time.Corpus
+import Duckling.Time.EN.Corpus
+import Duckling.TimeGrain.Types (Grain(..))
+
+tests :: TestTree
+tests = testGroup "EN Tests"
+  [ makeCorpusTest [This Time] corpus
+  , makeNegativeCorpusTest [This Time] negativeCorpus
+  , exactSecondTests
+  , valuesTest
+  , intersectTests
+  ]
+
+exactSecondTests :: TestTree
+exactSecondTests = testCase "Exact Second Tests" $
+  mapM_ (analyzedFirstTest context . withTargets [This Time]) xs
+  where
+    context = testContext {referenceTime = refTime (2016, 12, 6, 13, 21, 42) 1}
+    xs = concat
+      [ examples (datetime (2016, 12, 6, 13, 21, 45) Second)
+                 [ "in 3 seconds"
+                 ]
+      , examples (datetime (2016, 12, 6, 13, 31, 42) Second)
+                 [ "in ten minutes"
+                 ]
+      , examples (datetimeInterval ((2016, 12, 6, 13, 21, 42), (2016, 12, 12, 0, 0, 0)) Second)
+                 [ "by next week"
+                 , "by Monday"
+                 ]
+      ]
+
+valuesTest :: TestTree
+valuesTest = testCase "Values Test" $
+  mapM_ (analyzedFirstTest testContext . withTargets [This Time]) xs
+  where
+    xs = examplesCustom (parserCheck 1 parseValuesSize)
+                        [ "now"
+                        , "8 o'clock tonight"
+                        , "tonight at 8 o'clock"
+                        ]
+    parseValuesSize :: Value -> Maybe Int
+    parseValuesSize x = length <$> parseValues x
+    parseValues :: Value -> Maybe [Object]
+    parseValues = parseMaybe $ withObject "value object" (.: "values")
+
+intersectTests :: TestTree
+intersectTests = testCase "Intersect Test" $
+  mapM_ (analyzedNTest testContext . withTargets [This Time]) xs
+  where
+    xs = [ ("tomorrow July", 2)
+         , ("Mar tonight", 2)
+         , ("Feb tomorrow", 1) -- we are in February
+         ]
diff --git a/tests/Duckling/Time/ES/Tests.hs b/tests/Duckling/Time/ES/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/ES/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Time.ES.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Time.ES.Corpus
+
+tests :: TestTree
+tests = testGroup "ES Tests"
+  [ makeCorpusTest [This Time] corpus
+  ]
diff --git a/tests/Duckling/Time/FR/Tests.hs b/tests/Duckling/Time/FR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/FR/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Time.FR.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Time.FR.Corpus
+
+tests :: TestTree
+tests = testGroup "FR Tests"
+  [ makeCorpusTest [This Time] corpus
+  ]
diff --git a/tests/Duckling/Time/GA/Tests.hs b/tests/Duckling/Time/GA/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/GA/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Time.GA.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Time.GA.Corpus
+
+tests :: TestTree
+tests = testGroup "GA Tests"
+  [ makeCorpusTest [This Time] corpus
+  ]
diff --git a/tests/Duckling/Time/HE/Tests.hs b/tests/Duckling/Time/HE/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/HE/Tests.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+module Duckling.Time.HE.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Time.HE.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "HE Tests"
+  [ makeCorpusTest [This Time] corpus
+  ]
diff --git a/tests/Duckling/Time/HR/Tests.hs b/tests/Duckling/Time/HR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/HR/Tests.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+module Duckling.Time.HR.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Time.HR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "HR Tests"
+  [ makeCorpusTest [This Time] corpus
+  ]
diff --git a/tests/Duckling/Time/IT/Tests.hs b/tests/Duckling/Time/IT/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/IT/Tests.hs
@@ -0,0 +1,24 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Time.IT.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Time.IT.Corpus
+
+tests :: TestTree
+tests = testGroup "IT Tests"
+  [ makeCorpusTest [This Time] corpus
+  , makeNegativeCorpusTest [This Time] negativeCorpus
+  ]
diff --git a/tests/Duckling/Time/KO/Tests.hs b/tests/Duckling/Time/KO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/KO/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Time.KO.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Time.KO.Corpus
+
+tests :: TestTree
+tests = testGroup "KO Tests"
+  [ makeCorpusTest [This Time] corpus
+  ]
diff --git a/tests/Duckling/Time/NB/Tests.hs b/tests/Duckling/Time/NB/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/NB/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Time.NB.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Time.NB.Corpus
+
+tests :: TestTree
+tests = testGroup "NB Tests"
+  [ makeCorpusTest [This Time] corpus
+  ]
diff --git a/tests/Duckling/Time/PL/Tests.hs b/tests/Duckling/Time/PL/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/PL/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Time.PL.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Time.PL.Corpus
+
+tests :: TestTree
+tests = testGroup "PL Tests"
+  [ makeCorpusTest [This Time] corpus
+  ]
diff --git a/tests/Duckling/Time/PT/Tests.hs b/tests/Duckling/Time/PT/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/PT/Tests.hs
@@ -0,0 +1,24 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Time.PT.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Time.PT.Corpus
+
+tests :: TestTree
+tests = testGroup "PT Tests"
+  [ makeCorpusTest [This Time] corpus
+  , makeNegativeCorpusTest [This Time] negativeCorpus
+  ]
diff --git a/tests/Duckling/Time/RO/Tests.hs b/tests/Duckling/Time/RO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/RO/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Time.RO.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Time.RO.Corpus
+
+tests :: TestTree
+tests = testGroup "RO Tests"
+  [ makeCorpusTest [This Time] corpus
+  ]
diff --git a/tests/Duckling/Time/SV/Tests.hs b/tests/Duckling/Time/SV/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/SV/Tests.hs
@@ -0,0 +1,51 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections #-}
+
+module Duckling.Time.SV.Tests
+  ( tests ) where
+
+import qualified Data.HashSet as HashSet
+import Prelude
+import Data.String
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import Duckling.Api
+import Duckling.Dimensions.Types
+import Duckling.Lang
+import Duckling.Resolve
+import Duckling.Testing.Asserts
+import Duckling.Testing.Types hiding (examples)
+import Duckling.Time.Corpus
+import Duckling.Time.SV.Corpus
+import Duckling.TimeGrain.Types (Grain(..))
+
+tests :: TestTree
+tests = testGroup "SV Tests"
+  [ makeCorpusTest [This Time] corpus
+  , ambiguousTests
+  ]
+
+-- Ambiguous examples occur when multiple tokens have the same value,
+-- but one is more flexible (they differ in the `values` field).
+-- For example, "i morgonen" can both mean "this morning" and "in the morning".
+ambiguousTests :: TestTree
+ambiguousTests = testCase "Ambiguous Tests" $ mapM_ check xs
+  where
+    xs = examples (datetimeInterval ((2013, 2, 12, 4, 0, 0), (2013, 2, 12, 12, 0, 0)) Hour)
+                  [ "i morgonen"
+                  ]
+    ctx = testContext {lang = SV}
+    dims = HashSet.singleton $ This Time
+    check :: Example -> IO ()
+    check (input, predicate) = case analyze input ctx dims of
+      [] -> assertFailure $ "empty result on " ++ show input
+      xs -> mapM_ (assertBool ("predicate on " ++ show input) . predicate ctx) xs
diff --git a/tests/Duckling/Time/Tests.hs b/tests/Duckling/Time/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/Tests.hs
@@ -0,0 +1,127 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Time.Tests (tests) where
+
+import Data.Aeson
+import Data.Aeson.Types ((.:), parseMaybe, withObject)
+import Data.String
+import Data.Text (Text)
+import Data.Time
+import Prelude
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Testing.Types
+import Duckling.Time.Types
+import Duckling.TimeGrain.Types
+import qualified Duckling.Time.DA.Tests as DA
+import qualified Duckling.Time.DE.Tests as DE
+import qualified Duckling.Time.EN.Tests as EN
+import qualified Duckling.Time.ES.Tests as ES
+import qualified Duckling.Time.FR.Tests as FR
+import qualified Duckling.Time.GA.Tests as GA
+import qualified Duckling.Time.HR.Tests as HR
+import qualified Duckling.Time.HE.Tests as HE
+import qualified Duckling.Time.IT.Tests as IT
+import qualified Duckling.Time.KO.Tests as KO
+import qualified Duckling.Time.NB.Tests as NB
+import qualified Duckling.Time.PL.Tests as PL
+import qualified Duckling.Time.PT.Tests as PT
+import qualified Duckling.Time.RO.Tests as RO
+import qualified Duckling.Time.SV.Tests as SV
+import qualified Duckling.Time.VI.Tests as VI
+import qualified Duckling.Time.ZH.Tests as ZH
+
+tests :: TestTree
+tests = testGroup "Time Tests"
+  [ DA.tests
+  , DE.tests
+  , EN.tests
+  , ES.tests
+  , FR.tests
+  , GA.tests
+  , HR.tests
+  , HE.tests
+  , IT.tests
+  , KO.tests
+  , NB.tests
+  , PL.tests
+  , PT.tests
+  , RO.tests
+  , SV.tests
+  , VI.tests
+  , ZH.tests
+  , timeFormatTest
+  , timeIntersectTest
+  ]
+
+timeFormatTest :: TestTree
+timeFormatTest = testCase "Format Test" $
+  mapM_ (analyzedFirstTest testContext . withTargets [This Time]) xs
+  where
+    xs = examplesCustom (parserCheck expected parseValue) ["now"]
+    expected = "2013-02-12T04:30:00.000-02:00"
+
+parseValue :: Value -> Maybe Text
+parseValue = parseMaybe . withObject "value object" $ \o -> o .: "value"
+
+timeIntersectTest :: TestTree
+timeIntersectTest = testCase "Intersect Test" $ mapM_ check
+  [ ((t01, t01), Just t01)
+  , ((t01, t12), Nothing)
+  , ((t12, t13), Just t12)
+  , ((t12, t34), Nothing)
+  , ((t13, t23), Just t23)
+  , ((t13, t24), Just t23)
+  , ((t0_, t0_), Just t0_)
+  , ((t0_, t1_), Nothing)
+  , ((t0_, t01), Just t01)
+  , ((t0_, t13), Nothing)
+  , ((t0_, t2_), Nothing)
+  , ((t0m, t13), Just t13)
+
+  , ((t12, t01), Nothing)
+  , ((t13, t12), Just t12)
+  , ((t34, t12), Nothing)
+  , ((t23, t13), Just t23)
+  , ((t24, t13), Just t23)
+  , ((t1_, t0_), Nothing)
+  , ((t01, t0_), Just t01)
+  , ((t13, t0_), Nothing)
+  , ((t2_, t0_), Nothing)
+  , ((t13, t0m), Just t13)
+
+  , ((t13, t2m), Just t23)
+  , ((t2m, t13), Just t23)
+  ]
+  where
+    check :: ((TimeObject, TimeObject), Maybe TimeObject) -> IO ()
+    check ((t1, t2), exp) =
+      assertEqual "wrong intersect" exp $ timeIntersect t1 t2
+    t0_ = TimeObject t0 Second Nothing
+    t0m = TimeObject t0 Minute Nothing
+    t01 = TimeObject t0 Second $ Just t1
+    t1_ = TimeObject t1 Second Nothing
+    t12 = TimeObject t1 Second $ Just t2
+    t13 = TimeObject t1 Second $ Just t3
+    t2_ = TimeObject t2 Second Nothing
+    t2m = TimeObject t2 Minute Nothing
+    t23 = TimeObject t2 Second $ Just t3
+    t24 = TimeObject t2 Second $ Just t4
+    t34 = TimeObject t3 Second $ Just t4
+    t0 = UTCTime day 0
+    t1 = UTCTime day 1
+    t2 = UTCTime day 2
+    t3 = UTCTime day 3
+    t4 = UTCTime day 4
+    day = fromGregorian 2017 2 8
diff --git a/tests/Duckling/Time/VI/Tests.hs b/tests/Duckling/Time/VI/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/VI/Tests.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+module Duckling.Time.VI.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Time.VI.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "VI Tests"
+  [ makeCorpusTest [This Time] corpus
+  ]
diff --git a/tests/Duckling/Time/ZH/Tests.hs b/tests/Duckling/Time/ZH/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Time/ZH/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Time.ZH.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Time.ZH.Corpus
+
+tests :: TestTree
+tests = testGroup "ZH Tests"
+  [ makeCorpusTest [This Time] corpus
+  ]
diff --git a/tests/Duckling/Url/Tests.hs b/tests/Duckling/Url/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Url/Tests.hs
@@ -0,0 +1,39 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Duckling.Url.Tests
+  ( tests
+  ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Testing.Types
+import Duckling.Url.Corpus
+import Duckling.Url.Types
+
+tests :: TestTree
+tests = testGroup "Url Tests"
+  [ makeCorpusTest [This Url] corpus
+  , makeNegativeCorpusTest [This Url] negativeCorpus
+  , surroundTests
+  ]
+
+surroundTests :: TestTree
+surroundTests = testCase "Surround Tests" $
+  mapM_ (analyzedFirstTest testContext . withTargets [This Url]) xs
+  where
+    xs = examples (UrlData "www.lets-try-this-one.co.uk/episode-7" "lets-try-this-one.co.uk")
+                  [ "phishing link: www.lets-try-this-one.co.uk/episode-7  If you want my job"
+                  ]
diff --git a/tests/Duckling/Volume/EN/Tests.hs b/tests/Duckling/Volume/EN/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Volume/EN/Tests.hs
@@ -0,0 +1,24 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Volume.EN.Tests
+  ( tests
+  ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Volume.EN.Corpus
+
+tests :: TestTree
+tests = testGroup "EN Tests"
+  [ makeCorpusTest [This Volume] corpus
+  ]
diff --git a/tests/Duckling/Volume/ES/Tests.hs b/tests/Duckling/Volume/ES/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Volume/ES/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Volume.ES.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Volume.ES.Corpus
+
+tests :: TestTree
+tests = testGroup "ES Tests"
+  [ makeCorpusTest [This Volume] corpus
+  ]
diff --git a/tests/Duckling/Volume/FR/Tests.hs b/tests/Duckling/Volume/FR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Volume/FR/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Volume.FR.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Volume.FR.Corpus
+
+tests :: TestTree
+tests = testGroup "FR Tests"
+  [ makeCorpusTest [This Volume] corpus
+  ]
diff --git a/tests/Duckling/Volume/GA/Tests.hs b/tests/Duckling/Volume/GA/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Volume/GA/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Volume.GA.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Volume.GA.Corpus
+
+tests :: TestTree
+tests = testGroup "GA Tests"
+  [ makeCorpusTest [This Volume] corpus
+  ]
diff --git a/tests/Duckling/Volume/HR/Tests.hs b/tests/Duckling/Volume/HR/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Volume/HR/Tests.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+module Duckling.Volume.HR.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Volume.HR.Corpus
+import Duckling.Testing.Asserts
+
+tests :: TestTree
+tests = testGroup "HR Tests"
+  [ makeCorpusTest [This Volume] corpus
+  ]
diff --git a/tests/Duckling/Volume/IT/Tests.hs b/tests/Duckling/Volume/IT/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Volume/IT/Tests.hs
@@ -0,0 +1,22 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+module Duckling.Volume.IT.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Volume.IT.Corpus
+
+tests :: TestTree
+tests = testGroup "IT Tests"
+  [ makeCorpusTest [This Volume] corpus
+  ]
diff --git a/tests/Duckling/Volume/KO/Tests.hs b/tests/Duckling/Volume/KO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Volume/KO/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Volume.KO.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Volume.KO.Corpus
+
+tests :: TestTree
+tests = testGroup "KO Tests"
+  [ makeCorpusTest [This Volume] corpus
+  ]
diff --git a/tests/Duckling/Volume/NL/Tests.hs b/tests/Duckling/Volume/NL/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Volume/NL/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Volume.NL.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Volume.NL.Corpus
+
+tests :: TestTree
+tests = testGroup "NL Tests"
+  [ makeCorpusTest [This Volume] corpus
+  ]
diff --git a/tests/Duckling/Volume/PT/Tests.hs b/tests/Duckling/Volume/PT/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Volume/PT/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Volume.PT.Tests
+  ( tests ) where
+
+import Data.String
+import Prelude
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Volume.PT.Corpus
+
+tests :: TestTree
+tests = testGroup "PT Tests"
+  [ makeCorpusTest [This Volume] corpus
+  ]
diff --git a/tests/Duckling/Volume/RO/Tests.hs b/tests/Duckling/Volume/RO/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Volume/RO/Tests.hs
@@ -0,0 +1,23 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Volume.RO.Tests
+  ( tests ) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import Duckling.Dimensions.Types
+import Duckling.Testing.Asserts
+import Duckling.Volume.RO.Corpus
+
+tests :: TestTree
+tests = testGroup "RO Tests"
+  [ makeCorpusTest [This Volume] corpus
+  ]
diff --git a/tests/Duckling/Volume/Tests.hs b/tests/Duckling/Volume/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Duckling/Volume/Tests.hs
@@ -0,0 +1,38 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+module Duckling.Volume.Tests (tests) where
+
+import Prelude
+import Data.String
+import Test.Tasty
+
+import qualified Duckling.Volume.EN.Tests as EN
+import qualified Duckling.Volume.ES.Tests as ES
+import qualified Duckling.Volume.FR.Tests as FR
+import qualified Duckling.Volume.GA.Tests as GA
+import qualified Duckling.Volume.HR.Tests as HR
+import qualified Duckling.Volume.IT.Tests as IT
+import qualified Duckling.Volume.KO.Tests as KO
+import qualified Duckling.Volume.NL.Tests as NL
+import qualified Duckling.Volume.PT.Tests as PT
+import qualified Duckling.Volume.RO.Tests as RO
+
+tests :: TestTree
+tests = testGroup "Volume Tests"
+  [ EN.tests
+  , ES.tests
+  , FR.tests
+  , GA.tests
+  , HR.tests
+  , IT.tests
+  , KO.tests
+  , NL.tests
+  , PT.tests
+  , RO.tests
+  ]
diff --git a/tests/TestMain.hs b/tests/TestMain.hs
new file mode 100644
--- /dev/null
+++ b/tests/TestMain.hs
@@ -0,0 +1,16 @@
+-- Copyright (c) 2016-present, Facebook, Inc.
+-- All rights reserved.
+--
+-- This source code is licensed under the BSD-style license found in the
+-- LICENSE file in the root directory of this source tree. An additional grant
+-- of patent rights can be found in the PATENTS file in the same directory.
+
+
+import Data.String
+import Test.Tasty
+
+import Duckling.Tests
+import Prelude
+
+main :: IO ()
+main = defaultMain tests
