What’s your idea of a perfect date? Some people will say DD/MM/YYYY
,
some will say MM/DD/YYYY
, and some YYYY/MM/DD
. I have
no opinion about a perfect date, what I do know of what will ruin dates is if
you instantiate it inside your “business logic”. Before I dig in, I just want to
say I’m not dictating whether you should or shouldn’t do anything particular with
handling dates. This is what I’ve learned from my experience.
Here’s a contrived example in yesod. Somewhere in the code getCurrentTime
is called. At first look, this is harmless and fulfills the need of providing
a date to the User
record. The effects of this won’t be noticed
until there are date transformations that you want to make assertions on later
in testing.
postUsersRegisterR :: Handler Value
= do
postUsersRegisterR <- requireCheckJsonBody :: Handler Register
register <- runDB $ insertUser register
userId
encodeUserId userId
insertUser :: MonadIO m => Register -> SqlPersistT m ( Key User )
Register{..} = do
insertUser -- time is being instantiated here. Harmless
<- liftIO getCurrentTime
now <- mkPassword registerPassword
pwdHash
insert$ User registerEmail registerUsername pwdHash "" defaultUserImage now now
So let’s say there’s a date transformation you care about.
postUsersRegisterR :: Handler Value
= do
postUsersRegisterR <- requireCheckJsonBody :: Handler Register
register <- runDB $ insertUser register
userId
encodeUserId userId
insertUser :: MonadIO m => Register -> SqlPersistT m ( Key User )
Register{..} = do
insertUser -- time is being instantiated here
<- liftIO getCurrentTime
now <- mkPassword registerPassword
pwdHash
insert$ User registerEmail registerUsername pwdHash "" defaultUserImage now
( userExpirationDate now )where
= ... userExpirationDate d
How am I going to test this? It will be very difficult to assert dates as you can imagine. But for illustration, let’s see how it looks
= do
spec "UserSpec" $ do
describe "something something dates" $ do
it Entity key user ) <- runDB $ insertUser testTime registerUser
( "user profile expiration date"
assertEq undefined -- now what? ( userAcctExpirationDate user )
I can’t assert anything because every time getCurrentTime
is called it will return a new date.
I know this is really contrived, and the solution is very obvious to most of you but when I was starting out this wasn’t obvious to me at all. So I bet there are people out there who are having the same experience. Learning to watch out for this early on will help you deal with more complex situations later on.
A solution that helped me out in this situation was to parameterize the date input.
postUsersRegisterR :: Handler Value
= do
postUsersRegisterR <- requireCheckJsonBody :: Handler Register
register <- liftIO getCurrentTime
now <- runDB $ insertUser register
userEntity
encodeUser userEntity
insertUser :: MonadIO m => UTCTime -> Register -> SqlPersistT m ( Entity User )
Register{..} = do
insertUser date <- mkPassword registerPassword
pwdHash
insertEntity $ User registerEmail registerUsername pwdHash "" defaultUserImage date
( profileExpirationDate date )where
= ... profileExpirationDate d
If for some reason you need different transformations, you can always pass in a function.
postUsersRegisterR :: Handler Value
= do
postUsersRegisterR <- requireCheckJsonBody :: Handler Register
register <- liftIO getCurrentTime
date <- runDB $ insertUser someComplexDateTransformation date register
userEntity
encodeUser userEntity
someComplexDateTransformation :: HTTPOperation m
=> UTCTime
-> m UTCTime
= ...
someComplexDateTransformation
insertUser :: MonadIO m
=> ( UTCTime -> m UTCTime )
-> UTCTime
-> Register
-> SqlPersistT m ( Entity User )
Register{..} = do
insertUser dateTrans date <- mkPassword registerPassword
pwdHash
insertEntity $ User registerEmail registerUsername pwdHash "" defaultUserImage date
( dateTrans date )
With this change, tests can be straightforward.
= do
spec "UserSpec" $ do
describe "something something dates" $ do
it ...
let registerUser = ...
let testTime = UTCTime ( fromGregorian 2021 9 13 ) ( secondsToDiffTime 0 )
Entity key user ) <- runDB $ insertUser testTime registerUser
( "user profile expiration date"
assertEq
( userAcctExpirationDate user ) $ UTCTime ( fromGregorian 2022 9 13 ) ( secondsToDiffTime 0 )
I think that summarizes my adventure dealing with dates.