001: package gnu.math;
002:
003: import java.io.*;
004:
005: public class Duration extends Quantity implements Externalizable {
006: public Unit unit;
007:
008: /** Number of whole months. May be negative. */
009: int months;
010:
011: /** Does not include any leap seconds.
012: * I.e. @code{sign * ((24 * days + hours) * 60 + minutes) * 60 + seconds},
013: * where {@code hours >= 0 && hours < 24 && minutes >= 0 && minutes < 60
014: * && secconds >= 0 && minutes > 60}.
015: */
016: long seconds;
017:
018: /** Number of nanoseconds.
019: * We could possibly include leap seconds in here. */
020: int nanos;
021:
022: public static Duration make(int months, long seconds, int nanos,
023: Unit unit) {
024: Duration d = new Duration();
025: d.months = months;
026: d.seconds = seconds;
027: d.nanos = nanos;
028: d.unit = unit;
029: return d;
030: }
031:
032: public static Duration makeMonths(int months) {
033: Duration d = new Duration();
034: d.unit = Unit.month;
035: d.months = months;
036: return d;
037: }
038:
039: public static Duration makeMinutes(int minutes) {
040: Duration d = new Duration();
041: d.unit = Unit.second;
042: d.seconds = 60 * minutes;
043: return d;
044: }
045:
046: public static Duration parse(String str, Unit unit) {
047: Duration d = Duration.valueOf(str, unit);
048: if (d == null)
049: throw new IllegalArgumentException("not a valid "
050: + unit.getName() + " duration: '" + str + "'");
051: return d;
052: }
053:
054: public static Duration parseDuration(String str) {
055: return parse(str, Unit.duration);
056: }
057:
058: public static Duration parseYearMonthDuration(String str) {
059: return parse(str, Unit.month);
060: }
061:
062: public static Duration parseDayTimeDuration(String str) {
063: return parse(str, Unit.second);
064: }
065:
066: /** Parse a duration lexical value as specified by XML Schama.
067: * Return null if invalid syntax.
068: */
069: public static Duration valueOf(String str, Unit unit) {
070: str = str.trim();
071: int pos = 0;
072: int len = str.length();
073: boolean negative;
074: if (pos < len && str.charAt(pos) == '-') {
075: negative = true;
076: pos++;
077: } else
078: negative = false;
079: if (pos + 1 >= len || str.charAt(pos) != 'P')
080: return null;
081: pos++;
082: int months = 0, nanos = 0;
083: long seconds = 0;
084: long part = scanPart(str, pos);
085: pos = ((int) part) >> 16;
086: char ch = (char) part;
087: if (unit == Unit.second && (ch == 'Y' || ch == 'M'))
088: return null;
089: if (ch == 'Y') {
090: months = 12 * (int) (part >> 32);
091: pos = ((int) part) >> 16;
092: part = scanPart(str, pos);
093: ch = (char) part;
094: }
095: if (ch == 'M') {
096: months += (part >> 32);
097: pos = ((int) part) >> 16;
098: part = scanPart(str, pos);
099: ch = (char) part;
100: }
101: if (unit == Unit.month && pos != len)
102: return null;
103: if (ch == 'D') {
104: if (unit == Unit.month)
105: return null;
106: seconds = (long) (24 * 60 * 60) * (int) (part >> 32);
107: pos = ((int) part) >> 16;
108: part = scanPart(str, pos);
109: }
110: if (part != (pos << 16))
111: return null;
112: if (pos == len) {
113: // No time part
114: } else if (str.charAt(pos) != 'T' || ++pos == len)
115: return null;
116: else // saw 'T'
117: {
118: if (unit == Unit.month)
119: return null;
120: part = scanPart(str, pos);
121: ch = (char) part;
122: if (ch == 'H') {
123: seconds += (60 * 60) * (int) (part >> 32);
124: pos = ((int) part) >> 16;
125: part = scanPart(str, pos);
126: ch = (char) part;
127: }
128: if (ch == 'M') {
129: seconds += 60 * (int) (part >> 32);
130: pos = ((int) part) >> 16;
131: part = scanPart(str, pos);
132: ch = (char) part;
133: }
134: if (ch == 'S' || ch == '.') {
135: seconds += (int) (part >> 32);
136: pos = ((int) part) >> 16;
137: }
138: if (ch == '.' && pos + 1 < len
139: && Character.digit(str.charAt(pos), 10) >= 0) {
140: int nfrac = 0;
141: for (; pos < len; nfrac++) {
142: ch = str.charAt(pos++);
143: int dig = Character.digit(ch, 10);
144: if (dig < 0)
145: break;
146: if (nfrac < 9)
147: nanos = 10 * nanos + dig;
148: else if (nfrac == 9 && dig >= 5)
149: nanos++;
150: }
151: while (nfrac++ < 9)
152: nanos = 10 * nanos;
153: if (ch != 'S')
154: return null;
155: }
156: }
157: if (pos != len)
158: return null;
159: Duration d = new Duration();
160: if (negative) {
161: months = -months;
162: seconds = -seconds;
163: nanos = -nanos;
164: }
165: d.months = months;
166: d.seconds = seconds;
167: d.nanos = nanos;
168: d.unit = unit;
169: return d;
170: }
171:
172: public Numeric add(Object y, int k) {
173: if (y instanceof Duration)
174: return Duration.add(this , (Duration) y, k);
175: if (y instanceof DateTime && k == 1)
176: return DateTime.add((DateTime) y, this , 1);
177: throw new IllegalArgumentException();
178: }
179:
180: public Numeric mul(Object y) {
181: if (y instanceof RealNum)
182: return Duration.times(this , ((RealNum) y).doubleValue());
183: return ((Numeric) y).mulReversed(this );
184: }
185:
186: public Numeric mulReversed(Numeric x) {
187: if (!(x instanceof RealNum))
188: throw new IllegalArgumentException();
189: return Duration.times(this , ((RealNum) x).doubleValue());
190: }
191:
192: public static double div(Duration dur1, Duration dur2) {
193: int months1 = dur1.months;
194: int months2 = dur2.months;
195: double sec1 = (double) dur1.seconds + dur1.nanos * 0.000000001;
196: double sec2 = (double) dur2.seconds + dur1.nanos * 0.000000001;
197: if (months2 == 0 && sec2 == 0)
198: throw new ArithmeticException("divide duration by zero");
199: if (months2 == 0) {
200: if (months1 == 0)
201: return sec1 / sec2;
202: } else if (sec2 == 0) {
203: if (sec1 == 0)
204: return (double) months1 / (double) months2;
205: }
206: throw new ArithmeticException(
207: "divide of incompatible durations");
208: }
209:
210: public Numeric div(Object y) {
211: if (y instanceof RealNum) {
212: double dy = ((RealNum) y).doubleValue();
213: if (dy == 0 || Double.isNaN(dy))
214: throw new ArithmeticException(
215: "divide of duration by 0 or NaN");
216: return Duration.times(this , 1.0 / dy);
217: }
218: if (y instanceof Duration)
219: return new DFloNum(div(this , (Duration) y));
220: return ((Numeric) y).divReversed(this );
221: }
222:
223: public static Duration add(Duration x, Duration y, int k) {
224: long months = (long) x.months + k * (long) y.months;
225: // FIXME does not handle leap-seconds represented as multiples of
226: // 10^9 in the nanos field.
227: long nanos = x.seconds * 1000000000L + (long) x.nanos + k
228: * (y.seconds * 1000000000L + y.nanos);
229: // FIXME check for overflow
230: // FIXME handle inconsistent signs.
231: Duration d = new Duration();
232: d.months = (int) months;
233: d.seconds = (int) (nanos / 1000000000L);
234: d.nanos = (int) (nanos % 1000000000L);
235: if (x.unit != y.unit || x.unit == Unit.duration)
236: throw new ArithmeticException(
237: "cannot add these duration types");
238: d.unit = x.unit;
239: return d;
240: }
241:
242: public static Duration times(Duration x, double y) {
243: if (x.unit == Unit.duration)
244: throw new IllegalArgumentException(
245: "cannot multiply general duration");
246: double months = x.months * y;
247: if (Double.isInfinite(months) || Double.isNaN(months))
248: throw new ArithmeticException(
249: "overflow/NaN when multiplying a duration");
250: double nanos = (x.seconds * 1000000000L + x.nanos) * y;
251: Duration d = new Duration();
252: d.months = (int) Math.floor(months + 0.5);
253: d.seconds = (int) (nanos / 1000000000L);
254: d.nanos = (int) (nanos % 1000000000L);
255: d.unit = x.unit;
256: return d;
257: }
258:
259: public static int compare(Duration x, Duration y) {
260: long months = (long) x.months - (long) y.months;
261: long nanos = x.seconds * 1000000000L + (long) x.nanos
262: - (y.seconds * 1000000000L + y.nanos);
263: if (months < 0 && nanos <= 0)
264: return -1;
265: if (months > 0 && nanos >= 0)
266: return 1;
267: if (months == 0)
268: return nanos < 0 ? -1 : nanos > 0 ? 1 : 0;
269: return -2;
270: }
271:
272: public int compare(Object obj) {
273: if (obj instanceof Duration)
274: return compare(this , (Duration) obj);
275: // Could also compare other Quanties if units match appropriately. FIXME.
276: throw new IllegalArgumentException();
277: }
278:
279: public String toString() {
280: StringBuffer sbuf = new StringBuffer();
281: int m = months;
282: long s = seconds;
283: int n = nanos;
284: boolean neg = m < 0 || s < 0 || n < 0;
285: if (neg) {
286: m = -m;
287: s = -s;
288: n = -n;
289: sbuf.append('-');
290: }
291: sbuf.append('P');
292: int y = m / 12;
293: if (y != 0) {
294: sbuf.append(y);
295: sbuf.append('Y');
296: m -= y * 12;
297: }
298: if (m != 0) {
299: sbuf.append(m);
300: sbuf.append('M');
301: }
302: long d = s / (24 * 60 * 60);
303: if (d != 0) {
304: sbuf.append(d);
305: sbuf.append('D');
306: s -= 24 * 60 * 60 * d;
307: }
308: if (s != 0 || n != 0) {
309: sbuf.append('T');
310: long hr = s / (60 * 60);
311: if (hr != 0) {
312: sbuf.append(hr);
313: sbuf.append('H');
314: s -= 60 * 60 * hr;
315: }
316: long mn = s / 60;
317: if (mn != 0) {
318: sbuf.append(mn);
319: sbuf.append('M');
320: s -= 60 * mn;
321: }
322: if (s != 0 || n != 0) {
323: sbuf.append(s);
324: appendNanoSeconds(n, sbuf);
325: sbuf.append('S');
326: }
327: } else if (sbuf.length() == 1)
328: sbuf.append(unit == Unit.month ? "0M" : "T0S");
329: return sbuf.toString();
330: }
331:
332: static void appendNanoSeconds(int nanoSeconds, StringBuffer sbuf) {
333: if (nanoSeconds == 0)
334: return;
335: sbuf.append('.');
336: int pos = sbuf.length();
337: sbuf.append(nanoSeconds);
338: int len = sbuf.length();
339: int pad = pos + 9 - len;
340: while (--pad >= 0)
341: sbuf.insert(pos, '0');
342: len = pos + 9;
343: do {
344: --len;
345: } while (sbuf.charAt(len) == '0');
346: sbuf.setLength(len + 1);
347: }
348:
349: /** Parse digits following by a terminator char
350: * @return {@code (VALUE << 32)|(FOLLOWING_POS<<16)|FOLLOWING_CHAR}.
351: * If there are no digits return @code{START<<16}.
352: * Otherwise, on overflow or digits followed by end-of-string, return -1.
353: */
354: private static long scanPart(String str, int start) {
355: int i = start;
356: long val = -1;
357: int len = str.length();
358: while (i < len) {
359: char ch = str.charAt(i);
360: i++;
361: int dig = Character.digit(ch, 10);
362: if (dig < 0) {
363: if (val < 0)
364: return start << 16;
365: return (val << 32) | (i << 16) | ((int) ch);
366: }
367: val = val < 0 ? dig : 10 * val + dig;
368: if (val > Integer.MAX_VALUE)
369: return -1; // overflow
370: }
371: return val < 0 ? (start << 16) : -1;
372: }
373:
374: /** The number of years in the canonical representation. */
375: public int getYears() {
376: return months / 12;
377: }
378:
379: public int getMonths() {
380: return months % 12;
381: }
382:
383: public int getDays() {
384: return (int) (seconds / (24 * 60 * 60));
385: }
386:
387: public int getHours() {
388: return (int) ((seconds / (60 * 60)) % 24);
389: }
390:
391: public int getMinutes() {
392: return (int) ((seconds / 60) % 60);
393: }
394:
395: public int getSecondsOnly() {
396: return (int) (seconds % 60);
397: }
398:
399: public int getNanoSecondsOnly() {
400: return nanos;
401: }
402:
403: public int getTotalMonths() {
404: return months;
405: }
406:
407: public long getTotalSeconds() {
408: return seconds;
409: }
410:
411: public long getTotalMinutes() {
412: return seconds / 60;
413: }
414:
415: public long getNanoSeconds() {
416: return seconds * 1000000000L + nanos;
417: }
418:
419: public boolean isZero() {
420: return months == 0 && seconds == 0 && nanos == 0;
421: }
422:
423: public boolean isExact() {
424: return false;
425: }
426:
427: public void writeExternal(ObjectOutput out) throws IOException {
428: out.writeInt(months);
429: out.writeLong(seconds);
430: out.writeInt(nanos);
431: out.writeObject(unit);
432: }
433:
434: public void readExternal(ObjectInput in) throws IOException,
435: ClassNotFoundException {
436: months = in.readInt();
437: seconds = in.readLong();
438: nanos = in.readInt();
439: unit = (Unit) in.readObject();
440: }
441:
442: public Unit unit() {
443: return unit;
444: }
445:
446: public Complex number() {
447: throw new Error("number needs to be implemented!");
448: }
449:
450: public int hashCode() {
451: return months ^ (int) seconds ^ nanos;
452: }
453:
454: /** Compare for equality.
455: * Ignores unit.
456: */
457: public static boolean equals(Duration x, Duration y) {
458: return x.months == y.months && x.seconds == y.seconds
459: && x.nanos == y.nanos;
460: }
461:
462: /** Compare for equality.
463: * Ignores unit.
464: */
465: public boolean equals(Object obj) {
466: if (obj == null || !(obj instanceof Duration))
467: return false;
468: return Duration.equals(this , (Duration) obj);
469: }
470: }
|