Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell Date parsing

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

like image 901
nomen Avatar asked Sep 30 '22 21:09

nomen


2 Answers

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]
like image 161
Tom Ellis Avatar answered Oct 26 '22 23:10

Tom Ellis


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.

like image 34
Davorak Avatar answered Oct 26 '22 23:10

Davorak