1 | /* |
2 | * Copyright 2001-2005 Stephen Colebourne |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | package org.joda.time.chrono; |
17 | |
18 | import java.util.ArrayList; |
19 | import java.util.HashMap; |
20 | import java.util.Locale; |
21 | import java.util.Map; |
22 | |
23 | import org.joda.time.Chronology; |
24 | import org.joda.time.DateTimeField; |
25 | import org.joda.time.DateTimeUtils; |
26 | import org.joda.time.DateTimeZone; |
27 | import org.joda.time.DurationField; |
28 | import org.joda.time.IllegalFieldValueException; |
29 | import org.joda.time.Instant; |
30 | import org.joda.time.ReadableInstant; |
31 | import org.joda.time.ReadablePartial; |
32 | import org.joda.time.field.BaseDateTimeField; |
33 | import org.joda.time.field.DecoratedDurationField; |
34 | import org.joda.time.format.DateTimeFormatter; |
35 | import org.joda.time.format.ISODateTimeFormat; |
36 | |
37 | /** |
38 | * Implements the Gregorian/Julian calendar system which is the calendar system |
39 | * used in most of the world. Wherever possible, it is recommended to use the |
40 | * {@link ISOChronology} instead. |
41 | * <p> |
42 | * The Gregorian calendar replaced the Julian calendar, and the point in time |
43 | * when this chronology switches can be controlled using the second parameter |
44 | * of the getInstance method. By default this cutover is set to the date the |
45 | * Gregorian calendar was first instituted, October 15, 1582. |
46 | * <p> |
47 | * Before this date, this chronology uses the proleptic Julian calendar |
48 | * (proleptic means extending indefinitely). The Julian calendar has leap years |
49 | * every four years, whereas the Gregorian has special rules for 100 and 400 |
50 | * years. A meaningful result will thus be obtained for all input values. |
51 | * However before 8 CE, Julian leap years were irregular, and before 45 BCE |
52 | * there was no Julian calendar. |
53 | * <p> |
54 | * This chronology differs from |
55 | * {@link java.util.GregorianCalendar GregorianCalendar} in that years |
56 | * in BCE are returned correctly. Thus year 1 BCE is returned as -1 instead of 1. |
57 | * The yearOfEra field produces results compatible with GregorianCalendar. |
58 | * <p> |
59 | * The Julian calendar does not have a year zero, and so year -1 is followed by |
60 | * year 1. If the Gregorian cutover date is specified at or before year -1 |
61 | * (Julian), year zero is defined. In other words, the proleptic Gregorian |
62 | * chronology used by this class has a year zero. |
63 | * <p> |
64 | * To create a pure proleptic Julian chronology, use {@link JulianChronology}, |
65 | * and to create a pure proleptic Gregorian chronology, use |
66 | * {@link GregorianChronology}. |
67 | * <p> |
68 | * GJChronology is thread-safe and immutable. |
69 | * |
70 | * @author Brian S O'Neill |
71 | * @author Stephen Colebourne |
72 | * @since 1.0 |
73 | */ |
74 | public final class GJChronology extends AssembledChronology { |
75 | |
76 | /** Serialization lock */ |
77 | private static final long serialVersionUID = -2545574827706931671L; |
78 | |
79 | /** |
80 | * Convert a datetime from one chronology to another. |
81 | */ |
82 | private static long convertByYear(long instant, Chronology from, Chronology to) { |
83 | return to.getDateTimeMillis |
84 | (from.year().get(instant), |
85 | from.monthOfYear().get(instant), |
86 | from.dayOfMonth().get(instant), |
87 | from.millisOfDay().get(instant)); |
88 | } |
89 | |
90 | /** |
91 | * Convert a datetime from one chronology to another. |
92 | */ |
93 | private static long convertByWeekyear(final long instant, Chronology from, Chronology to) { |
94 | long newInstant; |
95 | newInstant = to.weekyear().set(0, from.weekyear().get(instant)); |
96 | newInstant = to.weekOfWeekyear().set(newInstant, from.weekOfWeekyear().get(instant)); |
97 | newInstant = to.dayOfWeek().set(newInstant, from.dayOfWeek().get(instant)); |
98 | newInstant = to.millisOfDay().set(newInstant, from.millisOfDay().get(instant)); |
99 | return newInstant; |
100 | } |
101 | |
102 | /** |
103 | * The default GregorianJulian cutover point. |
104 | */ |
105 | static final Instant DEFAULT_CUTOVER = new Instant(-12219292800000L); |
106 | |
107 | /** Cache of zone to chronology list */ |
108 | private static final Map cCache = new HashMap(); |
109 | |
110 | /** |
111 | * Factory method returns instances of the default GJ cutover |
112 | * chronology. This uses a cutover date of October 15, 1582 (Gregorian) |
113 | * 00:00:00 UTC. For this value, October 4, 1582 (Julian) is followed by |
114 | * October 15, 1582 (Gregorian). |
115 | * |
116 | * <p>The first day of the week is designated to be |
117 | * {@link org.joda.time.DateTimeConstants#MONDAY Monday}, |
118 | * and the minimum days in the first week of the year is 4. |
119 | * |
120 | * <p>The time zone of the returned instance is UTC. |
121 | */ |
122 | public static GJChronology getInstanceUTC() { |
123 | return getInstance(DateTimeZone.UTC, DEFAULT_CUTOVER, 4); |
124 | } |
125 | |
126 | /** |
127 | * Factory method returns instances of the default GJ cutover |
128 | * chronology. This uses a cutover date of October 15, 1582 (Gregorian) |
129 | * 00:00:00 UTC. For this value, October 4, 1582 (Julian) is followed by |
130 | * October 15, 1582 (Gregorian). |
131 | * |
132 | * <p>The first day of the week is designated to be |
133 | * {@link org.joda.time.DateTimeConstants#MONDAY Monday}, |
134 | * and the minimum days in the first week of the year is 4. |
135 | * |
136 | * <p>The returned chronology is in the default time zone. |
137 | */ |
138 | public static GJChronology getInstance() { |
139 | return getInstance(DateTimeZone.getDefault(), DEFAULT_CUTOVER, 4); |
140 | } |
141 | |
142 | /** |
143 | * Factory method returns instances of the GJ cutover chronology. This uses |
144 | * a cutover date of October 15, 1582 (Gregorian) 00:00:00 UTC. For this |
145 | * value, October 4, 1582 (Julian) is followed by October 15, 1582 |
146 | * (Gregorian). |
147 | * |
148 | * <p>The first day of the week is designated to be |
149 | * {@link org.joda.time.DateTimeConstants#MONDAY Monday}, |
150 | * and the minimum days in the first week of the year is 4. |
151 | * |
152 | * @param zone the time zone to use, null is default |
153 | */ |
154 | public static GJChronology getInstance(DateTimeZone zone) { |
155 | return getInstance(zone, DEFAULT_CUTOVER, 4); |
156 | } |
157 | |
158 | /** |
159 | * Factory method returns instances of the GJ cutover chronology. Any |
160 | * cutover date may be specified. |
161 | * |
162 | * <p>The first day of the week is designated to be |
163 | * {@link org.joda.time.DateTimeConstants#MONDAY Monday}, |
164 | * and the minimum days in the first week of the year is 4. |
165 | * |
166 | * @param zone the time zone to use, null is default |
167 | * @param gregorianCutover the cutover to use, null means default |
168 | */ |
169 | public static GJChronology getInstance( |
170 | DateTimeZone zone, |
171 | ReadableInstant gregorianCutover) { |
172 | |
173 | return getInstance(zone, gregorianCutover, 4); |
174 | } |
175 | |
176 | /** |
177 | * Factory method returns instances of the GJ cutover chronology. Any |
178 | * cutover date may be specified. |
179 | * |
180 | * @param zone the time zone to use, null is default |
181 | * @param gregorianCutover the cutover to use, null means default |
182 | * @param minDaysInFirstWeek minimum number of days in first week of the year; default is 4 |
183 | */ |
184 | public static synchronized GJChronology getInstance( |
185 | DateTimeZone zone, |
186 | ReadableInstant gregorianCutover, |
187 | int minDaysInFirstWeek) { |
188 | |
189 | zone = DateTimeUtils.getZone(zone); |
190 | Instant cutoverInstant; |
191 | if (gregorianCutover == null) { |
192 | cutoverInstant = DEFAULT_CUTOVER; |
193 | } else { |
194 | cutoverInstant = gregorianCutover.toInstant(); |
195 | } |
196 | |
197 | GJChronology chrono; |
198 | |
199 | ArrayList chronos = (ArrayList)cCache.get(zone); |
200 | if (chronos == null) { |
201 | chronos = new ArrayList(2); |
202 | cCache.put(zone, chronos); |
203 | } else { |
204 | for (int i=chronos.size(); --i>=0; ) { |
205 | chrono = (GJChronology)chronos.get(i); |
206 | if (minDaysInFirstWeek == chrono.getMinimumDaysInFirstWeek() && |
207 | cutoverInstant.equals(chrono.getGregorianCutover())) { |
208 | |
209 | return chrono; |
210 | } |
211 | } |
212 | } |
213 | |
214 | if (zone == DateTimeZone.UTC) { |
215 | chrono = new GJChronology |
216 | (JulianChronology.getInstance(zone, minDaysInFirstWeek), |
217 | GregorianChronology.getInstance(zone, minDaysInFirstWeek), |
218 | cutoverInstant); |
219 | } else { |
220 | chrono = getInstance(DateTimeZone.UTC, cutoverInstant, minDaysInFirstWeek); |
221 | chrono = new GJChronology |
222 | (ZonedChronology.getInstance(chrono, zone), |
223 | chrono.iJulianChronology, |
224 | chrono.iGregorianChronology, |
225 | chrono.iCutoverInstant); |
226 | } |
227 | |
228 | chronos.add(chrono); |
229 | |
230 | return chrono; |
231 | } |
232 | |
233 | /** |
234 | * Factory method returns instances of the GJ cutover chronology. Any |
235 | * cutover date may be specified. |
236 | * |
237 | * @param zone the time zone to use, null is default |
238 | * @param gregorianCutover the cutover to use |
239 | * @param minDaysInFirstWeek minimum number of days in first week of the year; default is 4 |
240 | */ |
241 | public static GJChronology getInstance( |
242 | DateTimeZone zone, |
243 | long gregorianCutover, |
244 | int minDaysInFirstWeek) { |
245 | |
246 | Instant cutoverInstant; |
247 | if (gregorianCutover == DEFAULT_CUTOVER.getMillis()) { |
248 | cutoverInstant = null; |
249 | } else { |
250 | cutoverInstant = new Instant(gregorianCutover); |
251 | } |
252 | return getInstance(zone, cutoverInstant, minDaysInFirstWeek); |
253 | } |
254 | |
255 | //----------------------------------------------------------------------- |
256 | private JulianChronology iJulianChronology; |
257 | private GregorianChronology iGregorianChronology; |
258 | private Instant iCutoverInstant; |
259 | |
260 | private long iCutoverMillis; |
261 | private long iGapDuration; |
262 | |
263 | /** |
264 | * @param julian chronology used before the cutover instant |
265 | * @param gregorian chronology used at and after the cutover instant |
266 | * @param cutoverInstant instant when the gregorian chronology began |
267 | */ |
268 | private GJChronology(JulianChronology julian, |
269 | GregorianChronology gregorian, |
270 | Instant cutoverInstant) { |
271 | super(null, new Object[] {julian, gregorian, cutoverInstant}); |
272 | } |
273 | |
274 | /** |
275 | * Called when applying a time zone. |
276 | */ |
277 | private GJChronology(Chronology base, |
278 | JulianChronology julian, |
279 | GregorianChronology gregorian, |
280 | Instant cutoverInstant) { |
281 | super(base, new Object[] {julian, gregorian, cutoverInstant}); |
282 | } |
283 | |
284 | /** |
285 | * Serialization singleton |
286 | */ |
287 | private Object readResolve() { |
288 | return getInstance(getZone(), iCutoverInstant, getMinimumDaysInFirstWeek()); |
289 | } |
290 | |
291 | public DateTimeZone getZone() { |
292 | Chronology base; |
293 | if ((base = getBase()) != null) { |
294 | return base.getZone(); |
295 | } |
296 | return DateTimeZone.UTC; |
297 | } |
298 | |
299 | // Conversion |
300 | //----------------------------------------------------------------------- |
301 | /** |
302 | * Gets the Chronology in the UTC time zone. |
303 | * |
304 | * @return the chronology in UTC |
305 | */ |
306 | public Chronology withUTC() { |
307 | return withZone(DateTimeZone.UTC); |
308 | } |
309 | |
310 | /** |
311 | * Gets the Chronology in a specific time zone. |
312 | * |
313 | * @param zone the zone to get the chronology in, null is default |
314 | * @return the chronology |
315 | */ |
316 | public Chronology withZone(DateTimeZone zone) { |
317 | if (zone == null) { |
318 | zone = DateTimeZone.getDefault(); |
319 | } |
320 | if (zone == getZone()) { |
321 | return this; |
322 | } |
323 | return getInstance(zone, iCutoverInstant, getMinimumDaysInFirstWeek()); |
324 | } |
325 | |
326 | public long getDateTimeMillis(int year, int monthOfYear, int dayOfMonth, |
327 | int millisOfDay) |
328 | throws IllegalArgumentException |
329 | { |
330 | Chronology base; |
331 | if ((base = getBase()) != null) { |
332 | return base.getDateTimeMillis(year, monthOfYear, dayOfMonth, millisOfDay); |
333 | } |
334 | |
335 | // Assume date is Gregorian. |
336 | long instant = iGregorianChronology.getDateTimeMillis |
337 | (year, monthOfYear, dayOfMonth, millisOfDay); |
338 | if (instant < iCutoverMillis) { |
339 | // Maybe it's Julian. |
340 | instant = iJulianChronology.getDateTimeMillis |
341 | (year, monthOfYear, dayOfMonth, millisOfDay); |
342 | if (instant >= iCutoverMillis) { |
343 | // Okay, it's in the illegal cutover gap. |
344 | throw new IllegalArgumentException("Specified date does not exist"); |
345 | } |
346 | } |
347 | return instant; |
348 | } |
349 | |
350 | public long getDateTimeMillis(int year, int monthOfYear, int dayOfMonth, |
351 | int hourOfDay, int minuteOfHour, |
352 | int secondOfMinute, int millisOfSecond) |
353 | throws IllegalArgumentException |
354 | { |
355 | Chronology base; |
356 | if ((base = getBase()) != null) { |
357 | return base.getDateTimeMillis |
358 | (year, monthOfYear, dayOfMonth, |
359 | hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond); |
360 | } |
361 | |
362 | // Assume date is Gregorian. |
363 | long instant = iGregorianChronology.getDateTimeMillis |
364 | (year, monthOfYear, dayOfMonth, |
365 | hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond); |
366 | if (instant < iCutoverMillis) { |
367 | // Maybe it's Julian. |
368 | instant = iJulianChronology.getDateTimeMillis |
369 | (year, monthOfYear, dayOfMonth, |
370 | hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond); |
371 | if (instant >= iCutoverMillis) { |
372 | // Okay, it's in the illegal cutover gap. |
373 | throw new IllegalArgumentException("Specified date does not exist"); |
374 | } |
375 | } |
376 | return instant; |
377 | } |
378 | |
379 | /** |
380 | * Gets the cutover instant between Gregorian and Julian chronologies. |
381 | * @return the cutover instant |
382 | */ |
383 | public Instant getGregorianCutover() { |
384 | return iCutoverInstant; |
385 | } |
386 | |
387 | /** |
388 | * Gets the minimum days needed for a week to be the first week in a year. |
389 | * |
390 | * @return the minimum days |
391 | */ |
392 | public int getMinimumDaysInFirstWeek() { |
393 | return iGregorianChronology.getMinimumDaysInFirstWeek(); |
394 | } |
395 | |
396 | /** |
397 | * Checks if this chronology instance equals another. |
398 | * |
399 | * @param obj the object to compare to |
400 | * @return true if equal |
401 | * @since 1.6 |
402 | */ |
403 | public boolean equals(Object obj) { |
404 | return super.equals(obj); |
405 | } |
406 | |
407 | /** |
408 | * A suitable hash code for the chronology. |
409 | * |
410 | * @return the hash code |
411 | * @since 1.6 |
412 | */ |
413 | public int hashCode() { |
414 | return "GJ".hashCode() * 11 + iJulianChronology.hashCode() + |
415 | iGregorianChronology.hashCode() + iCutoverInstant.hashCode(); |
416 | } |
417 | |
418 | // Output |
419 | //----------------------------------------------------------------------- |
420 | /** |
421 | * Gets a debugging toString. |
422 | * |
423 | * @return a debugging string |
424 | */ |
425 | public String toString() { |
426 | StringBuffer sb = new StringBuffer(60); |
427 | sb.append("GJChronology"); |
428 | sb.append('['); |
429 | sb.append(getZone().getID()); |
430 | |
431 | if (iCutoverMillis != DEFAULT_CUTOVER.getMillis()) { |
432 | sb.append(",cutover="); |
433 | DateTimeFormatter printer; |
434 | if (withUTC().dayOfYear().remainder(iCutoverMillis) == 0) { |
435 | printer = ISODateTimeFormat.date(); |
436 | } else { |
437 | printer = ISODateTimeFormat.dateTime(); |
438 | } |
439 | printer.withChronology(withUTC()).printTo(sb, iCutoverMillis); |
440 | } |
441 | |
442 | if (getMinimumDaysInFirstWeek() != 4) { |
443 | sb.append(",mdfw="); |
444 | sb.append(getMinimumDaysInFirstWeek()); |
445 | } |
446 | sb.append(']'); |
447 | |
448 | return sb.toString(); |
449 | } |
450 | |
451 | protected void assemble(Fields fields) { |
452 | Object[] params = (Object[])getParam(); |
453 | |
454 | JulianChronology julian = (JulianChronology)params[0]; |
455 | GregorianChronology gregorian = (GregorianChronology)params[1]; |
456 | Instant cutoverInstant = (Instant)params[2]; |
457 | iCutoverMillis = cutoverInstant.getMillis(); |
458 | |
459 | iJulianChronology = julian; |
460 | iGregorianChronology = gregorian; |
461 | iCutoverInstant = cutoverInstant; |
462 | |
463 | if (getBase() != null) { |
464 | return; |
465 | } |
466 | |
467 | if (julian.getMinimumDaysInFirstWeek() != gregorian.getMinimumDaysInFirstWeek()) { |
468 | throw new IllegalArgumentException(); |
469 | } |
470 | |
471 | // Compute difference between the chronologies at the cutover instant |
472 | iGapDuration = iCutoverMillis - julianToGregorianByYear(iCutoverMillis); |
473 | |
474 | // Begin field definitions. |
475 | |
476 | // First just copy all the Gregorian fields and then override those |
477 | // that need special attention. |
478 | fields.copyFieldsFrom(gregorian); |
479 | |
480 | // Assuming cutover is at midnight, all time of day fields can be |
481 | // gregorian since they are unaffected by cutover. |
482 | |
483 | // Verify assumption. |
484 | if (gregorian.millisOfDay().get(iCutoverMillis) == 0) { |
485 | // Cutover is sometime in the day, so cutover fields are required |
486 | // for time of day. |
487 | |
488 | fields.millisOfSecond = new CutoverField(julian.millisOfSecond(), fields.millisOfSecond, iCutoverMillis); |
489 | fields.millisOfDay = new CutoverField(julian.millisOfDay(), fields.millisOfDay, iCutoverMillis); |
490 | fields.secondOfMinute = new CutoverField(julian.secondOfMinute(), fields.secondOfMinute, iCutoverMillis); |
491 | fields.secondOfDay = new CutoverField(julian.secondOfDay(), fields.secondOfDay, iCutoverMillis); |
492 | fields.minuteOfHour = new CutoverField(julian.minuteOfHour(), fields.minuteOfHour, iCutoverMillis); |
493 | fields.minuteOfDay = new CutoverField(julian.minuteOfDay(), fields.minuteOfDay, iCutoverMillis); |
494 | fields.hourOfDay = new CutoverField(julian.hourOfDay(), fields.hourOfDay, iCutoverMillis); |
495 | fields.hourOfHalfday = new CutoverField(julian.hourOfHalfday(), fields.hourOfHalfday, iCutoverMillis); |
496 | fields.clockhourOfDay = new CutoverField(julian.clockhourOfDay(), fields.clockhourOfDay, iCutoverMillis); |
497 | fields.clockhourOfHalfday = new CutoverField(julian.clockhourOfHalfday(), |
498 | fields.clockhourOfHalfday, iCutoverMillis); |
499 | fields.halfdayOfDay = new CutoverField(julian.halfdayOfDay(), fields.halfdayOfDay, iCutoverMillis); |
500 | } |
501 | |
502 | // These fields just require basic cutover support. |
503 | { |
504 | fields.era = new CutoverField(julian.era(), fields.era, iCutoverMillis); |
505 | } |
506 | |
507 | // DayOfYear and weekOfWeekyear require special handling since cutover |
508 | // year has fewer days and weeks. Extend the cutover to the start of |
509 | // the next year or weekyear. This keeps the sequence unbroken during |
510 | // the cutover year. |
511 | |
512 | { |
513 | long cutover = gregorian.year().roundCeiling(iCutoverMillis); |
514 | fields.dayOfYear = new CutoverField( |
515 | julian.dayOfYear(), fields.dayOfYear, cutover); |
516 | } |
517 | |
518 | { |
519 | long cutover = gregorian.weekyear().roundCeiling(iCutoverMillis); |
520 | fields.weekOfWeekyear = new CutoverField( |
521 | julian.weekOfWeekyear(), fields.weekOfWeekyear, cutover, true); |
522 | } |
523 | |
524 | // These fields are special because they have imprecise durations. The |
525 | // family of addition methods need special attention. Override affected |
526 | // duration fields as well. |
527 | { |
528 | fields.year = new ImpreciseCutoverField( |
529 | julian.year(), fields.year, iCutoverMillis); |
530 | fields.years = fields.year.getDurationField(); |
531 | fields.yearOfEra = new ImpreciseCutoverField( |
532 | julian.yearOfEra(), fields.yearOfEra, fields.years, iCutoverMillis); |
533 | fields.yearOfCentury = new ImpreciseCutoverField( |
534 | julian.yearOfCentury(), fields.yearOfCentury, fields.years, iCutoverMillis); |
535 | |
536 | fields.centuryOfEra = new ImpreciseCutoverField( |
537 | julian.centuryOfEra(), fields.centuryOfEra, iCutoverMillis); |
538 | fields.centuries = fields.centuryOfEra.getDurationField(); |
539 | |
540 | fields.monthOfYear = new ImpreciseCutoverField( |
541 | julian.monthOfYear(), fields.monthOfYear, iCutoverMillis); |
542 | fields.months = fields.monthOfYear.getDurationField(); |
543 | |
544 | fields.weekyear = new ImpreciseCutoverField( |
545 | julian.weekyear(), fields.weekyear, null, iCutoverMillis, true); |
546 | fields.weekyearOfCentury = new ImpreciseCutoverField( |
547 | julian.weekyearOfCentury(), fields.weekyearOfCentury, fields.weekyears, iCutoverMillis); |
548 | fields.weekyears = fields.weekyear.getDurationField(); |
549 | } |
550 | |
551 | // These fields require basic cutover support, except they must link to |
552 | // imprecise durations. |
553 | { |
554 | CutoverField cf = new CutoverField |
555 | (julian.dayOfMonth(), fields.dayOfMonth, iCutoverMillis); |
556 | cf.iRangeDurationField = fields.months; |
557 | fields.dayOfMonth = cf; |
558 | } |
559 | } |
560 | |
561 | long julianToGregorianByYear(long instant) { |
562 | return convertByYear(instant, iJulianChronology, iGregorianChronology); |
563 | } |
564 | |
565 | long gregorianToJulianByYear(long instant) { |
566 | return convertByYear(instant, iGregorianChronology, iJulianChronology); |
567 | } |
568 | |
569 | long julianToGregorianByWeekyear(long instant) { |
570 | return convertByWeekyear(instant, iJulianChronology, iGregorianChronology); |
571 | } |
572 | |
573 | long gregorianToJulianByWeekyear(long instant) { |
574 | return convertByWeekyear(instant, iGregorianChronology, iJulianChronology); |
575 | } |
576 | |
577 | //----------------------------------------------------------------------- |
578 | /** |
579 | * This basic cutover field adjusts calls to 'get' and 'set' methods, and |
580 | * assumes that calls to add and addWrapField are unaffected by the cutover. |
581 | */ |
582 | private class CutoverField extends BaseDateTimeField { |
583 | private static final long serialVersionUID = 3528501219481026402L; |
584 | |
585 | final DateTimeField iJulianField; |
586 | final DateTimeField iGregorianField; |
587 | final long iCutover; |
588 | final boolean iConvertByWeekyear; |
589 | |
590 | protected DurationField iDurationField; |
591 | protected DurationField iRangeDurationField; |
592 | |
593 | /** |
594 | * @param julianField field from the chronology used before the cutover instant |
595 | * @param gregorianField field from the chronology used at and after the cutover |
596 | * @param cutoverMillis the millis of the cutover |
597 | */ |
598 | CutoverField(DateTimeField julianField, DateTimeField gregorianField, long cutoverMillis) { |
599 | this(julianField, gregorianField, cutoverMillis, false); |
600 | } |
601 | |
602 | /** |
603 | * @param julianField field from the chronology used before the cutover instant |
604 | * @param gregorianField field from the chronology used at and after the cutover |
605 | * @param cutoverMillis the millis of the cutover |
606 | * @param convertByWeekyear |
607 | */ |
608 | CutoverField(DateTimeField julianField, DateTimeField gregorianField, |
609 | long cutoverMillis, boolean convertByWeekyear) { |
610 | super(gregorianField.getType()); |
611 | iJulianField = julianField; |
612 | iGregorianField = gregorianField; |
613 | iCutover = cutoverMillis; |
614 | iConvertByWeekyear = convertByWeekyear; |
615 | // Although average length of Julian and Gregorian years differ, |
616 | // use the Gregorian duration field because it is more accurate. |
617 | iDurationField = gregorianField.getDurationField(); |
618 | |
619 | DurationField rangeField = gregorianField.getRangeDurationField(); |
620 | if (rangeField == null) { |
621 | rangeField = julianField.getRangeDurationField(); |
622 | } |
623 | iRangeDurationField = rangeField; |
624 | } |
625 | |
626 | public boolean isLenient() { |
627 | return false; |
628 | } |
629 | |
630 | public int get(long instant) { |
631 | if (instant >= iCutover) { |
632 | return iGregorianField.get(instant); |
633 | } else { |
634 | return iJulianField.get(instant); |
635 | } |
636 | } |
637 | |
638 | public String getAsText(long instant, Locale locale) { |
639 | if (instant >= iCutover) { |
640 | return iGregorianField.getAsText(instant, locale); |
641 | } else { |
642 | return iJulianField.getAsText(instant, locale); |
643 | } |
644 | } |
645 | |
646 | public String getAsText(int fieldValue, Locale locale) { |
647 | return iGregorianField.getAsText(fieldValue, locale); |
648 | } |
649 | |
650 | public String getAsShortText(long instant, Locale locale) { |
651 | if (instant >= iCutover) { |
652 | return iGregorianField.getAsShortText(instant, locale); |
653 | } else { |
654 | return iJulianField.getAsShortText(instant, locale); |
655 | } |
656 | } |
657 | |
658 | public String getAsShortText(int fieldValue, Locale locale) { |
659 | return iGregorianField.getAsShortText(fieldValue, locale); |
660 | } |
661 | |
662 | public long add(long instant, int value) { |
663 | return iGregorianField.add(instant, value); |
664 | } |
665 | |
666 | public long add(long instant, long value) { |
667 | return iGregorianField.add(instant, value); |
668 | } |
669 | |
670 | public int[] add(ReadablePartial partial, int fieldIndex, int[] values, int valueToAdd) { |
671 | // overridden as superclass algorithm can't handle |
672 | // 2004-02-29 + 48 months -> 2008-02-29 type dates |
673 | if (valueToAdd == 0) { |
674 | return values; |
675 | } |
676 | if (DateTimeUtils.isContiguous(partial)) { |
677 | long instant = 0L; |
678 | for (int i = 0, isize = partial.size(); i < isize; i++) { |
679 | instant = partial.getFieldType(i).getField(GJChronology.this).set(instant, values[i]); |
680 | } |
681 | instant = add(instant, valueToAdd); |
682 | return GJChronology.this.get(partial, instant); |
683 | } else { |
684 | return super.add(partial, fieldIndex, values, valueToAdd); |
685 | } |
686 | } |
687 | |
688 | public int getDifference(long minuendInstant, long subtrahendInstant) { |
689 | return iGregorianField.getDifference(minuendInstant, subtrahendInstant); |
690 | } |
691 | |
692 | public long getDifferenceAsLong(long minuendInstant, long subtrahendInstant) { |
693 | return iGregorianField.getDifferenceAsLong(minuendInstant, subtrahendInstant); |
694 | } |
695 | |
696 | public long set(long instant, int value) { |
697 | if (instant >= iCutover) { |
698 | instant = iGregorianField.set(instant, value); |
699 | if (instant < iCutover) { |
700 | // Only adjust if gap fully crossed. |
701 | if (instant + iGapDuration < iCutover) { |
702 | instant = gregorianToJulian(instant); |
703 | } |
704 | // Verify that new value stuck. |
705 | if (get(instant) != value) { |
706 | throw new IllegalFieldValueException |
707 | (iGregorianField.getType(), new Integer(value), null, null); |
708 | } |
709 | } |
710 | } else { |
711 | instant = iJulianField.set(instant, value); |
712 | if (instant >= iCutover) { |
713 | // Only adjust if gap fully crossed. |
714 | if (instant - iGapDuration >= iCutover) { |
715 | instant = julianToGregorian(instant); |
716 | } |
717 | // Verify that new value stuck. |
718 | if (get(instant) != value) { |
719 | throw new IllegalFieldValueException |
720 | (iJulianField.getType(), new Integer(value), null, null); |
721 | } |
722 | } |
723 | } |
724 | return instant; |
725 | } |
726 | |
727 | public long set(long instant, String text, Locale locale) { |
728 | if (instant >= iCutover) { |
729 | instant = iGregorianField.set(instant, text, locale); |
730 | if (instant < iCutover) { |
731 | // Only adjust if gap fully crossed. |
732 | if (instant + iGapDuration < iCutover) { |
733 | instant = gregorianToJulian(instant); |
734 | } |
735 | // Cannot verify that new value stuck because set may be lenient. |
736 | } |
737 | } else { |
738 | instant = iJulianField.set(instant, text, locale); |
739 | if (instant >= iCutover) { |
740 | // Only adjust if gap fully crossed. |
741 | if (instant - iGapDuration >= iCutover) { |
742 | instant = julianToGregorian(instant); |
743 | } |
744 | // Cannot verify that new value stuck because set may be lenient. |
745 | } |
746 | } |
747 | return instant; |
748 | } |
749 | |
750 | public DurationField getDurationField() { |
751 | return iDurationField; |
752 | } |
753 | |
754 | public DurationField getRangeDurationField() { |
755 | return iRangeDurationField; |
756 | } |
757 | |
758 | public boolean isLeap(long instant) { |
759 | if (instant >= iCutover) { |
760 | return iGregorianField.isLeap(instant); |
761 | } else { |
762 | return iJulianField.isLeap(instant); |
763 | } |
764 | } |
765 | |
766 | public int getLeapAmount(long instant) { |
767 | if (instant >= iCutover) { |
768 | return iGregorianField.getLeapAmount(instant); |
769 | } else { |
770 | return iJulianField.getLeapAmount(instant); |
771 | } |
772 | } |
773 | |
774 | public DurationField getLeapDurationField() { |
775 | return iGregorianField.getLeapDurationField(); |
776 | } |
777 | |
778 | |
779 | public int getMinimumValue() { |
780 | // For all precise fields, the Julian and Gregorian limits are |
781 | // identical. Choose Julian to tighten up the year limits. |
782 | return iJulianField.getMinimumValue(); |
783 | } |
784 | |
785 | public int getMinimumValue(ReadablePartial partial) { |
786 | return iJulianField.getMinimumValue(partial); |
787 | } |
788 | |
789 | public int getMinimumValue(ReadablePartial partial, int[] values) { |
790 | return iJulianField.getMinimumValue(partial, values); |
791 | } |
792 | |
793 | public int getMinimumValue(long instant) { |
794 | if (instant < iCutover) { |
795 | return iJulianField.getMinimumValue(instant); |
796 | } |
797 | |
798 | int min = iGregorianField.getMinimumValue(instant); |
799 | |
800 | // Because the cutover may reduce the length of this field, verify |
801 | // the minimum by setting it. |
802 | instant = iGregorianField.set(instant, min); |
803 | if (instant < iCutover) { |
804 | min = iGregorianField.get(iCutover); |
805 | } |
806 | |
807 | return min; |
808 | } |
809 | |
810 | public int getMaximumValue() { |
811 | // For all precise fields, the Julian and Gregorian limits are |
812 | // identical. |
813 | return iGregorianField.getMaximumValue(); |
814 | } |
815 | |
816 | public int getMaximumValue(long instant) { |
817 | if (instant >= iCutover) { |
818 | return iGregorianField.getMaximumValue(instant); |
819 | } |
820 | |
821 | int max = iJulianField.getMaximumValue(instant); |
822 | |
823 | // Because the cutover may reduce the length of this field, verify |
824 | // the maximum by setting it. |
825 | instant = iJulianField.set(instant, max); |
826 | if (instant >= iCutover) { |
827 | max = iJulianField.get(iJulianField.add(iCutover, -1)); |
828 | } |
829 | |
830 | return max; |
831 | } |
832 | |
833 | public int getMaximumValue(ReadablePartial partial) { |
834 | long instant = GJChronology.getInstanceUTC().set(partial, 0L); |
835 | return getMaximumValue(instant); |
836 | } |
837 | |
838 | public int getMaximumValue(ReadablePartial partial, int[] values) { |
839 | Chronology chrono = GJChronology.getInstanceUTC(); |
840 | long instant = 0L; |
841 | for (int i = 0, isize = partial.size(); i < isize; i++) { |
842 | DateTimeField field = partial.getFieldType(i).getField(chrono); |
843 | if (values[i] <= field.getMaximumValue(instant)) { |
844 | instant = field.set(instant, values[i]); |
845 | } |
846 | } |
847 | return getMaximumValue(instant); |
848 | } |
849 | |
850 | public long roundFloor(long instant) { |
851 | if (instant >= iCutover) { |
852 | instant = iGregorianField.roundFloor(instant); |
853 | if (instant < iCutover) { |
854 | // Only adjust if gap fully crossed. |
855 | if (instant + iGapDuration < iCutover) { |
856 | instant = gregorianToJulian(instant); |
857 | } |
858 | } |
859 | } else { |
860 | instant = iJulianField.roundFloor(instant); |
861 | } |
862 | return instant; |
863 | } |
864 | |
865 | public long roundCeiling(long instant) { |
866 | if (instant >= iCutover) { |
867 | instant = iGregorianField.roundCeiling(instant); |
868 | } else { |
869 | instant = iJulianField.roundCeiling(instant); |
870 | if (instant >= iCutover) { |
871 | // Only adjust if gap fully crossed. |
872 | if (instant - iGapDuration >= iCutover) { |
873 | instant = julianToGregorian(instant); |
874 | } |
875 | } |
876 | } |
877 | return instant; |
878 | } |
879 | |
880 | public int getMaximumTextLength(Locale locale) { |
881 | return Math.max(iJulianField.getMaximumTextLength(locale), |
882 | iGregorianField.getMaximumTextLength(locale)); |
883 | } |
884 | |
885 | public int getMaximumShortTextLength(Locale locale) { |
886 | return Math.max(iJulianField.getMaximumShortTextLength(locale), |
887 | iGregorianField.getMaximumShortTextLength(locale)); |
888 | } |
889 | |
890 | protected long julianToGregorian(long instant) { |
891 | if (iConvertByWeekyear) { |
892 | return julianToGregorianByWeekyear(instant); |
893 | } else { |
894 | return julianToGregorianByYear(instant); |
895 | } |
896 | } |
897 | |
898 | protected long gregorianToJulian(long instant) { |
899 | if (iConvertByWeekyear) { |
900 | return gregorianToJulianByWeekyear(instant); |
901 | } else { |
902 | return gregorianToJulianByYear(instant); |
903 | } |
904 | } |
905 | } |
906 | |
907 | //----------------------------------------------------------------------- |
908 | /** |
909 | * Cutover field for variable length fields. These fields internally call |
910 | * set whenever add is called. As a result, the same correction applied to |
911 | * set must be applied to add and addWrapField. Knowing when to use this |
912 | * field requires specific knowledge of how the GJ fields are implemented. |
913 | */ |
914 | private final class ImpreciseCutoverField extends CutoverField { |
915 | private static final long serialVersionUID = 3410248757173576441L; |
916 | |
917 | /** |
918 | * Creates a duration field that links back to this. |
919 | */ |
920 | ImpreciseCutoverField(DateTimeField julianField, DateTimeField gregorianField, long cutoverMillis) { |
921 | this(julianField, gregorianField, null, cutoverMillis, false); |
922 | } |
923 | |
924 | /** |
925 | * Uses a shared duration field rather than creating a new one. |
926 | * |
927 | * @param durationField shared duration field |
928 | */ |
929 | ImpreciseCutoverField(DateTimeField julianField, DateTimeField gregorianField, |
930 | DurationField durationField, long cutoverMillis) |
931 | { |
932 | this(julianField, gregorianField, durationField, cutoverMillis, false); |
933 | } |
934 | |
935 | /** |
936 | * Uses a shared duration field rather than creating a new one. |
937 | * |
938 | * @param durationField shared duration field |
939 | */ |
940 | ImpreciseCutoverField(DateTimeField julianField, DateTimeField gregorianField, |
941 | DurationField durationField, |
942 | long cutoverMillis, boolean convertByWeekyear) |
943 | { |
944 | super(julianField, gregorianField, cutoverMillis, convertByWeekyear); |
945 | if (durationField == null) { |
946 | durationField = new LinkedDurationField(iDurationField, this); |
947 | } |
948 | iDurationField = durationField; |
949 | } |
950 | |
951 | public long add(long instant, int value) { |
952 | if (instant >= iCutover) { |
953 | instant = iGregorianField.add(instant, value); |
954 | if (instant < iCutover) { |
955 | // Only adjust if gap fully crossed. |
956 | if (instant + iGapDuration < iCutover) { |
957 | instant = gregorianToJulian(instant); |
958 | } |
959 | } |
960 | } else { |
961 | instant = iJulianField.add(instant, value); |
962 | if (instant >= iCutover) { |
963 | // Only adjust if gap fully crossed. |
964 | if (instant - iGapDuration >= iCutover) { |
965 | instant = julianToGregorian(instant); |
966 | } |
967 | } |
968 | } |
969 | return instant; |
970 | } |
971 | |
972 | public long add(long instant, long value) { |
973 | if (instant >= iCutover) { |
974 | instant = iGregorianField.add(instant, value); |
975 | if (instant < iCutover) { |
976 | // Only adjust if gap fully crossed. |
977 | if (instant + iGapDuration < iCutover) { |
978 | instant = gregorianToJulian(instant); |
979 | } |
980 | } |
981 | } else { |
982 | instant = iJulianField.add(instant, value); |
983 | if (instant >= iCutover) { |
984 | // Only adjust if gap fully crossed. |
985 | if (instant - iGapDuration >= iCutover) { |
986 | instant = julianToGregorian(instant); |
987 | } |
988 | } |
989 | } |
990 | return instant; |
991 | } |
992 | |
993 | public int getDifference(long minuendInstant, long subtrahendInstant) { |
994 | if (minuendInstant >= iCutover) { |
995 | if (subtrahendInstant >= iCutover) { |
996 | return iGregorianField.getDifference(minuendInstant, subtrahendInstant); |
997 | } |
998 | // Remember, the add is being reversed. Since subtrahend is |
999 | // Julian, convert minuend to Julian to match. |
1000 | minuendInstant = gregorianToJulian(minuendInstant); |
1001 | return iJulianField.getDifference(minuendInstant, subtrahendInstant); |
1002 | } else { |
1003 | if (subtrahendInstant < iCutover) { |
1004 | return iJulianField.getDifference(minuendInstant, subtrahendInstant); |
1005 | } |
1006 | // Remember, the add is being reversed. Since subtrahend is |
1007 | // Gregorian, convert minuend to Gregorian to match. |
1008 | minuendInstant = julianToGregorian(minuendInstant); |
1009 | return iGregorianField.getDifference(minuendInstant, subtrahendInstant); |
1010 | } |
1011 | } |
1012 | |
1013 | public long getDifferenceAsLong(long minuendInstant, long subtrahendInstant) { |
1014 | if (minuendInstant >= iCutover) { |
1015 | if (subtrahendInstant >= iCutover) { |
1016 | return iGregorianField.getDifferenceAsLong(minuendInstant, subtrahendInstant); |
1017 | } |
1018 | // Remember, the add is being reversed. Since subtrahend is |
1019 | // Julian, convert minuend to Julian to match. |
1020 | minuendInstant = gregorianToJulian(minuendInstant); |
1021 | return iJulianField.getDifferenceAsLong(minuendInstant, subtrahendInstant); |
1022 | } else { |
1023 | if (subtrahendInstant < iCutover) { |
1024 | return iJulianField.getDifferenceAsLong(minuendInstant, subtrahendInstant); |
1025 | } |
1026 | // Remember, the add is being reversed. Since subtrahend is |
1027 | // Gregorian, convert minuend to Gregorian to match. |
1028 | minuendInstant = julianToGregorian(minuendInstant); |
1029 | return iGregorianField.getDifferenceAsLong(minuendInstant, subtrahendInstant); |
1030 | } |
1031 | } |
1032 | |
1033 | // Since the imprecise fields have durations longer than the gap |
1034 | // duration, keep these methods simple. The inherited implementations |
1035 | // produce incorrect results. |
1036 | // |
1037 | // Degenerate case: If this field is a month, and the cutover is set |
1038 | // far into the future, then the gap duration may be so large as to |
1039 | // reduce the number of months in a year. If the missing month(s) are |
1040 | // at the beginning or end of the year, then the minimum and maximum |
1041 | // values are not 1 and 12. I don't expect this case to ever occur. |
1042 | |
1043 | public int getMinimumValue(long instant) { |
1044 | if (instant >= iCutover) { |
1045 | return iGregorianField.getMinimumValue(instant); |
1046 | } else { |
1047 | return iJulianField.getMinimumValue(instant); |
1048 | } |
1049 | } |
1050 | |
1051 | public int getMaximumValue(long instant) { |
1052 | if (instant >= iCutover) { |
1053 | return iGregorianField.getMaximumValue(instant); |
1054 | } else { |
1055 | return iJulianField.getMaximumValue(instant); |
1056 | } |
1057 | } |
1058 | } |
1059 | |
1060 | //----------------------------------------------------------------------- |
1061 | /** |
1062 | * Links the duration back to a ImpreciseCutoverField. |
1063 | */ |
1064 | private static class LinkedDurationField extends DecoratedDurationField { |
1065 | private static final long serialVersionUID = 4097975388007713084L; |
1066 | |
1067 | private final ImpreciseCutoverField iField; |
1068 | |
1069 | LinkedDurationField(DurationField durationField, ImpreciseCutoverField dateTimeField) { |
1070 | super(durationField, durationField.getType()); |
1071 | iField = dateTimeField; |
1072 | } |
1073 | |
1074 | public long add(long instant, int value) { |
1075 | return iField.add(instant, value); |
1076 | } |
1077 | |
1078 | public long add(long instant, long value) { |
1079 | return iField.add(instant, value); |
1080 | } |
1081 | |
1082 | public int getDifference(long minuendInstant, long subtrahendInstant) { |
1083 | return iField.getDifference(minuendInstant, subtrahendInstant); |
1084 | } |
1085 | |
1086 | public long getDifferenceAsLong(long minuendInstant, long subtrahendInstant) { |
1087 | return iField.getDifferenceAsLong(minuendInstant, subtrahendInstant); |
1088 | } |
1089 | } |
1090 | |
1091 | } |