You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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.

Step 1: Parse & Validate the Input Date

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.
Step 2: Parse the Duration String

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.
Step 3: Calculate the End Date

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).
Step 4: Put It All Together

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]).
Test It Out!

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 06:41:50