001: package net.sf.saxon.value;
002:
003: import net.sf.saxon.expr.XPathContext;
004: import net.sf.saxon.om.FastStringBuffer;
005: import net.sf.saxon.trans.DynamicError;
006: import net.sf.saxon.trans.XPathException;
007: import net.sf.saxon.type.ItemType;
008: import net.sf.saxon.type.Type;
009: import net.sf.saxon.type.TypeHierarchy;
010:
011: import java.util.StringTokenizer;
012: import java.math.BigDecimal;
013:
014: /**
015: * A value of type xsd:yearMonthDuration
016: */
017:
018: public final class MonthDurationValue extends DurationValue implements
019: Comparable {
020:
021: /**
022: * Private constructor for internal use
023: */
024:
025: private MonthDurationValue() {
026: }
027:
028: /**
029: * Constructor: create a duration value from a supplied string, in
030: * ISO 8601 format [+|-]PnYnM
031: */
032:
033: public MonthDurationValue(CharSequence s) throws XPathException {
034:
035: StringTokenizer tok = new StringTokenizer(trimWhitespace(s)
036: .toString(), "-+PYM", true);
037: try {
038: if (!tok.hasMoreElements())
039: badDuration("empty string", s);
040: String part = (String) tok.nextElement();
041: if ("+".equals(part)) {
042: part = (String) tok.nextElement();
043: } else if ("-".equals(part)) {
044: negative = true;
045: part = (String) tok.nextElement();
046: }
047: if (!"P".equals(part))
048: badDuration("missing 'P'", s);
049: int state = 0;
050: while (tok.hasMoreElements()) {
051: part = (String) tok.nextElement();
052: if ("T".equals(part)) {
053: state = 4;
054: part = (String) tok.nextElement();
055: }
056: int value = Integer.parseInt(part);
057: if (!tok.hasMoreElements())
058: badDuration("missing unit letter at end", s);
059: char delim = ((String) tok.nextElement()).charAt(0);
060: switch (delim) {
061: case 'Y':
062: if (state > 0)
063: badDuration("Y is out of sequence", s);
064: years = value;
065: state = 1;
066: break;
067: case 'M':
068: if (state == 4 || state == 5) {
069: minutes = value;
070: state = 6;
071: break;
072: } else if (state == 0 || state == 1) {
073: months = value;
074: state = 2;
075: break;
076: } else {
077: badDuration("M is out of sequence", s);
078: }
079:
080: default:
081: badDuration("misplaced " + delim, s);
082: }
083: }
084: normalize();
085:
086: } catch (NumberFormatException err) {
087: badDuration("non-numeric component", s);
088: }
089: }
090:
091: /**
092: * Convert to string
093: * @return ISO 8601 representation.
094: */
095:
096: public CharSequence getStringValueCS() {
097:
098: // The canonical representation has months in the range 0-11
099:
100: int mm = years * 12 + months;
101: int y = mm / 12;
102: int m = mm % 12;
103:
104: FastStringBuffer sb = new FastStringBuffer(32);
105: if (negative) {
106: sb.append('-');
107: }
108: sb.append('P');
109: if (y != 0) {
110: sb.append(y + "Y");
111: }
112: if (m != 0 || y == 0) {
113: sb.append(m + "M");
114: }
115: return sb;
116:
117: }
118:
119: /**
120: * Normalize the value, for example 90M becomes 1H30M
121: */
122:
123: public void normalize() {
124: if (months >= 12) {
125: years += (months / 12);
126: months = months % 12;
127: }
128: }
129:
130: /**
131: * Get the number of months in the duration
132: */
133:
134: public int getLengthInMonths() {
135: return (years * 12 + months) * (negative ? -1 : +1);
136: }
137:
138: /**
139: * Construct a duration value as a number of months.
140: */
141:
142: public static MonthDurationValue fromMonths(int months) {
143: MonthDurationValue mdv = new MonthDurationValue();
144: mdv.negative = (months < 0);
145: mdv.months = (months < 0 ? -months : months);
146: mdv.normalize();
147: return mdv;
148: }
149:
150: /**
151: * Multiply duration by a number
152: */
153:
154: public DurationValue multiply(double n, XPathContext context)
155: throws XPathException {
156: if (Double.isNaN(n)) {
157: DynamicError err = new DynamicError(
158: "Cannot multiply/divide a duration by NaN");
159: err.setErrorCode("FOCA0005");
160: err.setXPathContext(context);
161: throw err;
162: }
163: double m = (double) getLengthInMonths();
164: double product = n * m;
165: if (Double.isInfinite(product) || product > Integer.MAX_VALUE
166: || product < Integer.MIN_VALUE) {
167: DynamicError err = new DynamicError(
168: "Overflow when multiplying/dividing a duration by a number");
169: err.setErrorCode("FODT0002");
170: err.setXPathContext(context);
171: throw err;
172: }
173: return fromMonths((int) Math.round(product));
174: }
175:
176: /**
177: * Find the ratio between two durations
178: * @param other the dividend
179: * @return the ratio, as a decimal
180: * @throws XPathException
181: */
182:
183: public DecimalValue divide(DurationValue other, XPathContext context)
184: throws XPathException {
185: if (other instanceof MonthDurationValue) {
186: BigDecimal v1 = new BigDecimal(this .getLengthInMonths());
187: BigDecimal v2 = new BigDecimal(((MonthDurationValue) other)
188: .getLengthInMonths());
189: return new DecimalValue(v1.divide(v2, 20,
190: BigDecimal.ROUND_HALF_EVEN));
191: } else {
192: throw new DynamicError(
193: "Cannot divide two durations of different type");
194: }
195: }
196:
197: /**
198: * Add two year-month-durations
199: */
200:
201: public DurationValue add(DurationValue other, XPathContext context)
202: throws XPathException {
203: if (other instanceof MonthDurationValue) {
204: return fromMonths(this .getLengthInMonths()
205: + ((MonthDurationValue) other).getLengthInMonths());
206: } else {
207: throw new DynamicError(
208: "Cannot add two durations of different type");
209: }
210: }
211:
212: /**
213: * Subtract two year-month-durations
214: */
215:
216: public DurationValue subtract(DurationValue other,
217: XPathContext context) throws XPathException {
218: if (other instanceof MonthDurationValue) {
219: return fromMonths(this .getLengthInMonths()
220: - ((MonthDurationValue) other).getLengthInMonths());
221: } else {
222: throw new DynamicError(
223: "Cannot subtract two durations of different type");
224: }
225: }
226:
227: /**
228: * Determine the data type of the exprssion
229: * @return Type.YEAR_MONTH_DURATION,
230: * @param th
231: */
232:
233: public ItemType getItemType(TypeHierarchy th) {
234: return Type.YEAR_MONTH_DURATION_TYPE;
235: }
236:
237: /**
238: * Convert to Java object (for passing to external functions)
239: */
240:
241: public Object convertToJava(Class target, XPathContext context)
242: throws XPathException {
243: if (target.isAssignableFrom(DurationValue.class)) {
244: return this ;
245: } else if (target == String.class
246: || target == CharSequence.class) {
247: return getStringValue();
248: } else if (target == Object.class) {
249: return getStringValue();
250: } else {
251: throw new DynamicError(
252: "Conversion of yearMonthDuration to "
253: + target.getName() + " is not supported");
254: }
255: }
256:
257: /**
258: * Compare the value to another duration value
259: * @param other The other dateTime value
260: * @return negative value if this one is the earler, 0 if they are chronologically equal,
261: * positive value if this one is the later. For this purpose, dateTime values with an unknown
262: * timezone are considered to be UTC values (the Comparable interface requires
263: * a total ordering).
264: * @throws ClassCastException if the other value is not a DateTimeValue (the parameter
265: * is declared as Object to satisfy the Comparable interface)
266: */
267:
268: public int compareTo(Object other) {
269: if (other instanceof MonthDurationValue) {
270: return this .getLengthInMonths()
271: - ((MonthDurationValue) other).getLengthInMonths();
272: } else {
273: throw new ClassCastException(
274: "Cannot compare a yearMonthDuration to an object of class "
275: + other.getClass());
276: }
277: }
278:
279: }
280:
281: //
282: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
283: // you may not use this file except in compliance with the License. You may obtain a copy of the
284: // License at http://www.mozilla.org/MPL/
285: //
286: // Software distributed under the License is distributed on an "AS IS" basis,
287: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
288: // See the License for the specific language governing rights and limitations under the License.
289: //
290: // The Original Code is: all this file.
291: //
292: // The Initial Developer of the Original Code is Michael H. Kay
293: //
294: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
295: //
296: // Contributor(s): none.
297: //
|