1 /*
   2  * Copyright (c) 2009, 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.util.Calendar;
  29 import java.util.GregorianCalendar;
  30 import java.util.Date;
  31 import java.util.Formatter;
  32 import java.util.Locale;
  33 import java.util.TimeZone;
  34 import java.util.concurrent.TimeUnit;
  35 
  36 /**
  37  * Represents the value of a file's time stamp attribute. For example, it may
  38  * represent the time that the file was last
  39  * {@link BasicFileAttributes#lastModifiedTime() modified},
  40  * {@link BasicFileAttributes#lastAccessTime() accessed},
  41  * or {@link BasicFileAttributes#creationTime() created}.
  42  *
  43  * <p> Instances of this class are immutable.
  44  *
  45  * @since 1.7
  46  * @see java.nio.file.Files#setLastModifiedTime
  47  * @see java.nio.file.Files#getLastModifiedTime
  48  */
  49 
  50 public final class FileTime
  51     implements Comparable<FileTime>
  52 {
  53     /**
  54      * The value since the epoch; can be negative.
  55      */
  56     private final long value;
  57 
  58     /**
  59      * The unit of granularity to interpret the value.
  60      */
  61     private final TimeUnit unit;
  62 
  63     /**
  64      * The value return by toString (created lazily)
  65      */
  66     private String valueAsString;
  67 
  68     /**
  69      * The value in days and excess nanos (created lazily)
  70      */
  71     private DaysAndNanos daysAndNanos;
  72 
  73     /**
  74      * Returns a DaysAndNanos object representing the value.
  75      */
  76     private DaysAndNanos asDaysAndNanos() {
  77         if (daysAndNanos == null)
  78             daysAndNanos = new DaysAndNanos(value, unit);
  79         return daysAndNanos;
  80     }
  81 
  82     /**
  83      * Initializes a new instance of this class.
  84      */
  85     private FileTime(long value, TimeUnit unit) {
  86         if (unit == null)
  87             throw new NullPointerException();
  88         this.value = value;
  89         this.unit = unit;
  90     }
  91 
  92     /**
  93      * Returns a {@code FileTime} representing a value at the given unit of
  94      * granularity.
  95      *
  96      * @param   value
  97      *          the value since the epoch (1970-01-01T00:00:00Z); can be
  98      *          negative
  99      * @param   unit
 100      *          the unit of granularity to interpret the value
 101      *
 102      * @return  a {@code FileTime} representing the given value
 103      */
 104     public static FileTime from(long value, TimeUnit unit) {
 105         return new FileTime(value, unit);
 106     }
 107 
 108     /**
 109      * Returns a {@code FileTime} representing the given value in milliseconds.
 110      *
 111      * @param   value
 112      *          the value, in milliseconds, since the epoch
 113      *          (1970-01-01T00:00:00Z); can be negative
 114      *
 115      * @return  a {@code FileTime} representing the given value
 116      */
 117     public static FileTime fromMillis(long value) {
 118         return new FileTime(value, TimeUnit.MILLISECONDS);
 119     }
 120 
 121     /**
 122      * Returns the value at the given unit of granularity.
 123      *
 124      * <p> Conversion from a coarser granularity that would numerically overflow
 125      * saturate to {@code Long.MIN_VALUE} if negative or {@code Long.MAX_VALUE}
 126      * if positive.
 127      *
 128      * @param   unit
 129      *          the unit of granularity for the return value
 130      *
 131      * @return  value in the given unit of granularity, since the epoch
 132      *          since the epoch (1970-01-01T00:00:00Z); can be negative
 133      */
 134     public long to(TimeUnit unit) {
 135         return unit.convert(this.value, this.unit);
 136     }
 137 
 138     /**
 139      * Returns the value in milliseconds.
 140      *
 141      * <p> Conversion from a coarser granularity that would numerically overflow
 142      * saturate to {@code Long.MIN_VALUE} if negative or {@code Long.MAX_VALUE}
 143      * if positive.
 144      *
 145      * @return  the value in milliseconds, since the epoch (1970-01-01T00:00:00Z)
 146      */
 147     public long toMillis() {
 148         return unit.toMillis(value);
 149     }
 150 
 151     /**
 152      * Tests this {@code FileTime} for equality with the given object.
 153      *
 154      * <p> The result is {@code true} if and only if the argument is not {@code
 155      * null} and is a {@code FileTime} that represents the same time. This
 156      * method satisfies the general contract of the {@code Object.equals} method.
 157      *
 158      * @param   obj
 159      *          the object to compare with
 160      *
 161      * @return  {@code true} if, and only if, the given object is a {@code
 162      *          FileTime} that represents the same time
 163      */
 164     @Override
 165     public boolean equals(Object obj) {
 166         return (obj instanceof FileTime) ? compareTo((FileTime)obj) == 0 : false;
 167     }
 168 
 169     /**
 170      * Computes a hash code for this file time.
 171      *
 172      * <p> The hash code is based upon the value represented, and satisfies the
 173      * general contract of the {@link Object#hashCode} method.
 174      *
 175      * @return  the hash-code value
 176      */
 177     @Override
 178     public int hashCode() {
 179         // hashcode of days/nanos representation to satisfy contract with equals
 180         return asDaysAndNanos().hashCode();
 181     }
 182 
 183     /**
 184      * Compares the value of two {@code FileTime} objects for order.
 185      *
 186      * @param   other
 187      *          the other {@code FileTime} to be compared
 188      *
 189      * @return  {@code 0} if this {@code FileTime} is equal to {@code other}, a
 190      *          value less than 0 if this {@code FileTime} represents a time
 191      *          that is before {@code other}, and a value greater than 0 if this
 192      *          {@code FileTime} represents a time that is after {@code other}
 193      */
 194     @Override
 195     public int compareTo(FileTime other) {
 196         // same granularity
 197         if (unit == other.unit) {
 198             return (value < other.value) ? -1 : (value == other.value ? 0 : 1);
 199         } else {
 200             // compare using days/nanos representation when unit differs
 201             return asDaysAndNanos().compareTo(other.asDaysAndNanos());
 202         }
 203     }
 204 
 205     /**
 206      * Returns the string representation of this {@code FileTime}. The string
 207      * is returned in the <a
 208      * href="http://www.w3.org/TR/NOTE-datetime">ISO&nbsp;8601</a> format:
 209      * <pre>
 210      *     YYYY-MM-DDThh:mm:ss[.s+]Z
 211      * </pre>
 212      * where "{@code [.s+]}" represents a dot followed by one of more digits
 213      * for the decimal fraction of a second. It is only present when the decimal
 214      * fraction of a second is not zero. For example, {@code
 215      * FileTime.fromMillis(1234567890000L).toString()} yields {@code
 216      * "2009-02-13T23:31:30Z"}, and {@code FileTime.fromMillis(1234567890123L).toString()}
 217      * yields {@code "2009-02-13T23:31:30.123Z"}.
 218      *
 219      * <p> A {@code FileTime} is primarly intended to represent the value of a
 220      * file's time stamp. Where used to represent <i>extreme values</i>, where
 221      * the year is less than "{@code 0001}" or greater than "{@code 9999}" then
 222      * the year may be expanded to more than four digits and may be
 223      * negative-signed. If more than four digits then leading zeros are not
 224      * present. The year before "{@code 0001}" is "{@code -0001}".
 225      *
 226      * @return  the string representation of this file time
 227      */
 228     @Override
 229     public String toString() {
 230         String v = valueAsString;
 231         if (v == null) {
 232             // overflow saturates to Long.MIN_VALUE or Long.MAX_VALUE so this
 233             // limits the range:
 234             // [-292275056-05-16T16:47:04.192Z,292278994-08-17T07:12:55.807Z]
 235             long ms = toMillis();
 236 
 237             // nothing to do when seconds/minutes/hours/days
 238             String fractionAsString = "";
 239             if (unit.compareTo(TimeUnit.SECONDS) < 0) {
 240                 long fraction = asDaysAndNanos().fractionOfSecondInNanos();
 241                 if (fraction != 0L) {
 242                     // fraction must be positive
 243                     if (fraction < 0L) {
 244                         final long MAX_FRACTION_PLUS_1 = 1000L * 1000L * 1000L;
 245                         fraction += MAX_FRACTION_PLUS_1;
 246                         if (ms != Long.MIN_VALUE) ms--;
 247                     }
 248 
 249                     // convert to String, adding leading zeros as required and
 250                     // stripping any trailing zeros
 251                     String s = Long.toString(fraction);
 252                     int len = s.length();
 253                     int width = 9 - len;
 254                     StringBuilder sb = new StringBuilder(".");
 255                     while (width-- > 0) {
 256                         sb.append('0');
 257                     }
 258                     if (s.charAt(len-1) == '0') {
 259                         // drop trailing zeros
 260                         len--;
 261                         while (s.charAt(len-1) == '0')
 262                             len--;
 263                         sb.append(s.substring(0, len));
 264                     } else {
 265                         sb.append(s);
 266                     }
 267                     fractionAsString = sb.toString();
 268                 }
 269             }
 270 
 271             // create calendar to use with formatter.
 272             GregorianCalendar cal =
 273                 new GregorianCalendar(TimeZone.getTimeZone("UTC"), Locale.ROOT);
 274             if (value < 0L)
 275                 cal.setGregorianChange(new Date(Long.MIN_VALUE));
 276             cal.setTimeInMillis(ms);
 277 
 278             // years are negative before common era
 279             String sign = (cal.get(Calendar.ERA) == GregorianCalendar.BC) ? "-" : "";
 280 
 281             // [-]YYYY-MM-DDThh:mm:ss[.s]Z
 282             v = new Formatter(Locale.ROOT)
 283                 .format("%s%tFT%tR:%tS%sZ", sign, cal, cal, cal, fractionAsString)
 284                 .toString();
 285             valueAsString = v;
 286         }
 287         return v;
 288     }
 289 
 290     /**
 291      * Represents a FileTime's value as two longs: the number of days since
 292      * the epoch, and the excess (in nanoseconds). This is used for comparing
 293      * values with different units of granularity.
 294      */
 295     private static class DaysAndNanos implements Comparable<DaysAndNanos> {
 296         // constants for conversion
 297         private static final long C0 = 1L;
 298         private static final long C1 = C0 * 24L;
 299         private static final long C2 = C1 * 60L;
 300         private static final long C3 = C2 * 60L;
 301         private static final long C4 = C3 * 1000L;
 302         private static final long C5 = C4 * 1000L;
 303         private static final long C6 = C5 * 1000L;
 304 
 305         /**
 306          * The value (in days) since the epoch; can be negative.
 307          */
 308         private final long days;
 309 
 310         /**
 311          * The excess (in nanoseconds); can be negative if days <= 0.
 312          */
 313         private final long excessNanos;
 314 
 315         /**
 316          * Initializes a new instance of this class.
 317          */
 318         DaysAndNanos(long value, TimeUnit unit) {
 319             long scale;
 320             switch (unit) {
 321                 case DAYS         : scale = C0; break;
 322                 case HOURS        : scale = C1; break;
 323                 case MINUTES      : scale = C2; break;
 324                 case SECONDS      : scale = C3; break;
 325                 case MILLISECONDS : scale = C4; break;
 326                 case MICROSECONDS : scale = C5; break;
 327                 case NANOSECONDS  : scale = C6; break;
 328                 default : throw new AssertionError("Unit not handled");
 329             }
 330             this.days = unit.toDays(value);
 331             this.excessNanos = unit.toNanos(value - (this.days * scale));
 332         }
 333 
 334         /**
 335          * Returns the fraction of a second, in nanoseconds.
 336          */
 337         long fractionOfSecondInNanos() {
 338             return excessNanos % (1000L * 1000L * 1000L);
 339         }
 340 
 341         @Override
 342         public boolean equals(Object obj) {
 343             return (obj instanceof DaysAndNanos) ?
 344                 compareTo((DaysAndNanos)obj) == 0 : false;
 345         }
 346 
 347         @Override
 348         public int hashCode() {
 349             return (int)(days ^ (days >>> 32) ^
 350                          excessNanos ^ (excessNanos >>> 32));
 351         }
 352 
 353         @Override
 354         public int compareTo(DaysAndNanos other) {
 355             if (this.days != other.days)
 356                 return (this.days < other.days) ? -1 : 1;
 357             return (this.excessNanos < other.excessNanos) ? -1 :
 358                    (this.excessNanos == other.excessNanos) ? 0 : 1;
 359         }
 360     }
 361 }