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 */ FileTime(long value, TimeUnit unit, Instant instant)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 */ from(long value, TimeUnit unit)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 */ fromMillis(long value)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 */ from(Instant instant)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 */ to(TimeUnit unit)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 */ toMillis()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 */ scale(long d, long m, long over)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 */ toInstant()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 equals(Object obj)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 hashCode()303 public int hashCode() { 304 // hashcode of instant representation to satisfy contract with equals 305 return toInstant().hashCode(); 306 } 307 toDays()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 toExcessNanos(long days)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 compareTo(FileTime other)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 append(StringBuilder sb, int w, int d)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 toString()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