1
16 package org.joda.time.chrono;
17
18 import java.util.HashMap;
19 import java.util.Locale;
20
21 import org.joda.time.Chronology;
22 import org.joda.time.DateTimeConstants;
23 import org.joda.time.DateTimeField;
24 import org.joda.time.DateTimeZone;
25 import org.joda.time.DurationField;
26 import org.joda.time.IllegalFieldValueException;
27 import org.joda.time.IllegalInstantException;
28 import org.joda.time.ReadablePartial;
29 import org.joda.time.field.BaseDateTimeField;
30 import org.joda.time.field.BaseDurationField;
31
32
41 public final class ZonedChronology extends AssembledChronology {
42
43
44 private static final long serialVersionUID = -1079258847191166848L;
45
46
54 public static ZonedChronology getInstance(Chronology base, DateTimeZone zone) {
55 if (base == null) {
56 throw new IllegalArgumentException("Must supply a chronology");
57 }
58 base = base.withUTC();
59 if (base == null) {
60 throw new IllegalArgumentException("UTC chronology must not be null");
61 }
62 if (zone == null) {
63 throw new IllegalArgumentException("DateTimeZone must not be null");
64 }
65 return new ZonedChronology(base, zone);
66 }
67
68 static boolean useTimeArithmetic(DurationField field) {
69
70
71 return field != null && field.getUnitMillis() < DateTimeConstants.MILLIS_PER_HOUR * 12;
72 }
73
74
80 private ZonedChronology(Chronology base, DateTimeZone zone) {
81 super(base, zone);
82 }
83
84 public DateTimeZone getZone() {
85 return (DateTimeZone)getParam();
86 }
87
88 public Chronology withUTC() {
89 return getBase();
90 }
91
92 public Chronology withZone(DateTimeZone zone) {
93 if (zone == null) {
94 zone = DateTimeZone.getDefault();
95 }
96 if (zone == getParam()) {
97 return this;
98 }
99 if (zone == DateTimeZone.UTC) {
100 return getBase();
101 }
102 return new ZonedChronology(getBase(), zone);
103 }
104
105 public long getDateTimeMillis(int year, int monthOfYear, int dayOfMonth,
106 int millisOfDay)
107 throws IllegalArgumentException
108 {
109 return localToUTC(getBase().getDateTimeMillis
110 (year, monthOfYear, dayOfMonth, millisOfDay));
111 }
112
113 public long getDateTimeMillis(int year, int monthOfYear, int dayOfMonth,
114 int hourOfDay, int minuteOfHour,
115 int secondOfMinute, int millisOfSecond)
116 throws IllegalArgumentException
117 {
118 return localToUTC(getBase().getDateTimeMillis
119 (year, monthOfYear, dayOfMonth,
120 hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond));
121 }
122
123 public long getDateTimeMillis(long instant,
124 int hourOfDay, int minuteOfHour,
125 int secondOfMinute, int millisOfSecond)
126 throws IllegalArgumentException
127 {
128 return localToUTC(getBase().getDateTimeMillis
129 (instant + getZone().getOffset(instant),
130 hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond));
131 }
132
133
137 private long localToUTC(long localInstant) {
138 DateTimeZone zone = getZone();
139 int offset = zone.getOffsetFromLocal(localInstant);
140 long utcInstant = localInstant - offset;
141 int offsetBasedOnUtc = zone.getOffset(utcInstant);
142 if (offset != offsetBasedOnUtc) {
143 throw new IllegalInstantException(localInstant, zone.getID());
144 }
145 return utcInstant;
146 }
147
148 protected void assemble(Fields fields) {
149
150
151 HashMap<Object, Object> converted = new HashMap<Object, Object>();
152
153
154
155 fields.eras = convertField(fields.eras, converted);
156 fields.centuries = convertField(fields.centuries, converted);
157 fields.years = convertField(fields.years, converted);
158 fields.months = convertField(fields.months, converted);
159 fields.weekyears = convertField(fields.weekyears, converted);
160 fields.weeks = convertField(fields.weeks, converted);
161 fields.days = convertField(fields.days, converted);
162
163 fields.halfdays = convertField(fields.halfdays, converted);
164 fields.hours = convertField(fields.hours, converted);
165 fields.minutes = convertField(fields.minutes, converted);
166 fields.seconds = convertField(fields.seconds, converted);
167 fields.millis = convertField(fields.millis, converted);
168
169
170
171 fields.year = convertField(fields.year, converted);
172 fields.yearOfEra = convertField(fields.yearOfEra, converted);
173 fields.yearOfCentury = convertField(fields.yearOfCentury, converted);
174 fields.centuryOfEra = convertField(fields.centuryOfEra, converted);
175 fields.era = convertField(fields.era, converted);
176 fields.dayOfWeek = convertField(fields.dayOfWeek, converted);
177 fields.dayOfMonth = convertField(fields.dayOfMonth, converted);
178 fields.dayOfYear = convertField(fields.dayOfYear, converted);
179 fields.monthOfYear = convertField(fields.monthOfYear, converted);
180 fields.weekOfWeekyear = convertField(fields.weekOfWeekyear, converted);
181 fields.weekyear = convertField(fields.weekyear, converted);
182 fields.weekyearOfCentury = convertField(fields.weekyearOfCentury, converted);
183
184 fields.millisOfSecond = convertField(fields.millisOfSecond, converted);
185 fields.millisOfDay = convertField(fields.millisOfDay, converted);
186 fields.secondOfMinute = convertField(fields.secondOfMinute, converted);
187 fields.secondOfDay = convertField(fields.secondOfDay, converted);
188 fields.minuteOfHour = convertField(fields.minuteOfHour, converted);
189 fields.minuteOfDay = convertField(fields.minuteOfDay, converted);
190 fields.hourOfDay = convertField(fields.hourOfDay, converted);
191 fields.hourOfHalfday = convertField(fields.hourOfHalfday, converted);
192 fields.clockhourOfDay = convertField(fields.clockhourOfDay, converted);
193 fields.clockhourOfHalfday = convertField(fields.clockhourOfHalfday, converted);
194 fields.halfdayOfDay = convertField(fields.halfdayOfDay, converted);
195 }
196
197 private DurationField convertField(DurationField field, HashMap<Object, Object> converted) {
198 if (field == null || !field.isSupported()) {
199 return field;
200 }
201 if (converted.containsKey(field)) {
202 return (DurationField)converted.get(field);
203 }
204 ZonedDurationField zonedField = new ZonedDurationField(field, getZone());
205 converted.put(field, zonedField);
206 return zonedField;
207 }
208
209 private DateTimeField convertField(DateTimeField field, HashMap<Object, Object> converted) {
210 if (field == null || !field.isSupported()) {
211 return field;
212 }
213 if (converted.containsKey(field)) {
214 return (DateTimeField)converted.get(field);
215 }
216 ZonedDateTimeField zonedField =
217 new ZonedDateTimeField(field, getZone(),
218 convertField(field.getDurationField(), converted),
219 convertField(field.getRangeDurationField(), converted),
220 convertField(field.getLeapDurationField(), converted));
221 converted.put(field, zonedField);
222 return zonedField;
223 }
224
225
226
234 public boolean equals(Object obj) {
235 if (this == obj) {
236 return true;
237 }
238 if (obj instanceof ZonedChronology == false) {
239 return false;
240 }
241 ZonedChronology chrono = (ZonedChronology) obj;
242 return
243 getBase().equals(chrono.getBase()) &&
244 getZone().equals(chrono.getZone());
245 }
246
247
253 public int hashCode() {
254 return 326565 + getZone().hashCode() * 11 + getBase().hashCode() * 7;
255 }
256
257
262 public String toString() {
263 return "ZonedChronology[" + getBase() + ", " + getZone().getID() + ']';
264 }
265
266
267
274 static class ZonedDurationField extends BaseDurationField {
275 private static final long serialVersionUID = -485345310999208286L;
276
277 final DurationField iField;
278 final boolean iTimeField;
279 final DateTimeZone iZone;
280
281 ZonedDurationField(DurationField field, DateTimeZone zone) {
282 super(field.getType());
283 if (!field.isSupported()) {
284 throw new IllegalArgumentException();
285 }
286 iField = field;
287 iTimeField = useTimeArithmetic(field);
288 iZone = zone;
289 }
290
291 public boolean isPrecise() {
292 return iTimeField ? iField.isPrecise() : iField.isPrecise() && this.iZone.isFixed();
293 }
294
295 public long getUnitMillis() {
296 return iField.getUnitMillis();
297 }
298
299 public int getValue(long duration, long instant) {
300 return iField.getValue(duration, addOffset(instant));
301 }
302
303 public long getValueAsLong(long duration, long instant) {
304 return iField.getValueAsLong(duration, addOffset(instant));
305 }
306
307 public long getMillis(int value, long instant) {
308 return iField.getMillis(value, addOffset(instant));
309 }
310
311 public long getMillis(long value, long instant) {
312 return iField.getMillis(value, addOffset(instant));
313 }
314
315 public long add(long instant, int value) {
316 int offset = getOffsetToAdd(instant);
317 instant = iField.add(instant + offset, value);
318 return instant - (iTimeField ? offset : getOffsetFromLocalToSubtract(instant));
319 }
320
321 public long add(long instant, long value) {
322 int offset = getOffsetToAdd(instant);
323 instant = iField.add(instant + offset, value);
324 return instant - (iTimeField ? offset : getOffsetFromLocalToSubtract(instant));
325 }
326
327 public int getDifference(long minuendInstant, long subtrahendInstant) {
328 int offset = getOffsetToAdd(subtrahendInstant);
329 return iField.getDifference
330 (minuendInstant + (iTimeField ? offset : getOffsetToAdd(minuendInstant)),
331 subtrahendInstant + offset);
332 }
333
334 public long getDifferenceAsLong(long minuendInstant, long subtrahendInstant) {
335 int offset = getOffsetToAdd(subtrahendInstant);
336 return iField.getDifferenceAsLong
337 (minuendInstant + (iTimeField ? offset : getOffsetToAdd(minuendInstant)),
338 subtrahendInstant + offset);
339 }
340
341 private int getOffsetToAdd(long instant) {
342 int offset = this.iZone.getOffset(instant);
343 long sum = instant + offset;
344
345 if ((instant ^ sum) < 0 && (instant ^ offset) >= 0) {
346 throw new ArithmeticException("Adding time zone offset caused overflow");
347 }
348 return offset;
349 }
350
351 private int getOffsetFromLocalToSubtract(long instant) {
352 int offset = this.iZone.getOffsetFromLocal(instant);
353 long diff = instant - offset;
354
355 if ((instant ^ diff) < 0 && (instant ^ offset) < 0) {
356 throw new ArithmeticException("Subtracting time zone offset caused overflow");
357 }
358 return offset;
359 }
360
361 private long addOffset(long instant) {
362 return iZone.convertUTCToLocal(instant);
363 }
364
365 @Override
366 public boolean equals(Object obj) {
367 if (this == obj) {
368 return true;
369 } else if (obj instanceof ZonedDurationField) {
370 ZonedDurationField other = (ZonedDurationField) obj;
371 return iField.equals(other.iField) &&
372 iZone.equals(other.iZone);
373 }
374 return false;
375 }
376
377 @Override
378 public int hashCode() {
379 return iField.hashCode() ^ iZone.hashCode();
380 }
381 }
382
383
389 static final class ZonedDateTimeField extends BaseDateTimeField {
390 @SuppressWarnings("unused")
391 private static final long serialVersionUID = -3968986277775529794L;
392
393 final DateTimeField iField;
394 final DateTimeZone iZone;
395 final DurationField iDurationField;
396 final boolean iTimeField;
397 final DurationField iRangeDurationField;
398 final DurationField iLeapDurationField;
399
400 ZonedDateTimeField(DateTimeField field,
401 DateTimeZone zone,
402 DurationField durationField,
403 DurationField rangeDurationField,
404 DurationField leapDurationField) {
405 super(field.getType());
406 if (!field.isSupported()) {
407 throw new IllegalArgumentException();
408 }
409 iField = field;
410 iZone = zone;
411 iDurationField = durationField;
412 iTimeField = useTimeArithmetic(durationField);
413 iRangeDurationField = rangeDurationField;
414 iLeapDurationField = leapDurationField;
415 }
416
417 public boolean isLenient() {
418 return iField.isLenient();
419 }
420
421 public int get(long instant) {
422 long localInstant = iZone.convertUTCToLocal(instant);
423 return iField.get(localInstant);
424 }
425
426 public String getAsText(long instant, Locale locale) {
427 long localInstant = iZone.convertUTCToLocal(instant);
428 return iField.getAsText(localInstant, locale);
429 }
430
431 public String getAsShortText(long instant, Locale locale) {
432 long localInstant = iZone.convertUTCToLocal(instant);
433 return iField.getAsShortText(localInstant, locale);
434 }
435
436 public String getAsText(int fieldValue, Locale locale) {
437 return iField.getAsText(fieldValue, locale);
438 }
439
440 public String getAsShortText(int fieldValue, Locale locale) {
441 return iField.getAsShortText(fieldValue, locale);
442 }
443
444 public long add(long instant, int value) {
445 if (iTimeField) {
446 int offset = getOffsetToAdd(instant);
447 long localInstant = iField.add(instant + offset, value);
448 return localInstant - offset;
449 } else {
450 long localInstant = iZone.convertUTCToLocal(instant);
451 localInstant = iField.add(localInstant, value);
452 return iZone.convertLocalToUTC(localInstant, false, instant);
453 }
454 }
455
456 public long add(long instant, long value) {
457 if (iTimeField) {
458 int offset = getOffsetToAdd(instant);
459 long localInstant = iField.add(instant + offset, value);
460 return localInstant - offset;
461 } else {
462 long localInstant = iZone.convertUTCToLocal(instant);
463 localInstant = iField.add(localInstant, value);
464 return iZone.convertLocalToUTC(localInstant, false, instant);
465 }
466 }
467
468 public long addWrapField(long instant, int value) {
469 if (iTimeField) {
470 int offset = getOffsetToAdd(instant);
471 long localInstant = iField.addWrapField(instant + offset, value);
472 return localInstant - offset;
473 } else {
474 long localInstant = iZone.convertUTCToLocal(instant);
475 localInstant = iField.addWrapField(localInstant, value);
476 return iZone.convertLocalToUTC(localInstant, false, instant);
477 }
478 }
479
480 public long set(long instant, int value) {
481 long localInstant = iZone.convertUTCToLocal(instant);
482 localInstant = iField.set(localInstant, value);
483 long result = iZone.convertLocalToUTC(localInstant, false, instant);
484 if (get(result) != value) {
485 IllegalInstantException cause = new IllegalInstantException(localInstant, iZone.getID());
486 IllegalFieldValueException ex = new IllegalFieldValueException(iField.getType(), Integer.valueOf(value), cause.getMessage());
487 ex.initCause(cause);
488 throw ex;
489 }
490 return result;
491 }
492
493 public long set(long instant, String text, Locale locale) {
494
495 long localInstant = iZone.convertUTCToLocal(instant);
496 localInstant = iField.set(localInstant, text, locale);
497 return iZone.convertLocalToUTC(localInstant, false, instant);
498 }
499
500 public int getDifference(long minuendInstant, long subtrahendInstant) {
501 int offset = getOffsetToAdd(subtrahendInstant);
502 return iField.getDifference
503 (minuendInstant + (iTimeField ? offset : getOffsetToAdd(minuendInstant)),
504 subtrahendInstant + offset);
505 }
506
507 public long getDifferenceAsLong(long minuendInstant, long subtrahendInstant) {
508 int offset = getOffsetToAdd(subtrahendInstant);
509 return iField.getDifferenceAsLong
510 (minuendInstant + (iTimeField ? offset : getOffsetToAdd(minuendInstant)),
511 subtrahendInstant + offset);
512 }
513
514 public final DurationField getDurationField() {
515 return iDurationField;
516 }
517
518 public final DurationField getRangeDurationField() {
519 return iRangeDurationField;
520 }
521
522 public boolean isLeap(long instant) {
523 long localInstant = iZone.convertUTCToLocal(instant);
524 return iField.isLeap(localInstant);
525 }
526
527 public int getLeapAmount(long instant) {
528 long localInstant = iZone.convertUTCToLocal(instant);
529 return iField.getLeapAmount(localInstant);
530 }
531
532 public final DurationField getLeapDurationField() {
533 return iLeapDurationField;
534 }
535
536 public long roundFloor(long instant) {
537 if (iTimeField) {
538 int offset = getOffsetToAdd(instant);
539 instant = iField.roundFloor(instant + offset);
540 return instant - offset;
541 } else {
542 long localInstant = iZone.convertUTCToLocal(instant);
543 localInstant = iField.roundFloor(localInstant);
544 return iZone.convertLocalToUTC(localInstant, false, instant);
545 }
546 }
547
548 public long roundCeiling(long instant) {
549 if (iTimeField) {
550 int offset = getOffsetToAdd(instant);
551 instant = iField.roundCeiling(instant + offset);
552 return instant - offset;
553 } else {
554 long localInstant = iZone.convertUTCToLocal(instant);
555 localInstant = iField.roundCeiling(localInstant);
556 return iZone.convertLocalToUTC(localInstant, false, instant);
557 }
558 }
559
560 public long remainder(long instant) {
561 long localInstant = iZone.convertUTCToLocal(instant);
562 return iField.remainder(localInstant);
563 }
564
565 public int getMinimumValue() {
566 return iField.getMinimumValue();
567 }
568
569 public int getMinimumValue(long instant) {
570 long localInstant = iZone.convertUTCToLocal(instant);
571 return iField.getMinimumValue(localInstant);
572 }
573
574 public int getMinimumValue(ReadablePartial instant) {
575 return iField.getMinimumValue(instant);
576 }
577
578 public int getMinimumValue(ReadablePartial instant, int[] values) {
579 return iField.getMinimumValue(instant, values);
580 }
581
582 public int getMaximumValue() {
583 return iField.getMaximumValue();
584 }
585
586 public int getMaximumValue(long instant) {
587 long localInstant = iZone.convertUTCToLocal(instant);
588 return iField.getMaximumValue(localInstant);
589 }
590
591 public int getMaximumValue(ReadablePartial instant) {
592 return iField.getMaximumValue(instant);
593 }
594
595 public int getMaximumValue(ReadablePartial instant, int[] values) {
596 return iField.getMaximumValue(instant, values);
597 }
598
599 public int getMaximumTextLength(Locale locale) {
600 return iField.getMaximumTextLength(locale);
601 }
602
603 public int getMaximumShortTextLength(Locale locale) {
604 return iField.getMaximumShortTextLength(locale);
605 }
606
607 private int getOffsetToAdd(long instant) {
608 int offset = this.iZone.getOffset(instant);
609 long sum = instant + offset;
610
611 if ((instant ^ sum) < 0 && (instant ^ offset) >= 0) {
612 throw new ArithmeticException("Adding time zone offset caused overflow");
613 }
614 return offset;
615 }
616
617 @Override
618 public boolean equals(Object obj) {
619 if (this == obj) {
620 return true;
621 } else if (obj instanceof ZonedDateTimeField) {
622 ZonedDateTimeField other = (ZonedDateTimeField) obj;
623 return iField.equals(other.iField) &&
624 iZone.equals(other.iZone) &&
625 iDurationField.equals(other.iDurationField) &&
626 iRangeDurationField.equals(other.iRangeDurationField);
627 }
628 return false;
629 }
630
631 @Override
632 public int hashCode() {
633 return iField.hashCode() ^ iZone.hashCode();
634 }
635 }
636
637 }
638