Package org.exist.xquery.value

Source Code of org.exist.xquery.value.AbstractDateTimeValue$Parser

/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-2007 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*  $Id$
*/
package org.exist.xquery.value;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.Collator;
import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;

import org.apache.xerces.util.DatatypeMessageFormatter;
import org.exist.xquery.Constants;
import org.exist.xquery.ErrorCodes;
import org.exist.xquery.XPathException;

/**
* @author wolf
* @author <a href="mailto:piotr@ideanest.com">Piotr Kaminski</a>
* @author ljo
*/
public abstract class AbstractDateTimeValue extends ComputableValue {
 
  //Provisionally public
  public final XMLGregorianCalendar calendar;
  private XMLGregorianCalendar implicitCalendar, canonicalCalendar, trimmedCalendar;
 
  protected static Pattern negativeDateStart = Pattern.compile("^\\d\\d?-(\\d+)-(.*)");

  public final static int YEAR = 0;
  public final static int MONTH = 1;
  public final static int DAY = 2;
  public final static int HOUR = 3;
  public final static int MINUTE = 4;
  public final static int SECOND = 5;
  public final static int MILLISECOND = 6;

    protected static byte[] daysPerMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    protected static final short[] monthData = {306, 337, 0, 31, 61, 92, 122, 153, 184, 214, 245, 275};

    /**
   * Create a new date time value based on the given calendar.  The calendar is
   * <em>not</em> cloned, so it is the subclass's responsibility to make sure there are
   * no external references to it that would allow for mutation.
   *
   * @param calendar the calendar to wrap into an XPath value
   */
  protected AbstractDateTimeValue(XMLGregorianCalendar calendar) {
    this.calendar = calendar;
  }
 
  protected AbstractDateTimeValue(String lexicalValue) throws XPathException {
        lexicalValue = StringValue.trimWhitespace(lexicalValue);

        //lexicalValue = normalizeDate(lexicalValue);
        //lexicalValue = normalizeTime(getType(), lexicalValue);
    try {
      calendar = parse(lexicalValue);
    } catch (final IllegalArgumentException e) {
      throw new XPathException(ErrorCodes.FORG0001, "illegal lexical form for date-time-like value '" + lexicalValue + "' " + e.getMessage(), e);
    }
  }
 
  /**
   * Return a calendar with the timezone field set, to be used for order comparison.
   * If the original calendar did not specify a timezone, set the local timezone (unadjusted
   * for daylight savings).  The returned calendars will be totally ordered between themselves.
   * We also set any missing fields to ensure that normalization doesn't discard important data!
   * (This is probably a bug in the JAXP implementation, but the workaround doesn't hurt us,
   * so it's faster to just fix it here.)
   *
   * @return the calendar represented by this object, with the timezone field filled in with an implicit value if necessary
   */
  protected XMLGregorianCalendar getImplicitCalendar() {
    if (implicitCalendar == null) {
      implicitCalendar = (XMLGregorianCalendar) calendar.clone();
      if (calendar.getTimezone() == DatatypeConstants.FIELD_UNDEFINED) {
        implicitCalendar.setTimezone(TimeUtils.getInstance().getLocalTimezoneOffsetMinutes());
      }
      // fill in fields from default reference; don't have to worry about weird combinations of fields being set, since we control that on creation
      switch(getType()) {
        case Type.DATE: implicitCalendar.setTime(0,0,0); break;
        case Type.TIME: implicitCalendar.setYear(1972); implicitCalendar.setMonth(12); implicitCalendar.setDay(31); break;
        default:
      }
      implicitCalendar = implicitCalendar.normalize()// the comparison routines will normalize it anyway, just do it once here
    }
    return implicitCalendar;
  }
 
  // TODO: method not currently used, apparently the XPath spec never needs to canonicalize
  // date/times after all (see section 17.1.2 on casting)
  protected XMLGregorianCalendar getCanonicalCalendar() {
    if (canonicalCalendar == null) {
      canonicalCalendar = getTrimmedCalendar().normalize();
    }
    return canonicalCalendar;
  }
 
  public XMLGregorianCalendar getTrimmedCalendar() {
    if (trimmedCalendar == null) {
      trimmedCalendar = cloneXMLGregorianCalendar(calendar);
      final BigDecimal fract = trimmedCalendar.getFractionalSecond();
      if (fract != null) {
        // TODO: replace following algorithm in JDK 1.5 with fract.stripTrailingZeros();
        final String s = fract.toString();
        int i = s.length();
        while (i > 0 && s.charAt(i-1) == '0') i--;
        if (i == 0) {trimmedCalendar.setFractionalSecond(null);}
        else if (i != s.length()) {trimmedCalendar.setFractionalSecond(new BigDecimal(s.substring(0, i)));}
      }
    }
    return trimmedCalendar;
  }
 
  protected XMLGregorianCalendar getCanonicalOrTrimmedCalendar() {
    try {
      return getCanonicalCalendar();
    } catch (final Exception e) {
      return getTrimmedCalendar();
    }
   
  }

  protected abstract AbstractDateTimeValue createSameKind(XMLGregorianCalendar cal) throws XPathException;
 
  public long getTimeInMillis() {
    // use getImplicitCalendar() rather than relying on toGregorianCalendar timezone defaulting
    // to maintain consistency
    return getImplicitCalendar().toGregorianCalendar().getTimeInMillis();
  }
 
  protected abstract QName getXMLSchemaType();
 
  public String getStringValue() throws XPathException {
    String r = getTrimmedCalendar().toXMLFormat();
    // hacked to match the format mandated in XPath 2 17.1.2, which is different from the XML Schema canonical format
    //if (r.charAt(r.length()-1) == 'Z') r = r.substring(0, r.length()-1) + "+00:00";
   
    //Let's try these lexical transformations...   
    final boolean startsWithDashDash = r.startsWith("--");
    r = r.replaceAll("--", "");
    if (startsWithDashDash)
      {r = "--" + r;}
   
    final Matcher m = negativeDateStart.matcher(r);
    if (m.matches()) {
      final int year = Integer.valueOf(m.group(1)).intValue();
      final DecimalFormat df = new DecimalFormat("0000");
      r = "-" + df.format(year) + "-" + m.group(2);
    }
   
    return r;
  }

  public boolean effectiveBooleanValue() throws XPathException {
    throw new XPathException(ErrorCodes.FORG0006, "effective boolean value invalid operand type: " + Type.getTypeName(getType()));
  }
 
  public abstract AtomicValue convertTo(int requiredType) throws XPathException;

  public int getPart(int part) {
    switch (part) {
      case YEAR: return calendar.getYear();
      case MONTH: return calendar.getMonth();
      case DAY: return calendar.getDay();
      case HOUR: return calendar.getHour();
      case MINUTE: return calendar.getMinute();
      case SECOND: return calendar.getSecond();
      case MILLISECOND: final int mSec=calendar.getMillisecond();
                                          if(mSec == DatatypeConstants.FIELD_UNDEFINED)
                                              {return 0;}
                                          else
                                              {return calendar.getMillisecond();}
      default: throw new IllegalArgumentException("Invalid argument to method getPart");
    }
  }
 
  private static final Duration tzLowerBound = TimeUtils.getInstance().newDurationDayTime("-PT14H");
  private static final Duration tzUpperBound = tzLowerBound.negate();
  protected void validateTimezone(DayTimeDurationValue offset) throws XPathException {
    final Duration tz = offset.duration;
    final Number secs = tz.getField(DatatypeConstants.SECONDS);
    if (secs != null && ((BigDecimal) secs).compareTo(BigDecimal.valueOf(0)) != 0)
      {throw new XPathException(ErrorCodes.FODT0003, "duration " + offset + " has fractional minutes so cannot be used as a timezone offset");}
    if (! (
        tz.equals(tzLowerBound) ||
        tz.equals(tzUpperBound) ||
        (tz.isLongerThan(tzLowerBound) && tz.isShorterThan(tzUpperBound))
      ))
      {throw new XPathException(ErrorCodes.FODT0003, "duration " + offset + " outside valid timezone offset range");}
  }

  public AbstractDateTimeValue adjustedToTimezone(DayTimeDurationValue offset) throws XPathException {
    if (offset == null) {offset = new DayTimeDurationValue(TimeUtils.getInstance().getLocalTimezoneOffsetMillis());}
    validateTimezone(offset);
    XMLGregorianCalendar xgc = (XMLGregorianCalendar) calendar.clone();
    if (xgc.getTimezone() != DatatypeConstants.FIELD_UNDEFINED) {
      if (getType() == Type.DATE) {xgc.setTime(0,0,0);// set the fields so we don't lose precision when shifting timezones
      xgc = xgc.normalize();
      xgc.add(offset.duration);
    }
    try {
      xgc.setTimezone((int) (offset.getValue()/60));
    } catch (final IllegalArgumentException e) {
      throw new XPathException(ErrorCodes.FORG0001, "illegal timezone offset " + offset, e);
    }
    return createSameKind(xgc);
  }
 
  public AbstractDateTimeValue withoutTimezone() throws XPathException {
    final XMLGregorianCalendar xgc = (XMLGregorianCalendar) calendar.clone();
    xgc.setTimezone(DatatypeConstants.FIELD_UNDEFINED);
    return createSameKind(xgc);
  }
 
  public Sequence getTimezone() throws XPathException {
    final int tz = calendar.getTimezone();
    if (tz == DatatypeConstants.FIELD_UNDEFINED) {return Sequence.EMPTY_SEQUENCE;}
    return new DayTimeDurationValue(tz * 60000L);
  }
 
  public boolean compareTo(Collator collator, int operator, AtomicValue other) throws XPathException {
    final int cmp = compareTo(collator, other);
    switch (operator) {
      case Constants.EQ: return cmp == 0;
      case Constants.NEQ: return cmp != 0;
      case Constants.LT: return cmp < 0;
      case Constants.LTEQ: return cmp <= 0;
      case Constants.GT: return cmp > 0;
      case Constants.GTEQ: return cmp >= 0;
      default :
        throw new XPathException("Unknown operator type in comparison");
    }
  }

  public int compareTo(Collator collator, AtomicValue other) throws XPathException {
    if (other.getType() == getType()) {
      // filling in missing timezones with local timezone, should be total order as per XPath 2.0 10.4
      final int r =  getImplicitCalendar().compare(((AbstractDateTimeValue) other).getImplicitCalendar());
      if (r == DatatypeConstants.INDETERMINATE) {throw new RuntimeException("indeterminate order between " + this + " and " + other);}
      return r;
    }
    throw new XPathException(
      "Type error: cannot compare " + Type.getTypeName(getType()) + " to "
        + Type.getTypeName(other.getType()));
  }

  public AtomicValue max(Collator collator, AtomicValue other) throws XPathException {
    final AbstractDateTimeValue otherDate = other.getType() == getType() ? (AbstractDateTimeValue) other : (AbstractDateTimeValue) other.convertTo(getType());
    return getImplicitCalendar().compare(otherDate.getImplicitCalendar()) > 0 ? this : other;
  }

  public AtomicValue min(Collator collator, AtomicValue other) throws XPathException {
    final AbstractDateTimeValue otherDate = other.getType() == getType() ? (AbstractDateTimeValue) other : (AbstractDateTimeValue) other.convertTo(getType());
    return getImplicitCalendar().compare(otherDate.getImplicitCalendar()) < 0 ? this : other;
  }

  // override for xs:time
  public ComputableValue plus(ComputableValue other) throws XPathException {
    switch(other.getType()) {
      case Type.YEAR_MONTH_DURATION:
      case Type.DAY_TIME_DURATION:
        return other.plus(this);
      default:
        throw new XPathException(
            "Operand to plus should be of type xdt:dayTimeDuration or xdt:yearMonthDuration; got: "
              + Type.getTypeName(other.getType()));
    }
  }

  public ComputableValue mult(ComputableValue other) throws XPathException {
    throw new XPathException("multiplication is not supported for type " + Type.getTypeName(getType()));
  }

  public ComputableValue div(ComputableValue other) throws XPathException {
    throw new XPathException("division is not supported for type " + Type.getTypeName(getType()));
  }

  public int conversionPreference(Class<?> javaClass) {
    if (javaClass.isAssignableFrom(DateValue.class)) {return 0;}
    if (javaClass.isAssignableFrom(XMLGregorianCalendar.class)) {return 1;}
    if (javaClass.isAssignableFrom(GregorianCalendar.class)) {return 2;}
    if (javaClass == Date.class) {return 3;}
    return Integer.MAX_VALUE;
  }

        @Override
  public <T> T toJavaObject(Class<T> target) throws XPathException {
            if (target == Object.class || target.isAssignableFrom(DateValue.class)) {
                return (T)this;
            } else if (target.isAssignableFrom(XMLGregorianCalendar.class)) {
                return (T)calendar.clone();
            } else if (target.isAssignableFrom(GregorianCalendar.class)) {
                return (T)calendar.toGregorianCalendar();
            } else if (target == Date.class) {
                return (T)calendar.toGregorianCalendar().getTime();
            }

            throw new XPathException("cannot convert value of type " + Type.getTypeName(getType()) + " to Java object of type " + target.getName());
  }
   
    /* (non-Javadoc)
     * @see java.lang.Comparable#compareTo(java.lang.Object)
     */
    public int compareTo(Object o)
    {
        if (o instanceof AbstractDateTimeValue) {
      final AbstractDateTimeValue dt = (AbstractDateTimeValue) o;
        return calendar.compare(dt.calendar);
    }

        final AtomicValue other = (AtomicValue)o;
        if(Type.subTypeOf(other.getType(), Type.DATE_TIME))
          try {
            //TODO : find something that will consume less resources
            return calendar.compare(TimeUtils.getInstance().newXMLGregorianCalendar(other.getStringValue()));
          } catch (final XPathException e) {
            System.out.println("Failed to get string value of '" + other + "'");
            //Why not ?
            return Constants.SUPERIOR;
          }
        else
            {return getType() > other.getType() ? Constants.SUPERIOR : Constants.INFERIOR;}
    } 

    /**
     * Utility method that is able to clone a calendar whose year is 0
     * (whatever a year 0 means).
     * It looks like the JDK is unable to do that.
     * @param calendar The Calendar to clone
     * @return the cloned Calendar
     */
    public static XMLGregorianCalendar cloneXMLGregorianCalendar(XMLGregorianCalendar calendar) {
      boolean hacked = false;
    if (calendar.getYear() == 0) {
      calendar.setYear(1);
      hacked = true;
    }
    final XMLGregorianCalendar result = (XMLGregorianCalendar)calendar.clone();
    if (hacked) {
      //reset everything
      calendar.setYear(0);
      //-1 could also be considered
      result.setYear(0);
    }
    return result;
    }

    public boolean equals(Object obj) {
      if (obj instanceof AbstractDateTimeValue) {
        final AbstractDateTimeValue dt = (AbstractDateTimeValue) obj;
      return calendar.equals(dt.calendar);
    }
     
      return false;
  }
   
    public int hashCode() {
      return calendar.hashCode();
    }

    public int getDayOfWeek() {
        return calendar.toGregorianCalendar().get(Calendar.DAY_OF_WEEK);
    }

    public int getDayWithinYear() {
        final int j = getJulianDayNumber(calendar.getYear(), calendar.getMonth(), calendar.getDay());
        final int k = getJulianDayNumber(calendar.getYear(), 1, 1);
        return j - k + 1;
    }

    public int getWeekWithinYear() {
        return calendar.toGregorianCalendar().get(Calendar.WEEK_OF_YEAR);
    }

    public int getWeekWithinMonth() {
        return calendar.toGregorianCalendar().get(Calendar.WEEK_OF_MONTH);
    }

    //copy from org.apache.xerces.jaxp.datatype.XMLGregorianCalendarImpl
    private XMLGregorianCalendar parse(String lexicalRepresentation) {
        // compute format string for this lexical representation.
        String format = null;
        final String lexRep = lexicalRepresentation;
        final int NOT_FOUND = -1;
        int lexRepLength = lexRep.length();

        // current parser needs a format string,
        // use following heuristics to figure out what xml schema date/time
        // datatype this lexical string could represent.
        if (lexRep.indexOf('T') != NOT_FOUND) {
            // found Date Time separater, must be xsd:DateTime
            format = "%Y-%M-%DT%h:%m:%s" + "%z";
        }
        else if (lexRepLength >= 3 && lexRep.charAt(2) == ':') {
            // found ":", must be xsd:Time
            format = "%h:%m:%s" +"%z";
        }
        else if (lexRep.startsWith("--")) {
            // check for GDay || GMonth || GMonthDay
            if (lexRepLength >= 3 && lexRep.charAt(2) == '-') {
                // GDAY
                // Fix 4971612: invalid SCCS macro substitution in data string
                format = "---%D" + "%z";
            }
            else if (lexRepLength == 4 || (lexRepLength >= 6 && (lexRep.charAt(4) == '+' || (lexRep.charAt(4) == '-' && (lexRep.charAt(5) == '-' || lexRepLength == 10))))) {
                // GMonth
                // Fix 4971612: invalid SCCS macro substitution in data string
                format = "--%M--%Z";
                final Parser p = new Parser(format, lexRep);
                try {
                  final XMLGregorianCalendar c = p.parse();
                    // check for validity
                    if (!c.isValid()) {
                        throw new IllegalArgumentException(
                                DatatypeMessageFormatter.formatMessage(null,"InvalidXGCRepresentation", new Object[]{lexicalRepresentation})
                                //"\"" + lexicalRepresentation + "\" is not a valid representation of an XML Gregorian Calendar value."
                        );
                    }
                    return c;
                }
                catch(final IllegalArgumentException e) {
                    format = "--%M%z";
                }
            }
            else {
                // GMonthDay or invalid lexicalRepresentation
                format = "--%M-%D" + "%z";
            }
        }
        else {
            // check for Date || GYear | GYearMonth
            int countSeparator = 0;

            // start at index 1 to skip potential negative sign for year.


            final int timezoneOffset = lexRep.indexOf(':');
            if (timezoneOffset != NOT_FOUND) {

                // found timezone, strip it off for distinguishing
                // between Date, GYear and GYearMonth so possible
                // negative sign in timezone is not mistaken as
                // a separator.
                lexRepLength -= 6;
            }

            for (int i=1; i < lexRepLength; i++) {
                if (lexRep.charAt(i) == '-') {
                    countSeparator++;
                }
            }
            if (countSeparator == 0) {
                // GYear
                format = "%Y" + "%z";
            }
            else if (countSeparator == 1) {
                // GYearMonth
                format = "%Y-%M" + "%z";
            }
            else {
                // Date or invalid lexicalRepresentation
                // Fix 4971612: invalid SCCS macro substitution in data string
                format = "%Y-%M-%D" + "%z";
            }
        }
        final Parser p = new Parser(format, lexRep);
        final XMLGregorianCalendar c = p.parse();

        // check for validity
        if (!c.isValid()) {
            throw new IllegalArgumentException(
                    DatatypeMessageFormatter.formatMessage(null,"InvalidXGCRepresentation", new Object[]{lexicalRepresentation})
                    //"\"" + lexicalRepresentation + "\" is not a valid representation of an XML Gregorian Calendar value."
            );
        }
        return c;
    }
   
    private final class Parser {
        private final String format;
        private final String value;

        private final int flen;
        private final int vlen;

        private int fidx;
        private int vidx;
       
        private BigInteger year = null;
        private int month = DatatypeConstants.FIELD_UNDEFINED;
        private int day = DatatypeConstants.FIELD_UNDEFINED;

        private int timezone = DatatypeConstants.FIELD_UNDEFINED;

        private int hour = DatatypeConstants.FIELD_UNDEFINED;
        private int minute = DatatypeConstants.FIELD_UNDEFINED;
        private int second = DatatypeConstants.FIELD_UNDEFINED ;
       
        private BigDecimal fractionalSecond = null;

        private Parser(String format, String value) {
            this.format = format;
            this.value = value;
            this.flen = format.length();
            this.vlen = value.length();
        }
       
        /**
         * <p>Parse a formated <code>String</code> into an <code>XMLGregorianCalendar</code>.</p>
         *
         * <p>If <code>String</code> is not formated as a legal <code>XMLGregorianCalendar</code> value,
         * an <code>IllegalArgumentException</code> is thrown.</p>
         *
         * @throws IllegalArgumentException If <code>String</code> is not formated as a legal <code>XMLGregorianCalendar</code> value.
         */
        public XMLGregorianCalendar parse() throws IllegalArgumentException {
          char vch;
          while (fidx < flen) {
                final char fch = format.charAt(fidx++);

                if (fch != '%') { // not a meta character
                    skip(fch);
                    continue;
                }

                // seen meta character. we don't do error check against the format
                switch (format.charAt(fidx++)) {
                    case 'Y' : // year
                        parseYear();
                        break;

                    case 'M' : // month
                      month = parseInt(2, 2);
                        break;

                    case 'D' : // days
                      day = parseInt(2, 2);
                        break;

                    case 'h' : // hours
                      hour = parseInt(2, 2);
                        break;

                    case 'm' : // minutes
                      minute = parseInt(2, 2);
                        break;

                    case 's' : // parse seconds.
                      second = parseInt(2, 2);

                        if (peek() == '.') {
                          fractionalSecond = parseBigDecimal();
                        }
                        break;

                    case 'z' : // time zone. missing, 'Z', or [+-]nn:nn
                        vch = peek();
                        if (vch == 'Z') {
                            vidx++;
                            timezone = 0;
                        }
                        else if (vch == '+' || vch == '-') {
                            vidx++;
                            final int h = parseInt(2, 2);
                            skip(':');
                            final int m = parseInt(2, 2);

                            if (m >= 60 || m < 0)
                                throw new IllegalArgumentException(
                                        DatatypeMessageFormatter.formatMessage(null, "InvalidFieldValue", new Object[]{ Integer.valueOf(m), "timezone minutes"})
                                );
                           
                            timezone = (h * 60 + m) * (vch == '+' ? 1 : -1);
                        }
                        break;

                    case 'Z' : // time zone. 'Z', or [+-]nn:nn
                        vch = peek();
                        if (vch == 'Z') {
                            vidx++;
                            timezone = 0;
                        }
                        else if (vch == '+' || vch == '-') {
                            vidx++;
                            final int h = parseInt(2, 2);
                            skip(':');
                            final int m = parseInt(2, 2);

                            if (m >= 60 || m < 0)
                                throw new IllegalArgumentException(
                                        DatatypeMessageFormatter.formatMessage(null, "InvalidFieldValue", new Object[]{ Integer.valueOf(m), "timezone minutes"})
                                );
                           
                            timezone = (h * 60 + m) * (vch == '+' ? 1 : -1);
                        } else {
                            throw new IllegalArgumentException(
                                    DatatypeMessageFormatter.formatMessage(null, "InvalidFieldValue", new Object[]{ "do not defined", "timezone"})
                            );
                        }
                        break;

                    default :
                        // illegal meta character. impossible.
                        throw new InternalError();
                }
            }

            if (vidx != vlen) {
                // some tokens are left in the input
                throw new IllegalArgumentException(value); //,vidx);
            }
           
             if (hour == 24 && minute == 0 && second == 0) {
                if (getType() == Type.TIME)
                  {hour = 0;}
             }
           
            return TimeUtils.getInstance().getFactory()
            .newXMLGregorianCalendar(year, month, day, hour, minute, second, fractionalSecond, timezone);
        }
       
        private char peek() throws IllegalArgumentException {
            if (vidx == vlen) {
                return (char) -1;
            }
            return value.charAt(vidx);
        }
       
        private char read() throws IllegalArgumentException {
            if (vidx == vlen) {
                throw new IllegalArgumentException(value); //,vidx);
            }
            return value.charAt(vidx++);
        }
       
        private void skip(char ch) throws IllegalArgumentException {
            if (read() != ch) {
                throw new IllegalArgumentException(value); //,vidx-1);
            }
        }
       
        private void parseYear()
            throws IllegalArgumentException {
            final int vstart = vidx;
            int sign = 0;
           
            // skip leading negative, if it exists
            if (peek() == '-') {
                vidx++;
                sign = 1;
            }
            while (isDigit(peek())) {
                vidx++;
            }
            final int digits = vidx - vstart - sign;
            if (digits < 4) {
                // we are expecting more digits
                throw new IllegalArgumentException(value); //,vidx);
            }
            final String yearString = value.substring(vstart, vidx);
//            if (digits < 10) {
//              year = Integer.parseInt(yearString);
//            }
//            else {
              year = new BigInteger(yearString);
//            }
        }
       
        private int parseInt(int minDigits, int maxDigits)
            throws IllegalArgumentException {
            final int vstart = vidx;
            while (isDigit(peek()) && (vidx - vstart) < maxDigits) {
                vidx++;
            }
            if ((vidx - vstart) < minDigits) {
                // we are expecting more digits
                throw new IllegalArgumentException(value); //,vidx);
            }

            // NumberFormatException is IllegalArgumentException           
            //           try {
            return Integer.parseInt(value.substring(vstart, vidx));
            //            } catch( NumberFormatException e ) {
            //                // if the value is too long for int, NumberFormatException is thrown
            //                throw new IllegalArgumentException(value,vstart);
            //            }
        }

        private BigDecimal parseBigDecimal()
            throws IllegalArgumentException {
            final int vstart = vidx;

            if (peek() == '.') {
                vidx++;
            } else {
                throw new IllegalArgumentException(value);
            }
            while (isDigit(peek())) {
                vidx++;
            }
            return new BigDecimal(value.substring(vstart, vidx));
        }
    }

    private static boolean isDigit(char ch) {
        return '0' <= ch && ch <= '9';
    }

    /**
     * Calculate the Julian day number at 00:00 on a given date. Code taken from saxon
     * {@link <a href="http://saxon.sourceforge.net">http://saxon.sourceforge.net</a>}. Original algorithm is taken from
     * http://vsg.cape.com/~pbaum/date/jdalg.htm and
     * http://vsg.cape.com/~pbaum/date/jdalg2.htm
     * (adjusted to handle BC dates correctly)
     * <p/>
     * <p>Note that this assumes dates in the proleptic Gregorian calendar</p>
     *
     * @param year  the year
     * @param month the month (1-12)
     * @param day   the day (1-31)
     * @return the Julian day number
     */
    public static int getJulianDayNumber(int year, int month, int day) {
        int z = year - (month < 3 ? 1 : 0);
        final short f = monthData[month - 1];
        if (z >= 0) {
            return day + f + 365 * z + z / 4 - z / 100 + z / 400 + 1721118;
        } else {
            // for negative years, add 12000 years and then subtract the days!
            z += 12000;
            final int j = day + f + 365 * z + z / 4 - z / 100 + z / 400 + 1721118;
            return j - (365 * 12000 + 12000 / 4 - 12000 / 100 + 12000 / 400)// number of leap years in 12000 years
        }
    }

}
TOP

Related Classes of org.exist.xquery.value.AbstractDateTimeValue$Parser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.