Haskell addDays函数问题咨询:跨年度闰年适配与月份切换异常
addDays Function: Resolving Leap Year & Month-End Bugs Great job refactoring your code with Dan Robertson's approach—this recursive structure directly addresses both of your original issues. Let's break down how it fixes each problem, plus a small tweak to make your code even cleaner.
Issue 1: Stuck Using Original Year's Leap Day Array After Crossing Years
Root Cause in Original Code
Your first addDays function calculated yearType once using the initial input year yy, then reused this fixed array for all subsequent calculations—even when you crossed into a new year that might be a common year (or vice versa). For example, adding 400 days to 2000-05-01 lands you in 2001, but your original code still used 2000's leap year month lengths, leading to incorrect day counts.
Fix in Refactored Code
Your refactored code solves this by:
- Creating a reusable
isLeapYearhelper to check any year's leap status dynamically - Generating month lengths per current year with
monthLength yy(we'll tweak this function to remove an unused parameter below) - Recursively recalculating month lengths every time you roll over to a new year (like
addDays (1,1,yy+1)), ensuring you always use the correct days per month for the year you're working with.
Issue 2: Incorrect Month Switch on 30-Day Month Ends
Root Cause in Original Code
Your original code had convoluted logic for calculating daysofMonth and noofMonths, especially handling edge cases where the total days landed exactly at the end of a month. This led to off-by-one errors, like incorrectly switching months when adding days to the last day of a 30-day month.
Fix in Refactored Code
The recursive approach simplifies this entirely by breaking the problem into clear checks:
- First, check if days exceed the remaining days in the current year: If so, roll over to January 1 of the next year and subtract the remaining days in the current year from the total days to add.
- If not, check if days exceed the remaining days in the current month: For example, if you're on 2024-04-30 (a 30-day month),
leftInMonthreturns1(since there's 1 day left in the month: the 30th itself). Adding 1 day triggers this condition, so we roll over to May 1 and subtract that 1 remaining day, leaving 0 days to add—resulting in the correct date(1,5,2024). - Otherwise, just add the days to the current date: No edge cases to handle here, since we've already confirmed the days fit within the current month.
Small Tweak for Cleaner Code
Your monthLength function currently takes an unused mm parameter. You can simplify it to:
monthLength :: Int -> [Int] monthLength yy = if isLeapYear yy then [31,29,31,30,31,30,31,31,30,31,30,31] else [31,28,31,30,31,30,31,31,30,31,30,31]
Then update calls to monthLength in other helpers to pass only the year:
leftInMonth :: (Int,Int,Int) -> Int leftInMonth (dd,mm,yy) = (monthLength yy !! (mm-1)) - dd + 1 daysSinceYearBegan :: (Int,Int,Int) -> Int daysSinceYearBegan (dd,mm,yy) = if mm == 1 then dd else dd + sum (take (mm-1) (monthLength yy))
This makes your code more readable and removes unnecessary parameters.
内容的提问来源于stack exchange,提问作者Alexia Desouza

