Is there a better way to convert from UTCTime to E

2019-04-06 18:43发布

I want to set a file's modification time to the time I got from exif data.

To get the time from exif, I found :

Graphics.Exif.getTag :: Exif -> String -> IO (Maybe String)

To set the file modification time, I found :

System.Posix.Files.setFileTimes :: FilePath -> EpochTime -> EpochTime -> IO ()

Assuming I do find a Time in Exif, I need to convert a String to an EpochTime.

  • With parseTime I can get a UTCTime.
  • With utcTimeToPOSIXSeconds I can get a POSIXTime
  • With a POSIXTime I can more or less get an EpochTime

To convert from a UTCTime to EpochTime this typechecks, but I'm not sure it's correct :

fromIntegral . fromEnum . utcTimeToPOSIXSeconds $ etime

This is part of a function getTime that will return the time from Exif data, if present, otherwise the file's modification time :

getTime (path,stat) = do
 let ftime                 = modificationTime $ stat
     err (SomeException _) = return ftime
 time <- liftIO $ handle err $ do
   exif <- Exif.fromFile path
   let getExifTime = MaybeT . liftIO . Exif.getTag exif
   res <- runMaybeT $ do
     tmp <- msum . map getExifTime $ [ "DateTimeOriginal","DateTimeDigitized", "DateTime" ]
     MaybeT . return . parseTime defaultTimeLocale "%Y:%m:%d %H:%M:%S" $ tmp
   case res of
     Nothing    -> return ftime
     Just etime -> return . fromIntegral . fromEnum . utcTimeToPOSIXSeconds $ etime
 return (path,time)

My question is

Is there a better/simpler way to convert the time ? ( maybe using different libaries )

2条回答
Juvenile、少年°
2楼-- · 2019-04-06 19:18

You can also use Data.Convertible.convert (from the convertible package):

import Data.Convertible (convert)
import System.Posix.Types (EpochTime(..))
import Data.Time.Clock (UTCTime(..))

utcTimeToEpochTime :: UTCTime -> EpochTime
utcTimeToEpochTime = convert
查看更多
爷的心禁止访问
3楼-- · 2019-04-06 19:21

Data.Time is the best supported time library, so I would definitely agree with your choice of using it to parse the string representation of date and time that you get out of the Exif data.

Are you sure you need to set the modification time of a file? That's unusual. But if so, then yes, you'll need to use the System.Posix libraries on a Posix system.

If you only need to read the modification of a file, you would be better off using the more generic function System.Directory.getModificationTime. Unfortunately, that function also uses a non-standard time library, System.Time from the long-deprecated old-time package in this case. So you would still need to do some similar machinations.

Your conversion from POSIXTime to EpochTime happens to be OK in this particular case, but in general it's not ideal way to go.

The EpochTime type, a.k.a the time_t type from C, does not support any direct way to be constructed in Haskell without going via an integral value, even though it itself is not necessarily integral depending on your operating system. You could go via C using the FFI if those potential fractions of a second are important to you. Here that's certainly not important, because you're getting the seconds from an %S format parameter which can have no fractional part. Anyway. you will still need to do some kind of rounding or truncating to get from the non-integral UTCTime type to EpochTime.

You're currently just using the Enum instance of POSIXTime to do the rounding/truncating for you, however it decides to. Again, in this particular case it doesn't really matter, because we happen to know that the value will be integral. But in general, it's better to specify it yourself explicitly by using floor, ceiling, or round. E.g.,

return $ maybe ftime (fromInteger . round . utcTimeToPOSIXSeconds) etime

(Note that you don't need to write out the case explicitly, you can use the maybe function from the Prelude.)

Notice that I am also explicitly using fromInteger to push the conversion through the Integer type. If you want to go via Int instead (watch out for the "Year 2038 Problem" on 32-bit machines), I would define a separate conversion function to make that clear:

  return $ maybe ftime utcTimeToEpochTime etime
...

-- Convert from UTCTime to EpochTime via Int
utcTimeToEpochTime :: UTCTime -> EpochTime
utcTimeToEpochTime = fromIntegral . toSecs
  where
    toSecs :: UTCTime -> Int
    toSecs = round . utcTimeToPOSIXSeconds
查看更多
登录 后发表回答