/*
* Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package java.nio.file.attribute;
import java.time.
Instant;
import java.time.
LocalDateTime;
import java.time.
ZoneOffset;
import java.util.
Objects;
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
* {@link BasicFileAttributes#lastModifiedTime() modified},
* {@link BasicFileAttributes#lastAccessTime() accessed},
* or {@link BasicFileAttributes#creationTime() created}.
*
* <p> Instances of this class are immutable.
*
* @since 1.7
* @see java.nio.file.Files#setLastModifiedTime
* @see java.nio.file.Files#getLastModifiedTime
*/
public final class
FileTime
implements
Comparable<
FileTime>
{
/**
* The unit of granularity to interpret the value. Null if
* this {@code FileTime} is converted from an {@code Instant},
* the {@code value} and {@code unit} pair will not be used
* in this scenario.
*/
private final
TimeUnit unit;
/**
* The value since the epoch; can be negative.
*/
private final long
value;
/**
* The value as Instant (created lazily, if not from an instant)
*/
private
Instant instant;
/**
* The value return by toString (created lazily)
*/
private
String valueAsString;
/**
* Initializes a new instance of this class.
*/
private
FileTime(long
value,
TimeUnit unit,
Instant instant) {
this.
value =
value;
this.
unit =
unit;
this.
instant =
instant;
}
/**
* 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) {
Objects.
requireNonNull(
unit, "unit");
return new
FileTime(
value,
unit, null);
}
/**
* 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, null);
}
/**
* Returns a {@code FileTime} representing the same point of time value
* on the time-line as the provided {@code Instant} object.
*
* @param instant
* the instant to convert
* @return a {@code FileTime} representing the same point on the time-line
* as the provided instant
* @since 1.8
*/
public static
FileTime from(
Instant instant) {
Objects.
requireNonNull(
instant, "instant");
return new
FileTime(0, null,
instant);
}
/**
* Returns the value at the given unit of granularity.
*
* <p> 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) {
Objects.
requireNonNull(
unit, "unit");
if (this.
unit != null) {
return
unit.
convert(this.
value, this.
unit);
} else {
long
secs =
unit.
convert(
instant.
getEpochSecond(),
TimeUnit.
SECONDS);
if (
secs ==
Long.
MIN_VALUE ||
secs ==
Long.
MAX_VALUE) {
return
secs;
}
long
nanos =
unit.
convert(
instant.
getNano(),
TimeUnit.
NANOSECONDS);
long
r =
secs +
nanos;
// Math.addExact() variant
if (((
secs ^
r) & (
nanos ^
r)) < 0) {
return (
secs < 0) ?
Long.
MIN_VALUE :
Long.
MAX_VALUE;
}
return
r;
}
}
/**
* Returns the value in milliseconds.
*
* <p> 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() {
if (
unit != null) {
return
unit.
toMillis(
value);
} else {
long
secs =
instant.
getEpochSecond();
int
nanos =
instant.
getNano();
// Math.multiplyExact() variant
long
r =
secs * 1000;
long
ax =
Math.
abs(
secs);
if (((
ax | 1000) >>> 31 != 0)) {
if ((
r / 1000) !=
secs) {
return (
secs < 0) ?
Long.
MIN_VALUE :
Long.
MAX_VALUE;
}
}
return
r +
nanos / 1000_000;
}
}
/**
* Time unit constants for conversion.
*/
private static final long
HOURS_PER_DAY = 24L;
private static final long
MINUTES_PER_HOUR = 60L;
private static final long
SECONDS_PER_MINUTE = 60L;
private static final long
SECONDS_PER_HOUR =
SECONDS_PER_MINUTE *
MINUTES_PER_HOUR;
private static final long
SECONDS_PER_DAY =
SECONDS_PER_HOUR *
HOURS_PER_DAY;
private static final long
MILLIS_PER_SECOND = 1000L;
private static final long
MICROS_PER_SECOND = 1000_000L;
private static final long
NANOS_PER_SECOND = 1000_000_000L;
private static final int
NANOS_PER_MILLI = 1000_000;
private static final int
NANOS_PER_MICRO = 1000;
// The epoch second of Instant.MIN.
private static final long
MIN_SECOND = -31557014167219200L;
// The epoch second of Instant.MAX.
private static final long
MAX_SECOND = 31556889864403199L;
/*
* Scale d by m, checking for overflow.
*/
private static long
scale(long
d, long
m, long
over) {
if (
d >
over) return
Long.
MAX_VALUE;
if (
d < -
over) return
Long.
MIN_VALUE;
return
d *
m;
}
/**
* Converts this {@code FileTime} object to an {@code Instant}.
*
* <p> The conversion creates an {@code Instant} that represents the
* same point on the time-line as this {@code FileTime}.
*
* <p> {@code FileTime} can store points on the time-line further in the
* future and further in the past than {@code Instant}. Conversion
* from such further time points saturates to {@link Instant#MIN} if
* earlier than {@code Instant.MIN} or {@link Instant#MAX} if later
* than {@code Instant.MAX}.
*
* @return an instant representing the same point on the time-line as
* this {@code FileTime} object
* @since 1.8
*/
public
Instant toInstant() {
if (
instant == null) {
long
secs = 0L;
int
nanos = 0;
switch (
unit) {
case
DAYS:
secs =
scale(
value,
SECONDS_PER_DAY,
Long.
MAX_VALUE/
SECONDS_PER_DAY);
break;
case
HOURS:
secs =
scale(
value,
SECONDS_PER_HOUR,
Long.
MAX_VALUE/
SECONDS_PER_HOUR);
break;
case
MINUTES:
secs =
scale(
value,
SECONDS_PER_MINUTE,
Long.
MAX_VALUE/
SECONDS_PER_MINUTE);
break;
case
SECONDS:
secs =
value;
break;
case
MILLISECONDS:
secs =
Math.
floorDiv(
value,
MILLIS_PER_SECOND);
nanos = (int)
Math.
floorMod(
value,
MILLIS_PER_SECOND)
*
NANOS_PER_MILLI;
break;
case
MICROSECONDS:
secs =
Math.
floorDiv(
value,
MICROS_PER_SECOND);
nanos = (int)
Math.
floorMod(
value,
MICROS_PER_SECOND)
*
NANOS_PER_MICRO;
break;
case
NANOSECONDS:
secs =
Math.
floorDiv(
value,
NANOS_PER_SECOND);
nanos = (int)
Math.
floorMod(
value,
NANOS_PER_SECOND);
break;
default : throw new
AssertionError("Unit not handled");
}
if (
secs <=
MIN_SECOND)
instant =
Instant.
MIN;
else if (
secs >=
MAX_SECOND)
instant =
Instant.
MAX;
else
instant =
Instant.
ofEpochSecond(
secs,
nanos);
}
return
instant;
}
/**
* Tests this {@code FileTime} for equality with the given object.
*
* <p> 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.
*
* <p> 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() {
// hashcode of instant representation to satisfy contract with equals
return
toInstant().
hashCode();
}
private long
toDays() {
if (
unit != null) {
return
unit.
toDays(
value);
} else {
return
TimeUnit.
SECONDS.
toDays(
toInstant().
getEpochSecond());
}
}
private long
toExcessNanos(long
days) {
if (
unit != null) {
return
unit.
toNanos(
value -
unit.
convert(
days,
TimeUnit.
DAYS));
} else {
return
TimeUnit.
SECONDS.
toNanos(
toInstant().
getEpochSecond()
-
TimeUnit.
DAYS.
toSeconds(
days));
}
}
/**
* 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 != null &&
unit ==
other.
unit) {
return
Long.
compare(
value,
other.
value);
} else {
// compare using instant representation when unit differs
long
secs =
toInstant().
getEpochSecond();
long
secsOther =
other.
toInstant().
getEpochSecond();
int
cmp =
Long.
compare(
secs,
secsOther);
if (
cmp != 0) {
return
cmp;
}
cmp =
Long.
compare(
toInstant().
getNano(),
other.
toInstant().
getNano());
if (
cmp != 0) {
return
cmp;
}
if (
secs !=
MAX_SECOND &&
secs !=
MIN_SECOND) {
return 0;
}
// if both this and other's Instant reps are MIN/MAX,
// use daysSinceEpoch and nanosOfDays, which will not
// saturate during calculation.
long
days =
toDays();
long
daysOther =
other.
toDays();
if (
days ==
daysOther) {
return
Long.
compare(
toExcessNanos(
days),
other.
toExcessNanos(
daysOther));
}
return
Long.
compare(
days,
daysOther);
}
}
// days in a 400 year cycle = 146097
// days in a 10,000 year cycle = 146097 * 25
// seconds per day = 86400
private static final long
DAYS_PER_10000_YEARS = 146097L * 25L;
private static final long
SECONDS_PER_10000_YEARS = 146097L * 25L * 86400L;
private static final long
SECONDS_0000_TO_1970 = ((146097L * 5L) - (30L * 365L + 7L)) * 86400L;
// append year/month/day/hour/minute/second/nano with width and 0 padding
private
StringBuilder append(
StringBuilder sb, int
w, int
d) {
while (
w > 0) {
sb.
append((char)(
d/
w + '0'));
d =
d %
w;
w /= 10;
}
return
sb;
}
/**
* Returns the string representation of this {@code FileTime}. The string
* is returned in the <a
* href="http://www.w3.org/TR/NOTE-datetime">ISO 8601</a> format:
* <pre>
* YYYY-MM-DDThh:mm:ss[.s+]Z
* </pre>
* 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"}.
*
* <p> A {@code FileTime} is primarily intended to represent the value of a
* file's time stamp. Where used to represent <i>extreme values</i>, where
* the year is less than "{@code 0001}" or greater than "{@code 9999}" then
* this method deviates from ISO 8601 in the same manner as the
* <a href="http://www.w3.org/TR/xmlschema-2/#deviantformats">XML Schema
* language</a>. That is, 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() {
if (
valueAsString == null) {
long
secs = 0L;
int
nanos = 0;
if (
instant == null &&
unit.
compareTo(
TimeUnit.
SECONDS) >= 0) {
secs =
unit.
toSeconds(
value);
} else {
secs =
toInstant().
getEpochSecond();
nanos =
toInstant().
getNano();
}
LocalDateTime ldt;
int
year = 0;
if (
secs >= -
SECONDS_0000_TO_1970) {
// current era
long
zeroSecs =
secs -
SECONDS_PER_10000_YEARS +
SECONDS_0000_TO_1970;
long
hi =
Math.
floorDiv(
zeroSecs,
SECONDS_PER_10000_YEARS) + 1;
long
lo =
Math.
floorMod(
zeroSecs,
SECONDS_PER_10000_YEARS);
ldt =
LocalDateTime.
ofEpochSecond(
lo -
SECONDS_0000_TO_1970,
nanos,
ZoneOffset.
UTC);
year =
ldt.
getYear() + (int)
hi * 10000;
} else {
// before current era
long
zeroSecs =
secs +
SECONDS_0000_TO_1970;
long
hi =
zeroSecs /
SECONDS_PER_10000_YEARS;
long
lo =
zeroSecs %
SECONDS_PER_10000_YEARS;
ldt =
LocalDateTime.
ofEpochSecond(
lo -
SECONDS_0000_TO_1970,
nanos,
ZoneOffset.
UTC);
year =
ldt.
getYear() + (int)
hi * 10000;
}
if (
year <= 0) {
year =
year - 1;
}
int
fraction =
ldt.
getNano();
StringBuilder sb = new
StringBuilder(64);
sb.
append(
year < 0 ? "-" : "");
year =
Math.
abs(
year);
if (
year < 10000) {
append(
sb, 1000,
Math.
abs(
year));
} else {
sb.
append(
String.
valueOf(
year));
}
sb.
append('-');
append(
sb, 10,
ldt.
getMonthValue());
sb.
append('-');
append(
sb, 10,
ldt.
getDayOfMonth());
sb.
append('T');
append(
sb, 10,
ldt.
getHour());
sb.
append(':');
append(
sb, 10,
ldt.
getMinute());
sb.
append(':');
append(
sb, 10,
ldt.
getSecond());
if (
fraction != 0) {
sb.
append('.');
// adding leading zeros and stripping any trailing zeros
int
w = 100_000_000;
while (
fraction % 10 == 0) {
fraction /= 10;
w /= 10;
}
append(
sb,
w,
fraction);
}
sb.
append('Z');
valueAsString =
sb.
toString();
}
return
valueAsString;
}
}