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 aUTCTime
. - With
utcTimeToPOSIXSeconds
I can get aPOSIXTime
- With a
POSIXTime
I can more or less get anEpochTime
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 )
You can also use
Data.Convertible.convert
(from the convertible package):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-deprecatedold-time
package in this case. So you would still need to do some similar machinations.Your conversion from
POSIXTime
toEpochTime
happens to be OK in this particular case, but in general it's not ideal way to go.The
EpochTime
type, a.k.a thetime_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-integralUTCTime
type toEpochTime
.You're currently just using the
Enum
instance ofPOSIXTime
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 usingfloor
,ceiling
, orround
. E.g.,(Note that you don't need to write out the
case
explicitly, you can use themaybe
function from the Prelude.)Notice that I am also explicitly using
fromInteger
to push the conversion through theInteger
type. If you want to go viaInt
instead (watch out for the "Year 2038 Problem" on 32-bit machines), I would define a separate conversion function to make that clear: