Haskell自定义Date ADT计算日期间隔与星期的偏差问题排查咨询
代码存在的问题
countLeapYears的月份判断逻辑错误
你在判断是否使用前一年闰年计数时,错误将May加入了判断列表:melem[Jan, Feb, May]。正确逻辑是只有1月、2月的日期才需要用前一年的闰年数,5月的日期被错误归类后会导致闰年计数少算1天,这就是你测试2009年8月13日出现1天误差的核心原因。
dateIsValid的分支顺序错误
你先判断了melem[Apr, Jun, Sep, Nov, Feb]的分支,已经包含了2月的情况,导致后续m == Feb的分支永远无法触发,2月的天数校验逻辑完全失效,平年/闰年2月的天数限制都变成了最大30天,虽然不直接影响天数差计算,但会导致无效日期校验错误。
Month类型定义顺序错误
你定义Month时将Aug放在了Jun、Jul前面,虽然当前代码没有用到Enum实例的顺序属性,但后续如果用到fromEnum、枚举遍历等操作时会出现逻辑错误,建议修正为正确的月份顺序。
- 星期计算的基准映射错误
2000年1月3日实际为周一,你当前代码中差为0时映射为Sun,会导致所有星期计算结果偏移1天。
- 星期计算的基准映射错误
- 跨历法偏差问题
你使用的格里高利历闰年规则(4年一闰、百年不闰、四百年再闰)是1582年才正式启用的,如果计算1582年之前的日期,和历史实际使用的儒略历规则会存在固定偏差,间隔数百年差2天就是这个原因,若仅需要计算1582年之后的日期该问题不会出现。
- 跨历法偏差问题
修复方案
-- 修正countLeapYears的判断条件 countLeapYears (Date d m y) | m `elem` [Jan, Feb] = (y-1) `div` 4 - (y-1) `div` 100 + (y-1) `div` 400 | otherwise = y `div` 4 - y `div` 100 + y `div` 400 -- 修正dateIsValid的分支,移除Feb的提前匹配,同时修正天数最小值判断 dateIsValid (Date d m y) | y < 0 || y > 9999 = False | d < 1 = False | m `elem` [Jan, Mar, May, Jul, Aug, Oct, Dec] = d <= 31 | m `elem` [Apr, Jun, Sep, Nov] = d <= 30 | m == Feb = if leapYear y then d <= 29 else d <= 28 | otherwise = False -- 修正Month定义顺序 data Month = Jan | Feb | Mar | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov | Dec deriving (Enum, Eq, Show) -- 修正weekday的基准映射,2000-01-03为周一 weekday (Date d m y) = case daysBetween (Date d m y) (Date 3 Jan 2000) `mod` 7 of 0 -> Mon 1 -> Tue 2 -> Wen 3 -> Thu 4 -> Fri 5 -> Sat _ -> Sun
替代实现方案
可以使用泽勒一致性算法,无需先计算日期间隔即可直接得到星期结果,也可以基于该算法实现日期间隔计算:
-- 泽勒公式计算星期,适用于格里高利历 weekday :: Date -> Dow weekday (Date d m y) = let m' = if getMonthNr m < 3 then getMonthNr m + 12 else getMonthNr m y' = if getMonthNr m < 3 then y - 1 else y q = d k = y' `mod` 100 j = y' `div` 100 h = (q + (13 * (m' + 1)) `div` 5 + k + k `div` 4 + j `div` 4 + 5 * j) `mod` 7 in case h of 0 -> Sat 1 -> Sun 2 -> Mon 3 -> Tue 4 -> Wen 5 -> Thu _ -> Fri
内容的提问来源于stack exchange,提问作者Biohazard
相关产品推荐
相关产品推荐

