package org.basex.query.item; import static org.basex.query.util.Err.*; import java.math.BigDecimal; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.Duration; import javax.xml.datatype.XMLGregorianCalendar; import org.basex.query.QueryException; import org.basex.util.InputInfo; import org.basex.util.Token; import org.basex.util.Util; /** * Date container. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public abstract class Date extends Item { /** Date pattern. */ static final String ZONE = "((\\+|-)([0-9]{2}):([0-9]{2})|Z)?"; /** Day per months. */ static final byte[] DAYS = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; /** Date pattern. */ private static final Pattern DAT = Pattern.compile( "(-?)([0-9]{4})-([0-9]{2})-([0-9]{2})" + ZONE); /** Time pattern. */ private static final Pattern TIM = Pattern.compile( "([0-9]{2}):([0-9]{2}):([0-9]{2})(\\.([0-9]+))?" + ZONE); /** Data factory. */ public static DatatypeFactory df; /** Calendar instance. */ public XMLGregorianCalendar xc; static { try { df = DatatypeFactory.newInstance(); } catch(final Exception ex) { Util.notexpected(); } } /** * Constructor. * @param typ data type * @param d date reference */ Date(final Type typ, final Date d) { super(typ); xc = (XMLGregorianCalendar) d.xc.clone(); } /** * Constructor. * @param typ data type * @param d date reference * @param e expected format * @param ii input info * @throws QueryException query exception */ Date(final Type typ, final byte[] d, final String e, final InputInfo ii) throws QueryException { super(typ); try { xc = df.newXMLGregorianCalendar(Token.string(d).trim()); if(xc.getHour() == 24) xc.add(df.newDuration(0)); } catch(final IllegalArgumentException ex) { throw dateErr(d, e, ii); } } /** * Checks the date format. * @param d input format * @param e expected format * @param ii input info * @throws QueryException query exception */ final void date(final byte[] d, final String e, final InputInfo ii) throws QueryException { final Matcher mt = DAT.matcher(Token.string(d).trim()); if(!mt.matches()) dateErr(d, e, ii); zone(mt, 5, d, ii); } /** * Checks the time format. * @param d input format * @param e expected format * @param ii input info * @throws QueryException query exception */ final void time(final byte[] d, final String e, final InputInfo ii) throws QueryException { final Matcher mt = TIM.matcher(Token.string(d).trim()); if(!mt.matches()) dateErr(d, e, ii); final int h = Token.toInt(mt.group(1)); final int s = Token.toInt(mt.group(3)); if(s > 59) DATERANGE.thrw(ii, type, d); final double ms = mt.group(4) != null ? Double.parseDouble(mt.group(4)) : 0; if(h == 24 && ms > 0) dateErr(d, e, ii); zone(mt, 6, d, ii); } /** * Evaluates the timezone. * @param mt matcher * @param p matching position * @param val value * @param ii input info * @throws QueryException query exception */ static final void zone(final Matcher mt, final int p, final byte[] val, final InputInfo ii) throws QueryException { if(mt.group(p) == null || mt.group(p).equals("Z")) return; final int th = Token.toInt(mt.group(p + 2)); final int tm = Token.toInt(mt.group(p + 3)); if(th > 14 || tm > 59 || th == 14 && tm != 0) INVALIDZONE.thrw(ii, val); } /** * Add/subtract the specified duration. * @param a duration * @param p plus/minus flag * @param ii input info * @throws QueryException query exception */ final void calc(final Dur a, final boolean p, final InputInfo ii) throws QueryException { if(xc.getYear() + a.mon / 12 > 9999) DATERANGE.thrw(ii, type, a.string(ii)); final Duration dur = a.toJava(); xc.add(p ? dur : dur.negate()); if(xc.getYear() == 0) xc.setYear(p ^ dur.getSign() < 0 ? 1 : -1); } @Override public final byte[] string(final InputInfo ii) { String str = xc.toXMLFormat(); str = str.replaceAll("\\.0+(Z|-.*|\\+.*)?$", "$1"); str = str.replaceAll("(\\.\\d+?)0+(Z|-.*|\\+.*)?$", "$1$2"); return Token.token(str); } @Override public final boolean eq(final InputInfo ii, final Item it) throws QueryException { final long d1 = days(); final Date d = (Date) (it.type.isDate() ? it : type.cast(it, null, ii)); final long d2 = d.days(); return d1 == d2 && seconds().doubleValue() == d.seconds().doubleValue(); } @Override public int diff(final InputInfo ii, final Item it) throws QueryException { final long d1 = days(); final Date d = (Date) (it.type.isDate() ? it : type.cast(it, null, ii)); final long d2 = d.days(); if(d1 != d2) return (int) (d1 - d2); return seconds().subtract(d.seconds()).signum(); } @Override public final XMLGregorianCalendar toJava() { return xc; } /** * Returns the date in seconds. * @return seconds */ final BigDecimal seconds() { final int h = xc.getHour() == UNDEF ? 0 : xc.getHour(); final int m = xc.getMinute() == UNDEF ? 0 : xc.getMinute(); final int s = xc.getSecond() == UNDEF ? 0 : xc.getSecond(); final int z = xc.getTimezone() == UNDEF ? 0 : xc.getTimezone(); BigDecimal bd = xc.getFractionalSecond(); if(bd == null) bd = BigDecimal.valueOf(0); return bd.add(BigDecimal.valueOf(h * 3600 + m * 60 - z * 60 + s)); } /** * Returns the number of days since AD. * @return days */ final long days() { final int y = xc.getYear() == UNDEF ? 0 : xc.getYear(); final int m = xc.getMonth() == UNDEF ? 0 : xc.getMonth() - 1; final int d = xc.getDay() == UNDEF ? 0 : xc.getDay() - 1; final long s = days(y, m, d); return y > 0 ? s : -s; } /** * Returns days per month, considering leap years. * @param y year * @param m month * @return days */ private static long dpm(final int y, final int m) { return DAYS[m] + (m == 1 ? leap(y) : 0); } /** * Adds an offset for a leap year. * @param y year * @return result of check */ private static int leap(final int y) { return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0) ? 1 : 0; } /** * Returns the number of days since AD for the specified years, * months and days. * @param y year * @param m month * @param d days * @return days */ public static long days(final int y, final int m, final int d) { long n = 0; final int yy = Math.abs(y); for(int i = 0; i < yy; ++i) n += 365 + leap(i); for(int i = 0; i < m; ++i) n += dpm(y, i); return n + d; } @Override public final String toString() { return Util.info("\"%\"", string(null)); } }