Skip to content

Commit

Permalink
Extract out ISO 8601 time formatting and parsing functions into separ…
Browse files Browse the repository at this point in the history
…ate module.

This allows the functions to be reused elsewhere.
  • Loading branch information
jonathanknowles committed Jul 18, 2019
1 parent 43a9521 commit 9a1cbfe
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 25 deletions.
32 changes: 7 additions & 25 deletions lib/cli/src/Cardano/CLI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ import Data.Aeson
( (.:) )
import Data.Bifunctor
( bimap )
import Data.Either.Extra
( maybeToEither )
import Data.Functor
( (<$), (<&>) )
import Data.List.Extra
Expand All @@ -160,8 +162,8 @@ import Data.Text.Read
( decimal )
import Data.Time.Clock
( UTCTime )
import Data.Time.Format
( defaultTimeLocale, formatTime, parseTimeM )
import Data.Time.Text
( iso8601, iso8601ExtendedUtc, utcTimeFromText, utcTimeToText )
import Fmt
( Buildable, blockListF, fmt, nameF, pretty )
import GHC.Generics
Expand Down Expand Up @@ -847,37 +849,17 @@ newtype Iso8601Time = Iso8601Time
} deriving (Eq, Ord, Show)

instance ToText Iso8601Time where
toText = T.pack . formatTime defaultTimeLocale extendedUtc . getIso8601Time
toText = utcTimeToText iso8601ExtendedUtc . getIso8601Time

instance FromText Iso8601Time where
fromText t = maybe (Left err) (Right . Iso8601Time) $ parse $ T.unpack t
fromText t =
Iso8601Time <$> maybeToEither err (utcTimeFromText iso8601 t)
where
parse s =
foldr (<|>) Nothing $
flip (parseTimeM False defaultTimeLocale) s <$> supportedFormats
supportedFormats =
[ basicUtc, basicLocal, extendedUtc, extendedLocal ]
err = TextDecodingError $ mempty
<> "Unable to parse time argument: '"
<> T.unpack t
<> "'. Expecting ISO 8601 format (basic or extended)."

-- | ISO 8601 basic format (UTC).
basicUtc :: String
basicUtc = "%Y%m%dT%H%M%S%QZ"

-- | ISO 8601 basic format (with local timezone).
basicLocal :: String
basicLocal = "%Y%m%dT%H%M%S%Q%z"

-- | ISO 8601 extended format (UTC).
extendedUtc :: String
extendedUtc = "%Y-%m-%dT%H:%M:%S%QZ"

-- | ISO 8601 extended format (with local timezone).
extendedLocal :: String
extendedLocal = "%Y-%m-%dT%H:%M:%S%Q%z"

-- | Represents the number of words in a mnemonic sentence.
--
-- Only valid sizes are representable by this type.
Expand Down
1 change: 1 addition & 0 deletions lib/core/cardano-wallet-core.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ library
Cardano.Wallet.Transaction
Cardano.Wallet.Unsafe
Cardano.Wallet.Version
Data.Time.Text
Data.Quantity
Network.Wai.Middleware.ServantError
Network.Wai.Middleware.Logging
Expand Down
94 changes: 94 additions & 0 deletions lib/core/src/Data/Time/Text.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
-- |
-- Copyright: © 2019 IOHK
-- License: MIT
--
-- Utility functions for converting time values to and from text.

module Data.Time.Text
(
-- * Conversion to and from text
utcTimeToText
, utcTimeFromText

-- * Time format specification
, TimeFormat (..)

-- * Time formats
, iso8601BasicUtc
, iso8601BasicLocal
, iso8601ExtendedUtc
, iso8601ExtendedLocal

-- * Time format families
, iso8601
, iso8601Basic
, iso8601Extended

) where

import Prelude

import Control.Applicative
( (<|>) )
import Control.Monad
( join )
import Data.Text
( Text )
import Data.Time.Clock
( UTCTime )
import Data.Time.Format
( defaultTimeLocale, formatTime, parseTimeM )

import qualified Data.Text as T

-- | Convert the specified time value to text, using the specified time format.
--
utcTimeToText :: TimeFormat -> UTCTime -> Text
utcTimeToText f = T.pack . formatTime defaultTimeLocale (timeFormatPattern f)

-- | Attempt to use each of the specified time formats to parse the given text.
-- Returns a time value that corresponds to the first matching format, or
-- 'Nothing' if none of the formats matched.
--
utcTimeFromText :: [TimeFormat] -> Text -> Maybe UTCTime
utcTimeFromText fs t = foldr (<|>) Nothing $
flip (parseTimeM False defaultTimeLocale) (T.unpack t) . timeFormatPattern
<$> fs

-- | Represents a particular way of representing a moment in time in text.
data TimeFormat = TimeFormat
{ timeFormatName :: String
, timeFormatPattern :: String }
deriving Eq

-- | Represents the ISO 8601 family of formats.
iso8601 :: [TimeFormat]
iso8601 = join [iso8601Basic, iso8601Extended]

-- | Represents the ISO 8601 basic family of formats.
iso8601Basic :: [TimeFormat]
iso8601Basic = [iso8601BasicUtc, iso8601BasicLocal]

-- | Represents the ISO 8601 extended family of formats.
iso8601Extended :: [TimeFormat]
iso8601Extended = [iso8601ExtendedUtc, iso8601ExtendedLocal]

-- | Represents the ISO 8601 basic format (UTC).
iso8601BasicUtc :: TimeFormat
iso8601BasicUtc =
TimeFormat "ISO 8601 Basic UTC" "%Y%m%dT%H%M%S%QZ"

-- | Represents the ISO 8601 basic format (with local timezone).
iso8601BasicLocal :: TimeFormat
iso8601BasicLocal =
TimeFormat "ISO 8601 Basic Local" "%Y%m%dT%H%M%S%Q%z"

-- | Represents the ISO 8601 extended format (UTC).
iso8601ExtendedUtc :: TimeFormat
iso8601ExtendedUtc =
TimeFormat "ISO 8601 Extended UTC" "%Y-%m-%dT%H:%M:%S%QZ"

-- | Represents the ISO 8601 extended format (with local timezone).
iso8601ExtendedLocal :: TimeFormat
iso8601ExtendedLocal =
TimeFormat "ISO 8601 Extended Local" "%Y-%m-%dT%H:%M:%S%Q%z"

0 comments on commit 9a1cbfe

Please sign in to comment.