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 primarily 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      * this method deviates from ISO 8601 in the same manner as the
 223      * <a href="http://www.w3.org/TR/xmlschema-2/#deviantformats">XML Schema
 224      * language</a>. That is, the year may be expanded to more than four digits
 225      * and may be negative-signed. If more than four digits then leading zeros
 226      * are not present. The year before "{@code 0001}" is "{@code -0001}". 
 227      *
 228      * @return  the string representation of this file time
 229      */
 230     @Override
 231     public String toString() {
 232         String v = valueAsString;
 233         if (v == null) {
 234             // overflow saturates to Long.MIN_VALUE or Long.MAX_VALUE so this
 235             // limits the range:
 236             // [-292275056-05-16T16:47:04.192Z,292278994-08-17T07:12:55.807Z]
 237             long ms = toMillis();
 238 
 239             // nothing to do when seconds/minutes/hours/days
 240             String fractionAsString = "";
 241             if (unit.compareTo(TimeUnit.SECONDS) < 0) {
 242                 long fraction = asDaysAndNanos().fractionOfSecondInNanos();
 243                 if (fraction != 0L) {
 244                     // fraction must be positive
 245                     if (fraction < 0L) {
 246                         final long MAX_FRACTION_PLUS_1 = 1000L * 1000L * 1000L;
 247                         fraction += MAX_FRACTION_PLUS_1;
 248                         if (ms != Long.MIN_VALUE) ms--;
 249                     }
 250 
 251                     // convert to String, adding leading zeros as required and
 252                     // stripping any trailing zeros
 253                     String s = Long.toString(fraction);
 254                     int len = s.length();
 255                     int width = 9 - len;
 256                     StringBuilder sb = new StringBuilder(".");
 257                     while (width-- > 0) {
 258                         sb.append('0');
 259                     }
 260                     if (s.charAt(len-1) == '0') {
 261                         // drop trailing zeros
 262                         len--;
 263                         while (s.charAt(len-1) == '0')
 264                             len--;
 265                         sb.append(s.substring(0, len));
 266                     } else {
 267                         sb.append(s);
 268                     }
 269                     fractionAsString = sb.toString();
 270                 }
 271             }
 272 
 273             // create calendar to use with formatter.
 274             GregorianCalendar cal =
 275                 new GregorianCalendar(TimeZone.getTimeZone("UTC"), Locale.ROOT);
 276             if (value < 0L)
 277                 cal.setGregorianChange(new Date(Long.MIN_VALUE));
 278             cal.setTimeInMillis(ms);
 279 
 280             // years are negative before common era
 281             String sign = (cal.get(Calendar.ERA) == GregorianCalendar.BC) ? "-" : "";
 282 
 283             // [-]YYYY-MM-DDThh:mm:ss[.s]Z
 284             v = new Formatter(Locale.ROOT)
 285                 .format("%s%tFT%tR:%tS%sZ", sign, cal, cal, cal, fractionAsString)
 286                 .toString();
 287             valueAsString = v;
 288         }
 289         return v;
 290     }
 291 
 292     /**
 293      * Represents a FileTime's value as two longs: the number of days since
 294      * the epoch, and the excess (in nanoseconds). This is used for comparing
 295      * values with different units of granularity.
 296      */
 297     private static class DaysAndNanos implements Comparable<DaysAndNanos> {
 298         // constants for conversion
 299         private static final long C0 = 1L;
 300         private static final long C1 = C0 * 24L;
 301         private static final long C2 = C1 * 60L;
 302         private static final long C3 = C2 * 60L;
 303         private static final long C4 = C3 * 1000L;
 304         private static final long C5 = C4 * 1000L;
 305         private static final long C6 = C5 * 1000L;
 306 
 307         /**
 308          * The value (in days) since the epoch; can be negative.
 309          */
 310         private final long days;
 311 
 312         /**
 313          * The excess (in nanoseconds); can be negative if days <= 0.
 314          */
 315         private final long excessNanos;
 316 
 317         /**
 318          * Initializes a new instance of this class.
 319          */
 320         DaysAndNanos(long value, TimeUnit unit) {
 321             long scale;
 322             switch (unit) {
 323                 case DAYS         : scale = C0; break;
 324                 case HOURS        : scale = C1; break;
 325                 case MINUTES      : scale = C2; break;
 326                 case SECONDS      : scale = C3; break;
 327                 case MILLISECONDS : scale = C4; break;
 328                 case MICROSECONDS : scale = C5; break;
 329                 case NANOSECONDS  : scale = C6; break;
 330                 default : throw new AssertionError("Unit not handled");
 331             }
 332             this.days = unit.toDays(value);
 333             this.excessNanos = unit.toNanos(value - (this.days * scale));
 334         }
 335 
 336         /**
 337          * Returns the fraction of a second, in nanoseconds.
 338          */
 339         long fractionOfSecondInNanos() {
 340             return excessNanos % (1000L * 1000L * 1000L);
 341         }
 342 
 343         @Override
 344         public boolean equals(Object obj) {
 345             return (obj instanceof DaysAndNanos) ?
 346                 compareTo((DaysAndNanos)obj) == 0 : false;
 347         }
 348 
 349         @Override
 350         public int hashCode() {
 351             return (int)(days ^ (days >>> 32) ^
 352                          excessNanos ^ (excessNanos >>> 32));
 353         }
 354 
 355         @Override
 356         public int compareTo(DaysAndNanos other) {
 357             if (this.days != other.days)
 358                 return (this.days < other.days) ? -1 : 1;
 359             return (this.excessNanos < other.excessNanos) ? -1 :
 360                    (this.excessNanos == other.excessNanos) ? 0 : 1;
 361         }
 362     }
 363 }