5

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

nomen
  • 3,626
  • 2
  • 23
  • 40
  • I am not really sure if I understand exactly what you are after. Recently I was dealing with date parsing pain and I discovered this library which worked very nicely. https://github.com/singpolyma/git-date-haskell Hopefully that helps – Ecognium May 03 '14 at 00:41
  • The thing I'm after is a bugless way to parse an ISO 8601 date into a 'Day', which uses the modified Julian calendar. It looks like Yesod made the mistake I'm trying to avoid (I was looking at its code for an example) – nomen May 03 '14 at 00:51

2 Answers2

2

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.

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
Davorak
  • 7,362
  • 1
  • 38
  • 48
2

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]
Tom Ellis
  • 9,224
  • 1
  • 29
  • 54
  • It does do what I want, as you demonstrate. But the documentation is unclear. In particular, '%F' is just a "format" -- a way to parse and present dates. It's not a calendar. The library **must** be assuming that a %F formatted date is Gregorian. That is an undocumented assumption that violates the stated intention of the Day type. And it still means the library is broken, since it means I can't parseTime into the modified Julian date calendar. Thanks for your help, I think I'm taking this to the Cafe. – nomen May 03 '14 at 15:34
  • The `time` documentation can be hard to grok, granted. However I don't see why you think there's a bug. Can you demonstrate some buggy behaviour? A day is not "Gregorian" or "Julian". It's just a day. There can be Gregorian or Julian *representations* of it, however. – Tom Ellis May 04 '14 at 07:45
  • Please see the question's addendum. Basically, the documentation says that a **Day** is an MJD, not a physical day. – nomen May 04 '14 at 14:54
  • No it doesn't say that. It describes what a MJD is because it just so happens that `Day` is stored as an MJD. That doesn't mean a `Day` *is* as MJD. That's just its representation. Again, please demonstrate some buggy behaviour. – Tom Ellis May 04 '14 at 17:30
  • The purpose of the documentation is to tell me what a type represents, not what it's representation is. I don't see why you're asking me to demonstrate buggy behavior when you already demonstrated it. The documentation does not agree with the implementation. And I don't see why we're arguing about it. First, I said I was taking this elsewhere. Second, the documentation is very clear. And it's wrong. – nomen May 04 '14 at 17:55
  • If you are claiming that the documentation is misleading then I agree. Beyond that I don't see a bug. – Tom Ellis May 04 '14 at 18:42