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: import java.math.BigInteger;
014:
015: /**
016: * A value of type xsd:dayTimeDuration
017: */
018:
019: public final class SecondsDurationValue extends DurationValue implements
020: Comparable {
021:
022: /**
023: * Private constructor for internal use
024: */
025:
026: private SecondsDurationValue() {
027: }
028:
029: /**
030: * Constructor: create a duration value from a supplied string, in
031: * ISO 8601 format [+|-]PnDTnHnMnS
032: */
033:
034: public SecondsDurationValue(CharSequence s) throws XPathException {
035:
036: StringTokenizer tok = new StringTokenizer(trimWhitespace(s)
037: .toString(), "-+.PDTHMS", true);
038: try {
039: if (!tok.hasMoreElements())
040: badDuration("empty string", s);
041: String part = (String) tok.nextElement();
042: if ("+".equals(part)) {
043: part = (String) tok.nextElement();
044: } else if ("-".equals(part)) {
045: negative = true;
046: part = (String) tok.nextElement();
047: }
048: if (!"P".equals(part))
049: badDuration("missing 'P'", s);
050: int state = 0;
051: while (tok.hasMoreElements()) {
052: part = (String) tok.nextElement();
053: if ("T".equals(part)) {
054: state = 4;
055: part = (String) tok.nextElement();
056: }
057: int value = Integer.parseInt(part);
058: if (!tok.hasMoreElements())
059: badDuration("missing unit letter at end", s);
060: char delim = ((String) tok.nextElement()).charAt(0);
061: switch (delim) {
062: case 'D':
063: if (state > 2)
064: badDuration("D is out of sequence", s);
065: days = value;
066: state = 3;
067: break;
068: case 'H':
069: if (state != 4)
070: badDuration("H is out of sequence", s);
071: hours = value;
072: state = 5;
073: break;
074: case 'M':
075: if (state < 4 || state > 5)
076: badDuration("M is out of sequence", s);
077: minutes = value;
078: state = 6;
079: break;
080: case '.':
081: if (state < 4 || state > 6)
082: badDuration("misplaced decimal point", s);
083: seconds = value;
084: state = 7;
085: break;
086: case 'S':
087: if (state < 4 || state > 7)
088: badDuration("S is out of sequence", s);
089: if (state == 7) {
090: while (part.length() < 6)
091: part += "0";
092: if (part.length() > 6)
093: part = part.substring(0, 6);
094: microseconds = Integer.parseInt(part);
095: } else {
096: seconds = value;
097: }
098: state = 8;
099: break;
100: default:
101: badDuration("misplaced " + delim, s);
102: }
103: }
104:
105: normalize();
106:
107: } catch (NumberFormatException err) {
108: badDuration("non-numeric or out-of-range component", s);
109: }
110: }
111:
112: /**
113: * Create a dayTimeDuration given the number of days, hours, minutes, and seconds
114: */
115:
116: public SecondsDurationValue(int sign, int days, int hours,
117: int minutes, int seconds, int microseconds) {
118: this .negative = (sign < 0);
119: this .years = 0;
120: this .months = 0;
121: this .days = days;
122: this .hours = hours;
123: this .minutes = minutes;
124: this .seconds = seconds;
125: this .microseconds = microseconds;
126: }
127:
128: /**
129: * Convert to string
130: * @return ISO 8601 representation.
131: */
132:
133: public CharSequence getStringValueCS() {
134:
135: // We need to normalize the representation
136:
137: double length = getLengthInSeconds();
138: if (length < 0)
139: length = -length;
140:
141: long secs = (long) Math.floor(length);
142: long micros = (int) ((length % 1.0) * 1000000);
143:
144: long s = secs % 60;
145: long m = secs / 60;
146: long h = m / 60;
147: m = m % 60;
148: long d = h / 24;
149: h = h % 24;
150:
151: FastStringBuffer sb = new FastStringBuffer(32);
152: if (negative) {
153: sb.append('-');
154: }
155: sb.append('P');
156: if (d != 0) {
157: sb.append(d + "D");
158: }
159: if (d == 0 || h != 0 || m != 0 || s != 0 || micros != 0) {
160: sb.append('T');
161: }
162: if (h != 0) {
163: sb.append(h + "H");
164: }
165: if (m != 0) {
166: sb.append(m + "M");
167: }
168: if (s != 0 || micros != 0 || (d == 0 && m == 0 && h == 0)) {
169: if (micros == 0) {
170: sb.append(s + "S");
171: } else {
172: long ms = (s * 1000000) + micros;
173: String mss = ms + "";
174: if (s == 0) {
175: mss = "0000000" + mss;
176: mss = mss.substring(mss.length() - 7);
177: }
178: sb.append(mss.substring(0, mss.length() - 6));
179: sb.append('.');
180: int lastSigDigit = mss.length() - 1;
181: while (mss.charAt(lastSigDigit) == '0') {
182: lastSigDigit--;
183: }
184: sb.append(mss.substring(mss.length() - 6,
185: lastSigDigit + 1));
186: sb.append('S');
187: }
188: }
189: return sb;
190: }
191:
192: /**
193: * Normalize the value, for example 90M becomes 1H30M
194: */
195:
196: public void normalize() throws DynamicError {
197: long seconds2 = seconds;
198: long minutes2 = minutes;
199: long hours2 = hours;
200: long days2 = days;
201: if (microseconds >= 1000000) {
202: seconds2 += (microseconds / 1000000);
203: microseconds = microseconds % 1000000;
204: }
205: if (seconds >= 60) {
206: minutes2 += (seconds2 / 60);
207: seconds2 = (int) (seconds2 % 60);
208: }
209: if (minutes2 >= 60) {
210: hours2 += (minutes2 / 60);
211: minutes2 = (int) (minutes2 % 60);
212: }
213: if (hours2 >= 24) {
214: days2 += (hours2 / 24);
215: if (days2 > Integer.MAX_VALUE || days2 < Integer.MIN_VALUE) {
216: throw new DynamicError(
217: "Duration exceeds implementation-defined limits");
218: }
219: hours2 = (int) (hours2 % 24);
220: }
221: days = (int) days2;
222: hours = (int) hours2;
223: minutes = (int) minutes2;
224: seconds = (int) seconds2;
225: }
226:
227: /**
228: * Get length of duration in seconds
229: */
230:
231: public double getLengthInSeconds() {
232: double a = days;
233: a = a * 24 + hours;
234: a = a * 60 + minutes;
235: a = a * 60 + seconds;
236: a += ((double) microseconds / 1000000);
237: // System.err.println("Duration length " + days + "/" + hours + "/" + minutes + "/" + seconds + " is " + a);
238: return (negative ? -a : a);
239: }
240:
241: /**
242: * Get length of duration in milliseconds, as a long
243: */
244:
245: public long getLengthInMilliseconds() {
246: long a = days;
247: a = a * 24 + hours;
248: a = a * 60 + minutes;
249: a = a * 60 + seconds;
250: a = a * 1000 + (microseconds / 1000);
251: return (negative ? -a : a);
252: }
253:
254: /**
255: * Get length of duration in microseconds, as a long
256: */
257:
258: public long getLengthInMicroseconds() {
259: long a = days;
260: a = a * 24 + hours;
261: a = a * 60 + minutes;
262: a = a * 60 + seconds;
263: a = a * 1000000 + microseconds;
264: return (negative ? -a : a);
265: }
266:
267: /**
268: * Construct a duration value as a number of seconds.
269: */
270:
271: public static SecondsDurationValue fromSeconds(BigDecimal seconds)
272: throws XPathException {
273: SecondsDurationValue sdv = new SecondsDurationValue();
274: sdv.negative = (seconds.signum() < 0);
275: BigDecimal microseconds = seconds
276: .multiply(DecimalValue.ONE_MILLION);
277: if (sdv.negative) {
278: microseconds = microseconds.negate();
279: }
280: BigInteger intMicros = microseconds.toBigInteger();
281: BigInteger[] parts = intMicros.divideAndRemainder(BigInteger
282: .valueOf(1000000));
283: long secs = parts[0].longValue() * (sdv.negative ? -1 : +1);
284: // done this way to avoid overflow
285: sdv.days = (int) (secs / (24L * 60L * 60L));
286: sdv.seconds = (int) (secs % (24L * 60L * 60L));
287: sdv.microseconds = parts[1].intValue()
288: * (sdv.negative ? -1 : +1);
289: sdv.normalize();
290: return sdv;
291: }
292:
293: /**
294: * Construct a duration value as a number of milliseconds.
295: */
296:
297: public static SecondsDurationValue fromMilliseconds(
298: long milliseconds) throws XPathException {
299: SecondsDurationValue sdv = new SecondsDurationValue();
300: sdv.negative = (milliseconds < 0);
301: milliseconds = Math.abs(milliseconds);
302: long seconds = milliseconds / 1000;
303: sdv.days = (int) (seconds / (3600 * 24));
304: sdv.seconds = (int) (seconds % (3600 * 24));
305: sdv.microseconds = (int) (milliseconds % 1000) * 1000;
306: sdv.normalize();
307: return sdv;
308: }
309:
310: /**
311: * Construct a duration value as a number of microseconds.
312: */
313:
314: public static SecondsDurationValue fromMicroseconds(
315: long microseconds) throws XPathException {
316: SecondsDurationValue sdv = new SecondsDurationValue();
317: sdv.negative = (microseconds < 0);
318: microseconds = Math.abs(microseconds);
319: long seconds = microseconds / 1000000L;
320: sdv.days = (int) (seconds / (3600 * 24));
321: sdv.seconds = (int) (seconds % (3600 * 24));
322: sdv.microseconds = (int) (microseconds % 1000000L);
323: sdv.normalize();
324: return sdv;
325: }
326:
327: /**
328: * Multiply duration by a number
329: */
330:
331: public DurationValue multiply(double n, XPathContext context)
332: throws XPathException {
333: double m = (double) getLengthInMicroseconds();
334: double product = n * m;
335: if (Double.isInfinite(product) || product > Long.MAX_VALUE
336: || product < Long.MIN_VALUE) {
337: DynamicError err = new DynamicError(
338: "Overflow when multiplying/dividing a duration by a number");
339: err.setErrorCode("FODT0002");
340: err.setXPathContext(context);
341: throw err;
342: }
343: return fromMicroseconds((long) product);
344: }
345:
346: /**
347: * Find the ratio between two durations
348: * @param other the dividend
349: * @return the ratio, as a decimal
350: * @throws XPathException
351: */
352: public DecimalValue divide(DurationValue other, XPathContext context)
353: throws XPathException {
354: if (other instanceof SecondsDurationValue) {
355: BigDecimal v1 = new BigDecimal(this
356: .getLengthInMicroseconds());
357: BigDecimal v2 = new BigDecimal(
358: ((SecondsDurationValue) other)
359: .getLengthInMicroseconds());
360: return new DecimalValue(v1.divide(v2, 20,
361: BigDecimal.ROUND_HALF_EVEN));
362: } else {
363: throw new DynamicError(
364: "Cannot divide two durations of different type");
365: }
366: }
367:
368: /**
369: * Add two dayTimeDurations
370: */
371:
372: public DurationValue add(DurationValue other, XPathContext context)
373: throws XPathException {
374: if (other instanceof SecondsDurationValue) {
375: return fromMicroseconds(this .getLengthInMicroseconds()
376: + ((SecondsDurationValue) other)
377: .getLengthInMicroseconds());
378: } else {
379: throw new DynamicError(
380: "Cannot add two durations of different type");
381: }
382: }
383:
384: /**
385: * Subtract two dayTime-durations
386: */
387:
388: public DurationValue subtract(DurationValue other,
389: XPathContext context) throws XPathException {
390: if (other instanceof SecondsDurationValue) {
391: return fromMicroseconds(this .getLengthInMicroseconds()
392: - ((SecondsDurationValue) other)
393: .getLengthInMicroseconds());
394: } else {
395: throw new DynamicError(
396: "Cannot add two durations of different type");
397: }
398: }
399:
400: /**
401: * Compare the value to another duration value
402: * @param other The other dateTime value
403: * @return negative value if this one is the earler, 0 if they are chronologically equal,
404: * positive value if this one is the later. For this purpose, dateTime values with an unknown
405: * timezone are considered to be UTC values (the Comparable interface requires
406: * a total ordering).
407: * @throws ClassCastException if the other value is not a DateTimeValue (the parameter
408: * is declared as Object to satisfy the Comparable interface)
409: */
410:
411: public int compareTo(Object other) {
412: if (other instanceof SecondsDurationValue) {
413: long diff = this .getLengthInMicroseconds()
414: - ((SecondsDurationValue) other)
415: .getLengthInMicroseconds();
416: if (diff < 0) {
417: return -1;
418: } else if (diff > 0) {
419: return +1;
420: } else {
421: return 0;
422: }
423: } else {
424: throw new ClassCastException(
425: "Cannot compare a dayTimeDuration to an object of class "
426: + other.getClass());
427: }
428: }
429:
430: /**
431: * Determine the data type of the exprssion
432: * @return Type.DAY_TIME_DURATION,
433: * @param th
434: */
435:
436: public ItemType getItemType(TypeHierarchy th) {
437: return Type.DAY_TIME_DURATION_TYPE;
438: }
439:
440: /**
441: * Convert to Java object (for passing to external functions)
442: */
443:
444: public Object convertToJava(Class target, XPathContext context)
445: throws XPathException {
446: if (target.isAssignableFrom(DurationValue.class)) {
447: return this ;
448: } else if (target == String.class
449: || target == CharSequence.class) {
450: return getStringValue();
451: } else if (target == Object.class) {
452: return getStringValue();
453: } else {
454: throw new DynamicError("Conversion of dayTimeDuration to "
455: + target.getName() + " is not supported");
456: }
457: }
458:
459: }
460:
461: //
462: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
463: // you may not use this file except in compliance with the License. You may obtain a copy of the
464: // License at http://www.mozilla.org/MPL/
465: //
466: // Software distributed under the License is distributed on an "AS IS" basis,
467: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
468: // See the License for the specific language governing rights and limitations under the License.
469: //
470: // The Original Code is: all this file.
471: //
472: // The Initial Developer of the Original Code is Michael H. Kay
473: //
474: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
475: //
476: // Contributor(s): none.
477: //
|