/* * Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.nio.file.attribute; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Date; import java.util.Formatter; import java.util.Locale; import java.util.TimeZone; import java.util.concurrent.TimeUnit; /** * Represents the value of a file's time stamp attribute. For example, it may * represent the time that the file was last modified, accessed, or created. * *

Instances of this class are immutable. * * @since 1.7 * @see BasicFileAttributes * @see Attributes#setLastModifiedTime */ public final class FileTime implements Comparable { private final long value; private final TimeUnit unit; private String valueAsString; // created lazily private FileTime(long value, TimeUnit unit) { if (unit == null) throw new NullPointerException(); this.value = value; this.unit = unit; } /** * Returns a {@code FileTime} representing a value at the given unit of * granularity. * * @param value * the value since the epoch (1970-01-01T00:00:00Z); can be * negative * @param unit * the unit of granularity to interpret the value * * @return a {@code FileTime} representing the given value */ public static FileTime from(long value, TimeUnit unit) { return new FileTime(value, unit); } /** * Returns a {@code FileTime} representing the given value in milliseconds. * * @param value * the value, in milliseconds, since the epoch * (1970-01-01T00:00:00Z); can be negative * * @return a {@code FileTime} representing the given value */ public static FileTime fromMillis(long value) { return new FileTime(value, TimeUnit.MILLISECONDS); } /** * Returns the value at the given unit of granularity. * *

Conversion from a coarser granularity that would numerically overflow * saturate to {@code Long.MIN_VALUE} if negative or {@code Long.MAX_VALUE} * if positive. * * @param unit * the unit of granularity for the return value * * @return value in the given unit of granularity, since the epoch * since the epoch (1970-01-01T00:00:00Z); can be negative */ public long to(TimeUnit unit) { return unit.convert(this.value, this.unit); } /** * Returns the value in milliseconds. * *

Conversion from a coarser granularity that would numerically overflow * saturate to {@code Long.MIN_VALUE} if negative or {@code Long.MAX_VALUE} * if positive. * * @return the value in milliseconds, since the epoch (1970-01-01T00:00:00Z) */ public long toMillis() { return unit.toMillis(value); } /** * Tests this {@code FileTime} for equality with the given object. * *

The result is {@code true} if and only if the argument is not {@code * null} and is a {@code FileTime} that represents the same time. This * method satisfies the general contract of the {@code Object.equals} method. * * @param obj * the object to compare with * * @return {@code true} if, and only if, the given object is a {@code * FileTime} that represents the same time */ @Override public boolean equals(Object obj) { return (obj instanceof FileTime) ? compareTo((FileTime)obj) == 0 : false; } /** * Computes a hash code for this file time. * *

The hash code is based upon the value represented, and satisfies the * general contract of the {@link Object#hashCode} method. * * @return the hash-code value */ @Override public int hashCode() { // hash value for fixed granularity to satisfy contract with equals long ms = toMillis(); return (int)(ms ^ (ms >>> 32)); } /** * Compares the value of two {@code FileTime} objects for order. * * @param other * the other {@code FileTime} to be compared * * @return {@code 0} if this {@code FileTime} is equal to {@code other}, a * value less than 0 if this {@code FileTime} represents a time * that is before {@code other}, and a value greater than 0 if this * {@code FileTime} represents a time that is after {@code other} */ @Override public int compareTo(FileTime other) { // same granularity if (unit == other.unit) return (value < other.value) ? -1 : (value == other.value ? 0 : 1); // compare in days long thisValueInDays = unit.toDays(value); long otherValueInDays = other.unit.toDays(other.value); if (thisValueInDays != otherValueInDays) return (thisValueInDays < otherValueInDays) ? -1 : 1; // compare remainder in nanoseconds long thisRemainder = remainderInNanos(thisValueInDays); long otherRemainder = other.remainderInNanos(otherValueInDays); return (thisRemainder < otherRemainder) ? -1 : (thisRemainder == otherRemainder) ? 0 : 1; } private long remainderInNanos(long days) { // constants for conversion final long C0 = 1L; final long C1 = C0 * 24L; final long C2 = C1 * 60L; final long C3 = C2 * 60L; final long C4 = C3 * 1000L; final long C5 = C4 * 1000L; final long C6 = C5 * 1000L; long scale; switch (unit) { case DAYS : scale = C0; break; case HOURS : scale = C1; break; case MINUTES : scale = C2; break; case SECONDS : scale = C3; break; case MILLISECONDS : scale = C4; break; case MICROSECONDS : scale = C5; break; case NANOSECONDS : scale = C6; break; default: throw new AssertionError("Unit not handled"); } long rem = value - (days * scale); return unit.toNanos(rem); } /** * Returns the string representation of this {@code FileTime}. The string * is returned in the ISO 8601 format: *

     *     YYYY-MM-DDThh:mm:ss[.s+]Z
     * 
* where "{@code [.s+]}" represents a dot followed by one of more digits * for the decimal fraction of a second. It is only present when the decimal * fraction of a second is not zero. For example, {@code * FileTime.fromMillis(1234567890000L).toString()} yields {@code * "2009-02-13T23:31:30Z"}, and {@code FileTime.fromMillis(1234567890123L).toString()} * yields {@code "2009-02-13T23:31:30.123Z"}. * *

A {@code FileTime} is primarly intended to represent the value of a * file's time stamp. Where used to represent extreme values, where * the year is less than "{@code 0001}" or greater than "{@code 9999}" then * the year may be expanded to more than four digits and may be * negative-signed. If more than four digits then leading zeros are not * present. The year before "{@code 0001}" is "{@code -0001}". * * @return the string representation of this file time */ @Override public String toString() { String v = valueAsString; if (v == null) { // overflow saturates to Long.MIN_VALUE or Long.MAX_VALUE so this // limits the range: // [-292275056-05-16T16:47:04.192Z,292278994-08-17T07:12:55.807Z] long ms = toMillis(); // nothing to do when seconds/minutes/hours/days String fractionAsString = ""; if (unit.compareTo(TimeUnit.SECONDS) < 0) { // constants for conversion final long C0 = 1L; final long C1 = C0 * 1000L; final long C2 = C1 * 1000L; final long C3 = C2 * 1000L; long scale; int width; switch (unit) { case MILLISECONDS : scale = C1; width = 3; break; case MICROSECONDS : scale = C2; width = 6; break; case NANOSECONDS : scale = C3; width = 9; break; default: throw new AssertionError("Unit not handled"); } long fraction = value % scale; if (fraction != 0L) { // fraction must be positive if (fraction < 0L) { fraction += scale; if (ms != Long.MIN_VALUE) ms--; } // convert to String, adding leading zeros as required and // stripping any trailing zeros String s = Long.toString(fraction); int len = s.length(); width -= len; StringBuilder sb = new StringBuilder("."); while (width-- > 0) { sb.append('0'); } if (s.charAt(len-1) == '0') { // drop trailing zeros len--; while (s.charAt(len-1) == '0') len--; sb.append(s.substring(0, len)); } else { sb.append(s); } fractionAsString = sb.toString(); } } // create calendar to use with formatter. GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"), Locale.ROOT); if (value < 0L) cal.setGregorianChange(new Date(Long.MIN_VALUE)); cal.setTimeInMillis(ms); // years are negative before common era String sign = (cal.get(Calendar.ERA) == GregorianCalendar.BC) ? "-" : ""; // [-]YYYY-MM-DDThh:mm:ss[.s]Z v = new Formatter(Locale.ROOT) .format("%s%tFT%tR:%tS%sZ", sign, cal, cal, cal, fractionAsString) .toString(); valueAsString = v; } return v; } }