63 |
63 |
64 import static java.time.temporal.ChronoField.EPOCH_DAY; |
64 import static java.time.temporal.ChronoField.EPOCH_DAY; |
65 import static java.time.temporal.ChronoUnit.DAYS; |
65 import static java.time.temporal.ChronoUnit.DAYS; |
66 import static java.time.temporal.ChronoUnit.FOREVER; |
66 import static java.time.temporal.ChronoUnit.FOREVER; |
67 |
67 |
68 import java.io.InvalidObjectException; |
|
69 import java.io.Serializable; |
|
70 import java.time.DateTimeException; |
68 import java.time.DateTimeException; |
71 import java.time.LocalDate; |
69 import java.util.Collections; |
72 import java.time.format.DateTimeBuilder; |
70 import java.util.Map; |
73 |
71 |
74 /** |
72 /** |
75 * A set of date fields that provide access to Julian Days. |
73 * A set of date fields that provide access to Julian Days. |
76 * <p> |
74 * <p> |
77 * The Julian Day is a standard way of expressing date and time commonly used in the scientific community. |
75 * The Julian Day is a standard way of expressing date and time commonly used in the scientific community. |
78 * It is expressed as a decimal number of whole days where days start at midday. |
76 * It is expressed as a decimal number of whole days where days start at midday. |
79 * This class represents variations on Julian Days that count whole days from midnight. |
77 * This class represents variations on Julian Days that count whole days from midnight. |
|
78 * <p> |
|
79 * The fields are implemented relative to {@link ChronoField#EPOCH_DAY EPOCH_DAY}. |
|
80 * The fields are supported, and can be queried and set if {@code EPOCH_DAY} is available. |
|
81 * The fields work with all chronologies. |
80 * |
82 * |
81 * <h3>Specification for implementors</h3> |
83 * <h3>Specification for implementors</h3> |
82 * This is an immutable and thread-safe class. |
84 * This is an immutable and thread-safe class. |
83 * |
85 * |
84 * @since 1.8 |
86 * @since 1.8 |
97 * Julian Day is a well-known system that represents the count of whole days since day 0, |
99 * Julian Day is a well-known system that represents the count of whole days since day 0, |
98 * which is defined to be January 1, 4713 BCE in the Julian calendar, and -4713-11-24 Gregorian. |
100 * which is defined to be January 1, 4713 BCE in the Julian calendar, and -4713-11-24 Gregorian. |
99 * The field has "JulianDay" as 'name', and 'DAYS' as 'baseUnit'. |
101 * The field has "JulianDay" as 'name', and 'DAYS' as 'baseUnit'. |
100 * The field always refers to the local date-time, ignoring the offset or zone. |
102 * The field always refers to the local date-time, ignoring the offset or zone. |
101 * <p> |
103 * <p> |
102 * For date-times, 'JULIAN_DAY.doGet()' assumes the same value from |
104 * For date-times, 'JULIAN_DAY.getFrom()' assumes the same value from |
103 * midnight until just before the next midnight. |
105 * midnight until just before the next midnight. |
104 * When 'JULIAN_DAY.doWith()' is applied to a date-time, the time of day portion remains unaltered. |
106 * When 'JULIAN_DAY.adjustInto()' is applied to a date-time, the time of day portion remains unaltered. |
105 * 'JULIAN_DAY.doWith()' and 'JULIAN_DAY.doGet()' only apply to {@code Temporal} objects that |
107 * 'JULIAN_DAY.adjustInto()' and 'JULIAN_DAY.getFrom()' only apply to {@code Temporal} objects that |
106 * can be converted into {@link ChronoField#EPOCH_DAY}. |
108 * can be converted into {@link ChronoField#EPOCH_DAY}. |
107 * A {@link DateTimeException} is thrown for any other type of object. |
109 * A {@link DateTimeException} is thrown for any other type of object. |
108 * <p> |
110 * <p> |
109 * <h3>Astronomical and Scientific Notes</h3> |
111 * <h3>Astronomical and Scientific Notes</h3> |
110 * The standard astronomical definition uses a fraction to indicate the time-of-day, |
112 * The standard astronomical definition uses a fraction to indicate the time-of-day, |
127 * <p> |
129 * <p> |
128 * Julian Days are sometimes taken to imply Universal Time or UTC, but this |
130 * Julian Days are sometimes taken to imply Universal Time or UTC, but this |
129 * implementation always uses the Julian Day number for the local date, |
131 * implementation always uses the Julian Day number for the local date, |
130 * regardless of the offset or time-zone. |
132 * regardless of the offset or time-zone. |
131 */ |
133 */ |
132 public static final TemporalField JULIAN_DAY = new Field("JulianDay", DAYS, FOREVER, JULIAN_DAY_OFFSET); |
134 public static final TemporalField JULIAN_DAY = Field.JULIAN_DAY; |
133 |
135 |
134 /** |
136 /** |
135 * Modified Julian Day field. |
137 * Modified Julian Day field. |
136 * <p> |
138 * <p> |
137 * This is an integer-based version of the Modified Julian Day Number. |
139 * This is an integer-based version of the Modified Julian Day Number. |
138 * Modified Julian Day (MJD) is a well-known system that counts days continuously. |
140 * Modified Julian Day (MJD) is a well-known system that counts days continuously. |
139 * It is defined relative to astronomical Julian Day as {@code MJD = JD - 2400000.5}. |
141 * It is defined relative to astronomical Julian Day as {@code MJD = JD - 2400000.5}. |
140 * Each Modified Julian Day runs from midnight to midnight. |
142 * Each Modified Julian Day runs from midnight to midnight. |
141 * The field always refers to the local date-time, ignoring the offset or zone. |
143 * The field always refers to the local date-time, ignoring the offset or zone. |
142 * <p> |
144 * <p> |
143 * For date-times, 'MODIFIED_JULIAN_DAY.doGet()' assumes the same value from |
145 * For date-times, 'MODIFIED_JULIAN_DAY.getFrom()' assumes the same value from |
144 * midnight until just before the next midnight. |
146 * midnight until just before the next midnight. |
145 * When 'MODIFIED_JULIAN_DAY.doWith()' is applied to a date-time, the time of day portion remains unaltered. |
147 * When 'MODIFIED_JULIAN_DAY.adjustInto()' is applied to a date-time, the time of day portion remains unaltered. |
146 * 'MODIFIED_JULIAN_DAY.doWith()' and 'MODIFIED_JULIAN_DAY.doGet()' only apply to {@code Temporal} objects |
148 * 'MODIFIED_JULIAN_DAY.adjustInto()' and 'MODIFIED_JULIAN_DAY.getFrom()' only apply to {@code Temporal} objects |
147 * that can be converted into {@link ChronoField#EPOCH_DAY}. |
149 * that can be converted into {@link ChronoField#EPOCH_DAY}. |
148 * A {@link DateTimeException} is thrown for any other type of object. |
150 * A {@link DateTimeException} is thrown for any other type of object. |
149 * <p> |
151 * <p> |
150 * This implementation is an integer version of MJD with the decimal part rounded to floor. |
152 * This implementation is an integer version of MJD with the decimal part rounded to floor. |
151 * <p> |
153 * <p> |
163 * <p> |
165 * <p> |
164 * Modified Julian Days are sometimes taken to imply Universal Time or UTC, but this |
166 * Modified Julian Days are sometimes taken to imply Universal Time or UTC, but this |
165 * implementation always uses the Modified Julian Day for the local date, |
167 * implementation always uses the Modified Julian Day for the local date, |
166 * regardless of the offset or time-zone. |
168 * regardless of the offset or time-zone. |
167 */ |
169 */ |
168 public static final TemporalField MODIFIED_JULIAN_DAY = new Field("ModifiedJulianDay", DAYS, FOREVER, 40587L); |
170 public static final TemporalField MODIFIED_JULIAN_DAY = Field.MODIFIED_JULIAN_DAY; |
169 |
171 |
170 /** |
172 /** |
171 * Rata Die field. |
173 * Rata Die field. |
172 * <p> |
174 * <p> |
173 * Rata Die counts whole days continuously starting day 1 at midnight at the beginning of 0001-01-01 (ISO). |
175 * Rata Die counts whole days continuously starting day 1 at midnight at the beginning of 0001-01-01 (ISO). |
174 * The field always refers to the local date-time, ignoring the offset or zone. |
176 * The field always refers to the local date-time, ignoring the offset or zone. |
175 * <p> |
177 * <p> |
176 * For date-times, 'RATA_DIE.doGet()' assumes the same value from |
178 * For date-times, 'RATA_DIE.getFrom()' assumes the same value from |
177 * midnight until just before the next midnight. |
179 * midnight until just before the next midnight. |
178 * When 'RATA_DIE.doWith()' is applied to a date-time, the time of day portion remains unaltered. |
180 * When 'RATA_DIE.adjustInto()' is applied to a date-time, the time of day portion remains unaltered. |
179 * 'MODIFIED_JULIAN_DAY.doWith()' and 'RATA_DIE.doGet()' only apply to {@code Temporal} objects |
181 * 'RATA_DIE.adjustInto()' and 'RATA_DIE.getFrom()' only apply to {@code Temporal} objects |
180 * that can be converted into {@link ChronoField#EPOCH_DAY}. |
182 * that can be converted into {@link ChronoField#EPOCH_DAY}. |
181 * A {@link DateTimeException} is thrown for any other type of object. |
183 * A {@link DateTimeException} is thrown for any other type of object. |
182 */ |
184 */ |
183 public static final TemporalField RATA_DIE = new Field("RataDie", DAYS, FOREVER, 719163L); |
185 public static final TemporalField RATA_DIE = Field.RATA_DIE; |
184 |
186 |
185 /** |
187 /** |
186 * Restricted constructor. |
188 * Restricted constructor. |
187 */ |
189 */ |
188 private JulianFields() { |
190 private JulianFields() { |
189 throw new AssertionError("Not instantiable"); |
191 throw new AssertionError("Not instantiable"); |
190 } |
192 } |
191 |
193 |
192 /** |
194 /** |
193 * implementation of JulianFields. Each instance is a singleton. |
195 * Implementation of JulianFields. Each instance is a singleton. |
194 */ |
196 */ |
195 private static class Field implements TemporalField, Serializable { |
197 private static enum Field implements TemporalField { |
|
198 JULIAN_DAY("JulianDay", DAYS, FOREVER, JULIAN_DAY_OFFSET), |
|
199 MODIFIED_JULIAN_DAY("ModifiedJulianDay", DAYS, FOREVER, 40587L), |
|
200 RATA_DIE("RataDie", DAYS, FOREVER, 719163L); |
196 |
201 |
197 private static final long serialVersionUID = -7501623920830201812L; |
202 private static final long serialVersionUID = -7501623920830201812L; |
198 |
203 |
199 private final String name; |
204 private final transient String name; |
200 private final transient TemporalUnit baseUnit; |
205 private final transient TemporalUnit baseUnit; |
201 private final transient TemporalUnit rangeUnit; |
206 private final transient TemporalUnit rangeUnit; |
202 private final transient ValueRange range; |
207 private final transient ValueRange range; |
203 private final transient long offset; |
208 private final transient long offset; |
204 |
209 |
208 this.rangeUnit = rangeUnit; |
213 this.rangeUnit = rangeUnit; |
209 this.range = ValueRange.of(-365243219162L + offset, 365241780471L + offset); |
214 this.range = ValueRange.of(-365243219162L + offset, 365241780471L + offset); |
210 this.offset = offset; |
215 this.offset = offset; |
211 } |
216 } |
212 |
217 |
213 |
|
214 /** |
|
215 * Resolve the object from the stream to the appropriate singleton. |
|
216 * @return one of the singleton objects {@link #JULIAN_DAY}, |
|
217 * {@link #MODIFIED_JULIAN_DAY}, or {@link #RATA_DIE}. |
|
218 * @throws InvalidObjectException if the object in the stream is not one of the singletons. |
|
219 */ |
|
220 private Object readResolve() throws InvalidObjectException { |
|
221 if (JULIAN_DAY.getName().equals(name)) { |
|
222 return JULIAN_DAY; |
|
223 } else if (MODIFIED_JULIAN_DAY.getName().equals(name)) { |
|
224 return MODIFIED_JULIAN_DAY; |
|
225 } else if (RATA_DIE.getName().equals(name)) { |
|
226 return RATA_DIE; |
|
227 } else { |
|
228 throw new InvalidObjectException("Not one of the singletons"); |
|
229 } |
|
230 } |
|
231 |
|
232 //----------------------------------------------------------------------- |
218 //----------------------------------------------------------------------- |
233 @Override |
219 @Override |
234 public String getName() { |
220 public String getName() { |
235 return name; |
221 return name; |
236 } |
222 } |
250 return range; |
236 return range; |
251 } |
237 } |
252 |
238 |
253 //----------------------------------------------------------------------- |
239 //----------------------------------------------------------------------- |
254 @Override |
240 @Override |
255 public boolean doIsSupported(TemporalAccessor temporal) { |
241 public boolean isSupportedBy(TemporalAccessor temporal) { |
256 return temporal.isSupported(EPOCH_DAY); |
242 return temporal.isSupported(EPOCH_DAY); |
257 } |
243 } |
258 |
244 |
259 @Override |
245 @Override |
260 public ValueRange doRange(TemporalAccessor temporal) { |
246 public ValueRange rangeRefinedBy(TemporalAccessor temporal) { |
261 if (doIsSupported(temporal) == false) { |
247 if (isSupportedBy(temporal) == false) { |
262 throw new DateTimeException("Unsupported field: " + this); |
248 throw new DateTimeException("Unsupported field: " + this); |
263 } |
249 } |
264 return range(); |
250 return range(); |
265 } |
251 } |
266 |
252 |
267 @Override |
253 @Override |
268 public long doGet(TemporalAccessor temporal) { |
254 public long getFrom(TemporalAccessor temporal) { |
269 return temporal.getLong(EPOCH_DAY) + offset; |
255 return temporal.getLong(EPOCH_DAY) + offset; |
270 } |
256 } |
271 |
257 |
272 @Override |
258 @SuppressWarnings("unchecked") |
273 public <R extends Temporal> R doWith(R temporal, long newValue) { |
259 @Override |
|
260 public <R extends Temporal> R adjustInto(R temporal, long newValue) { |
274 if (range().isValidValue(newValue) == false) { |
261 if (range().isValidValue(newValue) == false) { |
275 throw new DateTimeException("Invalid value: " + name + " " + newValue); |
262 throw new DateTimeException("Invalid value: " + name + " " + newValue); |
276 } |
263 } |
277 return (R) temporal.with(EPOCH_DAY, Math.subtractExact(newValue, offset)); |
264 return (R) temporal.with(EPOCH_DAY, Math.subtractExact(newValue, offset)); |
278 } |
265 } |
279 |
266 |
280 //----------------------------------------------------------------------- |
267 //----------------------------------------------------------------------- |
281 @Override |
268 @Override |
282 public boolean resolve(DateTimeBuilder builder, long value) { |
269 public Map<TemporalField, Long> resolve(TemporalAccessor temporal, long value) { |
283 boolean changed = false; |
270 return Collections.<TemporalField, Long>singletonMap(EPOCH_DAY, Math.subtractExact(value, offset)); |
284 changed = resolve0(JULIAN_DAY, builder, changed); |
|
285 changed = resolve0(MODIFIED_JULIAN_DAY, builder, changed); |
|
286 changed = resolve0(RATA_DIE, builder, changed); |
|
287 return changed; |
|
288 } |
|
289 |
|
290 private boolean resolve0(TemporalField field, DateTimeBuilder builder, boolean changed) { |
|
291 if (builder.containsFieldValue(field)) { |
|
292 builder.addCalendrical(LocalDate.ofEpochDay(Math.subtractExact(builder.getFieldValue(JULIAN_DAY), JULIAN_DAY_OFFSET))); |
|
293 builder.removeFieldValue(JULIAN_DAY); |
|
294 changed = true; |
|
295 } |
|
296 return changed; |
|
297 } |
271 } |
298 |
272 |
299 //----------------------------------------------------------------------- |
273 //----------------------------------------------------------------------- |
300 @Override |
274 @Override |
301 public String toString() { |
275 public String toString() { |
302 return getName(); |
276 return name; |
303 } |
277 } |
304 } |
278 } |
305 } |
279 } |