1 /*
2 * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package java.nio.file.attribute;
27
28 import java.time.Instant;
29 import java.time.LocalDateTime;
30 import java.time.ZoneOffset;
31 import java.util.Objects;
32 import java.util.concurrent.TimeUnit;
33
34 /**
35 * Represents the value of a file's time stamp attribute. For example, it may
36 * represent the time that the file was last
37 * {@link BasicFileAttributes#lastModifiedTime() modified},
38 * {@link BasicFileAttributes#lastAccessTime() accessed},
39 * or {@link BasicFileAttributes#creationTime() created}.
40 *
41 * <p> Instances of this class are immutable.
42 *
43 * @since 1.7
44 * @see java.nio.file.Files#setLastModifiedTime
45 * @see java.nio.file.Files#getLastModifiedTime
46 */
47
48 public final class FileTime
49 implements Comparable<FileTime>
50 {
51 /**
52 * The unit of granularity to interpret the value. Null if
53 * this {@code FileTime} is converted from an {@code Instant},
54 * the {@code value} and {@code unit} pair will not be used
55 * in this scenario.
56 */
57 private final TimeUnit unit;
58
59 /**
60 * The value since the epoch; can be negative.
61 */
62 private final long value;
63
64 /**
65 * The value as Instant (created lazily, if not from an instant)
66 */
67 private Instant instant;
68
69 /**
70 * The value return by toString (created lazily)
71 */
72 private String valueAsString;
73
74 /**
75 * Initializes a new instance of this class.
76 */
77 private FileTime(long value, TimeUnit unit, Instant instant) {
78 this.value = value;
79 this.unit = unit;
80 this.instant = instant;
81 }
82
83 /**
84 * Returns a {@code FileTime} representing a value at the given unit of
85 * granularity.
86 *
87 * @param value
88 * the value since the epoch (1970-01-01T00:00:00Z); can be
89 * negative
90 * @param unit
91 * the unit of granularity to interpret the value
92 *
93 * @return a {@code FileTime} representing the given value
94 */
95 public static FileTime from(long value, TimeUnit unit) {
96 Objects.requireNonNull(unit, "unit");
97 return new FileTime(value, unit, null);
98 }
99
100 /**
101 * Returns a {@code FileTime} representing the given value in milliseconds.
102 *
103 * @param value
104 * the value, in milliseconds, since the epoch
105 * (1970-01-01T00:00:00Z); can be negative
106 *
107 * @return a {@code FileTime} representing the given value
108 */
109 public static FileTime fromMillis(long value) {
110 return new FileTime(value, TimeUnit.MILLISECONDS, null);
111 }
112
113 /**
114 * Returns a {@code FileTime} representing the same point of time value
115 * on the time-line as the provided {@code Instant} object.
116 *
117 * @param instant
118 * the instant to convert
119 * @return a {@code FileTime} representing the same point on the time-line
120 * as the provided instant
121 * @since 1.8
122 */
123 public static FileTime from(Instant instant) {
124 Objects.requireNonNull(instant, "instant");
125 return new FileTime(0, null, instant);
126 }
127
128 /**
129 * Returns the value at the given unit of granularity.
130 *
131 * <p> Conversion from a coarser granularity that would numerically overflow
132 * saturate to {@code Long.MIN_VALUE} if negative or {@code Long.MAX_VALUE}
133 * if positive.
134 *
135 * @param unit
136 * the unit of granularity for the return value
137 *
138 * @return value in the given unit of granularity, since the epoch
139 * since the epoch (1970-01-01T00:00:00Z); can be negative
140 */
141 public long to(TimeUnit unit) {
142 Objects.requireNonNull(unit, "unit");
143 if (this.unit != null) {
144 return unit.convert(this.value, this.unit);
145 } else {
146 long secs = unit.convert(instant.getEpochSecond(), TimeUnit.SECONDS);
147 if (secs == Long.MIN_VALUE || secs == Long.MAX_VALUE) {
148 return secs;
149 }
150 long nanos = unit.convert(instant.getNano(), TimeUnit.NANOSECONDS);
151 long r = secs + nanos;
152 // Math.addExact() variant
153 if (((secs ^ r) & (nanos ^ r)) < 0) {
154 return (secs < 0) ? Long.MIN_VALUE : Long.MAX_VALUE;
155 }
156 return r;
157 }
158 }
159
160 /**
161 * Returns the value in milliseconds.
162 *
163 * <p> Conversion from a coarser granularity that would numerically overflow
164 * saturate to {@code Long.MIN_VALUE} if negative or {@code Long.MAX_VALUE}
165 * if positive.
166 *
167 * @return the value in milliseconds, since the epoch (1970-01-01T00:00:00Z)
168 */
169 public long toMillis() {
170 if (unit != null) {
171 return unit.toMillis(value);
172 } else {
173 long secs = instant.getEpochSecond();
174 int nanos = instant.getNano();
175 // Math.multiplyExact() variant
176 long r = secs * 1000;
177 long ax = Math.abs(secs);
178 if (((ax | 1000) >>> 31 != 0)) {
179 if ((r / 1000) != secs) {
180 return (secs < 0) ? Long.MIN_VALUE : Long.MAX_VALUE;
181 }
182 }
183 return r + nanos / 1000_000;
184 }
185 }
186
187 /**
188 * Time unit constants for conversion.
189 */
190 private static final long HOURS_PER_DAY = 24L;
191 private static final long MINUTES_PER_HOUR = 60L;
192 private static final long SECONDS_PER_MINUTE = 60L;
193 private static final long SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
194 private static final long SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY;
195 private static final long MILLIS_PER_SECOND = 1000L;
196 private static final long MICROS_PER_SECOND = 1000_000L;
197 private static final long NANOS_PER_SECOND = 1000_000_000L;
198 private static final int NANOS_PER_MILLI = 1000_000;
199 private static final int NANOS_PER_MICRO = 1000;
200 // The epoch second of Instant.MIN.
201 private static final long MIN_SECOND = -31557014167219200L;
202 // The epoch second of Instant.MAX.
203 private static final long MAX_SECOND = 31556889864403199L;
204
205 /*
206 * Scale d by m, checking for overflow.
207 */
208 private static long scale(long d, long m, long over) {
209 if (d > over) return Long.MAX_VALUE;
210 if (d < -over) return Long.MIN_VALUE;
211 return d * m;
212 }
213
214 /**
215 * Converts this {@code FileTime} object to an {@code Instant}.
216 *
217 * <p> The conversion creates an {@code Instant} that represents the
218 * same point on the time-line as this {@code FileTime}.
219 *
220 * <p> {@code FileTime} can store points on the time-line further in the
221 * future and further in the past than {@code Instant}. Conversion
222 * from such further time points saturates to {@link Instant#MIN} if
223 * earlier than {@code Instant.MIN} or {@link Instant#MAX} if later
224 * than {@code Instant.MAX}.
225 *
226 * @return an instant representing the same point on the time-line as
227 * this {@code FileTime} object
228 * @since 1.8
229 */
230 public Instant toInstant() {
231 if (instant == null) {
232 long secs = 0L;
233 int nanos = 0;
234 switch (unit) {
235 case DAYS:
236 secs = scale(value, SECONDS_PER_DAY,
237 Long.MAX_VALUE/SECONDS_PER_DAY);
238 break;
239 case HOURS:
240 secs = scale(value, SECONDS_PER_HOUR,
241 Long.MAX_VALUE/SECONDS_PER_HOUR);
242 break;
243 case MINUTES:
244 secs = scale(value, SECONDS_PER_MINUTE,
245 Long.MAX_VALUE/SECONDS_PER_MINUTE);
246 break;
247 case SECONDS:
248 secs = value;
249 break;
250 case MILLISECONDS:
251 secs = Math.floorDiv(value, MILLIS_PER_SECOND);
252 nanos = (int)Math.floorMod(value, MILLIS_PER_SECOND)
253 * NANOS_PER_MILLI;
254 break;
255 case MICROSECONDS:
256 secs = Math.floorDiv(value, MICROS_PER_SECOND);
257 nanos = (int)Math.floorMod(value, MICROS_PER_SECOND)
258 * NANOS_PER_MICRO;
259 break;
260 case NANOSECONDS:
261 secs = Math.floorDiv(value, NANOS_PER_SECOND);
262 nanos = (int)Math.floorMod(value, NANOS_PER_SECOND);
263 break;
264 default : throw new AssertionError("Unit not handled");
265 }
266 if (secs <= MIN_SECOND)
267 instant = Instant.MIN;
268 else if (secs >= MAX_SECOND)
269 instant = Instant.MAX;
270 else
271 instant = Instant.ofEpochSecond(secs, nanos);
272 }
273 return instant;
274 }
275
276 /**
277 * Tests this {@code FileTime} for equality with the given object.
278 *
279 * <p> The result is {@code true} if and only if the argument is not {@code
280 * null} and is a {@code FileTime} that represents the same time. This
281 * method satisfies the general contract of the {@code Object.equals} method.
282 *
283 * @param obj
284 * the object to compare with
285 *
286 * @return {@code true} if, and only if, the given object is a {@code
287 * FileTime} that represents the same time
288 */
289 @Override
290 public boolean equals(Object obj) {
291 return (obj instanceof FileTime) ? compareTo((FileTime)obj) == 0 : false;
292 }
293
294 /**
295 * Computes a hash code for this file time.
296 *
297 * <p> The hash code is based upon the value represented, and satisfies the
298 * general contract of the {@link Object#hashCode} method.
299 *
300 * @return the hash-code value
301 */
302 @Override
303 public int hashCode() {
304 // hashcode of instant representation to satisfy contract with equals
305 return toInstant().hashCode();
306 }
307
308 private long toDays() {
309 if (unit != null) {
310 return unit.toDays(value);
311 } else {
312 return TimeUnit.SECONDS.toDays(toInstant().getEpochSecond());
313 }
314 }
315
316 private long toExcessNanos(long days) {
317 if (unit != null) {
318 return unit.toNanos(value - unit.convert(days, TimeUnit.DAYS));
319 } else {
320 return TimeUnit.SECONDS.toNanos(toInstant().getEpochSecond()
321 - TimeUnit.DAYS.toSeconds(days));
322 }
323 }
324
325 /**
326 * Compares the value of two {@code FileTime} objects for order.
327 *
328 * @param other
329 * the other {@code FileTime} to be compared
330 *
331 * @return {@code 0} if this {@code FileTime} is equal to {@code other}, a
332 * value less than 0 if this {@code FileTime} represents a time
333 * that is before {@code other}, and a value greater than 0 if this
334 * {@code FileTime} represents a time that is after {@code other}
335 */
336 @Override
337 public int compareTo(FileTime other) {
338 // same granularity
339 if (unit != null && unit == other.unit) {
340 return Long.compare(value, other.value);
341 } else {
342 // compare using instant representation when unit differs
343 long secs = toInstant().getEpochSecond();
344 long secsOther = other.toInstant().getEpochSecond();
345 int cmp = Long.compare(secs, secsOther);
346 if (cmp != 0) {
347 return cmp;
348 }
349 cmp = Long.compare(toInstant().getNano(), other.toInstant().getNano());
350 if (cmp != 0) {
351 return cmp;
352 }
353 if (secs != MAX_SECOND && secs != MIN_SECOND) {
354 return 0;
355 }
356 // if both this and other's Instant reps are MIN/MAX,
357 // use daysSinceEpoch and nanosOfDays, which will not
358 // saturate during calculation.
359 long days = toDays();
360 long daysOther = other.toDays();
361 if (days == daysOther) {
362 return Long.compare(toExcessNanos(days), other.toExcessNanos(daysOther));
363 }
364 return Long.compare(days, daysOther);
365 }
366 }
367
368 // days in a 400 year cycle = 146097
369 // days in a 10,000 year cycle = 146097 * 25
370 // seconds per day = 86400
371 private static final long DAYS_PER_10000_YEARS = 146097L * 25L;
372 private static final long SECONDS_PER_10000_YEARS = 146097L * 25L * 86400L;
373 private static final long SECONDS_0000_TO_1970 = ((146097L * 5L) - (30L * 365L + 7L)) * 86400L;
374
375 // append year/month/day/hour/minute/second/nano with width and 0 padding
376 private StringBuilder append(StringBuilder sb, int w, int d) {
377 while (w > 0) {
378 sb.append((char)(d/w + '0'));
379 d = d % w;
380 w /= 10;
381 }
382 return sb;
383 }
384
385 /**
386 * Returns the string representation of this {@code FileTime}. The string
387 * is returned in the <a
388 * href="http://www.w3.org/TR/NOTE-datetime">ISO 8601</a> format:
389 * <pre>
390 * YYYY-MM-DDThh:mm:ss[.s+]Z
391 * </pre>
392 * where "{@code [.s+]}" represents a dot followed by one of more digits
393 * for the decimal fraction of a second. It is only present when the decimal
394 * fraction of a second is not zero. For example, {@code
395 * FileTime.fromMillis(1234567890000L).toString()} yields {@code
396 * "2009-02-13T23:31:30Z"}, and {@code FileTime.fromMillis(1234567890123L).toString()}
397 * yields {@code "2009-02-13T23:31:30.123Z"}.
398 *
399 * <p> A {@code FileTime} is primarily intended to represent the value of a
400 * file's time stamp. Where used to represent <i>extreme values</i>, where
401 * the year is less than "{@code 0001}" or greater than "{@code 9999}" then
402 * this method deviates from ISO 8601 in the same manner as the
403 * <a href="http://www.w3.org/TR/xmlschema-2/#deviantformats">XML Schema
404 * language</a>. That is, the year may be expanded to more than four digits
405 * and may be negative-signed. If more than four digits then leading zeros
406 * are not present. The year before "{@code 0001}" is "{@code -0001}".
407 *
408 * @return the string representation of this file time
409 */
410 @Override
411 public String toString() {
412 if (valueAsString == null) {
413 long secs = 0L;
414 int nanos = 0;
415 if (instant == null && unit.compareTo(TimeUnit.SECONDS) >= 0) {
416 secs = unit.toSeconds(value);
417 } else {
418 secs = toInstant().getEpochSecond();
419 nanos = toInstant().getNano();
420 }
421 LocalDateTime ldt;
422 int year = 0;
423 if (secs >= -SECONDS_0000_TO_1970) {
424 // current era
425 long zeroSecs = secs - SECONDS_PER_10000_YEARS + SECONDS_0000_TO_1970;
426 long hi = Math.floorDiv(zeroSecs, SECONDS_PER_10000_YEARS) + 1;
427 long lo = Math.floorMod(zeroSecs, SECONDS_PER_10000_YEARS);
428 ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, nanos, ZoneOffset.UTC);
429 year = ldt.getYear() + (int)hi * 10000;
430 } else {
431 // before current era
432 long zeroSecs = secs + SECONDS_0000_TO_1970;
433 long hi = zeroSecs / SECONDS_PER_10000_YEARS;
434 long lo = zeroSecs % SECONDS_PER_10000_YEARS;
435 ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, nanos, ZoneOffset.UTC);
436 year = ldt.getYear() + (int)hi * 10000;
437 }
438 if (year <= 0) {
439 year = year - 1;
440 }
441 int fraction = ldt.getNano();
442 StringBuilder sb = new StringBuilder(64);
443 sb.append(year < 0 ? "-" : "");
444 year = Math.abs(year);
445 if (year < 10000) {
446 append(sb, 1000, Math.abs(year));
447 } else {
448 sb.append(String.valueOf(year));
449 }
450 sb.append('-');
451 append(sb, 10, ldt.getMonthValue());
452 sb.append('-');
453 append(sb, 10, ldt.getDayOfMonth());
454 sb.append('T');
455 append(sb, 10, ldt.getHour());
456 sb.append(':');
457 append(sb, 10, ldt.getMinute());
458 sb.append(':');
459 append(sb, 10, ldt.getSecond());
460 if (fraction != 0) {
461 sb.append('.');
462 // adding leading zeros and stripping any trailing zeros
463 int w = 100_000_000;
464 while (fraction % 10 == 0) {
465 fraction /= 10;
466 w /= 10;
467 }
468 append(sb, w, fraction);
469 }
470 sb.append('Z');
471 valueAsString = sb.toString();
472 }
473 return valueAsString;
474 }
475 }
476