简体   繁体   中英

Convert UTCTime to CSV field with Haskell/cassava

I started learning Haskell and I try to query data from a SQLite file and dump it again as CSV. I have an issue formating UTCTime fields, so that cassava can format it correctly. This is my type:

    module History.Types
      (Visit)
      where

    import Data.ByteString as B
    import Data.Csv
    import Data.Text (Text)
    import Data.Time
    import Data.Time.Format
    import Database.SQLite.Simple.FromRow

    data Visit = Visit
      { url :: Text
      , title :: Text
      , visit_count :: Int
      , typed_count :: Int
      , last_visit_time :: UTCTime
      , visit_time :: UTCTime
      , from_visit :: Int
      }
      deriving (Show)

    instance FromRow Visit where
      fromRow = Visit <$> field
                      <*> field
                      <*> field
                      <*> field
                      <*> field
                      <*> field
                      <*> field

    instance ToField UTCTime where
      toField t = formatTime defaultTimeLocale "%d-%m-%Y %H:%M:%S" t

    instance ToRecord Visit where
      toRecord (Visit url
                      title
                      visit_count
                      typed_count
                      last_visit_time
                      visit_time
                      from_visit) = record [ toField visit_time
                                           , toField url
                                           , toField title
                                           , toField visit_count
                                           , toField typed_count
                                           , toField from_visit
                                           , toField last_visit_time
                                           ]

And my SQLite code:

    {-# LANGUAGE OverloadedStrings #-}

    module History.DB
      (queryHistory)
      where

    import History.Types (Visit)

    import Data.Text (Text)
    import Database.SQLite.Simple (open, query_, close, Query)

    q :: Query
    q = "\
    \SELECT urls.url \
    \     , urls.title \
    \     , urls.visit_count \
    \     , urls.typed_count \
    \     , datetime(urls.last_visit_time/1000000-11644473600,'unixepoch','localtime') \
    \     , datetime(visits.visit_time/1000000-11644473600,'unixepoch','localtime') \
    \     , visits.from_visit \
    \FROM urls, visits \
    \WHERE urls.id = visits.id \
    \ORDER BY visits.visit_time"

    queryHistory :: FilePath -> IO [Visit]
    queryHistory dbPath =
      do conn <- open dbPath
         history <- query_ conn q
         close conn
         return history

Compiling this code leads to the following error:

    src/History/Types.hs:34:15:
        Couldn't match type ‘[Char]’ with ‘ByteString’
          Expected type: Field
          Actual type: String
        In the expression:
          formatTime defaultTimeLocale "%d-%m-%Y %H:%M:%S" t
        In an equation for ‘toField’:
          toField t = formatTime defaultTimeLocale "%d-%m-%Y %H:%M:%S" t

So clearly I make a mess with when formatting the date type into a string. I look at the type information of formatTime and don't really understand why my String (I assume the error relates to the date formatting string) has to be a ByteString . So my questions are:

  1. Is the error related to the time formatting string, and if yes, why is a regular string not working?
  2. Is this the right way to format date types to be written to a CSV file using cassava ?

Your problem is that toField has the signature a -> Field . If you look at the Field type its definition is type Field = ByteString . The result of formatTime is a String . So the problem is that you provide a String where a ByteString is expected.

Since String is a very common type the first thing you should do is to check if there is already an instance for ToField String you can use. If you look at the definition there is. This means that you can specialize the toField function to have the signature String -> Field and the use it like this:

instance ToField UTCTime where
      toField = toField . formatTime defaultTimeLocale "%d-%m-%Y %H:%M:%S"

Have you tried {-# LANGUAGE OverloadedStrings #-} in your History.Types file? Not sure if that would work, but maybe worth a try.

ToField has to return a ByteString because it returns a Field, and a Field IS a ByteString:

http://haddock.stackage.org/lts-2.17/cassava-0.4.3.0/Data-Csv.html#t:Field

You are calling formatTime correctly, its just that formatTime returns a String and not a ByteString.

You can directly convert with fromString or pack . So just call formatTime like you are now and convert the result.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM