Example #1
0
 /**
  * Constructor.
  *
  * @param value value
  * @param type item type
  * @param ii input info
  * @throws QueryException query exception
  */
 private Dur(final byte[] value, final Type type, final InputInfo ii) throws QueryException {
   this(type);
   final String val = Token.string(value).trim();
   final Matcher mt = DUR.matcher(val);
   if (!mt.matches() || val.endsWith("P") || val.endsWith("T")) throw dateError(value, XDURR, ii);
   yearMonth(value, mt, ii);
   dayTime(value, mt, 6, ii);
 }
Example #2
0
  /**
   * Initializes the date format.
   *
   * @param d input
   * @param e example 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 = DATE.matcher(Token.string(d).trim());
    if (!mt.matches()) throw dateError(d, e, ii);
    yea = toLong(mt.group(1), false, ii);
    // +1 is added to BC values to simplify computations
    if (yea < 0) yea++;
    mon = (byte) (Strings.toInt(mt.group(3)) - 1);
    day = (byte) (Strings.toInt(mt.group(4)) - 1);

    if (mon < 0 || mon >= 12 || day < 0 || day >= dpm(yea, mon)) throw dateError(d, e, ii);
    if (yea <= MIN_YEAR || yea > MAX_YEAR) throw DATERANGE_X_X.get(ii, type, chop(d, ii));
    zone(mt, 5, d, ii);
  }
Example #3
0
  /**
   * Initializes 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 = TIME.matcher(Token.string(d).trim());
    if (!mt.matches()) throw dateError(d, e, ii);

    hou = (byte) Strings.toInt(mt.group(1));
    min = (byte) Strings.toInt(mt.group(2));
    sec = toDecimal(mt.group(3), false, ii);
    if (min >= 60
        || sec.compareTo(BD60) >= 0
        || hou > 24
        || hou == 24 && (min > 0 || sec.compareTo(BigDecimal.ZERO) > 0)) throw dateError(d, e, ii);
    zone(mt, 5, d, ii);
    if (hou == 24) {
      hou = 0;
      add(DAYSECONDS);
    }
  }
Example #4
0
/**
 * Duration item ({@code xs:duration}).
 *
 * @author BaseX Team 2005-16, BSD License
 * @author Christian Gruen
 */
public class Dur extends ADateDur {
  /** Pattern for one or more digits. */
  static final String DP = "(\\d+)";

  /** Date pattern. */
  private static final Pattern DUR =
      Pattern.compile(
          "(-?)P("
              + DP
              + "Y)?("
              + DP
              + "M)?("
              + DP
              + "D)?(T("
              + DP
              + "H)?("
              + DP
              + "M)?((\\d+|\\d*\\.\\d+)?S)?)?");

  /** Number of months. */
  long mon;

  /**
   * Constructor.
   *
   * @param value value
   * @param ii input info
   * @throws QueryException query exception
   */
  public Dur(final byte[] value, final InputInfo ii) throws QueryException {
    this(value, AtomType.DUR, ii);
  }

  /**
   * Constructor.
   *
   * @param type item type
   */
  Dur(final Type type) {
    super(type);
  }

  /**
   * Constructor.
   *
   * @param dur duration
   */
  public Dur(final Dur dur) {
    this(dur, AtomType.DUR);
  }

  /**
   * Constructor.
   *
   * @param dur duration
   * @param type item type
   */
  private Dur(final Dur dur, final Type type) {
    this(type);
    mon = dur.mon;
    sec = dur.sec == null ? BigDecimal.ZERO : dur.sec;
  }

  /**
   * Constructor.
   *
   * @param value value
   * @param type item type
   * @param ii input info
   * @throws QueryException query exception
   */
  private Dur(final byte[] value, final Type type, final InputInfo ii) throws QueryException {
    this(type);
    final String val = Token.string(value).trim();
    final Matcher mt = DUR.matcher(val);
    if (!mt.matches() || val.endsWith("P") || val.endsWith("T")) throw dateError(value, XDURR, ii);
    yearMonth(value, mt, ii);
    dayTime(value, mt, 6, ii);
  }

  /**
   * Initializes the yearMonth component.
   *
   * @param vl value
   * @param mt matcher
   * @param ii input info
   * @throws QueryException query exception
   */
  void yearMonth(final byte[] vl, final Matcher mt, final InputInfo ii) throws QueryException {
    final long y = mt.group(2) != null ? toLong(mt.group(3), true, ii) : 0;
    final long m = mt.group(4) != null ? toLong(mt.group(5), true, ii) : 0;
    mon = y * 12 + m;
    double v = y * 12d + m;
    if (!mt.group(1).isEmpty()) {
      mon = -mon;
      v = -v;
    }
    if (v <= Long.MIN_VALUE || v >= Long.MAX_VALUE) throw DURRANGE_X_X.get(ii, type, vl);
  }

  /**
   * Initializes the dayTime component.
   *
   * @param vl value
   * @param mt matcher
   * @param p first matching position
   * @param ii input info
   * @throws QueryException query exception
   */
  void dayTime(final byte[] vl, final Matcher mt, final int p, final InputInfo ii)
      throws QueryException {

    final long d = mt.group(p) != null ? toLong(mt.group(p + 1), true, ii) : 0;
    final long h = mt.group(p + 3) != null ? toLong(mt.group(p + 4), true, ii) : 0;
    final long m = mt.group(p + 5) != null ? toLong(mt.group(p + 6), true, ii) : 0;
    final BigDecimal s =
        mt.group(p + 7) != null ? toDecimal(mt.group(p + 8), true, ii) : BigDecimal.ZERO;
    sec =
        s.add(BigDecimal.valueOf(d).multiply(DAYSECONDS))
            .add(BigDecimal.valueOf(h).multiply(BD3600))
            .add(BigDecimal.valueOf(m).multiply(BD60));
    if (!mt.group(1).isEmpty()) sec = sec.negate();
    final double v = sec.doubleValue();
    if (v <= Long.MIN_VALUE || v >= Long.MAX_VALUE) throw DURRANGE_X_X.get(ii, type, vl);
  }

  @Override
  public final long yea() {
    return mon / 12;
  }

  @Override
  public final long mon() {
    return mon % 12;
  }

  @Override
  public final long day() {
    return sec.divideToIntegralValue(DAYSECONDS).longValue();
  }

  @Override
  public final long hou() {
    return tim() / 3600;
  }

  @Override
  public final long min() {
    return tim() % 3600 / 60;
  }

  @Override
  public final BigDecimal sec() {
    return sec.remainder(BD60);
  }

  /**
   * Returns the time.
   *
   * @return time
   */
  private long tim() {
    return sec.remainder(DAYSECONDS).longValue();
  }

  @Override
  public byte[] string(final InputInfo ii) {
    final TokenBuilder tb = new TokenBuilder();
    final int ss = sec.signum();
    if (mon < 0 || ss < 0) tb.add('-');
    date(tb);
    time(tb);
    if (mon == 0 && ss == 0) tb.add("T0S");
    return tb.finish();
  }

  /**
   * Adds the date to the specified token builder.
   *
   * @param tb token builder
   */
  final void date(final TokenBuilder tb) {
    tb.add('P');
    final long y = yea();
    if (y != 0) {
      tb.addLong(Math.abs(y));
      tb.add('Y');
    }
    final long m = mon();
    if (m != 0) {
      tb.addLong(Math.abs(m));
      tb.add('M');
    }
    final long d = day();
    if (d != 0) {
      tb.addLong(Math.abs(d));
      tb.add('D');
    }
  }

  /**
   * Adds the time to the specified token builder.
   *
   * @param tb token builder
   */
  final void time(final TokenBuilder tb) {
    if (sec.remainder(DAYSECONDS).signum() == 0) return;
    tb.add('T');
    final long h = hou();
    if (h != 0) {
      tb.addLong(Math.abs(h));
      tb.add('H');
    }
    final long m = min();
    if (m != 0) {
      tb.addLong(Math.abs(m));
      tb.add('M');
    }
    final BigDecimal sc = sec();
    if (sc.signum() == 0) return;
    tb.add(Token.chopNumber(Token.token(sc.abs().toPlainString()))).add('S');
  }

  @Override
  public final boolean eq(
      final Item it, final Collation coll, final StaticContext sc, final InputInfo ii)
      throws QueryException {
    final Dur d = (Dur) (it instanceof Dur ? it : type.cast(it, null, null, ii));
    final BigDecimal s1 = sec == null ? BigDecimal.ZERO : sec;
    final BigDecimal s2 = d.sec == null ? BigDecimal.ZERO : d.sec;
    return mon == d.mon && s1.compareTo(s2) == 0;
  }

  @Override
  public int diff(final Item it, final Collation coll, final InputInfo ii) throws QueryException {
    throw diffError(ii, it, this);
  }

  @Override
  public final Duration toJava() {
    return ADate.DF.newDuration(Token.string(string(null)));
  }

  @Override
  public final int hash(final InputInfo ii) {
    return (int) (31 * mon + (sec == null ? 0 : sec.doubleValue()));
  }

  @Override
  public final String toString() {
    return Util.info("\"%\"", string(null));
  }
}
Example #5
0
/**
 * Abstract super class for date items.
 *
 * @author BaseX Team 2005-15, BSD License
 * @author Christian Gruen
 */
public abstract class ADate extends ADateDur {
  /** Maximum value for computations on year value based on long range. */
  static final long MAX_YEAR = (long) (Long.MAX_VALUE / 365.2425) - 2;
  /** Minimum year value. */
  static final long MIN_YEAR = -MAX_YEAR;
  /** Constant for counting negative years (divisible by 400). */
  private static final long ADD_NEG = (MAX_YEAR / 400 + 1) * 400;

  /** Pattern for two digits. */
  static final String DD = "(\\d{2})";
  /** Year pattern. */
  static final String YEAR = "(-?(000[1-9]|00[1-9]\\d|0[1-9]\\d{2}|[1-9]\\d{3,}))";
  /** Date pattern. */
  static final String ZONE = "((\\+|-)" + DD + ':' + DD + "|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 DATE = Pattern.compile(YEAR + '-' + DD + '-' + DD + ZONE);
  /** Time pattern. */
  private static final Pattern TIME =
      Pattern.compile(DD + ':' + DD + ':' + "(\\d{2}(\\.\\d+)?)" + ZONE);

  /**
   * Year.
   *
   * <ul>
   *   <li>1 - {@code Long#MAX_VALUE}-1: AD
   *   <li>0 - {@link Long#MIN_VALUE}: BC, +1 added
   *   <li>{@link Long#MAX_VALUE}: undefined
   * </ul>
   */
  long yea = Long.MAX_VALUE;
  /** Month ({@code 0-11}). {@code -1}: undefined. */
  byte mon = -1;
  /** Day ({@code 0-30}). {@code -1}: undefined. */
  byte day = -1;
  /** Hour ({@code 0-59}). {@code -1}: undefined. */
  byte hou = -1;
  /** Minute ({@code 0-59}). {@code -1}: undefined. */
  byte min = -1;
  /** Timezone in minutes ({@code -14*60-14*60}). {@link Short#MAX_VALUE}: undefined. */
  short tz = Short.MAX_VALUE;

  /** Data factory. */
  static final DatatypeFactory DF;

  static {
    try {
      DF = DatatypeFactory.newInstance();
    } catch (final Exception ex) {
      throw Util.notExpected(ex);
    }
  }

  /**
   * Constructor.
   *
   * @param type item type
   * @param date date reference
   */
  ADate(final Type type, final ADate date) {
    super(type);
    yea = date.yea;
    mon = date.mon;
    day = date.day;
    hou = date.hou;
    min = date.min;
    sec = date.sec;
    tz = date.tz;
  }

  /**
   * Constructor.
   *
   * @param type item type
   */
  ADate(final Type type) {
    super(type);
  }

  /**
   * Initializes the date format.
   *
   * @param d input
   * @param e example 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 = DATE.matcher(Token.string(d).trim());
    if (!mt.matches()) throw dateError(d, e, ii);
    yea = toLong(mt.group(1), false, ii);
    // +1 is added to BC values to simplify computations
    if (yea < 0) yea++;
    mon = (byte) (Strings.toInt(mt.group(3)) - 1);
    day = (byte) (Strings.toInt(mt.group(4)) - 1);

    if (mon < 0 || mon >= 12 || day < 0 || day >= dpm(yea, mon)) throw dateError(d, e, ii);
    if (yea <= MIN_YEAR || yea > MAX_YEAR) throw DATERANGE_X_X.get(ii, type, chop(d, ii));
    zone(mt, 5, d, ii);
  }

  /**
   * Initializes 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 = TIME.matcher(Token.string(d).trim());
    if (!mt.matches()) throw dateError(d, e, ii);

    hou = (byte) Strings.toInt(mt.group(1));
    min = (byte) Strings.toInt(mt.group(2));
    sec = toDecimal(mt.group(3), false, ii);
    if (min >= 60
        || sec.compareTo(BD60) >= 0
        || hou > 24
        || hou == 24 && (min > 0 || sec.compareTo(BigDecimal.ZERO) > 0)) throw dateError(d, e, ii);
    zone(mt, 5, d, ii);
    if (hou == 24) {
      hou = 0;
      add(DAYSECONDS);
    }
  }

  /**
   * Initializes the timezone.
   *
   * @param matcher matcher
   * @param pos first matching position
   * @param value value
   * @param ii input info
   * @throws QueryException query exception
   */
  final void zone(final Matcher matcher, final int pos, final byte[] value, final InputInfo ii)
      throws QueryException {

    final String z = matcher.group(pos);
    if (z == null) return;
    if ("Z".equals(z)) {
      tz = 0;
    } else {
      final int th = Strings.toInt(matcher.group(pos + 2));
      final int tm = Strings.toInt(matcher.group(pos + 3));
      if (th > 14 || tm > 59 || th == 14 && tm != 0) throw INVALIDZONE_X.get(ii, value);
      final int mn = th * 60 + tm;
      tz = (short) ("-".equals(matcher.group(pos + 1)) ? -mn : mn);
    }
  }

  /**
   * Adds/subtracts the specified dayTime duration.
   *
   * @param dur duration
   * @param plus plus/minus flag
   */
  final void calc(final DTDur dur, final boolean plus) {
    add(plus ? dur.sec : dur.sec.negate());
  }

  /**
   * Adds/subtracts the specified yearMonth duration.
   *
   * @param dur duration
   * @param plus plus/minus flag
   * @param ii input info
   * @throws QueryException query exception
   */
  final void calc(final YMDur dur, final boolean plus, final InputInfo ii) throws QueryException {
    final long m = plus ? dur.mon : -dur.mon;
    final long mn = mon + m;
    mon = (byte) mod(mn, 12);
    yea += div(mn, 12);
    day = (byte) Math.min(dpm(yea, mon) - 1, day);

    if (yea <= MIN_YEAR || yea > MAX_YEAR) throw YEARRANGE_X.get(ii, yea);
  }

  /**
   * Adds the specified dayTime duration.
   *
   * @param add value to be added
   */
  private void add(final BigDecimal add) {
    // normalized modulo: sc % 60  vs.  (-sc + sc % 60 + 60 + sc) % 60
    final BigDecimal sc = sec().add(add);
    sec =
        sc.signum() >= 0
            ? sc.remainder(BD60)
            : sc.negate().add(sc.remainder(BD60)).add(BD60).add(sc).remainder(BD60);

    final long mn = Math.max(min(), 0) + div(sc.longValue(), 60);
    min = (byte) mod(mn, 60);
    final long ho = Math.max(hou, 0) + div(mn, 60);
    hou = (byte) mod(ho, 24);
    final long da = div(ho, 24);

    final long[] ymd = ymd(days().add(BigDecimal.valueOf(da)));
    yea = ymd[0];
    mon = (byte) ymd[1];
    day = (byte) ymd[2];
  }

  /**
   * Returns a normalized module value for negative and positive values.
   *
   * @param value input value
   * @param mod modulo
   * @return result
   */
  private static long mod(final long value, final int mod) {
    return value > 0 ? value % mod : (Long.MAX_VALUE / mod * mod + value) % mod;
  }

  /**
   * Returns a normalized division value for negative and positive values.
   *
   * @param value input value
   * @param div divisor
   * @return result
   */
  private static long div(final long value, final int div) {
    return value < 0 ? (value + 1) / div - 1 : value / div;
  }

  /**
   * Adjusts the timezone.
   *
   * @param zone timezone
   * @param spec indicates if zone has been specified (may be {@code null})
   * @param ii input info
   * @throws QueryException query exception
   */
  public abstract void timeZone(final DTDur zone, final boolean spec, final InputInfo ii)
      throws QueryException;

  /**
   * Adjusts the timezone.
   *
   * @param zone timezone
   * @param spec indicates if zone has been specified (may be {@code null})
   * @param ii input info
   * @throws QueryException query exception
   */
  void tz(final DTDur zone, final boolean spec, final InputInfo ii) throws QueryException {
    final short t;
    if (spec && zone == null) {
      t = Short.MAX_VALUE;
    } else {
      if (zone == null) {
        final Calendar c = Calendar.getInstance();
        t = (short) ((c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET)) / 60000);
      } else {
        t = (short) (zone.min() + zone.hou() * 60);
        if (zone.sec().signum() != 0) throw ZONESEC_X.get(ii, zone);
        if (Math.abs(t) > 60 * 14 || zone.day() != 0) throw INVALZONE_X.get(ii, zone);
      }

      // change time if two competing time zones exist
      if (tz != Short.MAX_VALUE) add(BigDecimal.valueOf(60L * (t - tz)));
    }
    tz = t;
  }

  @Override
  public final long yea() {
    return yea > 0 ? yea : yea - 1;
  }

  @Override
  public final long mon() {
    return mon + 1;
  }

  @Override
  public final long day() {
    return day + 1;
  }

  @Override
  public final long hou() {
    return hou;
  }

  @Override
  public final long min() {
    return min;
  }

  @Override
  public final BigDecimal sec() {
    return sec == null ? BigDecimal.ZERO : sec;
  }

  /**
   * Returns the timezone in minutes.
   *
   * @return time zone
   */
  public final int tz() {
    return tz;
  }

  /**
   * Returns if the timezone is defined.
   *
   * @return time zone
   */
  public final boolean tzDefined() {
    return tz != Short.MAX_VALUE;
  }

  @Override
  public byte[] string(final InputInfo ii) {
    final TokenBuilder tb = new TokenBuilder();
    final boolean ymd = yea != Long.MAX_VALUE;
    if (ymd) {
      if (yea <= 0) tb.add('-');
      prefix(tb, Math.abs(yea()), 4);
      tb.add('-');
      prefix(tb, mon(), 2);
      tb.add('-');
      prefix(tb, day(), 2);
    }
    if (hou >= 0) {
      if (ymd) tb.add('T');
      prefix(tb, hou(), 2);
      tb.add(':');
      prefix(tb, min(), 2);
      tb.add(':');
      if (sec.intValue() < 10) tb.add('0');
      tb.addExt(Token.chopNumber(Token.token(sec().abs().toPlainString())));
    }
    zone(tb);
    return tb.finish();
  }

  /**
   * Adds the time zone to the specified token builder.
   *
   * @param tb token builder
   */
  void zone(final TokenBuilder tb) {
    if (tz == Short.MAX_VALUE) return;
    if (tz == 0) {
      tb.add('Z');
    } else {
      tb.add(tz > 0 ? '+' : '-');
      prefix(tb, Math.abs(tz) / 60, 2);
      tb.add(':');
      prefix(tb, Math.abs(tz) % 60, 2);
    }
  }

  /**
   * Prefixes the specified number of zero digits before a number.
   *
   * @param tb token builder
   * @param number number to be printed
   * @param zero maximum number of zero digits
   */
  static void prefix(final TokenBuilder tb, final long number, final int zero) {
    final byte[] t = Token.token(number);
    for (int i = t.length; i < zero; i++) tb.add('0');
    tb.add(t);
  }

  @Override
  public final boolean eq(
      final Item it, final Collation coll, final StaticContext sc, final InputInfo ii)
      throws QueryException {
    final ADate d = (ADate) (it instanceof ADate ? it : type.cast(it, null, null, ii));
    final BigDecimal d1 = seconds().add(days().multiply(DAYSECONDS));
    final BigDecimal d2 = d.seconds().add(d.days().multiply(DAYSECONDS));
    return d1.compareTo(d2) == 0;
  }

  @Override
  public int hash(final InputInfo ii) throws QueryException {
    return seconds().add(days().multiply(DAYSECONDS)).intValue();
  }

  @Override
  public int diff(final Item it, final Collation coll, final InputInfo ii) throws QueryException {
    final ADate d = (ADate) (it instanceof ADate ? it : type.cast(it, null, null, ii));
    final BigDecimal d1 = seconds().add(days().multiply(DAYSECONDS));
    final BigDecimal d2 = d.seconds().add(d.days().multiply(DAYSECONDS));
    return d1.compareTo(d2);
  }

  @Override
  public final XMLGregorianCalendar toJava() {
    return DF.newXMLGregorianCalendar(
        yea == Long.MAX_VALUE ? null : BigInteger.valueOf(yea > 0 ? yea : yea - 1),
        mon >= 0 ? mon + 1 : Integer.MIN_VALUE,
        day >= 0 ? day + 1 : Integer.MIN_VALUE,
        hou >= 0 ? hou : Integer.MIN_VALUE,
        min >= 0 ? min : Integer.MIN_VALUE,
        sec != null ? sec.intValue() : Integer.MIN_VALUE,
        sec != null ? sec.remainder(BigDecimal.ONE) : null,
        tz == Short.MAX_VALUE ? Integer.MIN_VALUE : tz);
  }

  /**
   * Returns the date in seconds.
   *
   * @return seconds
   */
  final BigDecimal seconds() {
    int z = tz;
    if (z == Short.MAX_VALUE) {
      // [CG] XQuery, DateTime: may be removed
      final long n = System.currentTimeMillis();
      z = Calendar.getInstance().getTimeZone().getOffset(n) / 60000;
    }
    return (sec == null ? BigDecimal.ZERO : sec)
        .add(BigDecimal.valueOf(Math.max(0, hou) * 3600 + Math.max(0, min) * 60 - z * 60));
  }

  /**
   * Returns a day count.
   *
   * @return days
   */
  final BigDecimal days() {
    final long y = yea == Long.MAX_VALUE ? 1 : yea;
    return days(y + ADD_NEG, Math.max(mon, 0), Math.max(day, 0));
  }

  /**
   * Returns a day count for the specified years, months and days. All values must be specified in
   * their internal representation (undefined values are supported, too). Algorithm is derived from
   * J R Stockton (http://www.merlyn.demon.co.uk/daycount.htm).
   *
   * @param year year
   * @param month month
   * @param day days
   * @return days
   */
  private static BigDecimal days(final long year, final int month, final int day) {
    final long y = year - (month < 2 ? 1 : 0);
    final int m = month + (month < 2 ? 13 : 1);
    final int d = day + 1;
    return BD365
        .multiply(BigDecimal.valueOf(y))
        .add(BigDecimal.valueOf(y / 4 - y / 100 + y / 400 - 92 + d + (153 * m - 2) / 5));
  }

  /**
   * Converts a day count into year, month and day components. Algorithm is derived from J R
   * Stockton (http://www.merlyn.demon.co.uk/daycount.htm).
   *
   * @param days day count
   * @return result array
   */
  private static long[] ymd(final BigDecimal days) {
    BigDecimal d = days;
    BigDecimal t =
        d.add(BD36525).multiply(BD4).divideToIntegralValue(BD146097).subtract(BigDecimal.ONE);
    BigDecimal y = BD100.multiply(t);
    d = d.subtract(BD36524.multiply(t).add(t.divideToIntegralValue(BD4)));
    t = d.add(BD366).multiply(BD4).divideToIntegralValue(BD1461).subtract(BigDecimal.ONE);
    y = y.add(t);
    d = d.subtract(BD365.multiply(t).add(t.divideToIntegralValue(BD4)));
    final BigDecimal m = BD5.multiply(d).add(BD2).divideToIntegralValue(BD153);
    d = d.subtract(BD153.multiply(m).add(BD2).divideToIntegralValue(BD5));
    long mm = m.longValue();
    if (mm > 9) {
      mm -= 12;
      y = y.add(BigDecimal.ONE);
    }
    return new long[] {y.subtract(BigDecimal.valueOf(ADD_NEG)).longValue(), mm + 2, d.longValue()};
  }

  /**
   * Returns days per month, considering leap years.
   *
   * @param yea year
   * @param mon month
   * @return days
   */
  public static int dpm(final long yea, final int mon) {
    final byte l = DAYS[mon];
    return mon == 1 && yea % 4 == 0 && (yea % 100 != 0 || yea % 400 == 0) ? l + 1 : l;
  }

  @Override
  public final String toString() {
    return Util.info("\"%\"", string(null));
  }
}