Prolog新手求助:如何根据起始日期与时长计算结束日期?
Hey there! Great question—calculating end dates from a start date and duration is totally doable in Prolog, and I’ll walk you through how to tackle your main pain point (date format handling) first, then build up to the full solution.
First, we need to turn that string like "2018/02/14" into a structured Prolog term (like date(2018, 2, 14)) so we can work with it. We’ll also add validation to make sure the date is real (no February 30th, for example):
% Parse a "YYYY/MM/DD" string into a date term parse_date(DateStr, date(Y, M, D)) :- split_string(DateStr, "/", "", [YStr, MStr, DStr]), number_string(Y, YStr), number_string(M, MStr), number_string(D, DStr), is_valid_date(Y, M, D). % Check if a date is valid (handles leap years for February) is_valid_date(Y, M, D) :- between(1, 12, M), days_in_month(Y, M, MaxDays), between(1, MaxDays, D). % Helper to get days in a month days_in_month(Y, 2, 29) :- leap_year(Y), !. days_in_month(_, 2, 28). days_in_month(_, M, 31) :- member(M, [1,3,5,7,8,10,12]), !. days_in_month(_, _, 30). % Leap year rules leap_year(Y) :- Y mod 4 =:= 0, Y mod 100 =\= 0, !. leap_year(Y) :- Y mod 400 =:= 0.
Next, we’ll handle strings like "99 days" or "99 months" by converting them into a structured duration term (like duration(99, day)). We’ll normalize the unit to handle plural/singular variations:
% Parse a "X days/months" string into a duration term parse_duration(DurStr, duration(Amt, Unit)) :- split_string(DurStr, " ", "", [AmtStr, UnitStr]), number_string(Amt, AmtStr), string_lower(UnitStr, LowerUnit), (LowerUnit = "day"; LowerUnit = "days") -> Unit = day ; Unit = month.
Now for the core logic—we’ll write two versions of an add_duration/3 predicate: one for adding days, one for adding months.
Adding Days (Using Julian Day Numbers)
The easiest way to add days without worrying about month boundaries is to convert the date to a Julian Day Number (a single integer representing days since a fixed epoch), add the days, then convert back:
% Convert date to Julian Day Number (JDN) date_to_jdn(date(Y, M, D), JDN) :- (M < 3 -> Y1 is Y - 1, M1 is M + 12 ; Y1 is Y, M1 is M), A is Y1 // 100, B is 2 - A + A // 4, JDN is floor(365.25*(Y1 + 4716)) + floor(30.6001*(M1 + 1)) + D + B - 1524.5. % Convert JDN back to a date term jdn_to_date(JDN, date(Y, M, D)) :- Z is floor(JDN + 0.5), A is Z + 1524, B is floor((A - 122.1)/365.25), C is floor(365.25*B), D1 is floor((A - C)/30.6001), D is A - C - floor(30.6001*D1), M is D1 - 1 - 12*floor(D1/14), Y is B - 4715 - floor((7 + M)/10). % Add days to a date add_duration(Date, duration(Amt, day), EndDate) :- date_to_jdn(Date, JDN), NewJDN is JDN + Amt, jdn_to_date(NewJDN, EndDate).
Adding Months
Adding months needs to handle year rollover (e.g., December + 1 month = January next year) and adjust days if the target month doesn’t have enough days (e.g., March 31 + 1 month = April 30):
% Add months to a date add_duration(date(Y, M, D), duration(Amt, month), EndDate) :- TotalMonths is M + Amt, YearsToAdd is TotalMonths // 12, NewMonth is TotalMonths mod 12, (NewMonth =:= 0 -> NewY is Y + YearsToAdd - 1, NewM = 12 ; NewY is Y + YearsToAdd, NewM = NewMonth ), days_in_month(NewY, NewM, MaxDays), NewD is min(D, MaxDays), EndDate = date(NewY, NewM, NewD).
Finally, we’ll build the top-level predicate you mentioned (date/3) that ties everything together—parsing inputs, calculating, and formatting the result back to a string:
% Top-level predicate: StartDateStr + DurationStr → EndDateStr date(StartDateStr, DurationStr, EndDateStr) :- parse_date(StartDateStr, StartDate), parse_duration(DurationStr, Duration), add_duration(StartDate, Duration, EndDate), date_to_string(EndDate, EndDateStr). % Convert a date term back to "YYYY/MM/DD" string (with leading zeros) date_to_string(date(Y, M, D), DateStr) :- format(string(DateStr), "~4d/~2..0d/~2..0d", [Y, M, D]).
Try these queries to see it in action:
% 2018/02/14 + 99 days → should return "2018/05/24" ?- date("2018/02/14", "99 days", End). % 2018/02/14 + 99 months → 99 months = 8 years 3 months → "2026/05/14" ?- date("2018/02/14", "99 months", End).
The key here is breaking the problem into small, focused predicates—each one handles one job (parsing, validation, calculation, formatting). This makes debugging easier and keeps your code clean. Prolog’s declarative style is perfect for these kinds of date manipulation tasks once you get the hang of the building blocks!
内容的提问来源于stack exchange,提问作者Sol

