% % This program provides a set of predicates for resoning with % the temporal order of date time descriptions. The date time % description must be represented in the ISO 8601 Date and Time % Formats. % % The following predicates are provided: % % 1) set_time_zone(+DefaultTimeZone) : changes the default time zone % that is used to normalize non-UTC date time to UTC date time. If the % default time zone value is not changed, this % program assumes EST (Eastern Standard Time UTC -05:00) as the % default value. % % 2) dt_before(+DateTime1,+DateTime2) : true if the date time % described by +DateTime1 is before the date time described by % +DateTime2. False otherwise. If either +DateTime1 or +DateTime2 is % not UTC, then it is normalized to a UTC. Comparison is done when % both date time descriptions are in their UTC form. % % 3) dt_after(+DateTime1,+DateTime2): true if the date time % described by +DateTime1 is after the date time described by % +DateTime2. False otherwise. If either +DateTime1 or +DateTime2 is % not UTC, then it is normalized to a UTC. Comparison is done when % both date time descriptions are in their UTC form. % % 4) dt_equals(+DateTime1,+DateTime2): true if the date time % described by +DateTime1 is not either before or after % +DateTime2. False otherwise. If either +DateTime1 or +DateTime2 is % not UTC, then it is normalized to a UTC. Comparison is done when % both date time descriptions are in their UTC form. % % 5) dt_is_well_formed(+DateTime): true if the described date time is % well formed according to the ISO 8601 Conventions. False otherwise. % % License & documentation: http://cobra.umbc.edu/time/ % % author: Harry Chen (harry.chen@umbc.edu, http://umbc.edu/~hchen4/) % % cvs: $Revision: 1.5 $, $Date: 2003/12/10 21:32:22 $ % :- dynamic set_time_zone/1. :- dynamic dt_tzone/2. % % The default time zone is set to US Eastern Standard Time. % UTC -05:00 % dt_tzone(-,5). set_time_zone(TZ) :- string_length(TZ,X), X =\= 6, !, fail. set_time_zone(TZ) :- sub_string(TZ,0,1,5,SignStr), sub_string(TZ,1,2,3,TZoneHHStr), string_to_atom(SignStr,SignAtom), string_to_list(TZoneHHStr,TZoneHHList), number_codes(TZoneHH,TZoneHHList), retract(dt_tzone(_,_)), assert(dt_tzone(SignAtom,TZoneHH)). % % dt_before(+T1, +T2). % % Input value must be type of string in the form % 'YYYY-MM-DDTHH:MM:SS' % dt_before(DateTime1,DateTime2) :- parse_date_time(DateTime1,DTTerm1), parse_date_time(DateTime2,DTTerm2), normalize(DTTerm1,Norm_DTList1), normalize(DTTerm2,Norm_DTList2), is_before(Norm_DTList1,Norm_DTList2). is_before([],[]) :- !, fail. is_before([H1|_],[H2|_]) :- H1 > H2, !, fail. is_before([H1|T1],[H2|T2]) :- H1 = H2, is_before(T1,T2). is_before([H1|_],[H2|_]) :- H1 < H2. dt_after(DateTime1,DateTime2) :- dt_before(DateTime2,DateTime1). dt_equals(DateTime1,DateTime2) :- not(dt_before(DateTime1,DateTime2)), not(dt_after(DateTime1,DateTime2)). dt_is_well_formed(DateTime) :- catch(parse_date_time(DateTime,_),_,(fail)). % a predicate that parses a string representation of date time into % a structured prolog term. % % dt([YYYY,MM,DD,HH,MM,SS],TimeZoneSign,TimeZoneHH). % parse_date_time(DateTime,dt([Year,Month,Day,Hour,Min,Sec], TZSign,TZ)) :- get_year(DateTime,Year), get_month(DateTime,Month), get_day(DateTime,Day), get_hour(DateTime,Hour), get_min(DateTime,Min), get_sec(DateTime,Sec), get_time_zone_sign(DateTime,TZSign), get_time_zone(DateTime,TZ). get_year(DateTime,Year) :- sub_string(DateTime,0,4,_,YearStr), string_to_list(YearStr,YearL), number_codes(Year,YearL), 0 < Year. get_month(DateTime,Month) :- sub_string(DateTime,4,1,_,'-'), % for synx check sub_string(DateTime,5,2,_,MonthStr), string_to_list(MonthStr,MonthL), number_codes(Month,MonthL), 0 < Month, Month =< 12. get_day(DateTime,Day) :- sub_string(DateTime,7,1,_,'-'), % for synx check sub_string(DateTime,8,2,_,DayStr), string_to_list(DayStr,DayL), number_codes(Day,DayL), 0 < Day, Day =< 31. get_hour(DateTime,Hour) :- sub_string(DateTime,10,1,_,'T'), % for synx check sub_string(DateTime,11,2,_,HourStr), string_to_list(HourStr,HourL), number_codes(Hour,HourL), 0 =< Hour, Hour =< 23. get_min(DateTime,Min) :- sub_string(DateTime,13,1,_,':'), % for synx check sub_string(DateTime,14,2,_,MinStr), string_to_list(MinStr,MinL), number_codes(Min,MinL), 0 =< Min, Min =< 60. get_sec(DateTime,Sec) :- sub_string(DateTime,16,1,_,':'), % for synx check sub_string(DateTime,17,2,_,SecStr), string_to_list(SecStr,SecL), number_codes(Sec,SecL), 0 =< Sec, Sec =< 60. % Handles an input value without time zone specified. % % YYYY-MM-DDTHH:MM:SS % % The TZSign value is set to the value that defined by % the first argument in dt_tzone/2. % get_time_zone_sign(DateTime,TZSign) :- string_length(DateTime,19), dt_tzone(TZSign,_). % Handles an input date time is a UTC % % YYYY-MM-DDTHH:MM:SSZ or % get_time_zone_sign(DateTime,TZSign) :- string_length(DateTime,20), sub_string(DateTime,19,1,0,'Z'), % for synx check string_to_atom('+',TZSign). % Handles an input date time that uses time zone % % YYYY-MM-DDTHH:MM:SS+HH:MM or % YYYY-MM-DDTHH:MM:SS-HH:MM or % get_time_zone_sign(DateTime,TZSign) :- string_length(DateTime,25), sub_string(DateTime,19,1,5,TZSignStr), string_to_atom(TZSignStr,TZSign), (TZSign == '-'; TZSign == '+'). % for synx chk % Handles an input date time that does not use time zone or is % not a UTC. Adds time zone information to the input date, using % the time zone defined by the second argument of dt_tzone/2. % % YYYY-MM-DDTHH:MM:SS % get_time_zone(DateTime,TZ) :- string_length(DateTime,19), dt_tzone(_,TZ). % Handles an input date time that is a UTC % % YYYY-MM-DDTHH:MM:SSZ % get_time_zone(DateTime,TZ) :- string_length(DateTime,20), number_codes(TZ,"0"). % Handles an input date time that uses a specified time zone % % YYYY-MM-DDTHH:MM:SS-HH:MM or % YYYY-MM-DDTHH:MM:SS+HH:MM or % get_time_zone(DateTime, TZHH) :- string_length(DateTime,25), sub_string(DateTime,20,2,_,TZHHStr), sub_string(DateTime,22,1,_,':'), sub_string(DateTime,23,2,_,TZMMStr), % for synx chk string_to_list(TZHHStr,TZHHList), number_codes(TZHH,TZHHList), string_to_list(TZMMStr,TZMMList), % for synx chk number_codes(TZMM,TZMMList), % for synx chk 0 =< TZHH, TZHH =< 14, 0 =:= TZMM. % for synx chk % % Implements the algorithm defined in the XML Schema Part 2: Datatypes % % http://www.w3.org/TR/2001/REC-xmlschema-2-20010502/ % :- arithmetic_function(fQuotient/2). :- arithmetic_function(fQuotient/3). :- arithmetic_function(modulo/2). :- arithmetic_function(modulo/3). :- arithmetic_function(maximumDayInMonthFor/2). fQuotient(A,B,Result) :- Q is A / B, Result is floor(Q). fQuotient(A,Low,High,Result) :- X is A - Low, Y is High - Low, fQuotient(X,Y,Result). modulo(A,B,Result) :- fQuotient(A,B,Q), Result is A - Q * B. modulo(A,Low,High,Result) :- X is A - Low, Y is High - Low, modulo(X,Y,Res), Result is Res + Low. maximumDayInMonthFor(Year,Month,Result) :- modulo(Month,1,13,M), fQuotient(Month,1,13,Res), Y is Year + Res, maxDayInMonth(M,Y,Result). % January, March, May, July, August, October, and December have 31 days. maxDayInMonth(1,_,31). maxDayInMonth(3,_,31). maxDayInMonth(5,_,31). maxDayInMonth(6,_,31). maxDayInMonth(7,_,31). maxDayInMonth(8,_,31). maxDayInMonth(9,_,31). maxDayInMonth(10,_,31). maxDayInMonth(12,_,31). % April, June, September, and November have 30 days maxDayInMonth(4,_,30). maxDayInMonth(6,_,30). maxDayInMonth(9,_,30). maxDayInMonth(11,_,30). maxDayInMonth(M,Y,R) :- M = 2, modulo(Y,400,Res1), modulo(Y,100,Res2), modulo(Y,4,Res3), (Res1 =:= 0 ; ((Res2 =\= 0), (Res3 =:= 0))), R is 29. maxDayInMonth(_,_,28). normalize(DTTerm,Norm_DTList) :- create_duration(DTTerm,DurationList), arg(1,DTTerm,DTList), add_duration_to_datetime(DurationList,DTList,Norm_DTList). % in order to do UTC normalization, we need to determine the duration % that we need to add to the specified date time. The sum of the % specified date time and the duration is a UTC time. create_duration(DTTerm,DurationList) :- functor(DTTerm,dt,3), arg(2,DTTerm,TZSign), arg(3,DTTerm,TZHH), normalize_tzsign_for_hour(TZSign,TZHH,Dur_HH), DurationList = [0,0,0,Dur_HH,0,0]. % if the sign is '-', meaning the specified date time is Hour behind % the UTC time, then we need to *add* X number of hours (Hour) to the % specified date time when doing the normalization. normalize_tzsign_for_hour(Sign,Hour,Norm_Hour) :- Sign = '-', Norm_Hour is Hour * 1. % if the sign is '+', meaning the specified date time is Hour ahead of % the UTC time, then we need to *subtract* X number of hours (Hour) % from the specified date time when doing the normalization. normalize_tzsign_for_hour(Sign,Hour,Norm_Hour) :- Sign = '+', Norm_Hour is Hour * -1. % the entry predicate that starts the whole noralization process. It % first normalizes the Second, Minutes and Hours fields. It then adds % carry-over from the Day field to the Month and the Year fields. % % The implemented algorithm can be found in Sec. E of % http://www.w3.org/TR/xmlschema-2 % add_duration_to_datetime(D,S,E) :- add_duration_to_datetime1(D,S,E1), E = [_,_,_,E_hr,E_mi,E_sc], E1 = [_,_,_,E1_hr,E1_mi,E1_sc], add_days_carry_to_datetime(E1,E), E_hr is E1_hr, E_mi is E1_mi, E_sc is E1_sc, !. % cut. do it once is enough. add_duration_to_datetime1(D,S,E) :- S = [S_yr,S_mo,_,S_hr,S_mi,S_sc], D = [D_yr,D_mo,_,D_hr,D_mi,D_sc], E = [E_yr,E_mo,_,E_hr,E_mi,E_sc], % Months Tmp1 is S_mo + D_mo, E_mo is modulo(Tmp1,1,13), Carry1 is fQuotient(Tmp1,1,13), % Years E_yr is S_yr + D_yr + Carry1, % Zone (Skipped) % Seconds Tmp2 is S_sc + D_sc, E_sc is modulo(Tmp2,60), Carry2 is fQuotient(Tmp2,60), % Minutes Tmp3 is S_mi + D_mi + Carry2, E_mi is modulo(Tmp3,60), Carry3 is fQuotient(Tmp3,60), % Hours Tmp4 is S_hr + D_hr + Carry3, E_hr is modulo(Tmp4,24), Carry4 is fQuotient(Tmp4,24), add_days_duration(D,S,E,Carry4). add_days_duration(D,S,E,Carry) :- S = [_,_,S_dy,_,_,_], D = [_,_,D_dy,_,_,_], E = [E_yr,E_mo,E_dy,_,_,_], S_dy > maximumDayInMonthFor(E_yr,E_mo), TmpDays is maximumDayInMonthFor(E_yr,E_mo), E_dy is TmpDays + D_dy + Carry. add_days_duration(D,S,E,Carry) :- S = [_,_,S_dy,_,_,_], D = [_,_,D_dy,_,_,_], E = [_,_,E_dy,_,_,_], S_dy < 1, TmpDays is 1, E_dy is TmpDays + D_dy + Carry. add_days_duration(D,S,E,Carry) :- S = [_,_,S_dy,_,_,_], D = [_,_,D_dy,_,_,_], E = [_,_,E_dy,_,_,_], TmpDays is S_dy, E_dy is TmpDays + D_dy + Carry. add_days_carry_to_datetime(Ein,Eout) :- Ein = [Ein_yr,Ein_mo,Ein_dy,_,_,_], T = [T_yr,T_mo,T_dy,_,_,_], Ein_dy < 1, TmpMonth is Ein_mo - 1, T_dy is Ein_dy + maximumDayInMonthFor(Ein_yr,TmpMonth), Carry is -1, Tmp is Ein_mo + Carry, T_mo is modulo(Tmp,1,13), T_yr is Ein_yr + fQuotient(Tmp,1,13), add_days_carry_to_datetime(T,Eout). add_days_carry_to_datetime(Ein,Eout) :- Ein = [Ein_yr,Ein_mo,Ein_dy,_,_,_], T = [T_yr,T_mo,T_dy,_,_,_], Ein_dy > maximumDayInMonthFor(Ein_yr,Ein_mo), T_dy is Ein_dy - maximumDayInMonthFor(Ein_yr,Ein_mo), Carry is 1, Tmp is Ein_mo + Carry, T_mo is modulo(Tmp,1,13), T_yr is Ein_yr + fQuotient(Tmp,1,13), add_days_carry_to_datetime(T,Eout). add_days_carry_to_datetime(Ein,Eout) :- Ein = [Ein_yr,Ein_mo,Ein_dy,_,_,_], (Ein_dy >= 1; Ein_dy =< maximumDayInMonthFor(Ein_yr,Ein_mo)), Ein = Eout.