package com.thaiopensource.datatype.xsd;
import org.relaxng.datatype.ValidationContext;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Calendar;
class DurationDatatype extends RegexDatatype implements OrderRelation {
static private final String PATTERN =
"-?P([0-9]+Y)?([0-9]+M)?([0-9]+D)?(T([0-9]+H)?([0-9]+M)?(([0-9]+(\\.[0-9]*)?|\\.[0-9]+)S)?)?";
DurationDatatype() {
super(PATTERN);
}
public boolean lexicallyAllows(String str) {
if (!super.lexicallyAllows(str))
return false;
char last = str.charAt(str.length()-1);
// This enforces that there must be at least one component
// and that T is omitted if all time components are omitted
return last != 'P' && last != 'T';
}
String getLexicalSpaceKey() {
return "duration";
}
static private class Duration {
private final BigInteger years;
private final BigInteger months;
private final BigInteger days;
private final BigInteger hours;
private final BigInteger minutes;
private final BigDecimal seconds;
Duration(boolean negative,
BigInteger years, BigInteger months, BigInteger days,
BigInteger hours, BigInteger minutes, BigDecimal seconds) {
if (negative) {
this.years = years.negate();
this.months = months.negate();
this.days = days.negate();
this.hours = hours.negate();
this.minutes = minutes.negate();
this.seconds = seconds.negate();
}
else {
this.years = years;
this.months = months;
this.days = days;
this.hours = hours;
this.minutes = minutes;
this.seconds = seconds;
}
}
BigInteger getYears() {
return years;
}
BigInteger getMonths() {
return months;
}
BigInteger getDays() {
return days;
}
BigInteger getHours() {
return hours;
}
BigInteger getMinutes() {
return minutes;
}
BigDecimal getSeconds() {
return seconds;
}
public boolean equals(Object obj) {
if (!(obj instanceof Duration))
return false;
Duration other = (Duration)obj;
return (this.years.equals(other.years)
&& this.months.equals(other.months)
&& this.days.equals(other.days)
&& this.hours.equals(other.hours)
&& this.minutes.equals(other.minutes)
&& this.seconds.compareTo(other.seconds) == 0);
}
public int hashCode() {
return (years.hashCode()
^ months.hashCode()
^ days.hashCode()
^ hours.hashCode()
^ minutes.hashCode()
^ seconds.hashCode());
}
}
Object getValue(String str, ValidationContext vc) {
int t = str.indexOf('T');
if (t < 0)
t = str.length();
String date = str.substring(0, t);
String time = str.substring(t);
return new Duration(str.charAt(0) == '-',
getIntegerField(date, 'Y'),
getIntegerField(date, 'M'),
getIntegerField(date, 'D'),
getIntegerField(time, 'H'),
getIntegerField(time, 'M'),
getDecimalField(time, 'S'));
}
static private BigInteger getIntegerField(String str, char code) {
int end = str.indexOf(code);
if (end < 0)
return BigInteger.valueOf(0);
int start = end;
while (Character.isDigit(str.charAt(start - 1)))
--start;
return new BigInteger(str.substring(start, end));
}
static private BigDecimal getDecimalField(String str, char code) {
int end = str.indexOf(code);
if (end < 0)
return BigDecimal.valueOf(0);
int start = end;
while (!Character.isLetter(str.charAt(start - 1)))
--start;
return new BigDecimal(str.substring(start, end));
}
OrderRelation getOrderRelation() {
return this;
}
private static final int[] REF_YEAR_MONTHS = { 1696, 9, 1697, 2, 1903, 3, 1903, 7 };
public boolean isLessThan(Object obj1, Object obj2) {
Duration d1 = (Duration)obj1;
Duration d2 = (Duration)obj2;
BigInteger months1 = computeMonths(d1);
BigInteger months2 = computeMonths(d2);
BigDecimal seconds1 = computeSeconds(d1);
BigDecimal seconds2 = computeSeconds(d2);
switch (months1.compareTo(months2)) {
case -1:
if (seconds1.compareTo(seconds2) <= 0)
return true;
break;
case 0:
return seconds1.compareTo(seconds2) < 0;
case 1:
if (seconds1.compareTo(seconds2) >= 0)
return false;
break;
}
for (int i = 0; i < REF_YEAR_MONTHS.length; i += 2) {
BigDecimal total1 = daysPlusSeconds(computeDays(months1, REF_YEAR_MONTHS[i], REF_YEAR_MONTHS[i + 1]), seconds1);
BigDecimal total2 = daysPlusSeconds(computeDays(months2, REF_YEAR_MONTHS[i], REF_YEAR_MONTHS[i + 1]), seconds2);
if (total1.compareTo(total2) >= 0)
return false;
}
return true;
}
/**
* Returns the number of days spanned by a period of months starting with a particular
* reference year and month.
*/
private static BigInteger computeDays(BigInteger months, int refYear, int refMonth) {
switch (months.signum()) {
case 0:
return BigInteger.valueOf(0);
case -1:
return computeDays(months.negate(), refYear, refMonth).negate();
}
// Complete cycle of Gregorian calendar is 400 years
BigInteger[] tem = months.divideAndRemainder(BigInteger.valueOf(400*12));
--refMonth; // use 0 base to match Java
int total = 0;
for (int rem = tem[1].intValue(); rem > 0; rem--) {
total += daysInMonth(refYear, refMonth);
if (++refMonth == 12) {
refMonth = 0;
refYear++;
}
}
// In the Gregorian calendar, there are 97 (= 100 + 4 - 1) leap years every 400 years.
return tem[0].multiply(BigInteger.valueOf(365*400 + 97)).add(BigInteger.valueOf(total));
}
private static int daysInMonth(int year, int month) {
switch (month) {
case Calendar.SEPTEMBER:
case Calendar.APRIL:
case Calendar.JUNE:
case Calendar.NOVEMBER:
return 30;
case Calendar.FEBRUARY:
return isLeapYear(year) ? 29 : 28;
}
return 31;
}
private static boolean isLeapYear(int year) {
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}
/**
* Returns the total number of seconds from a specified number of days and seconds.
*/
private static BigDecimal daysPlusSeconds(BigInteger days, BigDecimal seconds) {
return seconds.add(new BigDecimal(days.multiply(BigInteger.valueOf(24*60*60))));
}
/**
* Returns the total number of months specified by the year and month fields of the duration
*/
private static BigInteger computeMonths(Duration d) {
return d.getYears().multiply(BigInteger.valueOf(12)).add(d.getMonths());
}
/**
* Returns the total number of seconds specified by the days, hours, minuts and seconds fields of
* the duration.
*/
private static BigDecimal computeSeconds(Duration d) {
return d.getSeconds().add(new BigDecimal(d.getDays().multiply(BigInteger.valueOf(24))
.add(d.getHours()).multiply(BigInteger.valueOf(60))
.add(d.getMinutes()).multiply(BigInteger.valueOf(60))));
}
public static void main(String[] args) {
DurationDatatype dt = new DurationDatatype();
System.err.println(dt.isLessThan(dt.getValue(args[0], null), dt.getValue(args[1], null)));
}
}