1:- encoding(utf8).
    2:- module(
    3  xsd_grammar,
    4  [
    5    dayTimeDurationCanonicalMap//1, % +Duration
    6    dayTimeDurationMap//1,          % -Duration
    7    decimalLexicalMap//1,           % -Decimal
    8    decimalCanonicalMap//1,         % +Decimal
    9    durationCanonicalMap//1,        % +Duration
   10    durationMap//1                  % -Duration
   11  ]
   12).

XSD grammar

XSD grammar rules for parsing decimals and durations.

Compatibility
- XML Schema 1.1 Part 2 ― Datatypes

*/

   22:- use_module(library(arithmetic)).   23
   24:- use_module(library(abnf)).   25:- use_module(library(dcg)).   26:- use_module(library(default)).   27:- use_module(library(list_ext)).   28:- use_module(library(math_ext)).   29
   30:- arithmetic_function(xsd_div/2).   31:- arithmetic_function(xsd_mod/2).   32
   33:- op(400, yfx, xsd_div).   34:- op(400, yfx, xsd_mod).   35
   36% xsd_div(+M, +N, -Z) is det.
   37%
   38% If `M` and `N` are numbers, then `M div N` is the greatest integer
   39% less than or equal to `M / N`.
   40
   41xsd_div(X, Y, Z):-
   42  Z is floor(X rdiv Y).
 xsd_mod(+M, +N, -X) is det
If M and N are numbers, then M mod N is m-n * (m div n).
   48xsd_mod(X, Y, Z):-
   49  Z is X - Y * (X xsd_div Y).
 dateCanonicalMap(+DT)// is det
Maps a date value to a dateLexicalRep//1.

Arguments

Arguments:
Date- A complete date value.

Algorithm

Let D be:

  • yearCanonicalFragmentMap(da's year) &
  • '-' &
  • monthCanonicalFragmentMap(da's month) &
  • '-' &
  • dayCanonicalFragmentMap(da's day)

Return:

  • D, when da's timezoneOffset is absent
  • D & timezoneCanonicalFragmentMap(da's timezoneOffset), otherwise
   83dateCanonicalMap(date_time(Y,Mo,D,_,_,_,Off)) -->
   84  yearCanonicalFragmentMap(Y),
   85  "-",
   86  monthCanonicalFragmentMap(Mo),
   87  "-",
   88  dayCanonicalFragmentMap(D),
   89  ({var(Off)} -> "" ; timezoneCanonicalFragmentMap(Off)).
 dateLexicalMap(-DT)// is det
Maps a dateLexicalRep//1 to a date value.

Arguments

Arguments:
Date- A complete date value.

Algorithm

LEX necessarily includes:

Let tz be timezoneFragValue(T) when T is present, otherwise absent.

Return newDateTime(yearFragValue(Y), monthFragValue(M), dayFragValue(D), absent, absent, absent, tz).

  120dateLexicalMap(DT) -->
  121  yearFragValue(Y),
  122  "-",
  123  monthFragValue(Mo),
  124  "-",
  125  dayFragValue(D),
  126  ?(timezoneFragValue, Off),
  127  {newDateTime(Y, Mo, D, _, _, _, Off, DT)}.
 dateTimeCanonicalMap(+DT)// is det
Maps a datetime value to a datetimeLexicalRep//1.

Arguments

Arguments:
Date- A complete datetime value.

Algorithm

Let DT be:

  • yearCanonicalFragmentMap(dt's year) &
  • '-' &
  • monthCanonicalFragmentMap(dt's month) &
  • '-' &
  • dayCanonicalFragmentMap(dt's day) &
  • 'T' &
  • hourCanonicalFragmentMap(dt's hour) &
  • ':' &
  • minuteCanonicalFragmentMap(dt's minute) &
  • ':' &
  • secondCanonicalFragmentMap(dt's second)

Return:

  • DT, when dt's timezoneOffset is absent
  • DT & timezoneCanonicalFragmentMap(dt's timezoneOffset), otherwise
  172dateTimeCanonicalMap(date_time(Y,Mo,D,H,Mi,S,Off)) -->
  173  yearCanonicalFragmentMap(Y),
  174  "-",
  175  monthCanonicalFragmentMap(Mo),
  176  "-",
  177  dayCanonicalFragmentMap(D),
  178  "T",
  179  hourCanonicalFragmentMap(H),
  180  ":",
  181  minuteCanonicalFragmentMap(Mi),
  182  ":",
  183  secondCanonicalFragmentMap(S),
  184  ({var(Off)} -> "" ; timezoneCanonicalFragmentMap(Off)).
 dateTimeLexicalMap(-DT)// is det
Maps a dateTimeLexicalRep//1 to a dateTime value.

Arguments

Arguments:
Date- A complete dateTime value.

Algorithm

LEX necessarily includes:

Let tz be:

  • timezoneFragValue(T), when T is present
  • otherwise absent.

Return:

  • newDateTime(yearFragValue(Y), monthFragValue(MO), dayFragValue(D), 24, 0, 0, tz), when endOfDayFrag//1 is present
  • newDateTime(yearFragValue(Y), monthFragValue(MO), dayFragValue(D), hourFragValue(H), minuteFragValue(MI), secondFragValue(S), tz), otherwise
  228dateTimeLexicalMap(DT) -->
  229  yearFragValue(Y),
  230  "-",
  231  monthFragValue(Mo),
  232  "-",
  233  dayFragValue(D),
  234  "T",
  235  (   hourFragValue(H),
  236      ":",
  237      minuteFragValue(Mi),
  238      ":",
  239      secondFragValue(S)
  240  ;   endOfDayFrag(Y, Mi, S)
  241  ), !,
  242  ?(timezoneFragValue, Off),
  243  {newDateTime(Y, Mo, D, H, Mi, S, Off, DT)}.
 dateTimePlusDuration(+Duration, +DT1, -DT2) is det
Adds a du to a dt value, producing another date/time value.

Arguments

Arguments:
du- A duration value.
dt- A date/time value.

Result

A date/time value.

Algorithm

Let:

  • yr be dt's year
  • mo be dt's month
  • da be dt's day
  • hr be dt's hour
  • mi be dt's minute
  • se be dt's second
  • tz be dt's timezoneOffset

Steps:

  • Add du's months to mo
  • normalizeMonth(yr, mo), i.e., carry any over- or underflow, adjust month.
  • Set da to min(da, daysInMonth(yr, mo)), i.e., pin the value if necessary.
  • Add du's seconds to se.
  • normalizeSecond(yr, mo, da, hr, mi, se), i.e., carry over- or underflow of seconds up to minutes, hours, etc.
  • Return newDateTime(yr, mo, da, hr, mi, se, tz)
  296dateTimePlusDuration(duration(Mo0,S0), date_time(Y1,Mo1,D1,H1,Mi1,S1,Off), DT) :-
  297  Mo2_ is Mo1 + Mo0,
  298  normalizeMonth(Y1, Mo2_, Y2, Mo2),
  299  daysInMonth(Y2, Mo2, DaysInMonth),
  300  D2 is min(D1, DaysInMonth),
  301  S2 is S1 + S0,
  302  normalizeSecond(Y2, Mo2, D2, H1, Mi1, S2, Y3, Mo3, D3, H3, Mi3, S3),
  303  newDateTime(Y3, Mo3, D3, H3, Mi3, S3, Off, DT).
 dayCanonicalFragmentMap(+D:between(0,99))// is det
Maps an integer, presumably the day property of a date/timeSevenPropertyModel value, onto a dayFrag//1, part of a date/timeSevenPropertyModel's lexical representation.

Arguments

Arguments:
Day- An integer between 1 and 31 inclusive (may be limited further depending on associated year and month).

Algorithm

Return unsTwoDigitCanonicalFragmentMap(d).

  322dayCanonicalFragmentMap(D) -->
  323  {between(1, 31, D)},
  324  unsTwoDigitCanonicalFragmentMap(D).
 dayFragValue(-Day:between(1,31))//
Maps a dayFrag//1, part of a date/timeSevenPropertyModel's lexical representation, onto an integer, presumably the day property of a date/timeSevenPropertyModel value.
[58] dayFrag ::= ('0' [1-9]) | ([12] digit) | ('3' [01])

Arguments

Arguments:
Day- An integer.

Algorithm

Return unsignedNoDecimalMap(DA).

  346dayFragValue(Day) -->
  347  #(2, digit_weight, Ns),
  348  {
  349    integer_weights(Day, Ns),
  350    must_be(between(1, 31), Day)
  351  }.
 daysInMonth(?Year:integer, +Month:between(1,12), -Days:between(28,31)) is det
Returns the number of the last day of the month for any combination of year and month.

Algorithm

Return:

Compatibility
- /XML Schema 1.1: Datatypes/, appendix E.3.2 “Auxiliary Functions”.
  380% 28 when y is absent.
  381daysInMonth(Y, 2, 28):-
  382  var(Y), !.
  383% 28 when m is 2 and y is not evenly divisible by 4.
  384daysInMonth(Y, 2, 28):-
  385  Y rem 4 =\= 0, !.
  386% 28 when m is 2 and y is evenly divisible by 100 but not by 400.
  387daysInMonth(Y, 2, 28):-
  388  Y rem 100 =:= 0,
  389  Y rem 400 =\= 0, !.
  390% 29 when m is 2 and y is evenly divisible by 400.
  391daysInMonth(Y, 2, 29):-
  392  Y rem 400 =:= 0, !.
  393% 29 when m is 2 and y is evenly divisible by 4 but not by 100.
  394daysInMonth(Y, 2, 29):-
  395  Y rem 4 =:= 0,
  396  Y rem 100 =\= 0, !.
  397% 30 when m is 4, 6, 9, or 11.
  398daysInMonth(_, Month, 30):-
  399  memberchk(Month, [4,6,9,11]), !.
  400% 31 otherwise (m is 1, 3, 5, 7, 8, 10, or 12).
  401daysInMonth(_, _, 31).
 dayTimeDurationCanonicalMap(+Duration)// is det
Maps a dayTimeDuration's seconds value to a dayTimeDurationLexicalRep//1. (The months value is necessarily zero and is ignored.) dayTimeDurationCanonicalMap//1 is a restriction of durationCanonicalMap//1.

Algorithm

Let:

Return sgn & 'P' & duYearMonthCanonicalFragmentMap(|s|)

bug
- s' should be dt''s seconds rather than months.
- duYearMonthCanonicalFragmentMap should be duDayTimeCanonicalFragmentMap.
  427dayTimeDurationCanonicalMap(duration(0,S)) -->
  428  ({S < 0} -> "-", {SAbs is abs(S)} ; {SAbs is abs(S)}),
  429  "P",
  430  duDayTimeCanonicalFragmentMap(SAbs).
 dayTimeDurationMap(+Duration)// is det
Maps the lexical representation into the seconds of a dayTimeDuration value. (A dayTimeDuration's months is always zero.) dayTimeDurationMap//1 is a restriction of durationMap//1.

Arguments

Arguments:
Duration- A dayTimeDuration value.

Algorithm

DT necessarily consists of possibly a leading '-', followed by 'P' and then an instance D of duDayTimeFrag//1.

Return a dayTimeDuration whose:

  • months value is (necessarily) 0
  • seconds value is
    • -duDayTimeFragmentMap(D), if '-' is present in DT
    • duDayTimeFragmentMap(D), otherwise
  459dayTimeDurationMap(duration(0,S)) -->
  460  ("-" -> {Sg = -1} ; {Sg = 1}),
  461  "P", duDayTimeFragmentMap(Sabs),
  462  {S is Sg * Sabs}.
 decimalCanonicalMap(+Decimal:rational)// is det
Maps a Decimal to its canonical representation, a decimalLexicalRep//1.

Arguments

Arguments:
Decimal- A decimal value.

Algorithm

If d is an integer, then return noDecimalPtCanonicalMap(d). Otherwise, return decimalPtCanonicalMap(d).

  480decimalCanonicalMap(N) -->
  481  {integer(N)}, !,
  482  noDecimalPtCanonicalMap(N).
  483decimalCanonicalMap(N) -->
  484  decimalPtCanonicalMap(N).
 decimalLexicalMap(-Decimal:rational)// is det
Maps a decimalLexicalRep//1 onto a decimal value.

Arguments

Arguments:
Decimal- A decimal value.

Algorithm

Let d be a decimal value.

Set d to:

  508decimalLexicalMap(N) -->
  509  decimalPtMap(N), !.
  510decimalLexicalMap(N) -->
  511  noDecimalMap(N).
 decimalPtCanonicalMap(+Decimal:rational)// is det
Maps a decimal number to a decimalPtNumeral//1, its canonical representation.

Arguments

Arguments:
Decimal- A decimal number.

Algorithm

Return:

  • `'-' & unsignedDecimalPtCanonicalMap(-i)`, when i is negative
  • unsignedDecimalPtCanonicalMap(i), otherwise
  530decimalPtCanonicalMap(N) -->
  531  {N < 0}, !,
  532  "-",
  533  {N0 is abs(N)},
  534  unsignedDecimalPtCanonicalMap(N0).
  535decimalPtCanonicalMap(N) -->
  536  unsignedDecimalPtCanonicalMap(N).
 decimalPtMap(-Decimal:rational)// is det
Maps a decimalPtNumeral//1 to its numerical value.

Arguments

Arguments:
Decimal- A decimal number.

Algorithm

N necessarily consists of an optional sign ('+' or '-') and then an instance U of unsignedDecimalPtNumeral//1.

Return:

  • -unsignedDecimalPtMap(U), when '-' is present
  • unsignedDecimalPtMap(U), otherwise
  557decimalPtMap(N) -->
  558  ("-" -> {Sg = -1} ; "+" -> {Sg = 1} ; {Sg = 1}),
  559  unsignedDecimalPtMap(N0),
  560  {N is copysign(N0, Sg)}.
 digit(+Integer:between(0,9))// is det
Maps each integer between 0 and 9 to the corresponding digit.

In the XSD 1.1 specification this mapping is named digit, thus conflicting with the name of the grammar rule to which it is related.

Arguments

Arguments:
Integer- Between 0 and 9 inclusive.

Algorithm:

Return:

  • '0', when i = 0
  • '1', when i = 1
  • '2', when i = 2
  • etc.
 digitRemainderSeq(+I:nonneg, -Sequence:list(nonneg)) is det
Maps each nonnegative integer to a sequence of integers used by digitSeq/2 to ultimately create an unsignedNoDecimalPtNumeral//1.

Arguments

Arguments:
I- A nonnegative integer.
Seq- A infinite sequence of nonnegative integers

Algorithm

Return that sequence s for which

  • s_0 = i and
  • `s_{j+1} = s_j div 10`

According to this algorithm, Sequence ends in an inifinite number of zeros. This is no problem in Prolog:

?- digitRemainderSeq(123, Seq), append([X,Y,Z,A,B,C,D,E,F|_], _, Seq).
Seq = [123, 12, 1, 0, 0, 0, 0, 0, 0|...],
X = 123,
Y = 12,
Z = 1,
A = B, B = C, C = D, D = E, E = F, F = 0,
freeze(_G29912399, xsd_numer_aux: (_G29912399=[0|_G29912477], inflist(0, _G29912477))) .
  616digitRemainderSeq(0, L):- !,
  617  inflist(0, L).
  618digitRemainderSeq(I1, [I1|T]):-
  619  I2 is I1 xsd_div 10,
  620  digitRemainderSeq(I2, T).
 digitSeq(+I:nonneg, -Seq:list(nonneg)) is det
Maps each nonnegative integer to a sequence of integers used by unsignedNoDecimalPtCanonicalMap//1 to create an unsignedNoDecimalPtNumeral//1.

Arguments

Arguments:
I- A nonnegative integer.
Sequence- A sequence of integers where each term is between 0 and 9 inclusive.

Algorithm:

Return that sequence s for which `s_j = digitRemainderSeq(i)_j mod 10`.

  642digitSeq(0, L):- !,
  643  inflist(0, L).
  644digitSeq(I1, [I|T]):-
  645  I is I1 xsd_mod 10,
  646  I2 is I1 xsd_div 10,
  647  digitSeq(I2, T).
 digitSequenceValue(+Digits:list(between(0,9)), -Integer:nonneg) is det
Maps a sequence of digits to the position-weighted sum of the terms numerical values.

Arguments

Arguments:
Digits- A finite sequence of literals, each term matching digit//1.
Integer- A nonnegative integer.

Algorithm

Return the sum of `digitValue(S_i) × 10^{length(S)-i}`, where i runs over the domain of S.

  668digitSequenceValue(Ds, N) :-
  669  length(Ds, Len),
  670  aggregate(
  671    sum(D * 10 ^ (Len - I)),
  672    nth1(I, Ds, D),
  673    N
  674  ).
 digitValue(-Integer:between(0,9))// is det
Maps each digit to its numerical value.

Arguments

Arguments:
Integer- A nonnegative integer less than ten.

Algorithm

Return

  • 0 when d = '0'
  • 1 when d = '1'
  • 2 when d = '2'
  • etc.
  694digitValue(N) -->
  695  digit_weight(N).
 duDayFragmentMap(-D)// is det
Maps a duDayFrag//1 to an integer, intended as part of the value of the seconds property of a duration value.

Arguments

Arguments:
D- A nonnegative integer.

Algorithm

D is necessarily the letter 'D' followed by a numeral N.

Return noDecimalMap(N)

bug
- Swap letter and numeral.
  716duDayFragmentMap(D) -->
  717  noDecimalMap(D),
  718  "D".
 duDayCanonicalFragmentMap(+Day)// is det
Maps a nonnegative integer, presumably the day normalized value from the seconds of a duration value, to a duDayFrag//1, a fragment of a duration lexical representation.

Arguments

Arguments:
Day- A nonnegative integer.

Algorithm

Return:

  • unsignedNoDecimalPtCanonicalMap(d) & 'D', when d is not zero
  • the empty string (''), when d is zero
  740duDayCanonicalFragmentMap(0) --> !, [].
  741duDayCanonicalFragmentMap(D) -->
  742  unsignedNoDecimalPtCanonicalMap(D),
  743  "D".
 duDayTimeCanonicalFragmentMap(+Second:rational)// is det
Maps a nonnegative decimal number, presumably the absolute value of the seconds of a duration value, to a duDayTimeFrag//1, a fragment of a duration lexical representation.

Arguments

Arguments:
Second- A nonnegative decimal number.

Algorithm

Let:

  • d is ss div 86400
  • h is (ss mod 86400) div 3600
  • m is (ss mod 3600) div 60
  • s is ss mod 60

Return:

  • duDayCanonicalFragmentMap(d) & duTimeCanonicalFragmentMap(h, m, s), when ss is not zero
  • 'T0S', when ss is zero
  776duDayTimeCanonicalFragmentMap(0) --> !,
  777  "T0S".
  778duDayTimeCanonicalFragmentMap(S0) -->
  779  {
  780    % Days
  781    xsd_div(S0, 86400, D),
  782
  783    % Hours
  784    xsd_mod(S0, 86400, H0),
  785    xsd_div(H0, 3600, H),
  786
  787    % Minutes
  788    xsd_mod(S0, 3600, Mi0),
  789    xsd_div(Mi0, 60, Mi),
  790
  791    % Seconds
  792    xsd_mod(S0, 60, S)
  793  },
  794  duDayCanonicalFragmentMap(D),
  795  duTimeCanonicalFragmentMap(H, Mi, S).
 duDayTimeFragmentMap(-Second:rational)// is det
Maps a duDayTimeFrag//1 into a decimal number, which is the potential value of the seconds property of a duration value.

Arguments

Second A nonnegative decimal number.

Algorithm

DT necessarily consists of an instance D of duDayFrag//1 and/or an instance T of duTimeFrag//1.

Let:

Return 86400 × d + t

  821duDayTimeFragmentMap(S) -->
  822  (   duDayFragmentMap(D0)
  823  ->  (duTimeFragmentMap(S0) -> "" ; {S0 = 0})
  824  ;   {D0 = 0},
  825      duTimeFragmentMap(S0)
  826  ),
  827  {S is 86400 * D0 + S0}.
 duHourCanonicalFragmentMap(+Hour:nonneg)// is det
Maps a nonnegative integer, presumably the hour normalized value from the seconds of a duration value, to a duHourFrag//1, a fragment of a duration lexical representation.

Arguments

Arguments:
Hour- A nonnegative integer.

Algorithm

Return:

  • unsignedNoDecimalPtCanonicalMap(h) & 'H', when h is not zero
  • the empty string (''), when h is zero
  849duHourCanonicalFragmentMap(0) --> !, [].
  850duHourCanonicalFragmentMap(H) -->
  851  unsignedNoDecimalPtCanonicalMap(H),
  852  "H".
 duHourFragmentMap(-H)// is det
Maps a duHourFrag//1 to an integer, intended as part of the value of the seconds property of a duration value.

Arguments

Arguments:
H- A nonnegative integer.

Algorithm

  • D is necessarily the letter 'D' followed by a numeral N.
  • Return noDecimalMap(N).
bug
- Swap letter and numeral.
- D' should be H'.
  875duHourFragmentMap(H) -->
  876  noDecimalMap(H),
  877  "H".
 duMinuteCanonicalFragmentMap(+Minute:nonneg)// is det
Maps a nonnegative integer, presumably the minute normalized value from the seconds of a duration value, to a duMinuteFrag//1, a fragment of a duration lexical representation.

Arguments

Arguments:
Minute- A nonnegative integer.

Algorithm

Return:

  • unsignedNoDecimalPtCanonicalMap(m) & 'M', when m is not zero
  • the empty string (''), when m is zero
  899duMinuteCanonicalFragmentMap(0) --> !, [].
  900duMinuteCanonicalFragmentMap(M) -->
  901  unsignedNoDecimalPtCanonicalMap(M),
  902  "M".
 duMinuteFragmentMap(-Mi)// is det
Maps a duMinuteFrag//1 to an integer, intended as part of the value of the seconds property of a duration value.

Arguments

Arguments:
Mi- A nonnegative integer.

Algorithm

  • M is necessarily the letter 'M' followed by a numeral N.
  • Return noDecimalMap(N)
bug
- Swap letter and numeral.
  923duMinuteFragmentMap(Mi) -->
  924  noDecimalMap(Mi),
  925  "M".
 duMonthFragmentMap(-Mo)// is det
Maps a duMonthFrag//1 to an integer, intended as part of the value of the months property of a duration value.

Arguments

Arguments:
Mo- A nonnegative integer.

Algorithm

M is necessarily the letter 'M' followed by a numeral N.

Return noDecimalMap(N)

bug
- Swap letter and numeral.
  946duMonthFragmentMap(Mo) -->
  947  noDecimalMap(Mo),
  948  "M".
 durationCanonicalMap(+Duration)// is det
Maps a duration's property values to durationLexicalRep//1 fragments and combines the fragments into a complete durationLexicalRep//1.

Arguments

Arguments:
Duration- A complete duration value.

Algorithm

Let:

  • m be v's months
  • s be v's seconds
  • sgn be '-' if m or s is negative and the empty string ('') otherwise

Return:

  • sgn & 'P' & duYearMonthCanonicalFragmentMap(|m|) & duDayTimeCanonicalFragmentMap(|s|), when neither m nor s is zero
  • sgn & 'P' & duYearMonthCanonicalFragmentMap(|m|), when m is not zero but s is
  • sgn & 'P' & duDayTimeCanonicalFragmentMap(|s|), when m is zero
  982durationCanonicalMap(duration(Mo,S)) -->
  983  ({(Mo < 0 ; S < 0.0)} -> "-" ; ""),
  984  "P",
  985  (   {Mo =:= 0}
  986  ->  {SAbs is abs(S)},
  987      duDayTimeCanonicalFragmentMap(SAbs)
  988  ;   {S =:= 0}
  989  ->  {MoAbs is abs(Mo)},
  990      duYearMonthCanonicalFragmentMap(MoAbs)
  991  ;   {
  992        MoAbs is abs(Mo),
  993        SAbs is abs(S)
  994      },
  995      duYearMonthCanonicalFragmentMap(MoAbs),
  996      duDayTimeCanonicalFragmentMap(SAbs)
  997  ).
 durationMap(-Duration:compound)// is det
Separates the durationLexicalRep//1 into the month part and the seconds part, then maps them into the months and seconds of the duration value.

Arguments

Arguments:
Duration- A complete duration value.

Algorithm

DUR consists of:

Return a duration whose:

  • months value is:
    • 0, if Y is not present
    • -duYearMonthFragmentMap(Y), if both '-' and Y are present
    • duYearMonthFragmentMap(Y), otherwise
  • seconds value is:
    • 0, if D is not present
    • -duDayTimeFragmentMap(D), if both '-' and D are present
    • duDayTimeFragmentMap(D), otherwise
 1040durationMap(duration(Mo,S)) -->
 1041  ("-" -> {Sg = -1} ; {Sg = 1}),
 1042  "P",
 1043  (   duYearMonthFragmentMap(MoAbs)
 1044  ->  (duDayTimeFragmentMap(SAbs) -> "" ; {SAbs = 0})
 1045  ;   {MoAbs = 0},
 1046      duDayTimeFragmentMap(SAbs)
 1047  ),
 1048  {
 1049    Mo is copysign(MoAbs, Sg),
 1050    S is copysign(SAbs, Sg)
 1051  }.
 duSecondCanonicalFragmentMap(+Second:rational)// is det
Maps a nonnegative decimal number, presumably the second normalized value from the seconds of a duration value, to a duSecondFrag//1, a fragment of a duration lexical representation.

Arguments

Arguments:
Second- A nonnegative decimal number.

Algorithm

Return:

  • unsignedNoDecimalPtCanonicalMap(s) & 'S', when s is a non-zero integer
  • unsignedDecimalPtCanonicalMap(s) & 'S', when s is not an integer
  • the empty string (''), when s is zero
 1076duSecondCanonicalFragmentMap(S) -->
 1077  {S =:= 0}, !, [].
 1078duSecondCanonicalFragmentMap(S) -->
 1079  {integer(S)}, !,
 1080  unsignedNoDecimalPtCanonicalMap(S),
 1081  "S".
 1082duSecondCanonicalFragmentMap(S) -->
 1083  unsignedDecimalPtCanonicalMap(S),
 1084  "S".
 duSecondFragmentMap(-S:rational)// is det
Maps a duSecondFrag//1 to a decimal number, intended as part of the value of the seconds property of a duration value.

Arguments

Arguments:
Second- A nonnegative decimal number.

Algorithm

  • S is necessarily 'S' followed by a numeral N.
  • Return:
    • decimalPtMap(N), when '.' occurs in N
    • noDecimalMap(N), otherwise
 1107duSecondFragmentMap(S) -->
 1108  (decimalPtMap(S) ; noDecimalMap(S)),
 1109  "S".
 duTimeCanonicalFragmentMap(+Hour:nonneg, +Minute:nonneg, +Second:rational)// is det
Maps three nonnegative numbers, presumably the hour, minute, and second normalized values from a duration's seconds, to a duTimeFrag//1, a fragment of a duration lexical representation.

Arguments

Arguments:
Hour- A nonnegative integer.
Minute- A nonnegative integer.
Seconds- A nonnegative decimal number.

Algorithm

Return:

  • 'T' & duHourCanonicalFragmentMap(h) & duMinuteCanonicalFragmentMap(m) & duSecondCanonicalFragmentMap(s), when h, m, and s are not all zero
  • the empty string ('') when all arguments are zero
 1142duTimeCanonicalFragmentMap(0, 0, 0) --> !, [].
 1143duTimeCanonicalFragmentMap(H, Mi, S) -->
 1144  "T",
 1145  duHourCanonicalFragmentMap(H),
 1146  duMinuteCanonicalFragmentMap(Mi),
 1147  duSecondCanonicalFragmentMap(S).
 duTimeFragmentMap(-Seconds:rational)// is det
Maps a duTimeFrag//1 into a decimal number, intended as part of the seconds property of a duration value.

Arguments

Arguments:
Seconds- A nonnegative decimal number

Algorithm

T necessarily consists of an instance H of duHourFrag//1, and/or an instance M of duMinuteFrag//1, and/or an instance S of duSecondFrag//1.

Let:

  • h be duDayFragmentMap(H) (or 0 if H is not present)
  • m be duMinuteFragmentMap(M) (or 0 if M is not present)
  • s be duSecondFragmentMap(S) (or 0 if S is not present)

Return 3600 × h + 60 × m + s

To be done
- duDayFragmentMap should be duHourFragmentMap.
 1178duTimeFragmentMap(S) -->
 1179  "T",
 1180  (   duHourFragmentMap(H0)
 1181  ->  (duMinuteFragmentMap(Mi0) -> "" ; {Mi0 = 0}),
 1182      (duSecondFragmentMap(S0)  -> "" ; {S0  = 0})
 1183  ;   duMinuteFragmentMap(Mi0)
 1184  ->  {H0 = 0},
 1185      (duSecondFragmentMap(S0)  -> "" ; {S0  = 0})
 1186  ;   {H0 = 0, Mi0 = 0},
 1187      duSecondFragmentMap(S0)
 1188  ),
 1189  {S is 3600 * H0 + 60 * Mi0 + S0}.
 duYearMonthCanonicalFragmentMap(+Mo)// is det
Maps a nonnegative integer, presumably the absolute value of the months of a duration value, to a duYearMonthFrag//1, a fragment of a duration lexical representation.

Arguments

Arguments:
Mo- A nonnegative integer.

Algorithm

Let:

  • y be ym div 12
  • m be ym mod 12

Return:

  • unsignedNoDecimalPtCanonicalMap(y) & 'Y' & unsignedNoDecimalPtCanonicalMap(m) & 'M', when neither y nor m is zero
  • unsignedNoDecimalPtCanonicalMap(y) & 'Y', when y is not zero but m is
  • unsignedNoDecimalPtCanonicalMap(m) & 'M', when y is zero
 1222duYearMonthCanonicalFragmentMap(YM) -->
 1223  {
 1224    Y is YM xsd_div 12,
 1225    Mo is YM xsd_mod 12
 1226  },
 1227  (   {Y =:= 0}
 1228  ->  unsignedNoDecimalPtCanonicalMap(Mo),
 1229      "M"
 1230  ;   {Mo =:= 0}
 1231  ->  unsignedNoDecimalPtCanonicalMap(Y),
 1232      "Y"
 1233  ;   unsignedNoDecimalPtCanonicalMap(Y),
 1234      "Y",
 1235      unsignedNoDecimalPtCanonicalMap(Mo),
 1236      "M"
 1237  ).
 duYearFragmentMap(-Y)// is det
Maps a duYearFrag//1 to an integer, intended as part of the value of the months property of a duration value.

Arguments

Arguments:
Y- A nonnegative integer.

Algorithm

Y is necessarily the letter 'Y' followed by a numeral N.

Return noDecimalMap(N)

bug
- Swap letter and numeral.
 1258duYearFragmentMap(Y) -->
 1259  noDecimalMap(Y),
 1260  "Y".
 duYearMonthFragmentMap(-Month:nonneg)// is det
Maps a duYearMonthFrag//1 into an integer, intended as part of the months property of a duration value.

Arguments

Arguments:
Month- A nonnegative integer.

Algorithm

  • YM necessarily consists of an instance Y of duYearFrag//1 and/or an instance M of duMonthFrag//1.
  • Let:
    • y be duYearFragmentMap(Y) (or 0 if Y is not present)
    • m be duMonthFragmentMap(M) (or 0 if M is not present)
  • Return 12 × y + m .
 1286duYearMonthFragmentMap(Mo) -->
 1287  (   duYearFragmentMap(Y0)
 1288  ->  (duMonthFragmentMap(Mo0) -> "" ; {Mo0 = 0})
 1289  ;   {Y0 = 0},
 1290      duMonthFragmentMap(Mo0)
 1291  ),
 1292  {Mo is 12 * Y0 + Mo0}.
 endOfDayFrag(-H, -Mi, -S:rational)// is det
endOfDayFrag ::= '24:00:00' ('.' '0'+)?
bug
- Missing from the standard.
 1304endOfDayFrag(24, 0, 0) -->
 1305  "24:00:00",
 1306  ("." -> +("0") ; "").
 fourDigitCanonicalFragmentMap(+Integer:between(-9999,9999))// is det
Maps an integer between -10000 and 10000 onto an always-four-digit numeral.

Arguments

Arguments:
Integer- An integer whose absolute value is less than 10000.

Algorithm

Return:

  • '-' & unsTwoDigitCanonicalFragmentMap(-i div 100) & unsTwoDigitCanonicalFragmentMap(-i mod 100), when i is negative
  • unsTwoDigitCanonicalFragmentMap(i div 100) & unsTwoDigitCanonicalFragmentMap(i mod 100), otherwise
 1329fourDigitCanonicalFragmentMap(N) -->
 1330  ({N < 0} -> "-", {N0 is -N} ; {N0 = N}),
 1331  {N1 is N0 xsd_div 100},
 1332  unsTwoDigitCanonicalFragmentMap(N1),
 1333  {N2 is N0 xsd_mod 100},
 1334  unsTwoDigitCanonicalFragmentMap(N2).
 FractionDigitRemainderSeq(+Fraction:rational, -Sequence:list(compound)) is det
Maps each nonnegative decimal number less than 1 to a sequence of decimal numbers used by fractionDigitSeq/2 to ultimately create an unsignedNoDecimalPtNumeral//1.

Arguments:

Arguments:
Fraction- A nonnegative rational number smaller than 1.
Sequence- A sequence of nonnegative rational numbers.

Algorithm

Return that sequence s for which

  • s_0 = f - 10
  • `s_{j+1} = (s_j mod 1) - 10`
 1358'FractionDigitRemainderSeq'(0, L):- !,
 1359  inflist(0, L).
 1360'FractionDigitRemainderSeq'(F1, [F0|T]):-
 1361  F0 is F1 * 10,
 1362  F2 is F0 xsd_mod 1,
 1363  'FractionDigitRemainderSeq'(F2, T).
 fractionDigitSeq(+Fraction:rational, -Sequence:list(between(0,9))) is det
Maps each nonnegative decimal number less than 1 to a sequence of integers used by fractionDigitsCanonicalFragmentMap//1 to ultimately create an unsignedNoDecimalPtNumeral//1.

Arguments

Arguments:
Fraction- A nonnegative rational number smaller than 1.
Sequence- A sequence of integers where each term is between 0 and 9 inclusive.

Algorithm

For a given fraction f, return that sequence s for which `s_j = FractionDigitRemainderSeq(f)_j div 1`.

 1385fractionDigitSeq(0, L):- !,
 1386  inflist(0, L).
 1387fractionDigitSeq(F1, [F0|T]):-
 1388  F_ is F1 * 10,
 1389  F0 is F_ xsd_div 1,
 1390  F2 is F_ xsd_mod 1,
 1391  fractionDigitSeq(F2, T).
 fractionDigitsCanonicalFragmentMap(+Fraction:rational)// is det
Maps each nonnegative decimal number less than 1 to a literal used by unsignedDecimalPtCanonicalMap//1 to create an unsignedDecimalPtNumeral//1.

Arguments

Arguments:
Fraction- A nonnegative rational number smaller than 1.

Algorithm

For a given fraction f, return:

  • `digit(fractionDigitSeq(f)_0)` &
  • ... &
  • `digit(fractionDigitSeq(f)_{lastSignificantDigit(FractionDigitRemainderSeq(f))})`
 1415fractionDigitsCanonicalFragmentMap(Frac) -->
 1416  {
 1417    fractionDigitSeq(Frac, Seq),
 1418    'FractionDigitRemainderSeq'(Frac, RemSeq),
 1419    lastSignificantDigit(RemSeq, Last),
 1420    length(Ds, Last),
 1421    prefix(Ds, Seq)
 1422  },
 1423  '*!'(digit_weight, Ds), !.
 fractionDigitSequenceValue(+Digits:list(between(0,9)), -Fraction:rational) is det
Maps a sequence of digits to the position-weighted sum of the terms numerical values, weighted appropriately for fractional digits.

Arguments

Arguments:
Digits- A finite sequence of literals, each term matching digit.
Integer- A nonnegative integer.

Algorithm

Return the sum of digitValue(S_i) * 10^{-i}, where i runs over the domain of S.

 1444fractionDigitSequenceValue(Ds, F):-
 1445  aggregate(
 1446    % @bug The brackets are needed in the exponent.
 1447    sum(rdiv(D,10^I)),
 1448    nth1(I, Ds, D),
 1449    F
 1450  ).
 fractionFragValue(-Fraction:rational)// is det
Maps a fracFrag//1 to the appropriate fractional decimal number.

Arguments

Arguments:
Fraction- A nonnegative decimal number.

Algorithm

The parsed string is necessarily the left-to-right concatenation of a finite sequence S of literals, each term matching digit//1.

Return fractionDigitSequenceValue(S).

 1469fractionFragValue(Frac) -->
 1470  '*!'(digit_weight, Ds), !,
 1471  {fractionDigitSequenceValue(Ds, Frac)}.
 gDayCanonicalMap(+DT)// is det
Maps a gDay value to a gDayLexicalRep//1.

Arguments

Arguments:
Datetime- A complete gDay value.

Algorithm

Return:

  • '---' & dayCanonicalFragmentMap(gD's day), when gD's timezoneOffset is absent
  • '---' & dayCanonicalFragmentMap(gD's day) & timezoneCanonicalFragmentMap(gD's timezoneOffset), otherwise.
 1493gDayCanonicalMap(date_time(_,_,D,_,_,_,Off)) -->
 1494  "---",
 1495  dayCanonicalFragmentMap(D),
 1496  ({var(Off)} -> "" ; timezoneCanonicalFragmentMap(Off)).
 gDayLexicalMap(-DT)// is det
Maps a gDayLexicalRep//1 to a gDay value.

Arguments

Arguments:
DT- A complete gDay value.

#Algorithm

LEX necessarily includes an instance D of dayFrag//1, optionally followed by an instance T of timezoneFrag//1.

Let tz be timezoneFragValue(T) when T is present, otherwise absent.

Return newDateTime(gD, absent, absent, dayFragValue(D), absent, absent, absent, tz)

Return newDateTime(absent, absent, dayFragValue(D), absent, absent, absent, tz)

bug
- First return line is erroneous.
 1523gDayLexicalMap(DT) -->
 1524  "---",
 1525  dayFragValue(D),
 1526  ?(timezoneFragValue, Off),
 1527  {newDateTime(_, _, D, _, _, _, Off, DT)}.
 gMonthCanonicalMap(+DT)// is det
Maps a gMonth value to a gMonthLexicalRep//1.

Arguments

Arguments:
DT- A complete gMonth value.

Algorithm

Return:

  • '--' & monthCanonicalFragmentMap(gM's day), when gM's timezoneOffset is absent
  • '--' & monthCanonicalFragmentMap(gM's day) & timezoneCanonicalFragmentMap(gM's timezoneOffset), otherwise.
 1549gMonthCanonicalMap(date_time(_,Mo,_,_,_,_,Off)) -->
 1550  "--",
 1551  monthCanonicalFragmentMap(Mo),
 1552  ({var(Off)} -> "" ; timezoneCanonicalFragmentMap(Off)).
 gMonthDayCanonicalMap(+DT)// is det
Maps a gMonthDay value to a gMonthDayLexicalRep//1.

Arguments

Arguments:
DT- A complete gMonthDay value.

Algorithm

Let MD be:

  • '--' &
  • monthCanonicalFragmentMap(md's month) &
  • '-' &
  • dayCanonicalFragmentMap(md's day)

Return:

  • MD, when md's timezoneOffset is absent
  • MD & timezoneCanonicalFragmentMap(md's timezoneOffset), otherwise
 1583gMonthDayCanonicalMap(date_time(_,Mo,D,_,_,_,Off)) -->
 1584  "--",
 1585  monthCanonicalFragmentMap(Mo),
 1586  "-",
 1587  dayCanonicalFragmentMap(D),
 1588  ({var(Off)} -> "" ; timezoneCanonicalFragmentMap(Off)).
 gMonthDayLexicalMap(-DT)// is det
Maps a gMonthDayLexicalRep//1 to a gMonthDay value.

Arguments

Arguments:
DT- A complete gMonthDay value.

Algorithm

LEX necessarily includes an instance M of monthFrag//1 and an instance D of dayFrag//1, hyphen-separated and optionally followed by an instance T of timezoneFrag//1.

Let tz be timezoneFragValue(T) when T is present, otherwise absent.

Return newDateTime(absent, monthFragValue(M), dayFragValue(D), absent, absent, absent, tz)

 1611gMonthDayLexicalMap(DT) -->
 1612  "--",
 1613  monthFragValue(Mo),
 1614  "-",
 1615  dayFragValue(D),
 1616  ?(timezoneFragValue, Off),
 1617  {newDateTime(_, Mo, D, _, _, _, Off, DT)}.
 gMonthLexicalMap(-DT)// is det
Maps a gMonthLexicalRep//1 to a gMonth value.

Arguments

Arguments:
Date- A complete gMonth value.

Algorithm

LEX necessarily includes an instance M of monthFrag//1, optionally followed by an instance T of timezoneFrag//1.

Let tz be timezoneFragValue(T) when T is present, otherwise absent.

Return newDateTime(absent, monthFragValue(M), absent, absent, absent, absent, tz)

 1639gMonthLexicalMap(DT) -->
 1640  "--",
 1641  monthFragValue(Mo),
 1642  ?(timezoneFragValue, Off),
 1643  {newDateTime(_, Mo, _, _, _, _, Off, DT)}.
 gYearCanonicalMap(+DT)// is det
Maps a gYear value to a gYearLexicalRep//1.

Arguments

Arguments:
DT- A complete gYear value.

Algorithm

Return:

  • yearCanonicalFragmentMap(gY's year), when gY's timezoneOffset is absent
  • yearCanonicalFragmentMap(gY's year) & timezoneCanonicalFragmentMap(gY's timezoneOffset), otherwise
 1665gYearCanonicalMap(date_time(Y,_,_,_,_,_,Off)) -->
 1666  yearCanonicalFragmentMap(Y),
 1667  ({var(Off)} -> "" ; timezoneCanonicalFragmentMap(Off)).
 gYearLexicalMap(-DT)// is det
Maps a gYearLexicalRep//1 to a gYear value.

Arguments

Arguments:
DT- A complete gYear value.

Algorithm

LEX necessarily includes an instance Y of yearFrag//1, optionally followed by an instance T of timezoneFrag//1.

Let tz be timezoneFragValue(T) when T is present, otherwise absent.

Return newDateTime(yearFragValue(Y), absent, absent, absent, absent, absent, tz)

 1689gYearLexicalMap(DT) -->
 1690  yearFragValue(Y),
 1691  ?(timezoneFragValue, Off),
 1692  {newDateTime(Y, _, _, _, _, _, Off, DT)}.
 gYearMonthCanonicalMap(+DT)// is det
Maps a gYearMonth value to a gYearMonthLexicalRep//1.

Arguments

Arguments:
YM- A complete gYearMonth value.

Algorithm

Let YM be

  • yearCanonicalFragmentMap(ym's year) &
  • '-' &
  • monthCanonicalFragmentMap(ym's month)

Return:

  • YM, when ym's timezoneOffset is absent
  • YM & timezoneCanonicalFragmentMap(ym's timezoneOffset), otherwise
 1721gYearMonthCanonicalMap(date_time(Y,Mo,_,_,_,_,Off)) -->
 1722  yearCanonicalFragmentMap(Y),
 1723  "-",
 1724  monthCanonicalFragmentMap(Mo),
 1725  ({var(Off)} -> "" ; timezoneCanonicalFragmentMap(Off)).
 gYearMonthLexicalMap(-DT)// is det
Maps a gYearMonthLexicalRep//1 to a gYearMonth value.

Arguments

Arguments:
Date- A complete gYearMonth value.

Algorithm

LEX necessarily includes:

Let tz be timezoneFragValue(T) when T is present, otherwise absent.

Return newDateTime(yearFragValue(Y), monthFragValue(M), absent, absent, absent, absent, tz)

 1752gYearMonthLexicalMap(DT) -->
 1753  yearFragValue(Y),
 1754  "-",
 1755  monthFragValue(Mo),
 1756  ?(timezoneFragValue, Off),
 1757  {newDateTime(Y, Mo, _, _, _, _, Off, DT)}.
 hourCanonicalFragmentMap(+H:between(0,99))// is det
Maps an integer, presumably the hour property of a date/timeSevenPropertyModel value, onto a hourFrag//1, part of a date/timeSevenPropertyModel's lexical representation.

Arguments

Arguments:
Hour- An integer between 0 and 23 inclusive.

Algorithm

Return unsTwoDigitCanonicalFragmentMap(h).

 1775hourCanonicalFragmentMap(H) -->
 1776  {between(0, 23, H)},
 1777  unsTwoDigitCanonicalFragmentMap(H).
 hourFragValue(-Hour:between(0,23))// is det
Maps a hourFrag//1, part of a date/timeSevenPropertyModel's lexical representation, onto an integer, presumably the hour property of a date/timeSevenPropertyModel value.

Arguments

Arguments:
Hour- An integer.

Algorithm

Return unsignedNoDecimalMap(HR).

bug
- This is more generic than the grammar, which requires the hour to consist of exactly two digits.
 1798hourFragValue(Hour) -->
 1799  #(2, digit_weight, Ns),
 1800  {
 1801    integer_weights(Hour, Ns),
 1802    must_be(between(0, 23), Hour)
 1803  }.
 lastSignificantDigit(+Sequence:list(nonneg), -Last:nonneg) is det
Maps a sequence of nonnegative integers to the index of the last non-zero term (when reading from left to right).

This is zero iff the sequence consists of only zeros. This is a non-zero, count-by-1 index into Seq otherwise.

Arguments

Arguments:
Sequence- Aa sequence of nonnegative integers.
Index- A nonnegative integer.

Algorithm

For a sequence of nonnegative integers s, return the smallest nonnegative integer j such that `s(i)_{j+1} = 0`.

 1826lastSignificantDigit(Seq, J):-
 1827  nth0(J, Seq, N),
 1828  N =:= 0, !.
 minuteCanonicalFragmentMap(+Mi:between(0,99))// is det
Maps an integer, presumably the minute property of a date/timeSevenPropertyModel value, onto a minuteFrag//1, part of a date/timeSevenPropertyModel's lexical representation.

Arguments

Arguments:
Minute- An integer between 0 and 59 inclusive.

Algorithm

Return unsTwoDigitCanonicalFragmentMap(m).

 1846minuteCanonicalFragmentMap(Mi) -->
 1847  {between(0, 59, Mi)},
 1848  unsTwoDigitCanonicalFragmentMap(Mi).
 minuteFragValue(-Minute:between(0,59))// is det
Maps a minuteFrag//1, part of a date/timeSevenPropertyModel's lexical representation, onto an integer, presumably the minute property of a date/timeSevenPropertyModel value.

Arguments

Arguments:
Minute- An integer.

Algorithm

Return unsignedNoDecimalMap(MI).

 1866minuteFragValue(Minute) -->
 1867  #(2, digit_weight, Ns),
 1868  {
 1869    integer_weights(Minute, Ns),
 1870    must_be(between(0, 59), Minute)
 1871  }.
 monthCanonicalFragmentMap(+Mo:between(0,99))// is det
Maps an integer, presumably the month property of a date/timeSevenPropertyModel value, onto a monthFrag//1, part of a date/timeSevenPropertyModel's lexical representation.

Arguments

Arguments:
Month- An integer between 1 and 12 inclusive.

Algorithm

Return unsTwoDigitCanonicalFragmentMap(m).

 1889monthCanonicalFragmentMap(Mo) -->
 1890  {between(1, 12, Mo)},
 1891  unsTwoDigitCanonicalFragmentMap(Mo).
 monthFragValue(-Month:between(1,12))// is det
Maps a monthFrag//1, part of a date/timeSevenPropertyModel's lexical representation, onto an integer, presumably the month property of a date/timeSevenPropertyModel value.

Arguments

Arguments:
Month- An integer.

Algorithm

Return unsignedNoDecimalMap(MO).

 1909monthFragValue(Month) -->
 1910  #(2, digit_weight, Ns),
 1911  {
 1912    integer_weights(Month, Ns),
 1913    must_be(between(1, 12), Month)
 1914  }.
 newDateTime(?Y, ?Mo:between(1,12), ?D:between(1,31), ?H:between(0,24), ?Mi:between(0,59), ?S:rational, ?Off:between(-840,840), -DT) is det
Returns an instance of the date/timeSevenPropertyModel with property values as specified in the arguments. If an argument is omitted, the corresponding property is set to absent.

Arguments

Arguments:
Y- An optional integer.
Mo- An optional integer between 1 and 12 inclusive.
D- An optional integer between 1 and 31 inclusive.
H- An optional integer between 0 and 24 inclusive.
Mi- An optional integer between 0 and 59 inclusive.
S- An optional decimal number greater than or equal to 0 and less than 60.
Off- An optional integer between -840 and 840 inclusive.

Algorithm

Let:

  • dt be an instance of the date/timeSevenPropertyModel.
  • yr be Yr when Yr is not absent, otherwise 1.
  • mo be Mo when Mo is not absent, otherwise 1.
  • da be Da when Da is not absent, otherwise 1.
  • hr be Hr when Hr is not absent, otherwise 0.
  • mi be Mi when Mi is not absent, otherwise 0.
  • se be Se when Se is not absent, otherwise 0.

Steps:

  • normalizeSecond(yr, mo, da, hr, mi, se)
  • Set the year property of dt to absent when Yr is absent, otherwise yr.
  • Set the month property of dt to absent when Mo is absent, otherwise mo.
  • Set the day property of dt to absent when Da is absent, otherwise da.
  • Set the hour property of dt to absent when Hr is absent, otherwise hr.
  • Set the minute property of dt to absent when Mi is absent, otherwise mi.
  • Set the second property of dt to absent when Se is absent, otherwise se.
  • Set the timezoneOffset property of dt to Tz.
  • Return dt.
To be done
- Add type checking.
 1996newDateTime(
 1997  Y1, Mo1, D1, H1, Mi1, S1, Off,
 1998  date_time(Y4,Mo4,D4,H4,Mi4,S4,Off)
 1999) :-
 2000  % Set the values that are used for performing the normalization.
 2001  default_value(Y1, 1, Y2),
 2002  default_value(Mo1, 1, Mo2),
 2003  default_value(D1, 1, D2),
 2004  default_value(H1, 0, H2),
 2005  default_value(Mi1, 0, Mi2),
 2006  default_value(S1, 0, S2),
 2007  normalizeSecond(Y2, Mo2, D2, H2, Mi2, S2, Y3, Mo3, D3, H3, Mi3, S3),
 2008  % Variables stay variable.  Non-variables get the normalized value.
 2009  var_or_val(Y1, Y3, Y4),
 2010  var_or_val(Mo1, Mo3, Mo4),
 2011  var_or_val(D1, D3, D4),
 2012  var_or_val(H1, H3, H4),
 2013  var_or_val(Mi1, Mi3, Mi4),
 2014  var_or_val(S1, S3, S4).
 noDecimalMap(-Integer:integer)//
Maps an noDecimalPtNumeral//1 to its numerical value.

Arguments

Arguments:
Integer- An integer.

Algorithm

N necessarily consists of an optional sign ('+' or '-') and then a literal U that matches unsignedNoDecimalPtNumeral//1.

Return:

  • `-1 × unsignedNoDecimalMap(U)`, when '-' is present
  • unsignedNoDecimalMap(U), otherwise.
 2035noDecimalMap(N) -->
 2036  ("-" -> {Sg = -1} ; "+" -> {Sg = 1} ; {Sg = 1}),
 2037  unsignedNoDecimalMap(N0),
 2038  {N is copysign(N0, Sg)}.
 noDecimalPtCanonicalMap(+Integer:integer)// is det
Maps an integer to a noDecimalPtNumeral//1, its canonical representation.

Arguments

Arguments:
Integer- An integer.

Algorithm

For a given integer i, return:

  • '-' & unsignedNoDecimalPtCanonicalMap(-i), when i is negative
  • unsignedNoDecimalPtCanonicalMap(i), otherwise.
 2060noDecimalPtCanonicalMap(N) -->
 2061  {N < 0}, !,
 2062  "-",
 2063  {N0 is abs(N)},
 2064  unsignedNoDecimalPtCanonicalMap(N0).
 2065noDecimalPtCanonicalMap(N) -->
 2066  unsignedNoDecimalPtCanonicalMap(N).
 normalizeDay(+Y, +Mo, +D, -NormY, -NormMo, -NormD) is det
If month (mo) is out of range, or day (da) is out of range for the appropriate month, then adjust values accordingly, otherwise make no change.

Algorithm

To be done
- It is unclear what "from the table to" means.
 2101normalizeDay(Y1, Mo1, D1, Y3, Mo3, D3):-
 2102  normalizeMonth(Y1, Mo1, Y2, Mo2),
 2103  normalizeDay0(Y2, Mo2, D1, Y3, Mo3, D3).
 2104
 2105
 2106normalizeDay0(Y1, Mo1, D1, Y3, Mo3, D3):-
 2107  daysInMonth(Y1, Mo1, D1_max),
 2108  (   D1 > D1_max
 2109  ->  D2 is D1 - D1_max,
 2110      Mo1_succ is Mo1 + 1,
 2111      normalizeMonth(Y1, Mo1_succ, Y2, Mo2),
 2112      normalizeDay0(Y2, Mo2, D2, Y3, Mo3, D3)
 2113  ;   D1 < 0
 2114  ->  Mo1_pred is Mo1 - 1,
 2115      normalizeMonth(Y1, Mo1_pred, Y2, Mo2),
 2116      daysInMonth(Y2, Mo2, D1_max),
 2117      D2 is D1 + D1_max,
 2118      normalizeDay0(Y2, Mo2, D2, Y3, Mo3, D3)
 2119  ;   Y3 = Y1,
 2120      Mo3 = Mo1,
 2121      D3 = D1
 2122  ).
 normalizeMinute(+Y, +Mo, +D, +H, +Mi, -NormY, -NormMo, -NormD, -NormH, -NormMi) is det
Normalizes minute, hour, month, and year values to values that obey the appropriate constraints.

Algorithm:

 2146normalizeMinute(Y1, Mo1, D1, H1, Mi1, Y2, Mo2, D2, H2, Mi2):-
 2147  H1a is H1 + Mi1 xsd_div 60,
 2148  Mi2 is      Mi1 xsd_mod 60,
 2149  D1a is D1 + H1a xsd_div 24,
 2150  H2  is      H1a xsd_mod 24,
 2151  normalizeDay(Y1, Mo1, D1a, Y2, Mo2, D2).
 normalizeMonth(+Y, +Mo, -NormY, -NormMo) is det
If month (mo) is out of range, adjust month and year (yr) accordingly; otherwise, make no change.

Algorithm

 2166normalizeMonth(Y1, Mo1, Y2, Mo2):-
 2167  % Add (mo - 1) div 12 to yr.
 2168  Y2 is Y1 + (Mo1 - 1) xsd_div 12,
 2169  % Set mo to (mo - 1) mod 12 + 1.
 2170  Mo2 is (Mo1 - 1) xsd_mod 12 + 1.
 normalizeSecond(+Y, +Mo, +D, +H, +Mi, +S:rational, -NormY, -NormMo, -NormD, -NormH, -NormMi, -NormS:rational) is det
Normalizes second, minute, hour, month, and year values to values that obey the appropriate constraints (ignoring leap seconds).

Algorithm:

 2190normalizeSecond(Y1, Mo1, D1, H1, Mi1, S1, Y2, Mo2, D2, H2, Mi2, S2):-
 2191  Mi0 is Mi1 + S1 xsd_div 60,
 2192  S2 is S1 xsd_mod 60,
 2193  normalizeMinute(Y1, Mo1, D1, H1, Mi0, Y2, Mo2, D2, H2, Mi2).
 scientificCanonicalMap(+Decimal:rational)// is det
Maps a decimal number to a scientificNotationNumeral//1, its canonical representation.

Arguments

Arguments:
Decimal- A decimal number.

Algorithm

Return:

  • '-' & unsignedScientificCanonicalMap(-n), when n is negative
  • unsignedScientificCanonicalMap(i), otherwise
To be done
- i should be n.
 2219scientificCanonicalMap(N) -->
 2220  {N < 0}, !,
 2221  "-",
 2222  {N0 is abs(N)},
 2223  unsignedScientificCanonicalMap(N0).
 2224scientificCanonicalMap(N) -->
 2225  unsignedScientificCanonicalMap(N).
 scientificMap(-Decimal:rational)// is det
Maps a scientificNotationNumeral//1 to its numerical value.

Arguments

Arguments:
Decimal- A decimal number.

Algorithm

N necessarily consists of an instance C of:

Return:

  • `decimalPtMap(C) × 10 ^ unsignedDecimalPtMap(E)`, when a '.' is present in N, and
  • `noDecimalMap(C) × 10 ^ unsignedDecimalPtMap(E)`, otherwise.
 2254scientificMap(N) -->
 2255  (decimalPtMap(C) -> "" ; noDecimalMap(C)),
 2256  ("e" -> "" ; "E"),
 2257  noDecimalMap(E),
 2258  {N is C * 10 ^ E}.
 secondCanonicalFragmentMap(+S:rational)// is det
Maps a decimal number, presumably the second property of a date/timeSevenPropertyModel value, onto a secondFrag//1, part of a date/timeSevenPropertyModel's lexical representation.

Arguments

Arguments:
Second- A nonnegative decimal number less than 70.

Algorithm

Return:

  • unsTwoDigitCanonicalFragmentMap(s), when s is an integer
  • unsTwoDigitCanonicalFragmentMap(s div 1) & '.' & fractionDigitsCanonicalFragmentMap(s mod 1), otherwise
 2281secondCanonicalFragmentMap(S) -->
 2282  {integer(S)}, !,
 2283  unsTwoDigitCanonicalFragmentMap(S).
 2284secondCanonicalFragmentMap(S) -->
 2285  {
 2286    I is S xsd_div 1,
 2287    between(0, 59, I)
 2288  },
 2289  unsTwoDigitCanonicalFragmentMap(I),
 2290  ".",
 2291  {Frac is S xsd_mod 1},
 2292  fractionDigitsCanonicalFragmentMap(Frac).
 secondFragValue(-S:rational)// is det
Maps a secondFrag//1, part of a date/timeSevenPropertyModel's lexical representation, onto a decimal number, presumably the second property of a date/timeSevenPropertyModel value.

Arguments

Arguments:
Second- A decimal number.

Algorithm

Return:

  • unsignedNoDecimalMap(SE), when no decimal point occurs in SE
  • unsignedDecimalPtMap(SE) otherwise
 2314secondFragValue(S) -->
 2315  unsignedDecimalPtMap(S), !,
 2316  {
 2317    0 =< S,
 2318    S < 60
 2319  }.
 2320secondFragValue(S) -->
 2321  unsignedNoDecimalMap(S),
 2322  {between(0, 59, S)}.
 specialRepCanonicalMap(+SpecialValue:atom)// is det
Maps the special values used with some numerical datatypes to their canonical representations.

Arguments

Arguments:
SpecialValue- One of positiveInfinity, negativeInfinity, and notANumber.

Algorithm

Return:

  • 'INF', when c is positiveInfinity
  • '-INF', when c is negativeInfinity
  • 'NaN', when c is notANumber
 2346specialRepCanonicalMap(positiveInfinity) --> !, "INF".
 2347specialRepCanonicalMap(negativeInfinity) --> !, "-INF".
 2348specialRepCanonicalMap(notANumber) --> "NaN".
 specialRepValue(-SpecialValue:atom)// is det
Maps the lexical representations of special values used with some numerical datatypes to those special values.

Arguments

Arguments:
SpecialValue- One of positiveInfinity, negativeInfinity, or notANumber.

Algorithm

Return:

  • positiveInfinity, when S is 'INF' or '+INF'
  • negativeInfinity, when S is '-INF'
  • notANumber, when S is 'NaN'
 2369specialRepValue(positiveInfinity) --> "INF",  !.
 2370specialRepValue(positiveInfinity) --> "+INF", !.
 2371specialRepValue(negativeInfinity) --> "-INF", !.
 2372specialRepValue(notANumber) --> "NaN".
 timeCanonicalMap(+DT)// is det
Maps a time value to a timeLexicalRep//1.

Arguments

Arguments:
Time- A complete time value.

Algorithm

Let T be:

  • hourCanonicalFragmentMap(ti's hour) &
  • ':' &
  • minuteCanonicalFragmentMap(ti's minute) &
  • ':' &
  • secondCanonicalFragmentMap(ti's second)

Return:

  • T, when ti's timezoneOffset is absent
  • T & timezoneCanonicalFragmentMap(ti's timezoneOffset), otherwise
 2404timeCanonicalMap(date_time(_,_,_,H,Mi,S,Off)) -->
 2405  hourCanonicalFragmentMap(H),
 2406  ":",
 2407  minuteCanonicalFragmentMap(Mi),
 2408  ":",
 2409  secondCanonicalFragmentMap(S),
 2410  ({var(Off)} -> "" ; timezoneCanonicalFragmentMap(Off)).
 timeLexicalMap(-DT)// is det
Maps a timeLexicalRep//1 to a time value.

Arguments

Arguments:
DT- A complete time value.

Algorithm

LEX necessarily includes:

Let tz be timezoneFragValue(T) when T is present, otherwise absent.

Return:

  • newDateTime(absent, absent, absent, 0, 0, 0, tz), when endOfDayFrag//1 is present
  • newDateTime(absent, absent, absent, hourFragValue(H), minuteFragValue(MI), secondFragValue(S), tz), otherwise
 2445timeLexicalMap(DT) -->
 2446  (   hourFragValue(H),
 2447      ":",
 2448      minuteFragValue(Mi),
 2449      ":",
 2450      secondFragValue(S)
 2451  ;   endOfDayFrag(H, Mi, S)
 2452  ), !,
 2453  ?(timezoneFragValue, Off),
 2454  {newDateTime(_, _, _, H, Mi, S, Off, DT)}.
 timeOnTimeline(+DT:dt, -Seconds:rational) is det
Maps an XSD-compatible 7-property model date/time representation (type dt) to the decimal number denoting its position on the time line in seconds.

Algorithm

Let:

Steps:

Compatibility
- /XML Schema 1.1: Datatypes/, appendix E.3.4 “Time on Timeline”.
 2510timeOnTimeline(dt(Y1,Mo1,D1,H,Mi1,S,Off), ToTl5) :-
 2511  % Let ‘yr’ be 1971 when dt's year is absent, and (dt's year)-1
 2512  % otherwise.
 2513  (var(Y1) -> Y2 = 1971 ; Y2 is Y1 - 1),
 2514  % Let ‘mo’ be 12 or (dt's month), similarly.
 2515  default_value(Mo1, Mo2, 12),
 2516  % Let ‘da’ be daysInMonth(yr+1,mo)-1 or (dt's day)-1, similarly.
 2517  Y3 is Y2 + 1,
 2518  (   var(D1)
 2519  ->  daysInMonth(Y3, Mo2, D3),
 2520      D2 is D3 - 1
 2521  ;   D2 is D1 - 1
 2522  ),
 2523  % Let ‘hr’ be 0 or (dt's hour), similarly.
 2524  default_value(H, 0),
 2525  % Let ‘mi’ be 0 or (dt's minute), similarly.
 2526  default_value(Mi1, 0),
 2527  % Let ‘se’ be 0 or (dt's second), similarly.
 2528  default_value(S, 0),
 2529  % Subtract ‘timezoneOffset’ from ‘mi’ when ‘timezoneOffset’ is not
 2530  % absent.
 2531  (var(Off) -> Mi2 = Mi1 ; Mi2 is Mi1 - Off),
 2532  % Set ToTl to 31536000 × yr.
 2533  ToTl1 is 31536000 * Y2,
 2534  % Leap-year, month, and day.
 2535  % Add 86400 ⨯ (yr div 400 - yr div 100 + yr div 4) to ToTl.
 2536  ToTl2 is ToTl1 + 86400 * ((Y2 xsd_div 400) - (Y2 xsd_div 100) + (Y2 xsd_div 4)),
 2537  % Add 86400 × Sum_{m < mo} daysInMonth(yr+1,m) to ToTl.
 2538  Mo3 is Mo2 - 1,
 2539  aggregate_all(
 2540    sum(D0),
 2541    (
 2542      between(1, Mo3, Mo0),
 2543      daysInMonth(Y3, Mo0, D0)
 2544    ),
 2545    DaysInMonth
 2546  ),
 2547  ToTl3 is ToTl2 + 86400 * DaysInMonth,
 2548  % Add 86400 ⨯ ‘da’ to ToTl.
 2549  ToTl4 is ToTl3 + 86400 * D2,
 2550  % Hour, minute, and second.
 2551  % Add 3600 ⨯ hr + 60 ⨯ mi + se to ToTl.
 2552  ToTl5 is ToTl4 + 3600 * H + 60 * Mi2 + S.
 timezoneCanonicalFragmentMap(+Off:between(-840,840))// is det
Maps an integer, presumably the timezoneOffset property of a date/timeSevenPropertyModel value, onto a timezoneFrag//1, part of a date/timeSevenPropertyModel's lexical representation.

Arguments

Arguments:
Off- An integer between -840 and 840 inclusive.

Algorithm

Return:

  • 'Z', when t is zero
  • '-' & unsTwoDigitCanonicalFragmentMap(-t div 60) & ':' & unsTwoDigitCanonicalFragmentMap(-t mod 60), when t is negative
  • '+' & unsTwoDigitCanonicalFragmentMap(t div 60) & ':' & unsTwoDigitCanonicalFragmentMap(t mod 60), otherwise
 2578timezoneCanonicalFragmentMap(0) --> !,
 2579  "Z".
 2580timezoneCanonicalFragmentMap(Off) -->
 2581  ({Off < 0} -> "-", {OffAbs is abs(Off)} ; "+", {OffAbs = Off}),
 2582  {H is OffAbs xsd_div 60},
 2583  unsTwoDigitCanonicalFragmentMap(H),
 2584  ":",
 2585  {Mi is OffAbs xsd_mod 60},
 2586  unsTwoDigitCanonicalFragmentMap(Mi).
 timezoneFragValue(-Off:integer)// is det
Maps a timezoneFrag//1, part of a date/timeSevenPropertyModel's lexical representation, onto an integer, presumably the timezoneOffset property of a date/timeSevenPropertyModel value.

Arguments

Arguments:
Timezone- An integer.

Algorithm

TZ necessarily consists of either:

  • just 'Z', or
  • a sign ('+' or '-') followed by an instance H of hourFrag//1, a colon, and an instance M of minuteFrag//1.

Return:

  • 0, when TZ is 'Z'
  • -(unsignedDecimalPtMap(H) × 60 + unsignedDecimalPtMap(M)), when the sign is '-'
  • unsignedDecimalPtMap(H) × 60 + unsignedDecimalPtMap(M), otherwise
 2619timezoneFragValue(0) -->
 2620  "Z", !.
 2621timezoneFragValue(Off) -->
 2622  ("-" -> {Sg = -1} ; "+" -> {Sg = 1}),
 2623  hourFragValue(H),
 2624  ":",
 2625  minuteFragValue(Mi),
 2626  {Off is copysign(H * 60 + Mi, Sg)}.
 unsignedDecimalPtCanonicalMap(+Decimal:rational)// is det
Maps a nonnegative decimal number to a unsignedDecimalPtNumeral//1, its canonical representation.

Arguments

Arguments:
Decimal- A nonnegative decimal number.

Algorithm

Return:

  • unsignedNoDecimalPtCanonicalMap(n div 1) &
  • '.' &
  • fractionDigitsCanonicalFragmentMap(n mod 1)
 2646unsignedDecimalPtCanonicalMap(N) -->
 2647  {N1 is N xsd_div 1},
 2648  unsignedNoDecimalPtCanonicalMap(N1),
 2649  ".",
 2650  {N2 is N xsd_mod 1},
 2651  ({N2 =:= 0} -> "" ; fractionDigitsCanonicalFragmentMap(N2)).
 unsignedDecimalPtMap(-Decimal:rational)// is det
Maps an unsignedDecimalPtNumeral//1 to its numerical value.

Arguments

Arguments:
Decimal- A nonnegative decimal number.

Algorithm

D necessarily consists of an optional literal N matching:

Return:

  • unsignedNoDecimalMap(N), when F is not present.
  • fractionFragValue(F), when N is not present.
  • unsignedNoDecimalMap(N) + fractionFragValue(F), otherwise.
 2681unsignedDecimalPtMap(N) -->
 2682  unsignedNoDecimalMap(I), !,
 2683  ".",
 2684  (fractionFragValue(F) -> {N is I + F} ; {N is I}).
 2685unsignedDecimalPtMap(N) -->
 2686  ".",
 2687  fractionFragValue(N).
 unsignedNoDecimalMap(-Integer:nonneg)// is det
The parser for unsignedNoDecimalPtNumeral//1

Maps an unsignedNoDecimalPtNumeral//1 to its numerical value.

Arguments

Arguments:
Integer- A nonnegative integer.

Algorithm

N is the left-to-right concatenation of a finite sequence S of literals, each term matching digit//1.

Return digitSequenceValue(S).

 2708unsignedNoDecimalMap(N) -->
 2709  '*!'(digit_weight, Ds), !,
 2710  {digitSequenceValue(Ds, N)}.
 unsignedNoDecimalPtCanonicalMap(+Integer:nonneg)// is det
Maps a nonnegative integer to a unsignedNoDecimalPtNumeral//1, its canonical representation.

Arguments

Arguments:
Integer- A nonnegative integer.

Algorithm

Given an integer i, return:

  • `digit(digitSeq(i)_{lastSignificantDigit(digitRemainderSeq(i))})`
  • & ... &
  • `digit(digitSeq(i)_0)`

Note that the concatenation is in reverse order.

 2732unsignedNoDecimalPtCanonicalMap(N) -->
 2733  {
 2734    digitRemainderSeq(N, RemainderSeq),
 2735    lastSignificantDigit(RemainderSeq, Last),
 2736    digitSeq(N, Seq),
 2737    % Count-by-1.
 2738    length(Ds0, Last),
 2739    prefix(Ds0, Seq),
 2740    reverse(Ds0, Ds)
 2741  },
 2742  ({Ds == []} -> "0" ; '+!'(digit_weight, Ds), !).
 unsignedScientificCanonicalMap(+Decimal:rational)// is det
Maps a nonnegative decimal number to a unsignedScientificNotationNumeral//1, its canonical representation.

Arguments

Arguments:
Decimal- A nonnegative decimal number.

Algorithm

Return

  • unsignedDecimalPtCanonicalMap(n / 10^{log(n) div 1}) &
  • 'E' &
  • noDecimalPtCanonicalMap(log(n) div 1)
 2762unsignedScientificCanonicalMap(N) -->
 2763  {(  N =:= 0
 2764  ->  N1 = 0
 2765  ;   N1 is rationalize(N / 10 ^ (log10(N) xsd_div 1))
 2766  )},
 2767  unsignedDecimalPtCanonicalMap(N1),
 2768  "E",
 2769  {(N =:= 0 -> N2 = 0 ; N2 is rationalize(log10(N) xsd_div 1))},
 2770  noDecimalPtCanonicalMap(N2).
 unsTwoDigitCanonicalFragmentMap(+Integer:between(0,99))// is det
Maps a nonnegative integer less than 100 onto an unsigned always-two-digit numeral.

Arguments

Arguments:
Integer- A nonnegative integer less than 100

Algorithm

Return digit(i div 10) & digit(i mod 10).

 2787unsTwoDigitCanonicalFragmentMap(N) -->
 2788  {N1 is N xsd_div 10},
 2789  digit_weight(N1),
 2790  {N2 is N xsd_mod 10},
 2791  digit_weight(N2).
 yearCanonicalFragmentMap(+Y)// is det
Maps an integer, presumably the year property of a date/timeSevenPropertyModel value, onto a yearFrag//1, part of a date/timeSevenPropertyModel's lexical representation.

Arguments

Arguments:
Year- An integer.

Algorithm

Return:

  • noDecimalPtCanonicalMap(y), when |y| > 9999
  • fourDigitCanonicalFragmentMap(y), otherwise
 2813yearCanonicalFragmentMap(Y) -->
 2814  {abs(Y) > 9999}, !,
 2815  noDecimalPtCanonicalMap(Y).
 2816yearCanonicalFragmentMap(Y) -->
 2817  fourDigitCanonicalFragmentMap(Y).
 yearFragValue(-Y)// is det
Maps a yearFrag//1, part of a date/timeSevenPropertyModel's lexical representation, onto an integer, presumably the year property of a date/timeSevenPropertyModel value.

Arguments

Arguments:
Year- An integer.

Algorithm

Return noDecimalMap(YR).

 2835yearFragValue(YR) -->
 2836  noDecimalMap(YR).
 yearMonthDurationCanonicalMap(+Duration)// is det
Maps a yearMonthDuration's months value to a yearMonthDurationLexicalRep//1. (The seconds value is necessarily zero and is ignored.) yearMonthDurationCanonicalMap//1 is a restriction of durationCanonicalMap//1.

Arguments

Arguments:
Duration- A complete yearMonthDuration value.

Algorithm

Let:

  • m be ym's months
  • sgn be '-' if m is negative and the empty string ('') otherwise.

Return sgn & 'P' & duYearMonthCanonicalFragmentMap(|m|)

 2861yearMonthDurationCanonicalMap(duration(Mo,0)) -->
 2862  ({Mo < 0} -> "-", {MoAbs = -Mo} ; {MoAbs = Mo}),
 2863  "P",
 2864  duYearMonthCanonicalFragmentMap(MoAbs).
 yearMonthDurationMap(-Duration)// is det
Maps the lexical representation into the months of a yearMonthDuration value. (A yearMonthDuration's seconds is always zero.) yearMonthDurationMap//1 is a restriction of durationMap//1.

Arguments

Arguments:
Duration- A complete yearMonthDuration value.

Algorithm

YM necessarily consists of

Return a yearMonthDuration whose:

  • months value is:
    • -duYearMonthFragmentMap(Y), if '-' is present in YM
    • duYearMonthFragmentMap(Y), otherwise
  • seconds value is (necessarily) 0
 2898yearMonthDurationMap(duration(Mo,0)) -->
 2899  ("-" -> {Sg = -1} ; {Sg = 1}),
 2900  "P", duYearMonthFragmentMap(Moabs),
 2901  {Mo is Sg * Moabs}.
 2902
 2903
 2904
 2905
 2906
 2907% HELPERS %
 var_or_val(+Arg, +Val, -VarOrVal) is det
Makes sure that a variable Arg returns a fresh variable, and that a non-variable Arg returns Val.
 2914var_or_val(Arg, _, _):-
 2915  var(Arg), !.
 2916var_or_val(_, Val, Val)