I'm trying to make a FromJSON instance for Data.Time.Calendar's Day type. I am confused about the types, and this seems like a common enough situation that it ought to be solved.
So the Day type represents a modified Julian date. And the Data.Time.Calendar module defines "showGregorian", which converts the modified Julian date into a Gregorian date and outputs a String ISO 8601 representation.
The trouble is that Data.Time.Calendar doesn't have a good way to parse an ISO 8601 into a Gregorian date. If I use the ParseTime class, I can only pass in the string format, and not the calendar for which the string is a representation of a date. So, effectively, I'd pass in '20140502' and the ParseTime class would treat that string as the string representation of a modified Julian date.
It seems like this ought to be a solved problem. Ideally, I'd like a solution that does not use date-times. My model uses Gregorian calendar dates because that is all I need, and those are the things I will be comparing, searching, etc.
Of course, the whole reason for using calendar days when I only need calendar days was so that I wouldn't have to think about how to compare and convert them, but I suppose that is a whine for another day.
Addendum:
Days are defined as:
-- | The Modified Julian Day is a standard count of days, with zero being the day 1858-11-17.
newtype Day = ModifiedJulianDay {toModifiedJulianDay :: Integer} deriving (Eq,Ord)
This lead me to believe that a Day is not a physical day, but a day in the MJD calendar (under the principle that the exposed documentation should tell us what a type represents, not the representation for the type).
Why doesn't parseTime
do exactly what you want? parseTime defaultTimeLocale "%F"
is exactly the inverse (up to Just
) of showGregorian
, as demonstrated by the following snippet that you can paste into GHCi.
import System.Locale
import Data.Time.Format
import Data.Time.Calendar
let test = parseTime defaultTimeLocale "%F" . showGregorian :: Day -> Maybe Day
let notIdentity x = case test x of { Nothing -> True; Just x' -> x /= x' }
filter notIdentity [fromGregorian 0 1 1..fromGregorian 3000 12 31]
The GHC.Generics module is making this type of problem very easy to solve.
{-# language DeriveGeneric #-}
{-# language StandaloneDeriving #-}
import Data.Aeson
import Data.Time.Calendar
import GHC.Generics
deriving instance Generic Day
instance ToJSON Day
instance FromJSON Day
Though on reading your comment:
The thing I'm after is a bugless way to parse an ISO 8601 date into a 'Day', which uses the modified Julian calendar.
This may not be the solution you are looking for.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With