-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

module Duckling.Time.HolidayHelpers
  ( computeEarthHour, computeKingsDay
  ) where

import Data.Maybe
import Prelude

import Duckling.Time.Helpers
import Duckling.Time.Computed
import Duckling.Time.Types (TimeData (..))
import qualified Duckling.Time.Types as TTime
import qualified Duckling.TimeGrain.Types as TG

-- Last Saturday of March unless it falls on Holy Saturday,
-- in which case it's the Saturday before.
computeEarthHour :: Maybe TimeData
computeEarthHour =
  let holySaturday = cycleNthAfter False TG.Day (-1) easterSunday
      tentative = predLastOf (dayOfWeek 6) (month 3)
      alternative = cycleNthAfter False TG.Day (-7) tentative
    in do
      day <- intersectWithReplacement holySaturday tentative alternative
      start <- intersect day $ hourMinute True 20 30
      interval TTime.Closed start $ cycleNthAfter False TG.Minute 60 start

-- King's day is on April 27th unless it's on Sunday, in which case it's a day
-- earlier. We intersect with the last Sunday of April. If 4/27 is a Sunday it
-- will be the last Sunday in April.
computeKingsDay :: Maybe TimeData
computeKingsDay =
  let tentative = monthDay 4 27
      alternative = monthDay 4 26
      lastSundayOfApril = predLastOf (dayOfWeek 7) (month 4)
    in intersectWithReplacement lastSundayOfApril tentative alternative


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.
{- HLINT ignore "Use foldr1OrError" -}

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NoRebindableSyntax #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}

module Duckling.Time.Types where

import Control.DeepSeq
import Data.Aeson
import Data.Foldable (find)
import Data.Hashable
import Data.Maybe
import Data.Monoid
import Data.Text (Text)
import Data.Tuple.Extra (both)
import GHC.Generics hiding (from, to)
import Prelude
import TextShow (showt)
import qualified Data.HashMap.Strict as H
import qualified Data.List as List
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 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)

data TimeData = TimeData
  { timePred :: Predicate
  , latent :: Bool
  , timeGrain :: Grain -- needed for intersect
  , notImmediate :: Bool
  , form :: Maybe Form
  , direction :: Maybe IntervalDirection
  , okForThisNext :: Bool -- allows specific this+Time
  , holiday :: Maybe Text
  , hasTimezone :: Bool -- hack to prevent double timezone parsing
  }

instance Eq TimeData where
  (==) (TimeData _ l1 g1 n1 f1 d1 _ _ t1) (TimeData _ l2 g2 n2 f2 d2 _ _ t2) =
    l1 == l2 && g1 == g2 && n1 == n2 && f1 == f2 && d1 == d2 && t1 == t2

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 _ holiday tz) =
    "TimeData{" ++
    "latent=" ++ show latent ++
    ", grain=" ++ show grain ++
    ", form=" ++ show form ++
    ", direction=" ++ show dir ++
    ", holiday=" ++ show holiday ++
    ", hasTimezone=" ++ show tz ++
    "}"

instance NFData TimeData where
  rnf TimeData{..} = rnf (latent, timeGrain, notImmediate, form, direction)

instance Resolve TimeData where
  type ResolvedValue TimeData = TimeValue
  resolve _ Options {withLatent = False} TimeData {latent = True} = Nothing
  resolve context _ TimeData {timePred, latent, notImmediate, direction, holiday} = do
    value <- case future of
      [] -> listToMaybe past
      ahead:nextAhead:_
        | notImmediate && isJust (timeIntersect ahead refTime) -> Just nextAhead
      ahead:_ -> Just ahead
    values <- Just $ take 3 $ if List.null future then past else future
    Just $ case direction of
      Nothing -> (TimeValue (timeValue tzSeries value)
        (map (timeValue tzSeries) values) holiday, latent)
      Just d -> (TimeValue (openInterval tzSeries d value)
        (map (openInterval tzSeries d) values) holiday, latent)
    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
        }
      (past, future) = runPredicate timePred refTime tc

timedata' :: TimeData
timedata' = TimeData
  { timePred = mkEmptyPredicate
  , latent = False
  , timeGrain = TG.Second
  , notImmediate = False
  , form = Nothing
  , direction = Nothing
  , okForThisNext = False
  , holiday = Nothing
  , hasTimezone = False
  }

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] (Maybe Text)
  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 holiday) = case toJSON value of
    Object o ->
      Object $ insertHoliday holiday $ H.insert "values" (toJSON values) o
    _ -> Object H.empty
    where
      insertHoliday :: Maybe Text -> Object -> Object
      insertHoliday Nothing obj = obj
      insertHoliday (Just h) obj = H.insert "holidayBeta" (toJSON h) obj

-- | Return a tuple of (past, future) elements
type SeriesPredicate = TimeObject -> TimeContext -> ([TimeObject], [TimeObject])

data AMPM = AM | PM
  deriving (Eq, Show)

data SeasonName = Spring | Summer | Fall | Winter deriving (Enum,Eq,Ord,Show)

-- | Regular seasons of the Northern Hemisphere.
data Season = Season { startYear :: Integer, seasonName :: SeasonName }
  deriving (Eq,Ord,Show)

newtype NoShow a = NoShow a

instance Show (NoShow a) where
  show _ = "??"

data Predicate
  = SeriesPredicate (NoShow 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
  | ReplaceIntersectPredicate Predicate Predicate Predicate
  deriving Show

runPredicate :: Predicate -> SeriesPredicate
runPredicate EmptyPredicate{} = \_ _ -> ([], [])
runPredicate (SeriesPredicate (NoShow 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
runPredicate (ReplaceIntersectPredicate pred1 pred2 pred3) =
  runReplaceIntersectPredicate pred1 pred2 pred3

-- 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

-- For debugging find it useful to make it:
-- mkEmptyPredicate :: HasCallStack => Predicate
-- mkEmptyPredicate = EmptyPredicate callStack
-- This way I can track where EmptyPredicates get created
mkEmptyPredicate :: Predicate
mkEmptyPredicate = EmptyPredicate

mkSeriesPredicate :: SeriesPredicate -> Predicate
mkSeriesPredicate = SeriesPredicate . NoShow

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 a@EmptyPredicate{} _ = a
mkIntersectPredicate _ a@EmptyPredicate{} = a
mkIntersectPredicate
  (TimeDatePredicate a1 b1 c1 d1 e1 f1 g1 h1)
  (TimeDatePredicate a2 b2 c2 d2 e2 f2 g2 h2)
  = fromMaybe mkEmptyPredicate
      (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

mkReplaceIntersectPredicate :: Predicate -> Predicate -> Predicate -> Predicate
mkReplaceIntersectPredicate pred1 pred2 pred3 =
  ReplaceIntersectPredicate pred1 pred2 pred3

mkTimeIntervalsPredicate
  :: TimeIntervalType -> Predicate -> Predicate -> Predicate
mkTimeIntervalsPredicate _ a@EmptyPredicate{} _ = a
mkTimeIntervalsPredicate _ _ a@EmptyPredicate{} = a
-- `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 _ a b
  | containsTimeIntervalsPredicate a ||
    containsTimeIntervalsPredicate b = mkEmptyPredicate
  -- this is potentially quadratic, but the sizes involved should be small
mkTimeIntervalsPredicate t a b = TimeIntervalsPredicate t a b

containsTimeIntervalsPredicate :: Predicate -> Bool
containsTimeIntervalsPredicate TimeIntervalsPredicate{} = True
containsTimeIntervalsPredicate (IntersectPredicate a b) =
  containsTimeIntervalsPredicate a || containsTimeIntervalsPredicate b
containsTimeIntervalsPredicate _ = False
  -- SeriesPredicate might contain one, but we'll underapproximate for
  -- now

-- Computes the difference of the start time of two `TimeObject`s.
diffStartTime :: TimeObject -> TimeObject -> Time.NominalDiffTime
diffStartTime TimeObject{start = x} TimeObject{start = y} =
  abs (Time.diffUTCTime x y)

isEmptyPredicate :: Predicate -> Bool
isEmptyPredicate EmptyPredicate{} = True
isEmptyPredicate _ = False

seasonStart :: Season -> Time.Day
seasonStart (Season year Spring) = Time.fromGregorian year 3 20
seasonStart (Season year Summer) = Time.fromGregorian year 6 21
seasonStart (Season year Fall) = Time.fromGregorian year 9 23
seasonStart (Season year Winter) = Time.fromGregorian year 12 21

seasonEnd :: Season -> Time.Day
seasonEnd = Time.addDays (-1) . seasonStart . nextSeason

nextSeason :: Season -> Season
nextSeason (Season year Winter) = Season (year+1) Spring
nextSeason (Season year x) = Season year (succ x)

prevSeason :: Season -> Season
prevSeason (Season year Spring) = Season (year-1) Winter
prevSeason (Season year x) = Season year (pred x)

seasonOf :: Time.Day -> Season
seasonOf day = fromMaybe (Season (year-1) Winter) mbSeason
  where
  (year,_,_) = Time.toGregorian day
  mbSeason = find ((<= day) . seasonStart) $
               Season year <$> [Winter,Fall,Summer,Spring]

seasonPredicate :: Predicate
seasonPredicate = mkSeriesPredicate series
  where
  series t = const (past,future)
    where
    day = Time.utctDay (start t)
    (past,future) = both (map toTimeObj) (toZipper day)
    toTimeObj season = TimeObject { start = s, grain = TG.Day, end = Just e }
      where (s,e) = both toMidnight (seasonStart season, seasonEnd season)
    toZipper d = (before, currentAndAfter)
      where
      current = seasonOf d
      currentAndAfter = iterate nextSeason current
      before = iterate prevSeason (prevSeason current)

-- Predicate for weekdays, i.e., Mon to Fri.
weekdayPredicate :: Predicate
weekdayPredicate = mkSeriesPredicate series
  where
  series time = const (past,future)
    where
    day = Time.utctDay (start time)
    (_,_,dayOfWeek) = Time.toWeekDate day
    past = toTimeObj . toMidnight . fst <$>
      iterate prevWeekday (prevWeekday (day,dayOfWeek))
    future = toTimeObj . toMidnight <$>
      if dayOfWeek <= 5 then day:days else days
        where days = fst <$> iterate nextWeekday (nextWeekday (day,dayOfWeek))
    toTimeObj t = TimeObject { start = t, grain = TG.Day, end = Nothing }
    nextWeekday (d,dow)
      | dow < 5 = (Time.addDays 1 d, dow+1)
      | otherwise = (Time.addDays (toInteger $ 8-dow) d, 1)
    prevWeekday (d,dow)
      | dow == 1 = (Time.addDays (-3) d, 5)
      | dow == 7 = (Time.addDays (-2) d, 5)
      | otherwise = (Time.addDays (-1) d, dow-1)

-- Predicate for periodic events with known `given`
periodicPredicate :: TG.Grain -> Int -> TimeObject -> Predicate
periodicPredicate grain delta given = mkSeriesPredicate series
  where
  series t _ = (past', future')
    where
    (past, future) = timeSequence grain delta given
    (past', future') = if timeBefore t given
      then
        let (newer, older) = span (timeBefore t) past
        in (older, reverse newer ++ future)
      else
        let (older, newer) = span (`timeBefore` t) future
        in (reverse older ++ past, newer)

toMidnight :: Time.Day -> Time.UTCTime
toMidnight = flip Time.UTCTime (Time.timeOfDayToTime Time.midnight)

-- 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 time _ =
    ( 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 time
      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 time TG.Hour
      anchor = timePlus rounded TG.Hour $ toInteger $ mod (n' - h) step

runAMPMPredicate :: AMPM -> SeriesPredicate
runAMPMPredicate ampm = series
  where
  series time _ = (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 (time { 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 time 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 time _ =
    ( 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 d _ = start t
                         (year, month, _) = Time.toGregorian d
                     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 time
      Time.UTCTime day _ = start time
      (_, _, 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

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 n
      y = timePlus (timeRound t TG.Year) TG.Year $ year - tyear

-- Limits how deep into lists of segments to look
safeMax :: Int
safeMax = 10

runReplaceIntersectPredicate
  :: Predicate -> Predicate -> Predicate -> SeriesPredicate
runReplaceIntersectPredicate pred1 pred2 pred3 = runComposeWithReplacement
  (runPredicate pred1) (runPredicate pred2) (runPredicate pred3)

-- If pred1 intersects with pred2, returns pred3 otherwise pred2
-- Caveat: only works if all predicates are aligned (e.g. once a year)
runComposeWithReplacement
  :: SeriesPredicate -> SeriesPredicate -> SeriesPredicate -> SeriesPredicate
runComposeWithReplacement pred1 pred2 pred3 = series
  where
  series nowTime context = (backward, forward)
    where
    (past1, future1) = pred1 nowTime context
    (past2, future2) = pred2 nowTime context
    (past3, future3) = pred3 nowTime context

    computeSerie :: [[TimeObject]] -> [TimeObject]
    computeSerie [tokens1,tokens2,tokens3] =
      zipWith3 (\token1 token2 token3 -> case timeIntersect token1 token2 of
        Just _ -> token3
        Nothing -> token2
      ) tokens1 tokens2 tokens3
    computeSerie _ = []

    backwardBounded =
      takeWhile (\t -> timeStartsBeforeTheEndOf (minTime context) t)
      . take safeMax
    forwardBounded =
      takeWhile (\t -> timeStartsBeforeTheEndOf t (maxTime context))
      . take safeMax

    backward = computeSerie $ map backwardBounded [past1, past2, past3]
    forward = computeSerie $ map forwardBounded [future1, future2, future3]

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 ts = mapMaybe (`f` context) $ take safeMaxInterval ts

    (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 ts = if dontReverse then ts else reverse ts
    (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) = both (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
  TimeObject{start = s1, grain = g1}
  TimeObject{start = s2, end = e2, grain = g2} = TimeObject
  { start = s1
  , grain = g'
  , end = Just $ case intervalType of
      Open -> s2
      Closed -> fromMaybe (TG.add s2 g2' 1) e2
  }
  where
    g' = min g1 g2
    g2'
      | g1 < TG.Day && g2 < TG.Day = g'
      | otherwise = g2

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


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE OverloadedStrings #-}

module Duckling.Time.CA.Corpus
  ( corpus
  ) where

import Data.String
import Prelude

import Duckling.Locale
import Duckling.Resolve
import Duckling.Time.Corpus
import Duckling.TimeGrain.Types hiding (add)
import Duckling.Testing.Types hiding (examples)

context :: Context
context = testContext {locale = makeLocale CA Nothing}

corpus :: Corpus
corpus = (context, testOptions, allExamples)

allExamples :: [Example]
allExamples = concat
  [ examples (datetime (2013, 2, 12, 4, 30, 0) Second)
             [ "ara"
             , "ja"
             , "en aquest moment"
             , "en aquests moments"
             , "ara mateix"
             ]
  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
             [ "avui"
             ]
  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
             [ "ahir"
             ]
  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
             [ "abans d'ahir"
             ]
  {--
    This is intentional
    The purpose is to steer the classifier towards "tomorrow" rule
    instead of "morning" rule.
  --}
  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
             [ "demÃ "
             , "dema"
             ]
  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
             [ "demÃ  passat"
             , "dema passat"
             ]
  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
             [ "dilluns"
             , "dl"
             , "dl."
             , "aquest dilluns"
             ]
  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
             [ "dilluns, Febrer 18"
             ]
  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
             [ "dimarts"
             , "dm"
             , "dm."
             , "dimarts 19"
             ]
  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
             [ "dimecres"
             , "dc"
             , "dc."
             , "dimecres 13"
             ]
  , examples (datetime (2013, 2, 14, 0, 0, 0) Day)
             [ "dijous"
             , "dj"
             , "dj."
             ]
  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
             [ "divendres"
             , "dv"
             , "dv."
             , "dv. 15"
             ]
  , examples (datetime (2013, 2, 16, 0, 0, 0) Day)
             [ "dissabte"
             , "ds"
             , "ds."
             ]
  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
             [ "diumenge"
             , "dg"
             , "dg."
             ]
  , examples (datetime (2013, 5, 5, 0, 0, 0) Day)
             [ "el 5 de maig"
             , "el cinc de maig"
             ]
  , examples (datetime (2013, 5, 5, 0, 0, 0) Day)
             [ "el cinc de maig de 2013"
             , "5-5-2013"
             ]
  , examples (datetime (2013, 7, 4, 0, 0, 0) Day)
             [ "el 4 de juliol"
             , "el 4/7"
             ]
   , examples (datetime (2013, 8, 4, 0, 0, 0) Day)
             [ "el 4 d'agost"
             , "el 4/8"
             ]
  , examples (datetime (2013, 3, 3, 0, 0, 0) Day)
             [ "el 3 de marÃ§"
             , "3 de marÃ§"
             , "el 3-3"
             ]
   , examples (datetime (2013, 10, 24, 0, 0, 0) Day)
             [ "el 24 d'octubre"
             , "el 24/10"
             ]
   , examples (datetime (2013, 09, 24, 0, 0, 0) Day)
             [ "el 24 de setembre"
             , "el 24 de set"
             , "el 24/09"
             ]
   , examples (datetime (2013, 09, 0, 0, 0, 0) Month)
             [ "setembre"
             , "set"
             ]
  , examples (datetime (2013, 4, 5, 0, 0, 0) Day)
             [ "el 5 d'abril"
             , "5 d'abril"
             ]
  , examples (datetime (2013, 3, 1, 0, 0, 0) Day)
             [ "el 1 de marÃ§"
             , "l'1 de marÃ§"
             , "1 de marÃ§"
             , "el primer de marÃ§"
             , "l'u de marÃ§"
             , "u de marÃ§"
             ]
  , 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 febrer"
             ]
  , examples (datetime (2013, 2, 17, 0, 0, 0) Day)
             [ "el 17"
             , "17 de febrer"
             , "17-2"
             , "el 17/2"
             ]
  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
             [ "el 20"
             , "20 de febrer"
             , "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 dimarts vinent"
             , "dimarts vinent"
             ]
  , examples (datetime (2013, 2, 20, 0, 0, 0) Day)
             [ "dimecres vinent"
             , "el dimecres de la setmana vinent"
             ]
  , examples (datetime (2013, 2, 11, 0, 0, 0) Day)
             [ "el dilluns d'aquesta setmana"
             ]
  , examples (datetime (2013, 2, 12, 0, 0, 0) Day)
             [ "dimarts d'aquesta setmana"
             ]
  , examples (datetime (2013, 2, 13, 0, 0, 0) Day)
             [ "el dimecres d'aquesta setmana"
             ]
  , examples (datetime (2013, 2, 11, 0, 0, 0) Week)
             [ "aquesta setmana"
             ]
  , examples (datetime (2013, 2, 4, 0, 0, 0) Week)
             [ "la setmana passada"
             ]
  , examples (datetime (2013, 2, 18, 0, 0, 0) Week)
             [ "la setmana vinent"
             , "la propera setmana"
             , "setmana vinent"
             , "propera setmana"
             , "properes setmanes"
             , "segÃ¼ent setmana"
             ]
  , examples (datetime (2013, 1, 1, 0, 0, 0) Month)
             [ "el passat mes"
             ]
  , examples (datetime (2013, 3, 0, 0, 0, 0) Month)
             [ "el mes vinent"
             , "el proper mes"
             ]
  , examples (datetime (2012, 0, 0, 0, 0, 0) Year)
             [ "l'any passat"
             ]
  , examples (datetime (2013, 0, 0, 0, 0, 0) Year)
             [ "aquest any"
             ]
  , examples (datetime (2014, 0, 0, 0, 0, 0) Year)
             [ "l'any vinent"
             , "el proper any"
             ]
  , examples (datetime (2013, 2, 10, 0, 0, 0) Day)
             [ "el diumenge passat"
             ]
  , examples (datetime (2013, 2, 5, 0, 0, 0) Day)
             [ "el dimarts passat"
             ]
  , examples (datetime (2013, 2, 12, 15, 0, 0) Minute)
             [ "15:00"
             , "15.00"
             ]
  , examples (datetime (2013, 2, 13, 0, 0, 0) Hour)
             [ "mitjanit"
             ]
  , examples (datetime (2013, 2, 12, 12, 0, 0) Hour)
             [ "migdia"
             ]
  , examples (datetime (2013, 2, 12, 15, 15, 0) Minute)
             [ "tres i quart"
             , "15:15"
             ]
  , examples (datetime (2013, 2, 12, 15, 30, 0) Minute)
             [ "15:30"
             ]
  , examples (datetime (2013, 2, 12, 11, 45, 0) Minute)
             [ "11:45"
             ]
  , examples (datetime (2013, 2, 12, 4, 32, 0) Second)
             [ "en 2 minuts"
             , "en dos minuts"
             ]
  , examples (datetime (2013, 2, 12, 5, 30, 0) Second)
             [ "en 60 minuts"
             ]
  , examples (datetime (2013, 2, 12, 5, 30, 0) Minute)
             [ "en una hora"
             ]
  , examples (datetime (2013, 2, 12, 2, 30, 0) Minute)
             [ "fa dues hores"
             ]
  , examples (datetime (2013, 2, 13, 4, 30, 0) Minute)
             [ "en 24 hores"
             , "en vint-i-quatre hores"
             ]
  , examples (datetime (2013, 2, 13, 4, 0, 0) Hour)
             [ "en un dia"
             ]
  , examples (datetime (2013, 2, 19, 4, 0, 0) Hour)
             [ "en 7 dies"
             ]
  , examples (datetime (2013, 2, 19, 0, 0, 0) Day)
             [ "en una setmana"
             ]
  , examples (datetime (2013, 1, 22, 0, 0, 0) Day)
             [ "fa tres setmanes"
             ]
  , examples (datetime (2013, 4, 12, 0, 0, 0) Day)
             [ "en dos mesos"
             ]
  , examples (datetime (2012, 11, 12, 0, 0, 0) Day)
             [ "fa tres mesos"
             ]
  , examples (datetime (2014, 2, 0, 0, 0, 0) Month)
             [ "en un any"
             , "en 1 any"
             ]
  , examples (datetime (2011, 2, 0, 0, 0, 0) Month)
             [ "fa dos anys"
             ]
  , examples (datetimeInterval ((2013, 6, 21, 0, 0, 0), (2013, 9, 24, 0, 0, 0)) Day)
             [ "aquest estiu"
             ]
  , examples (datetimeInterval ((2012, 12, 21, 0, 0, 0), (2013, 3, 21, 0, 0, 0)) Day)
             [ "aquest hivern"
             ]
  , examples (datetimeHoliday (2013, 12, 25, 0, 0, 0) Day "Navidad")
             [ "Nadal"
             , "el Nadal"
             ]
  , examples (datetime (2013, 12, 31, 0, 0, 0) Day)
             [ "nit de cap d'any"
             ]
  , examples (datetime (2014, 1, 1, 0, 0, 0) Day)
             [ "cap d'any"
             ]
  , examples (datetime (2013, 2, 12, 21, 0, 0) Hour)
             [ "nou del vespre"
             ]
  , examples (datetimeInterval ((2013, 2, 12, 14, 0, 0), (2013, 2, 12, 19, 0, 0)) Hour)
             [ "aquesta tarda"
             ]
  , examples (datetimeInterval ((2013, 2, 13, 14, 0, 0), (2013, 2, 13, 19, 0, 0)) Hour)
             [ "demÃ  a la tarda"
             , "dema a la tarda"
             ]
  , examples (datetimeInterval ((2013, 2, 11, 14, 0, 0), (2013, 2, 11, 19, 0, 0)) Hour)
             [ "ahir per la tarda"
             ]
  , examples (datetimeInterval ((2013, 2, 12, 19, 0, 0), (2013, 2, 12, 22, 0, 0)) Hour)
             [ "aquest vespre"
             ]
  , examples (datetimeInterval ((2013, 2, 13, 19, 0, 0), (2013, 2, 13, 22, 0, 0)) Hour)
             [ "demÃ  al vespre"
             , "dema al vespre"
             ]
  , examples (datetimeInterval ((2013, 2, 11, 19, 0, 0), (2013, 2, 11, 22, 0, 0)) Hour)
             [ "ahir al vespre"
             ]
  , examples (datetimeInterval ((2013, 2, 12, 0, 0, 0), (2013, 2, 12, 1, 0, 0)) Hour)
             [ "aquesta nit"
             ]
  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 13, 1, 0, 0)) Hour)
             [ "demÃ  a la nit"
             , "dema a la nit"
             ]
  , examples (datetimeInterval ((2013, 2, 11, 0, 0, 0), (2013, 2, 11, 1, 0, 0)) Hour)
             [ "ahir per la nit"
             ]
  , examples (datetimeInterval ((2013, 2, 15, 18, 0, 0), (2013, 2, 18, 0, 0, 0)) Hour)
             [ "aquest cap de setmana"
             ]
  , examples (datetimeInterval ((2013, 2, 18, 6, 0, 0), (2013, 2, 18, 12, 0, 0)) Hour)
             [ "dilluns pel matÃ"
             ]
  , examples (datetimeInterval ((2013, 2, 15, 6, 0, 0), (2013, 2, 15, 12, 0, 0)) Hour)
             [ "el 15 de febrer pel matÃ"
             ]
  , examples (datetimeInterval ((2013, 2, 12, 4, 29, 58), (2013, 2, 12, 4, 30, 0)) Second)
             [ "darrers 2 segons" -- texte original pasados 2 segundos
             ]
  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 1), (2013, 2, 12, 4, 30, 4)) Second)
             [ "propers 3 segons"
             ]
  , examples (datetimeInterval ((2013, 2, 12, 4, 28, 0), (2013, 2, 12, 4, 30, 0)) Minute)
             [ "darrers 2 minuts"
             ]
  , examples (datetimeInterval ((2013, 2, 12, 4, 31, 0), (2013, 2, 12, 4, 34, 0)) Minute)
             [ "propers 3 minuts"
             ]
  , examples (datetimeInterval ((2013, 2, 12, 5, 0, 0), (2013, 2, 12, 8, 0, 0)) Hour)
             [ "properes 3 hores"
             ]
  , examples (datetimeInterval ((2013, 2, 10, 0, 0, 0), (2013, 2, 12, 0, 0, 0)) Day)
             [ "darrers 2 dies"
             ]
  , examples (datetimeInterval ((2013, 2, 13, 0, 0, 0), (2013, 2, 16, 0, 0, 0)) Day)
             [ "propers 3 dies"
             ]
  , examples (datetimeInterval ((2013, 1, 28, 0, 0, 0), (2013, 2, 11, 0, 0, 0)) Week)
             [ "darreres dues setmanes"
             ]
  , examples (datetimeInterval ((2013, 2, 18, 0, 0, 0), (2013, 3, 11, 0, 0, 0)) Week)
             [ "3 properes setmanes"
             ]
  , examples (datetimeInterval ((2012, 12, 0, 0, 0, 0), (2013, 2, 0, 0, 0, 0)) Month)
             [ "darrers 2 mesos"
             ]
  , examples (datetimeInterval ((2013, 3, 0, 0, 0, 0), (2013, 6, 0, 0, 0, 0)) Month)
             [ "3 propers mesos"
             , "propers tres mesos"
             ]
  , examples (datetimeInterval ((2011, 0, 0, 0, 0, 0), (2013, 0, 0, 0, 0, 0)) Year)
             [ "darrers 2 anys"
             ]
  , examples (datetimeInterval ((2014, 0, 0, 0, 0, 0), (2017, 0, 0, 0, 0, 0)) Year)
             [ "3 propers anys"
             ]
  , examples (datetimeInterval ((2013, 7, 13, 0, 0, 0), (2013, 7, 16, 0, 0, 0)) Day)
             [ "13 - 15 de juliol de 2013"
             ]
  , examples (datetimeInterval ((2013, 7, 1, 0, 0, 0), (2013, 7, 16, 0, 0, 0)) Day)
             [ "1 - 15 de juliol 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 des. al 6 de gen"
             ]
  , examples (datetimeInterval ((2013, 2, 12, 4, 30, 0), (2013, 2, 12, 7, 30, 0)) Second)
             [ "dintre de tres hores"
             ]
  , examples (datetime (2013, 8, 15, 0, 0, 0) Day)
             [ "dj 15"
             , "dj. 15"
             , "dijous 15"
             ]
  , examples (datetimeHoliday (2013, 12, 18, 0, 0, 0) Day "Dia Mundial de la Lengua Ã€rab")
             [ "dia mundial de la lengua Ã¡rabe"
             ]
  , examples (datetimeHoliday (2013, 3, 1, 0, 0, 0) Day "Dia de la Zero DiscriminaciÃ³")
             [ "dia de la zero discriminaciÃ³"
             ]
  , examples (datetimeHoliday (2019, 7, 6, 0, 0, 0) Day "Dia Internacional de les Cooperatives")
             [ "dia internacional de les cooperatives del 2019"
             ]
  , examples (datetimeHoliday (2013, 11, 17, 0, 0, 0) Day "Dia de la Prematuridad Mundial")
             [ "dia de la prematuritat mundial"
             , "dia mundial de l'infant prematur"
             ]
  , examples (datetimeHoliday (2013, 4, 1, 0, 0, 0) Day "Dia dels Innocents d'Abril")
             [ "dia dels innocents d'abril"
             ]
  , examples (datetime (2013, 3, 9, 0, 0, 0) Day)
             [ "dia nou"
             ]
  , examples (datetime (2013, 2, 15, 0, 0, 0) Day)
             [ "dia quinze"
             ]
  , examples (datetime (2013, 3, 11, 0, 0, 0) Day)
             [ "dia onze"
             ]
  , examples (datetime (1990, 0, 0, 0, 0, 0) Year)
             [ "mil nou-cents noranta"
             ]
  , examples (datetime (1990, 5, 4, 0, 0, 0) Day)
             [ "quatre de maig de mil nou-cents noranta"
             ]
  , examples (datetime (2013, 4, 1, 0, 0, 0) Quarter)
             [ "segon trimestre de 2013"
             ]
  ]


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE OverloadedStrings #-}

module Duckling.Time.DA.Corpus
  ( corpus
  ) where

import Data.String
import Prelude

import Duckling.Locale
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 {locale = makeLocale DA Nothing}, testOptions, 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"
             , "pÃ¥ mandag"
             ]
  , examples (datetime (2013, 2, 18, 0, 0, 0) Day)
             [ "Mandag den 18. februar"
             , "Mandag, 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"
             ]
  , 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"
             , "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"
             , "kl 15"
             , "15h"
             ]
  , examples (datetime (2013, 2, 12, 15, 0, 0) Hour)
             [ "ca. kl. 15"
             , "cirka 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"
             , "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"
             , "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"
             , "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, 24, 0, 0, 0) Day)
             [ "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"
             , "indtil kl 14"
             , "inden klokken 14"
             ]
  , examples (datetime (2013, 2, 12, 13, 0, 0) Minute)
             [ "@ 16 CET"
             , "kl. 16 CET"
             , "kl 16 CET"
             , "klokken 16 CET"
             ]
  , examples (datetime (2013, 2, 14, 6, 0, 0) Minute)
             [ "torsdag kl. 8:00 GMT"
             , "torsdag kl. 8:00 gmt"
             , "torsdag kl 8:00 gmt"
             , "torsdag klokken 8:00 GMT"
             , "torsdag klokken 8:00 gmt"
             , "torsdag 08:00 GMT"
             , "torsdag 08:00 gmt"
             ]
  , examples (datetime (2013, 2, 12, 14, 0, 0) Hour)
             [ "idag kl. 14"
             , "i dag kl. 14"
             , "i dag kl. 14"
             , "idag klokken 14"
             , "kl. 14"
             , "kl 14"
             , "klokken 14"
             ]
  , examples (datetime (2013, 4, 25, 16, 0, 0) Minute)
             [ "25/4 kl. 16:00"
             , "25/4 kl 16:00"
             , "25/4 klokken 16:00"
             , "25-04 klokken 16:00"
             , "25-4 kl. 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"
             , "kl 15:00 i morgen"
             , "klokken 15:00 i morgen"
             ]
  , examples (datetimeOpenInterval After (2013, 2, 12, 14, 0, 0) Hour)
             [ "efter kl. 14"
             , "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 kl 14"
             , "efter i morgen klokken 14"
             , "i morgen efter kl. 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 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 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"
             , "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"
             ]
  ]