Calculate days between two dates in Haskell from a custom date ADT

Viewed 105
-- a)
leapYear :: Int -> Bool
leapYear n = (n `mod` 4 == 0) && ( (n `mod` 100 /= 0) || (n `mod` 400 == 0) )

-- b)
data Month = Jan | Feb | Mar | Apr | May | Aug | Jun | Jul | Sep | Oct | Nov | Dec
    deriving (Enum, Eq, Show)

-- c)
type Day = Int
type Year = Int
data Date = Date Day Month Year
    deriving Show

-- d)
dateIsValid :: Date -> Bool
dateIsValid (Date d m y) | y < 0 || y > 9999                                = False
                         | d < 0                                            = False
                         | m `elem` [Jan, Mar, May, Jul, Aug, Oct, Dec]     = d < 32
                         | m `elem` [Apr, Jun, Sep, Nov, Feb]               = d < 31
                         | m == Feb                                         = if leapYear y then d < 30 else d < 29
                         | otherwise                                        = False

-- e)
daysBetween :: Date -> Date -> Int
daysBetween (Date a b c) (Date x y z) = if dateDay (Date a b c) > dateDay (Date x y z)
                                        then dateDay (Date a b c) - dateDay (Date x y z)
                                        else dateDay (Date x y z) - dateDay (Date a b c)
    where   monthDay m = case m of Jan -> 0
                                   Feb -> 31
                                   Mar -> 59
                                   Apr -> 90
                                   May -> 120
                                   Jun -> 151
                                   Jul -> 181
                                   Aug -> 211
                                   Sep -> 243
                                   Oct -> 273
                                   Nov -> 304
                                   Dec -> 334
            countLeapYears (Date d m y) | m `elem` [Jan, Feb, May] = (y-1) `div` 4 - (y-1) `div` 100 + (y-1) `div` 400
                                        | otherwise  = y `div` 4 - y `div` 100 + y `div` 400
            dateDay (Date d m y) = d + monthDay m + (y-1)*365 + countLeapYears (Date d m y)

getMonthNr :: Month -> Int
getMonthNr m = case m of Jan -> 1
                         Feb -> 2
                         Mar -> 3
                         Apr -> 4
                         May -> 5
                         Jun -> 6
                         Jul -> 7
                         Aug -> 8
                         Sep -> 9
                         Oct -> 10
                         Nov -> 11
                         Dec -> 12

-- f)
data Dow = Mon | Tue | Wen | Thu | Fri | Sat | Sun
    deriving Show

-- g)
weekday :: Date -> Dow
weekday (Date d m y) = case daysBetween (Date d m y) (Date 3 Jan 2000) `mod` 7 of 0 -> Sun
                                                                                  1 -> Mon
                                                                                  2 -> Tue
                                                                                  3 -> Wen
                                                                                  4 -> Thu
                                                                                  5 -> Fri
                                                                                  _ -> Sat

I have got this code where I need to calculate the number of days between two days and then find the weekday of that date. While this code is correct in finding the answer for the given examples in the exercises daysBetween (Date 4 Nov 2021) (Date 1 Jan 2000) → 7978 and January 1st, 2021 was a Friday, it shows problems with most other dates. One of my tests involved the date Date 13 Aug 2009 and Date 3 Jan 2000 where the error is one day. I am not allowed to use predefined functions to find the difference between two dates or the specific weekday. Is there something wrong with the logic I have used? Or is there another way to implement an algorithm that searches for the difference between two days?

getMonthNr can be ignored or not, since I wrote it down while experimenting and left it there in case I would need it.

Edit: After correcting the code a 1 day shift occurs when the days are a few years apart (almost a decade) or a 2 day shift when the days are centuries to a few millennia apart.

1 Answers

Do you just have Aug in the wrong place? It should be between Jul and Sep but it is between May and Jun in your code.

Related