packages feed

options-time (empty) → 1.0

raw patch · 5 files changed

+602/−0 lines, 5 filesdep +basedep +chelldep +old-localesetup-changed

Dependencies added: base, chell, old-locale, options, options-time, time

Files

+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ lib/Options/Time.hs view
@@ -0,0 +1,325 @@+{-# LANGUAGE CPP #-}++-- |+-- Module: Options.Time+-- License: MIT+--+-- The @options-time@ package provides 'OptionType' implementations and+-- associated 'SimpleOption' instances for types related to dates and times.+module Options.Time+	( optionType_duration+	, optionType_date+	, optionType_time+	, optionType_localTime+	, optionType_utcTime+	, optionType_zonedTime+	) where++import           Data.Fixed (divMod')+import           Data.Ratio (numerator)+import qualified Data.Time as T+import           Options+#if MIN_VERSION_time(1,3,0)+import           Data.Time.LocalTime (makeTimeOfDayValid)+#else+import           Data.Fixed (Pico)+#endif+#if MIN_VERSION_time(1,5,0)+import           Data.Time.Format (defaultTimeLocale)+#else+import           System.Locale (defaultTimeLocale)+#endif+import qualified Text.ParserCombinators.ReadP as R++-- | Store an option as a 'T.DiffTime'. The duration is specified in a format+-- such as @\"2h30m\"@ for two hours and thirty minutes.+--+-- Available units are:+--+-- * \"h\" for hours.+-- * \"m\" for minutes.+-- * \"s\" for seconds.+-- * \"ms\" for milliseconds.+-- * \"us\" or \"&#181;s\" for microseconds.+-- * \"ns\" for nanoseconds.+-- * \"ps\" for picoseconds.+--+-- Larger units are not available to avoid ambiguity when dealing with daylight+-- savings time.+optionType_duration :: OptionType T.DiffTime+optionType_duration = optionType "duration" 0 parseDuration formatDuration++instance SimpleOptionType T.DiffTime where+	simpleOptionType = optionType_duration++parseDuration :: String -> Either String T.DiffTime+parseDuration = parsedOrErr where+	parsedOrErr s = case R.readP_to_S parser s of+		(duration,_):_ -> Right duration+		[] -> Left (show s ++ " could not be parsed as a duration.")+	parser = orderedChoice [zero, units]+	zero = do+		_ <- R.char '0'+		R.eof+		return (toPicoseconds 0)+	units = do+		minus <- R.option 1 (R.char '-' >> return (-1))+		acc <- loop (toPicoseconds 0) 0+		return (minus * acc)+	loop acc dropIdx = do+		digits <- R.munch1 (\c -> c >= '0' && c <= '9')+		(multiplier, dropIdx')  <- orderedChoice (drop dropIdx parsers)+		let acc' = acc + multiplier (read digits)+		orderedChoice [done acc', loop acc' dropIdx']+	done acc = R.eof >> return acc+	parsers =+		[ R.char 'h' >> return (toHours, 1)+		, justMinuteSuffix >> return (toMinutes, 2)+		, R.char 's' >> return (toSeconds, 3)+		, R.string "ms" >> return (toMilliseconds, 4)+		, R.string "us" >> return (toMicroseconds, 6)+		, R.string "\181s" >> return (toMicroseconds, 6)+		, R.string "ns" >> return (toNanoseconds, 7)+		, R.string "ps" >> return (toPicoseconds, 8)+		]+	justMinuteSuffix = do+		ahead <- R.look+		case ahead of+			'm':'s':_ -> R.pfail+			_ -> R.char 'm'++orderedChoice :: [R.ReadP a] -> R.ReadP a+orderedChoice ps = case ps of+	[] -> R.pfail+	[p] -> p+	(p:ps') -> p R.<++ orderedChoice ps'++toPicoseconds :: Integer -> T.DiffTime+toPicoseconds = T.picosecondsToDiffTime++toNanoseconds :: Integer -> T.DiffTime+toNanoseconds = toPicoseconds . (*1000)++toMicroseconds :: Integer -> T.DiffTime+toMicroseconds = toNanoseconds . (*1000)++toMilliseconds :: Integer -> T.DiffTime+toMilliseconds = toMicroseconds . (*1000)++toSeconds :: Integer -> T.DiffTime+toSeconds = T.secondsToDiffTime++toMinutes :: Integer -> T.DiffTime+toMinutes = toSeconds . (*60)++toHours :: Integer -> T.DiffTime+toHours = toMinutes . (*60)++formatDuration :: T.DiffTime -> String+formatDuration t = formatted where+	formatted = if t == 0+		then "0s"+		else concat chunks+	(negative, absolute) = if t < 0+		then (True, t * (- 1))+		else (False, t)+	(rawSeconds, rawPicoFraction) = divMod' absolute 1 :: (Integer, T.DiffTime)+	(hours, rawMinutes) = divMod rawSeconds 3600+	(minutes, seconds) = divMod rawMinutes 60+	rawPicos = numerator (toRational (rawPicoFraction*1000000000000))+	(milliseconds, rawMicros) = divMod rawPicos 1000000000+	(microseconds, rawNanos) = divMod rawMicros 1000000+	(nanoseconds, picoseconds) = divMod rawNanos 1000+	chunks =+		[ if negative then "-" else ""+		, chunk hours "h"+		, chunk minutes "m"+		, chunk seconds "s"+		, chunk milliseconds "ms"+		, chunk microseconds "us"+		, chunk nanoseconds "ns"+		, chunk picoseconds "ps"+		]+	chunk 0 _ = ""+	chunk n suffix = show n ++ suffix++-- | Store an option as a 'T.Day'. Supported formats are:+--+-- * \"YYYY-MM-DD\"+-- * \"YYYYMMDD\"+-- * \"YYYY-DDD\"+optionType_date :: OptionType T.Day+optionType_date = optionType "date" (T.fromGregorian 1970 1 1) parseDate formatDate++instance SimpleOptionType T.Day where+	simpleOptionType = optionType_date++parseDate :: String -> Either String T.Day+parseDate s = parsedOrErr where+	parsedOrErr = case parsed of+		Just day -> Right day+		Nothing -> Left (show s ++ " could not be parsed as a date.")+	parsed = firstJust+		[ checkedParse "%Y-%m-%d" s+		, checkedParse "%Y-%j" s+		, checkedParse "%Y%m%d" s+		]++formatDate :: T.Day -> String+formatDate = T.formatTime defaultTimeLocale "%Y-%m-%d"++-- | Store an option as a 'T.TimeOfDay'. Supported formats are:+--+-- * \"HH:MM\"+-- * \"HH:MM:SS\"+-- * \"HH:MM:SS.FFFF\"+--+-- For example, the value @\"10:11:12.5\"@ is half a second past+-- 10:11:12 AM.+optionType_time :: OptionType T.TimeOfDay+optionType_time = optionType "time" T.midnight parseTime formatTime++instance SimpleOptionType T.TimeOfDay where+	simpleOptionType = optionType_time++parseTime :: String -> Either String T.TimeOfDay+parseTime s = parsedOrErr where+	parsedOrErr = case parsed >>= validateTime of+		Just time -> Right time+		Nothing -> Left (show s ++ " could not be parsed as a time.")+	parsed = firstJust+		[ checkedParse "%H:%M" s+		, checkedParse "%H:%M:%S%Q" s+		]++formatTime :: T.TimeOfDay -> String+formatTime = T.formatTime defaultTimeLocale "%H:%M:%S%Q"++validateTime :: T.TimeOfDay -> Maybe T.TimeOfDay+validateTime t = makeTimeOfDayValid (T.todHour t) (T.todMin t) (T.todSec t)++#if !MIN_VERSION_time(1,3,0)+-- Based on time-1.3:Data/Time/LocalTime/TimeOfDay.hs+makeTimeOfDayValid :: Int -> Int -> Pico -> Maybe T.TimeOfDay+makeTimeOfDayValid h m s = do+	_ <- clipValid 0 24 h+	_ <- clipValid 0 60 m+	_ <- clipValid 0 61 s+	return (T.TimeOfDay h m s)++-- Based on time-1.3:Data/Time/Calendar/Private.hs+clipValid :: (Ord t) => t -> t -> t -> Maybe t+clipValid a _ x | x < a = Nothing+clipValid _ b x | x >= b = Nothing+clipValid _ _ x = Just x+#endif++-- | Store an option as a 'T.LocalTime'. Supported formats are a combination+-- of those for 'optionType_date' and 'optionType_time'.+optionType_localTime :: OptionType T.LocalTime+optionType_localTime = optionType "local time" localEpoch parseLocalTime formatLocalTime++instance SimpleOptionType T.LocalTime where+	simpleOptionType = optionType_localTime++localEpoch :: T.LocalTime+localEpoch = T.LocalTime (T.fromGregorian 1970 1 1) T.midnight++parseLocalTime :: String -> Either String T.LocalTime+parseLocalTime s = parsedOrErr where+	parsedOrErr = case parsed >>= validateLocalTime of+		Just time -> Right time+		Nothing -> Left (show s ++ " could not be parsed as a local time.")+	parsed = firstJust $ do+		ymd <- ["%Y-%m-%d", "%Y-%j", "%Y%m%d"]+		hms <- ["%H:%M", "%H:%M:%S%Q", "%H%M%S%Q"]+		sep <- [" ", "T"]+		[checkedParse (ymd ++ sep ++ hms) s]++formatLocalTime :: T.LocalTime -> String+formatLocalTime = T.formatTime defaultTimeLocale "%Y-%m-%d %H:%M:%S%Q"++validateLocalTime :: T.LocalTime -> Maybe T.LocalTime+validateLocalTime t = do+	_ <- validateTime (T.localTimeOfDay t)+	return t++-- | Store an option as a 'T.UTCTime'. Supported formats are a combination+-- of those for 'optionType_date' and 'optionType_time'.+optionType_utcTime :: OptionType T.UTCTime+optionType_utcTime = optionType "utc time" utcEpoch parseUtcTime formatUtcTime++instance SimpleOptionType T.UTCTime where+	simpleOptionType = optionType_utcTime++utcEpoch :: T.UTCTime+utcEpoch = T.UTCTime (T.fromGregorian 1970 1 1) 0++parseUtcTime :: String -> Either String T.UTCTime+parseUtcTime s = parsedOrErr where+	parsedOrErr = case parsed >>= validateUtcTime of+		Just time -> Right time+		Nothing -> Left (show s ++ " could not be parsed as a UTC time.")+	parsed = firstJust $ do+		ymd <- ["%Y-%m-%d", "%Y-%j", "%Y%m%d"]+		hms <- ["%H:%M", "%H:%M:%S%Q", "%H%M%S%Q"]+		sep <- [" ", "T"]+		[checkedParse (ymd ++ sep ++ hms) s]++formatUtcTime :: T.UTCTime -> String+formatUtcTime = T.formatTime defaultTimeLocale "%Y-%m-%d %H:%M:%S%Q"++validateUtcTime :: T.UTCTime -> Maybe T.UTCTime+validateUtcTime t = case T.utctDayTime t of+	x | x >= 0 && x < 86401 -> Just t+	_ -> Nothing++-- | Store an option as a 'T.ZonedTime'. Supported formats are a combination+-- of those for 'optionType_date' and 'optionType_time'.+optionType_zonedTime :: OptionType T.ZonedTime+optionType_zonedTime = optionType "zoned time" zonedEpoch parseZonedTime formatZonedTime++instance SimpleOptionType T.ZonedTime where+	simpleOptionType = optionType_zonedTime++zonedEpoch :: T.ZonedTime+zonedEpoch = T.ZonedTime localEpoch T.utc++parseZonedTime :: String -> Either String T.ZonedTime+parseZonedTime s = parsedOrErr where+	parsedOrErr = case parsed >>= validateZonedTime of+		Just time -> Right time+		Nothing -> Left (show s ++ " could not be parsed as a zoned time.")+	parsed = firstJust $ do+		ymd <- ["%Y-%m-%d", "%Y-%j", "%Y%m%d"]+		hms <- ["%H:%M", "%H:%M:%S%Q", "%H%M%S%Q"]+		sep <- [" ", "T"]+		(tz, fixtz) <- [("", id), ("Z", setUTC), ("%z", id), (" %z", id), (" %Z", id)]+		-- TODO: This doesn't support +01:00 because checkedParse will format+		-- that to +0100 and fail.+		[fixtz `fmap` checkedParse (ymd ++ sep ++ hms ++ tz) s]+	setUTC t = t { T.zonedTimeZone = T.utc }++formatZonedTime :: T.ZonedTime -> String+formatZonedTime = T.formatTime defaultTimeLocale "%Y-%m-%d %H:%M:%S%Q %Z"++validateZonedTime :: T.ZonedTime -> Maybe T.ZonedTime+validateZonedTime t = do+	_ <- validateLocalTime (T.zonedTimeToLocalTime t)+	return t++checkedParse :: (T.FormatTime t, T.ParseTime t) => String -> String -> Maybe t+checkedParse fmt input = do+	parsed <- T.parseTime defaultTimeLocale fmt input+	-- Be fairly strict about the input format, because T.parseTime will+	-- try to silently fix invalid inputs.+	--+	-- For example, "2014-20-12" is parsed as "2014-12-12".+	if input == T.formatTime defaultTimeLocale fmt parsed+		then Just parsed+		else Nothing++firstJust :: [Maybe a] -> Maybe a+firstJust xs = case [x | Just x <- xs] of+	x:_ -> Just x+	[] -> Nothing
+ license.txt view
@@ -0,0 +1,22 @@+Copyright (c) 2012 John Millikin++Permission is hereby granted, free of charge, to any person+obtaining a copy of this software and associated documentation+files (the "Software"), to deal in the Software without+restriction, including without limitation the rights to use,+copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the+Software is furnished to do so, subject to the following+conditions:++The above copyright notice and this permission notice shall be+included in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR+OTHER DEALINGS IN THE SOFTWARE.
+ options-time.cabal view
@@ -0,0 +1,60 @@+name: options-time+version: 1.0+license: MIT+license-file: license.txt+author: John Millikin <john@john-millikin.com>+maintainer: John Millikin <john@john-millikin.com>+build-type: Simple+cabal-version: >= 1.8+category: Console+stability: stable+homepage: https://john-millikin.com/software/haskell-options/++synopsis: Command-line option types for dates and times.++source-repository head+  type: git+  location: https://john-millikin.com/code/haskell-options-time/++source-repository this+  type: git+  location: https://john-millikin.com/code/haskell-options-time/+  tag: haskell-options-time_1.0++flag old-locale++library+  ghc-options: -Wall -fno-warn-orphans+  hs-source-dirs: lib++  if impl(ghc > 7.4)+    ghc-options: -fwarn-unsafe++  build-depends:+      base >= 4.1 && < 5.0+    , options >= 1.0 && < 2.0++  if flag(old-locale)+    build-depends:+        time >= 1.1.3 && < 1.5+      , old-locale >= 1.0 && < 2.0+  else+    build-depends:+        time >= 1.5 && < 2.0++  exposed-modules:+    Options.Time++test-suite tests+  type: exitcode-stdio-1.0+  main-is: Tests.hs++  ghc-options: -Wall+  hs-source-dirs: tests++  build-depends:+      base >= 4.0 && < 5.0+    , chell >= 0.4 && < 0.5+    , options >= 1.0 && < 2.0+    , options-time+    , time >= 1.1.3 && < 2.0
+ tests/Tests.hs view
@@ -0,0 +1,193 @@+{-# LANGUAGE TemplateHaskell #-}++module Main (main) where++import qualified Data.Time as T+import           Options+import           Options.Time+import           Test.Chell++test_Duration :: Test+test_Duration = assertions "duration" $ do+	let parse = optionTypeParse optionType_duration+	let format = optionTypeShow optionType_duration+	$expect $ equal+		(parse "10h9m8s7ms6us5ns4ps")+		(Right (sum+			[ 10*60*60+			, 9*60+			, 8+			, 7*0.001+			, 6*0.000001+			, 5*0.000000001+			, 4*0.000000000001+			]))+	$expect $ equal (parse "1h9s") (Right 3609)+	$expect $ equal (parse "1h9ms") (Right 3600.009)+	$expect $ equal (parse "0") (Right 0)+	$expect $ equal (parse "-1h9s") (Right (-3609))+	$expect $ equal (parse "1s2us3ns") (parse "1s2\181s3ns")+	$expect $ equal+		(parse "bogus")+		(Left "\"bogus\" could not be parsed as a duration.")+	$expect $ equal+		(parse "1ps2ps")+		(Left "\"1ps2ps\" could not be parsed as a duration.")+	$expect $ equal+		(format (sum+			[ 10*60*60+			, 9*60+			, 8+			, 7*0.001+			, 6*0.000001+			, 5*0.000000001+			, 4*0.000000000001+			]))+		"10h9m8s7ms6us5ns4ps"+	$expect $ equal (format 0) "0s"+	$expect $ equal (format 3609) "1h9s"+	$expect $ equal (format (-3609)) "-1h9s"++test_Date :: Test+test_Date = assertions "date" $ do+	let parse = optionTypeParse optionType_date+	let format = optionTypeShow optionType_date+	$expect $ equal+		(parse "2011-12-13")+		(Right (T.fromGregorian 2011 12 13))+	$expect $ equal+		(parse "20111213")+		(Right (T.fromGregorian 2011 12 13))+	$expect $ equal+		(parse "2011-347")+		(Right (T.fromGregorian 2011 12 13))+	$expect $ equal+		(parse "bogus")+		(Left "\"bogus\" could not be parsed as a date.")+	$expect $ equal+		(parse "2011-10-32")+		(Left "\"2011-10-32\" could not be parsed as a date.")+	$expect $ equal (format (T.fromGregorian 2011 12 13)) "2011-12-13"++test_Time :: Test+test_Time = assertions "time" $ do+	let parse = optionTypeParse optionType_time+	let format = optionTypeShow optionType_time+	$expect $ equal+		(parse "10:11")+		(Right (T.TimeOfDay 10 11 0))+	$expect $ equal+		(parse "20:21")+		(Right (T.TimeOfDay 20 21 0))+	$expect $ equal+		(parse "20:21:22")+		(Right (T.TimeOfDay 20 21 22))+	$expect $ equal+		(parse "20:21:22.232425")+		(Right (T.TimeOfDay 20 21 22.232425))+	$expect $ equal+		(parse "bogus")+		(Left "\"bogus\" could not be parsed as a time.")+	$expect $ equal+		(parse "25:21:00")+		(Left "\"25:21:00\" could not be parsed as a time.")+	$expect $ equal+		(parse "20:21:60")+		(Right (T.TimeOfDay 20 21 60))+	$expect $ equal+		(parse "20:21:61")+		(Left "\"20:21:61\" could not be parsed as a time.")+	$expect $ equal (format (T.TimeOfDay 20 21 0)) "20:21:00"+	$expect $ equal (format (T.TimeOfDay 20 21 22.232425)) "20:21:22.232425"++test_LocalTime :: Test+test_LocalTime = assertions "local time" $ do+	let parse = optionTypeParse optionType_localTime+	let format = optionTypeShow optionType_localTime+	let lt y mo d h mi s = T.LocalTime (T.fromGregorian y mo d) (T.TimeOfDay h mi s)+	let rlt y mo d h mi s = Right (lt y mo d h mi s)+	$expect $ equal (parse "2011-12-13 14:15") (rlt 2011 12 13 14 15 0)+	$expect $ equal (parse "2011-12-13 14:15:16") (rlt 2011 12 13 14 15 16)+	$expect $ equal (parse "2011-12-13 14:15:16.1718") (rlt 2011 12 13 14 15 16.1718)+	$expect $ equal (parse "2011-347 14:15") (rlt 2011 12 13 14 15 0)+	$expect $ equal (parse "20111213 14:15") (rlt 2011 12 13 14 15 0)+	$expect $ equal (parse "20111213T141516") (rlt 2011 12 13 14 15 16)+	$expect $ equal (parse "20111213T141516.1718") (rlt 2011 12 13 14 15 16.1718)+	$expect $ equal+		(parse "bogus")+		(Left "\"bogus\" could not be parsed as a local time.")+	$expect $ equal (format (lt 2011 12 13 14 15 0)) "2011-12-13 14:15:00"+	$expect $ equal (format (lt 2011 12 13 14 15 16)) "2011-12-13 14:15:16"+	$expect $ equal (format (lt 2011 12 13 14 15 16.1718)) "2011-12-13 14:15:16.1718"++test_UtcTime :: Test+test_UtcTime = assertions "utc time" $ do+	let parse = optionTypeParse optionType_utcTime+	let format = optionTypeShow optionType_utcTime+	let ut y mo d h mi s = T.UTCTime (T.fromGregorian y mo d) (h*60*60 + mi*60 + s)+	let rut y mo d h mi s = Right (ut y mo d h mi s)+	$expect $ equal (parse "2011-12-13 14:15") (rut 2011 12 13 14 15 0)+	$expect $ equal (parse "2011-12-13 14:15:16") (rut 2011 12 13 14 15 16)+	$expect $ equal (parse "2011-12-13 14:15:16.1718") (rut 2011 12 13 14 15 16.1718)+	$expect $ equal (parse "2011-347 14:15") (rut 2011 12 13 14 15 0)+	$expect $ equal (parse "20111213 14:15") (rut 2011 12 13 14 15 0)+	$expect $ equal (parse "20111213T141516") (rut 2011 12 13 14 15 16)+	$expect $ equal (parse "20111213T141516.1718") (rut 2011 12 13 14 15 16.1718)+	$expect $ equal (parse "2011-12-13 23:59:60") (rut 2011 12 13 23 59 60)+	$expect $ equal+		(parse "bogus")+		(Left "\"bogus\" could not be parsed as a UTC time.")+	$expect $ equal+		(parse "2011-12-13 23:59:61")+		(Left "\"2011-12-13 23:59:61\" could not be parsed as a UTC time.")+	$expect $ equal (format (ut 2011 12 13 14 15 0)) "2011-12-13 14:15:00"+	$expect $ equal (format (ut 2011 12 13 14 15 16)) "2011-12-13 14:15:16"+	$expect $ equal (format (ut 2011 12 13 14 15 16.1718)) "2011-12-13 14:15:16.1718"++test_ZonedTime :: Test+test_ZonedTime = assertions "zoned time" $ do+	let parse = optionTypeParse optionType_zonedTime+	let format = optionTypeShow optionType_zonedTime+	let zt y mo d h mi s tz = T.ZonedTime (T.LocalTime (T.fromGregorian y mo d) (T.TimeOfDay h mi s)) tz+	let rzt y mo d h mi s tz = Right (zt y mo d h mi s tz)+	let noZone = T.TimeZone 0 False ""+	let tzPlus60 = T.TimeZone 60 False ""+	let tzMinus60 = T.TimeZone (-60) False ""+	let tzABC = T.TimeZone 0 False "ABC"+	$expect $ equal (parse "2011-12-13 14:15") (rzt 2011 12 13 14 15 0 noZone)+	$expect $ equal (parse "2011-12-13 14:15Z") (rzt 2011 12 13 14 15 0 T.utc)+	$expect $ equal (parse "2011-12-13 14:15+0100") (rzt 2011 12 13 14 15 0 tzPlus60)+	$expect $ equal (parse "2011-12-13 14:15 +0100") (rzt 2011 12 13 14 15 0 tzPlus60)+	$expect $ equal (parse "2011-12-13 14:15-0100") (rzt 2011 12 13 14 15 0 tzMinus60)+	$expect $ equal (parse "2011-12-13 14:15 -0100") (rzt 2011 12 13 14 15 0 tzMinus60)+	$expect $ equal (parse "2011-12-13 14:15 ABC") (rzt 2011 12 13 14 15 0 tzABC)+	$expect $ equal (parse "2011-12-13 14:15") (rzt 2011 12 13 14 15 0 noZone)+	$expect $ equal (parse "2011-12-13 14:15:16") (rzt 2011 12 13 14 15 16 noZone)+	$expect $ equal (parse "2011-12-13 14:15:16.1718") (rzt 2011 12 13 14 15 16.1718 noZone)+	$expect $ equal (parse "2011-347 14:15") (rzt 2011 12 13 14 15 0 noZone)+	$expect $ equal (parse "20111213 14:15") (rzt 2011 12 13 14 15 0 noZone)+	$expect $ equal (parse "20111213T141516") (rzt 2011 12 13 14 15 16 noZone)+	$expect $ equal (parse "20111213T141516.1718") (rzt 2011 12 13 14 15 16.1718 noZone)+	$expect $ equal+		(parse "bogus")+		(Left "\"bogus\" could not be parsed as a zoned time.")+	$expect $ equal (format (zt 2011 12 13 14 15 0 noZone)) "2011-12-13 14:15:00 +0000"+	$expect $ equal (format (zt 2011 12 13 14 15 0 T.utc)) "2011-12-13 14:15:00 UTC"+	$expect $ equal (format (zt 2011 12 13 14 15 0 tzPlus60)) "2011-12-13 14:15:00 +0100"+	$expect $ equal (format (zt 2011 12 13 14 15 0 tzMinus60)) "2011-12-13 14:15:00 -0100"+	$expect $ equal (format (zt 2011 12 13 14 15 16 noZone)) "2011-12-13 14:15:16 +0000"+	$expect $ equal (format (zt 2011 12 13 14 15 16.1718 noZone)) "2011-12-13 14:15:16.1718 +0000"++instance Eq T.ZonedTime where+	x == y = key x == key y where+		key t = (T.zonedTimeToLocalTime t, T.zonedTimeZone t)++main :: IO ()+main = Test.Chell.defaultMain [suite "tests"+	[ test_Duration+	, test_Date+	, test_Time+	, test_LocalTime+	, test_UtcTime+	, test_ZonedTime+	]]