packages feed

htsn-import-0.0.4: src/TSN/Picklers.hs

-- | (Un)picklers for data types present in The Sports Network XML
--   feed.
--
module TSN.Picklers (
  xp_date,
  xp_gamedate,
  xp_tba_time,
  xp_time,
  xp_time_stamp )
where

-- System imports.
import Data.Time.Clock ( NominalDiffTime, UTCTime, addUTCTime )
import Data.Time.Format ( formatTime, parseTime )
import System.Locale ( defaultTimeLocale )
import Text.XML.HXT.Arrow.Pickle (
  xpText,
  xpWrap,
  xpWrapMaybe )
import Text.XML.HXT.Arrow.Pickle.Xml ( PU )


-- | (Un)pickle a UTCTime without the time portion.
--
xp_date :: PU UTCTime
xp_date =
  (to_date, from_date) `xpWrapMaybe` xpText
  where
    format = "%-m/%-d/%Y"

    to_date :: String -> Maybe UTCTime
    to_date = parseTime defaultTimeLocale format

    from_date :: UTCTime -> String
    from_date = formatTime defaultTimeLocale format


-- | (Un)pickle a UTCTime from a weather forecast's gamedate. Example
--   input looks like,
--
--   \<forecast gamedate=\"Monday, December 30th\"\>
--
--   When unpickling we get rid of the suffixes \"st\", \"nd\", \"rd\", and
--   \"th\". During pickling, we add them back based on the last digit
--   of the date.
--
xp_gamedate :: PU UTCTime
xp_gamedate =
  (to_gamedate, from_gamedate) `xpWrapMaybe` xpText
  where
    format = "%A, %B %-d"

    to_gamedate :: String -> Maybe UTCTime
    to_gamedate s =
      parseTime defaultTimeLocale format s'
      where
        s' = case (reverse s) of
               (c2:c1:cs) -> let suffix = [c1,c2]
                             in
                               case suffix of
                                 "st" -> reverse cs
                                 "nd" -> reverse cs
                                 "rd" -> reverse cs
                                 "th" -> reverse cs
                                 _    -> s -- Unknown suffix, leave it alone.
               _ -> s -- The String is less than two characters long,
                      -- leave it alone.


    from_gamedate :: UTCTime -> String
    from_gamedate d = s ++ (suffix s)
      where
        s = formatTime defaultTimeLocale format d

        suffix :: String -> String
        suffix cs =
          case (reverse cs) of
            []       -> []
            ('1':_) -> "st"
            ('2':_) -> "nd"
            ('3':_) -> "rd"
            _        -> "th"



-- | The time format string used in 'xp_time' and 'xp_time_stamp'.
--
xp_time_format :: String
xp_time_format = "%I:%M %p"


-- | (Un)pickle a UTCTime without the date portion.
--
xp_time :: PU UTCTime
xp_time =
  (to_time, from_time) `xpWrapMaybe` xpText
  where
    to_time :: String -> Maybe UTCTime
    to_time = parseTime defaultTimeLocale xp_time_format

    from_time :: UTCTime -> String
    from_time = formatTime defaultTimeLocale xp_time_format


-- | (Un)pickle a UTCTime without the date portion, allowing for a
--   value of \"TBA\" (which gets translated to 'Nothing').
--
xp_tba_time :: PU (Maybe UTCTime)
xp_tba_time =
  (to_time, from_time) `xpWrap` xpText
  where
    to_time :: String -> Maybe UTCTime
    to_time s
      | s == "TBA" = Nothing
      | otherwise = parseTime defaultTimeLocale xp_time_format s

    from_time :: Maybe UTCTime -> String
    from_time Nothing = ""
    from_time (Just t) = formatTime defaultTimeLocale xp_time_format t


-- | (Un)pickle the \<time_stamp\> element format to/from a 'UTCTime'.
--
--   Example:  \<time_stamp\> January 6, 2014, at 10:11 PM ET \</time_stamp\>
--
--   TSN doesn't provide a proper time zone name, so we assume that
--   it's always Eastern Standard Time. EST is UTC-5, so we
--   add/subtract 5 hours to convert to/from UTC.
--
xp_time_stamp :: PU UTCTime
xp_time_stamp =
  (to_time_stamp, from_time_stamp) `xpWrapMaybe` xpText
  where
    -- This omits the timezone and trailing space.
    format = " %B %-d, %Y, at " ++ xp_time_format ++ " ET "

    five_hours :: NominalDiffTime
    five_hours = 5 * 60 * 60

    add_five :: UTCTime -> UTCTime
    add_five = addUTCTime five_hours

    subtract_five :: UTCTime -> UTCTime
    subtract_five = addUTCTime (-1 * five_hours)

    to_time_stamp :: String -> Maybe UTCTime
    to_time_stamp = fmap add_five . parseTime defaultTimeLocale format

    from_time_stamp :: UTCTime -> String
    from_time_stamp = formatTime defaultTimeLocale format . subtract_five