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 )
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
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