diff --git a/src/share/classes/java/nio/file/attribute/FileTime.java b/src/share/classes/java/nio/file/attribute/FileTime.java index be7c6989e7b78267d8a3f1917211553a77bbe2ba..6ad8ae49e8bab1a55a168ad50915bf23ccd35152 100644 --- a/src/share/classes/java/nio/file/attribute/FileTime.java +++ b/src/share/classes/java/nio/file/attribute/FileTime.java @@ -25,12 +25,10 @@ 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.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Objects; import java.util.concurrent.TimeUnit; /** @@ -51,42 +49,35 @@ public final class FileTime implements Comparable { /** - * The value since the epoch; can be negative. - */ - private final long value; - - /** - * The unit of granularity to interpret the value. + * 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 return by toString (created lazily) + * The value since the epoch; can be negative. */ - private String valueAsString; + private final long value; /** - * The value in days and excess nanos (created lazily) + * The value as Instant (created lazily, if not from an instant) */ - private DaysAndNanos daysAndNanos; + private Instant instant; /** - * Returns a DaysAndNanos object representing the value. + * The value return by toString (created lazily) */ - private DaysAndNanos asDaysAndNanos() { - if (daysAndNanos == null) - daysAndNanos = new DaysAndNanos(value, unit); - return daysAndNanos; - } + private String valueAsString; /** * Initializes a new instance of this class. */ - private FileTime(long value, TimeUnit unit) { - if (unit == null) - throw new NullPointerException(); + private FileTime(long value, TimeUnit unit, Instant instant) { this.value = value; this.unit = unit; + this.instant = instant; } /** @@ -102,7 +93,8 @@ public final class FileTime * @return a {@code FileTime} representing the given value */ public static FileTime from(long value, TimeUnit unit) { - return new FileTime(value, unit); + Objects.requireNonNull(unit, "unit"); + return new FileTime(value, unit, null); } /** @@ -115,7 +107,22 @@ public final class FileTime * @return a {@code FileTime} representing the given value */ public static FileTime fromMillis(long value) { - return new FileTime(value, TimeUnit.MILLISECONDS); + 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); } /** @@ -132,7 +139,22 @@ public final class FileTime * since the epoch (1970-01-01T00:00:00Z); can be negative */ public long to(TimeUnit unit) { - return unit.convert(this.value, this.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; + } } /** @@ -145,7 +167,110 @@ public final class FileTime * @return the value in milliseconds, since the epoch (1970-01-01T00:00:00Z) */ public long toMillis() { - return unit.toMillis(value); + 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}. + * + *

The conversion creates an {@code Instant} that represents the + * same point on the time-line as this {@code FileTime}. + * + *

{@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; } /** @@ -176,8 +301,25 @@ public final class FileTime */ @Override public int hashCode() { - // hashcode of days/nanos representation to satisfy contract with equals - return asDaysAndNanos().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)); + } } /** @@ -194,14 +336,52 @@ public final class FileTime @Override public int compareTo(FileTime other) { // same granularity - if (unit == other.unit) { - return (value < other.value) ? -1 : (value == other.value ? 0 : 1); + if (unit != null && unit == other.unit) { + return Long.compare(value, other.value); } else { - // compare using days/nanos representation when unit differs - return asDaysAndNanos().compareTo(other.asDaysAndNanos()); + // 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 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(); - } + 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(); } - - // 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; - } - - /** - * Represents a FileTime's value as two longs: the number of days since - * the epoch, and the excess (in nanoseconds). This is used for comparing - * values with different units of granularity. - */ - private static class DaysAndNanos implements Comparable { - // constants for conversion - private static final long C0 = 1L; - private static final long C1 = C0 * 24L; - private static final long C2 = C1 * 60L; - private static final long C3 = C2 * 60L; - private static final long C4 = C3 * 1000L; - private static final long C5 = C4 * 1000L; - private static final long C6 = C5 * 1000L; - - /** - * The value (in days) since the epoch; can be negative. - */ - private final long days; - - /** - * The excess (in nanoseconds); can be negative if days <= 0. - */ - private final long excessNanos; - - /** - * Initializes a new instance of this class. - */ - DaysAndNanos(long value, TimeUnit unit) { - 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"); + 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; } - this.days = unit.toDays(value); - this.excessNanos = unit.toNanos(value - (this.days * scale)); - } - - /** - * Returns the fraction of a second, in nanoseconds. - */ - long fractionOfSecondInNanos() { - return excessNanos % (1000L * 1000L * 1000L); - } - - @Override - public boolean equals(Object obj) { - return (obj instanceof DaysAndNanos) ? - compareTo((DaysAndNanos)obj) == 0 : false; - } - - @Override - public int hashCode() { - return (int)(days ^ (days >>> 32) ^ - excessNanos ^ (excessNanos >>> 32)); - } - - @Override - public int compareTo(DaysAndNanos other) { - if (this.days != other.days) - return (this.days < other.days) ? -1 : 1; - return (this.excessNanos < other.excessNanos) ? -1 : - (this.excessNanos == other.excessNanos) ? 0 : 1; + 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; } } diff --git a/test/java/nio/file/attribute/FileTime/Basic.java b/test/java/nio/file/attribute/FileTime/Basic.java index 9a8bdf68205e9cfb9b5831ba21e1821e62f45290..52eba9d1737877a3a6adeed6c0807f609dd34be9 100644 --- a/test/java/nio/file/attribute/FileTime/Basic.java +++ b/test/java/nio/file/attribute/FileTime/Basic.java @@ -22,14 +22,17 @@ */ /* @test - * @bug 6844313 + * @bug 6844313 8011647 * @summary Unit test for java.nio.file.FileTime */ + import java.nio.file.attribute.FileTime; +import java.time.Instant; import java.util.concurrent.TimeUnit; import static java.util.concurrent.TimeUnit.*; import java.util.Random; +import java.util.EnumSet; public class Basic { @@ -40,27 +43,59 @@ public class Basic { long tomorrowInDays = TimeUnit.DAYS.convert(now, MILLISECONDS) + 1; long yesterdayInDays = TimeUnit.DAYS.convert(now, MILLISECONDS) - 1; + Instant nowInstant = Instant.ofEpochMilli(now); + // equals eq(now, MILLISECONDS, now, MILLISECONDS); eq(now, MILLISECONDS, now*1000L, MICROSECONDS); neq(now, MILLISECONDS, 0, MILLISECONDS); neq(now, MILLISECONDS, 0, MICROSECONDS); + eq(nowInstant, now, MILLISECONDS); + eq(nowInstant, now*1000L, MICROSECONDS); + neq(nowInstant, 0, MILLISECONDS); + neq(nowInstant, 0, MICROSECONDS); + // compareTo cmp(now, MILLISECONDS, now, MILLISECONDS, 0); cmp(now, MILLISECONDS, now*1000L, MICROSECONDS, 0); cmp(now, MILLISECONDS, now-1234, MILLISECONDS, 1); cmp(now, MILLISECONDS, now+1234, MILLISECONDS, -1); + cmp(tomorrowInDays, DAYS, now, MILLISECONDS, 1); cmp(now, MILLISECONDS, tomorrowInDays, DAYS, -1); cmp(yesterdayInDays, DAYS, now, MILLISECONDS, -1); cmp(now, MILLISECONDS, yesterdayInDays, DAYS, 1); cmp(yesterdayInDays, DAYS, now, MILLISECONDS, -1); + cmp(Long.MAX_VALUE, DAYS, Long.MAX_VALUE, NANOSECONDS, 1); cmp(Long.MAX_VALUE, DAYS, Long.MIN_VALUE, NANOSECONDS, 1); cmp(Long.MIN_VALUE, DAYS, Long.MIN_VALUE, NANOSECONDS, -1); cmp(Long.MIN_VALUE, DAYS, Long.MAX_VALUE, NANOSECONDS, -1); + cmp(Instant.MIN, Long.MIN_VALUE, DAYS, 1); + cmp(Instant.MIN, Long.MIN_VALUE, HOURS, 1); + cmp(Instant.MIN, Long.MIN_VALUE, MINUTES, 1); + cmp(Instant.MIN, Long.MIN_VALUE, SECONDS, 1); + cmp(Instant.MIN, Instant.MIN.getEpochSecond() - 1, SECONDS, 1); + cmp(Instant.MIN, Instant.MIN.getEpochSecond() - 100, SECONDS, 1); + cmp(Instant.MIN, Instant.MIN.getEpochSecond(), SECONDS, 0); + + cmp(Instant.MAX, Long.MAX_VALUE, DAYS, -1); + cmp(Instant.MAX, Long.MAX_VALUE, HOURS, -1); + cmp(Instant.MAX, Long.MAX_VALUE, MINUTES, -1); + cmp(Instant.MAX, Long.MAX_VALUE, SECONDS, -1); + cmp(Instant.MAX, Instant.MAX.getEpochSecond() + 1, SECONDS, -1); + cmp(Instant.MAX, Instant.MAX.getEpochSecond() + 100, SECONDS, -1); + cmp(Instant.MAX, Instant.MAX.getEpochSecond(), SECONDS, 0); + + cmp(nowInstant, now, MILLISECONDS, 0); + cmp(nowInstant, now*1000L, MICROSECONDS, 0); + cmp(nowInstant, now-1234, MILLISECONDS, 1); + cmp(nowInstant, now+1234, MILLISECONDS, -1); + cmp(nowInstant, tomorrowInDays, DAYS, -1); + cmp(nowInstant, yesterdayInDays, DAYS, 1); + // to(TimeUnit) to(MILLISECONDS.convert(1, DAYS) - 1, MILLISECONDS); to(MILLISECONDS.convert(1, DAYS) + 0, MILLISECONDS); @@ -77,6 +112,64 @@ public class Basic { to(Long.MAX_VALUE, unit); } + // toInstant() + int N = 1000; + for (TimeUnit unit : EnumSet.allOf(TimeUnit.class)) { + for (int i = 0; i < N; i++) { + long value = rand.nextLong(); + FileTime ft = FileTime.from(value, unit); + Instant instant = ft.toInstant(); + if (instant != Instant.MIN && instant != Instant.MAX) { + eqTime(value, unit, instant); + } + } + } + for (TimeUnit unit : EnumSet.allOf(TimeUnit.class)) { + long value = Long.MIN_VALUE; + FileTime ft = FileTime.from(value, unit); + Instant instant = ft.toInstant(); + if (unit.compareTo(TimeUnit.SECONDS) < 0) { + eqTime(value, unit, instant); + } else if (!instant.equals(Instant.MIN)) { + throw new RuntimeException("should overflow to MIN"); + } + value = Long.MAX_VALUE; + ft = FileTime.from(value, unit); + instant = ft.toInstant(); + if (unit.compareTo(TimeUnit.SECONDS) < 0) { + eqTime(value, unit, instant); + } else if (!instant.equals(Instant.MAX)) { + throw new RuntimeException("should overflow to MAX"); + } + } + + // from(Instant) + final long MAX_SECOND = 31556889864403199L; + for (int i = 0; i < N; i++) { + long v = rand.nextLong(); + long secs = v % MAX_SECOND; + Instant instant = Instant.ofEpochSecond(secs, rand.nextInt(1000_000_000)); + FileTime ft = FileTime.from(instant); + if (!ft.toInstant().equals(instant) || ft.to(SECONDS) != secs) { + throw new RuntimeException("from(Instant) failed"); + } + long millis = v; + instant = Instant.ofEpochMilli(millis); + ft = FileTime.from(instant); + if (!ft.toInstant().equals(instant) || + ft.toMillis() != instant.toEpochMilli()) { + throw new RuntimeException("from(Instant) failed"); + } + long nanos = v; + ft = FileTime.from(nanos, NANOSECONDS); + secs = nanos / 1000_000_000; + nanos = nanos % 1000_000_000; + instant = Instant.ofEpochSecond(secs, nanos); + if (!ft.equals(FileTime.from(instant))) { + throw new RuntimeException("from(Instant) failed"); + } + } + // toString ts(1L, DAYS, "1970-01-02T00:00:00Z"); ts(1L, HOURS, "1970-01-01T01:00:00Z"); @@ -108,11 +201,18 @@ public class Basic { // NTFS epoch in usec. ts(-11644473600000000L, MICROSECONDS, "1601-01-01T00:00:00Z"); - // nulls + ts(Instant.MIN, "-1000000001-01-01T00:00:00Z"); + ts(Instant.MAX, "1000000000-12-31T23:59:59.999999999Z"); + try { FileTime.from(0L, null); throw new RuntimeException("NullPointerException expected"); } catch (NullPointerException npe) { } + try { + FileTime.from(null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { } + FileTime time = FileTime.fromMillis(now); if (time.equals(null)) throw new RuntimeException("should not be equal to null"); @@ -120,6 +220,39 @@ public class Basic { time.compareTo(null); throw new RuntimeException("NullPointerException expected"); } catch (NullPointerException npe) { } + + // Instant + toMilli() overflow + overflow(Long.MAX_VALUE, + FileTime.from(Instant.MAX).toMillis()); + overflow(Long.MAX_VALUE, + FileTime.from(Instant.ofEpochSecond(Long.MAX_VALUE / 1000 + 1)) + .toMillis()); + overflow(Long.MIN_VALUE, + FileTime.from(Instant.MIN).toMillis()); + overflow(Long.MIN_VALUE, + FileTime.from(Instant.ofEpochSecond(Long.MIN_VALUE / 1000 - 1)) + .toMillis()); + + // Instant + to(TimeUnit) overflow + overflow(Long.MAX_VALUE, + FileTime.from(Instant.ofEpochSecond(Long.MAX_VALUE / 1000 + 1)) + .to(MILLISECONDS)); + overflow(Long.MAX_VALUE, + FileTime.from(Instant.ofEpochSecond(Long.MAX_VALUE / 1000, + MILLISECONDS.toNanos(1000))) + .to(MILLISECONDS)); + overflow(Long.MIN_VALUE, + FileTime.from(Instant.ofEpochSecond(Long.MIN_VALUE / 1000 - 1)) + .to(MILLISECONDS)); + overflow(Long.MIN_VALUE, + FileTime.from(Instant.ofEpochSecond(Long.MIN_VALUE / 1000, + -MILLISECONDS.toNanos(1))) + .to(MILLISECONDS)); + } + + static void overflow(long minmax, long v) { + if (v != minmax) + throw new RuntimeException("saturates to Long.MIN/MAX_VALUE expected"); } static void cmp(long v1, TimeUnit u1, long v2, TimeUnit u2, int expected) { @@ -128,6 +261,12 @@ public class Basic { throw new RuntimeException("unexpected order"); } + static void cmp(Instant ins, long v2, TimeUnit u2, int expected) { + int result = FileTime.from(ins).compareTo(FileTime.from(v2, u2)); + if (result != expected) + throw new RuntimeException("unexpected order"); + } + static void eq(long v1, TimeUnit u1, long v2, TimeUnit u2) { FileTime t1 = FileTime.from(v1, u1); FileTime t2 = FileTime.from(v2, u2); @@ -137,6 +276,28 @@ public class Basic { throw new RuntimeException("hashCodes should be equal"); } + static void eq(Instant ins, long v2, TimeUnit u2) { + FileTime t1 = FileTime.from(ins); + FileTime t2 = FileTime.from(v2, u2); + if (!t1.equals(t2)) + throw new RuntimeException("not equal"); + if (t1.hashCode() != t2.hashCode()) + throw new RuntimeException("hashCodes should be equal"); + } + + static void eqTime(long value, TimeUnit unit, Instant instant) { + long secs = SECONDS.convert(value, unit); + long nanos = NANOSECONDS.convert(value - unit.convert(secs, SECONDS), unit); + if (nanos < 0) { // normalize nanoOfSecond to positive + secs -= 1; + nanos += 1000_000_000; + } + if (secs != instant.getEpochSecond() || (int)nanos != instant.getNano()) { + System.err.println(" ins=" + instant); + throw new RuntimeException("ft and instant are not the same time point"); + } + } + static void neq(long v1, TimeUnit u1, long v2, TimeUnit u2) { FileTime t1 = FileTime.from(v1, u1); FileTime t2 = FileTime.from(v2, u2); @@ -144,6 +305,13 @@ public class Basic { throw new RuntimeException("should not be equal"); } + static void neq(Instant ins, long v2, TimeUnit u2) { + FileTime t1 = FileTime.from(ins); + FileTime t2 = FileTime.from(v2, u2); + if (t1.equals(t2)) + throw new RuntimeException("should not be equal"); + } + static void to(long v, TimeUnit unit) { FileTime t = FileTime.from(v, unit); for (TimeUnit u: TimeUnit.values()) { @@ -164,4 +332,14 @@ public class Basic { throw new RuntimeException(); } } + + static void ts(Instant instant, String expected) { + String result = FileTime.from(instant).toString(); + if (!result.equals(expected)) { + System.err.format("FileTime.from(%s).toString() failed\n", instant); + System.err.format("Expected: %s\n", expected); + System.err.format(" Got: %s\n", result); + throw new RuntimeException(); + } + } }