xp_datetime,
xp_early_line_date,
xp_earnings,
+ xp_fracpart_only_double,
xp_gamedate,
xp_tba_time,
xp_time,
import Data.List.Split ( chunksOf )
import Data.Maybe ( catMaybes, listToMaybe )
import Data.String.Utils ( replace )
-import Data.Time.Clock ( NominalDiffTime, UTCTime, addUTCTime )
+import Data.Time.Clock ( UTCTime )
import Data.Time.Format ( formatTime, parseTime )
import Data.Tree.NTree.TypeDefs ( NTree(..) )
import System.Locale ( TimeLocale( wDays, months ), defaultTimeLocale )
import Test.Tasty ( TestTree, testGroup )
import Test.Tasty.HUnit ( (@?=), testCase )
+import Text.Read ( readMaybe )
import Text.XML.HXT.Arrow.Pickle (
xpText,
xpWrap,
-- >>> unpickleDoc xp_date_padded tn
-- Just 1983-02-15 00:00:00 UTC
--
+-- >>> let tn = text_node "06/07/2014"
+-- >>> unpickleDoc xp_date_padded tn
+-- Just 2014-06-07 00:00:00 UTC
+--
xp_date_padded :: PU UTCTime
xp_date_padded =
(to_date, from_date) `xpWrapMaybe` xpText
reverse (intercalate "," $ chunksOf 3 $ reverse $ show x)
+
-- | Parse \<Earnings\> from an 'AutoRaceResultsListing'. These are
-- essentially 'Int's, but they look like,
--
+-- | Pickle a 'Double' that can be missing its leading zero (for
+-- values less than one). For example, we've seen,
+--
+-- <TrackLength KPH=".805">0.5</TrackLength>
+--
+-- Which 'xpPrim' can't handle without the leading
+-- zero. Unfortunately there's no way pickle/unpickle can be
+-- inverses of each other here, since \"0.5\" and \".5\" should
+-- unpickle to the same 'Double'.
+--
+-- Examples:
+--
+-- >>> let tn = text_node "0.5"
+-- >>> unpickleDoc xp_fracpart_only_double tn
+-- Just 0.5
+--
+-- >>> let tn = text_node ".5"
+-- >>> unpickleDoc xp_fracpart_only_double tn
+-- Just 0.5
+--
+-- >>> let tn = text_node "foo"
+-- >>> unpickleDoc xp_fracpart_only_double tn
+-- Nothing
+--
+xp_fracpart_only_double :: PU Double
+xp_fracpart_only_double =
+ (to_double, from_double) `xpWrapMaybe` xpText
+ where
+ -- | Convert a 'String' to a 'Double', maybe. We always prepend a
+ -- zero, since it will fix the fraction-only values, and not hurt
+ -- the ones that already have a leading integer.
+ to_double :: String -> Maybe Double
+ to_double s = readMaybe ("0" ++ s)
+
+ from_double :: Double -> String
+ from_double = show
+
+
+
-- | (Un)pickle an unpadded 'UTCTime'. Used for example on the
-- \<RaceDate\> elements in an 'AutoRaceResults' message.
--
--
-- Examples:
--
+-- >>> import Data.Maybe ( fromJust )
-- >>> :{
--- let parse_date :: String -> Maybe UTCTime;
--- parse_date = parseTime defaultTimeLocale date_format;
+-- let parse_date :: String -> Maybe UTCTime
+-- parse_date = parseTime defaultTimeLocale date_format
-- :}
--
--- >>> let (Just d1) = parse_date "1/1/1970"
--- >>> date_suffix d1
--- "st"
---
--- >>> let (Just d2) = parse_date "1/2/1970"
--- >>> date_suffix d2
--- "nd"
---
--- >>> let (Just d3) = parse_date "1/3/1970"
--- >>> date_suffix d3
--- "rd"
---
--- >>> let (Just d4) = parse_date "1/4/1970"
--- >>> date_suffix d4
--- "th"
+-- >>> let dates = [ "1/" ++ (d : "/1970") | d <- ['1'..'9'] ]
+-- >>> let suffixes = map (date_suffix . fromJust . parse_date) dates
+-- >>> suffixes
+-- ["st","nd","rd","th","th","th","th","th","th"]
--
date_suffix :: UTCTime -> String
date_suffix t =
--
-- \<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.
+-- TSN doesn't provide a proper time zone name, only \"ET\" for
+-- \"Eastern Time\". But \"Eastern Time\" changes throughout the
+-- year, depending on one's location, for daylight-savings
+-- time. It's really not any more useful to be off by one hour than
+-- it is to be off by 5 hours, so rather than guess at EDT/EST, we
+-- just store the timestamp as UTC.
--
-- Examples:
--
-- >>> let tn = text_node " January 6, 2014, at 10:11 PM ET "
--- >>> unpickleDoc xp_time_stamp tn
--- Just 2014-01-07 03:11:00 UTC
+-- >>> let (Just tstamp) = unpickleDoc xp_time_stamp tn
+-- >>> tstamp
+-- 2014-01-06 22:11:00 UTC
+-- >>> pickleDoc xp_time_stamp tstamp
+-- NTree (XTag "/" []) [NTree (XText " January 6, 2014, at 10:11 PM ET ") []]
--
xp_time_stamp :: PU UTCTime
xp_time_stamp =
(parse_time_stamp, from_time_stamp) `xpWrapMaybe` xpText
where
- five_hours :: NominalDiffTime
- five_hours = 5 * 60 * 60
-
- subtract_five :: UTCTime -> UTCTime
- subtract_five = addUTCTime (-1 * five_hours)
-
+ -- | We have to re-pad the time_stamp_format with a leading and
+ -- trailing space; see the documentation of 'time_stamp_format'
+ -- for more information.
from_time_stamp :: UTCTime -> String
from_time_stamp =
- formatTime defaultTimeLocale time_stamp_format . subtract_five
+ formatTime defaultTimeLocale (" " ++ time_stamp_format ++ " ")
+
-- | (Un)pickle an ambiguous 12-hour AM/PM time, which is ambiguous
-- >>> pickleDoc xp_early_line_date result
-- NTree (XTag "/" []) [NTree (XText "SUNDAY, MAY 25TH (05/25/2014)") []]
--
+-- >>> let tn = text_node "SATURDAY, JUNE 7TH (06/07/2014)"
+-- >>> let (Just result) = unpickleDoc xp_early_line_date tn
+-- >>> result
+-- 2014-06-07 00:00:00 UTC
+-- >>> pickleDoc xp_early_line_date result
+-- NTree (XTag "/" []) [NTree (XText "SATURDAY, JUNE 7TH (06/07/2014)") []]
+--
xp_early_line_date :: PU UTCTime
xp_early_line_date =
(to_time, from_time) `xpWrapMaybe` xpText
wacko_date_formats :: [String]
wacko_date_formats =
- ["%A, %B %d" ++ suffix ++ " (" ++ date_format_padded ++ ")" |
+ ["%A, %B %-d" ++ suffix ++ " (" ++ date_format_padded ++ ")" |
suffix <- ["ST", "ND", "RD","TH"] ]
to_time :: String -> Maybe UTCTime
formatTime caps_time_locale fmt t
where
upper_suffix = map toUpper (date_suffix t)
- fmt = "%A, %B %d" ++ upper_suffix ++ " (" ++ date_format_padded ++ ")"
+ fmt = "%A, %B %-d" ++ upper_suffix ++ " (" ++ date_format_padded ++ ")"
+
+
-- | Create an 'XmlTree' containing only the given text. This is
-- useful for testing (un)picklers, where we don't want to have to