    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 * http://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019package org.apache.commons.compress.archivers.zip;
    020
    021import java.io.Serializable;
    022import java.nio.file.attribute.FileTime;
    023import java.util.Arrays;
    024import java.util.Date;
    025import java.util.Objects;
    026import java.util.zip.ZipException;
    027
    028import org.apache.commons.compress.utils.TimeUtils;
    029
    030/**
    031 * <p>An extra field that stores additional file and directory timestamp data
    032 * for ZIP entries.   Each ZIP entry can include up to three timestamps
    033 * (modify, access, create*).  The timestamps are stored as 32 bit signed
    034 * integers representing seconds since UNIX epoch (Jan 1st, 1970, UTC).
    035 * This field improves on ZIP's default timestamp granularity, since it
    036 * allows one to store additional timestamps, and, in addition, the timestamps
    037 * are stored using per-second granularity (zip's default behavior can only store
    038 * timestamps to the nearest <em>even</em> second).
    039 * </p><p>
    040 * Unfortunately, 32 (signed) bits can only store dates up to the year 2037,
    041 * and so this extra field will eventually be obsolete.  Enjoy it while it lasts!
    042 * </p>
    043 * <ul>
    044 * <li><b>modifyTime:</b>
    045 * most recent time of file/directory modification
    046 * (or file/dir creation if the entry has not been
    047 * modified since it was created).
    048 * </li>
    049 * <li><b>accessTime:</b>
    050 * most recent time file/directory was opened
    051 * (e.g., read from disk).  Many people disable
    052 * their operating systems from updating this value
    053 * using the NOATIME mount option to optimize disk behavior,
    054 * and thus it's not always reliable.  In those cases
    055 * it's always equal to modifyTime.
    056 * </li>
    057 * <li><b>*createTime:</b>
    058 * modern Linux file systems (e.g., ext2 and newer)
    059 * do not appear to store a value like this, and so
    060 * it's usually omitted altogether in the ZIP extra
    061 * field.  Perhaps other Unix systems track this.
    062 * </li></ul>
    063 * <p>
    064 * We're using the field definition given in Info-Zip's source archive:
    065 * zip-3.0.tar.gz/proginfo/extrafld.txt
    066 * </p>
    067 * <pre>
    068 * Value         Size        Description
    069 * -----         ----        -----------
    070 * 0x5455        Short       tag for this extra block type ("UT")
    071 * TSize         Short       total data size for this block
    072 * Flags         Byte        info bits
    073 * (ModTime)     Long        time of last modification (UTC/GMT)
    074 * (AcTime)      Long        time of last access (UTC/GMT)
    075 * (CrTime)      Long        time of original creation (UTC/GMT)
    076 *
    077 * Central-header version:
    078 *
    079 * Value         Size        Description
    080 * -----         ----        -----------
    081 * 0x5455        Short       tag for this extra block type ("UT")
    082 * TSize         Short       total data size for this block
    083 * Flags         Byte        info bits (refers to local header!)
    084 * (ModTime)     Long        time of last modification (UTC/GMT)
    085 * </pre>
    086 * @since 1.5
    087 */
    088public class X5455_ExtendedTimestamp implements ZipExtraField, Cloneable, Serializable {
    089    private static final long serialVersionUID = 1L;
    090
    091    /**
    092     * The header ID for this extra field.
    093     *
    094     * @since 1.23
    095     */
    096    public static final ZipShort HEADER_ID = new ZipShort(0x5455);
    097
    098    /**
    099     * The bit set inside the flags by when the last modification time
    100     * is present in this extra field.
    101     */
    102    public static final byte MODIFY_TIME_BIT = 1;
    103    /**
    104     * The bit set inside the flags by when the lasr access time is
    105     * present in this extra field.
    106     */
    107    public static final byte ACCESS_TIME_BIT = 2;
    108    /**
    109     * The bit set inside the flags by when the original creation time
    110     * is present in this extra field.
    111     */
    112    public static final byte CREATE_TIME_BIT = 4;
    113
    114    /**
    115     * Utility method converts java.util.Date (milliseconds since epoch)
    116     * into a ZipLong (seconds since epoch).
    117     * <p/>
    118     * Also makes sure the converted ZipLong is not too big to fit
    119     * in 32 unsigned bits.
    120     *
    121     * @param d java.util.Date to convert to ZipLong
    122     * @return ZipLong
    123     */
    124    private static ZipLong dateToZipLong(final Date d) {
    125        if (d == null) {
    126            return null;
    127        }
    128        return unixTimeToZipLong(d.getTime() / 1000);
    129    }
    130
    131    /**
    132     * Utility method converts {@link FileTime} into a ZipLong (seconds since epoch).
    133     * <p/>
    134     * Also makes sure the converted ZipLong is not too big to fit
    135     * in 32 unsigned bits.
    136     *
    137     * @param time {@link FileTime} to convert to ZipLong
    138     * @return ZipLong
    139     */
    140    private static ZipLong fileTimeToZipLong(final FileTime time) {
    141        return time == null ? null : unixTimeToZipLong(TimeUtils.toUnixTime(time));
    142    }
    143
    144    private static FileTime unixTimeToFileTime(final ZipLong unixTime) {
    145        return unixTime != null ? TimeUtils.unixTimeToFileTime(unixTime.getIntValue()) : null;
    146    }
    147    // The 3 boolean fields (below) come from this flag's byte.  The remaining 5 bits
    148    // are ignored according to the current version of the spec (December 2012).
    149
    150    private static ZipLong unixTimeToZipLong(final long unixTime) {
    151        if (!TimeUtils.isUnixTime(unixTime)) {
    152            throw new IllegalArgumentException("X5455 timestamps must fit in a signed 32 bit integer: " + unixTime);
    153        }
    154        return new ZipLong(unixTime);
    155    }
    156
    157    private static Date zipLongToDate(final ZipLong unixTime) {
    158        return unixTime != null ? new Date(unixTime.getIntValue() * 1000L) : null;
    159    }
    160
    161    private byte flags;
    162    // Note: even if bit1 and bit2 are set, the Central data will still not contain
    163    // access/create fields:  only local data ever holds those!  This causes
    164    // some of our implementation to look a little odd, with seemingly spurious
    165    // != null and length checks.
    166    private boolean bit0_modifyTimePresent;
    167    private boolean bit1_accessTimePresent;
    168
    169    private boolean bit2_createTimePresent;
    170
    171    private ZipLong modifyTime;
    172
    173    private ZipLong accessTime;
    174
    175    private ZipLong createTime;
    176
    177    /**
    178     * Constructor for X5455_ExtendedTimestamp.
    179     */
    180    public X5455_ExtendedTimestamp() {}
    181
    182    @Override
    183    public Object clone() throws CloneNotSupportedException {
    184        return super.clone();
    185    }
    186
    187    @Override
    188    public boolean equals(final Object o) {
    189        if (o instanceof X5455_ExtendedTimestamp) {
    190            final X5455_ExtendedTimestamp xf = (X5455_ExtendedTimestamp) o;
    191
    192            // The ZipLong==ZipLong clauses handle the cases where both are null.
    193            // and only last 3 bits of flags matter.
    194            return (flags & 0x07) == (xf.flags & 0x07) &&
    195                    Objects.equals(modifyTime, xf.modifyTime) &&
    196                    Objects.equals(accessTime, xf.accessTime) &&
    197                    Objects.equals(createTime, xf.createTime);
    198        }
    199        return false;
    200    }
    201
    202    /**
    203     * Gets the access time as a {@link FileTime}
    204     * of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
    205     * The milliseconds are always zeroed out, since the underlying data
    206     * offers only per-second precision.
    207     *
    208     * @return modify time as {@link FileTime} or null.
    209     * @since 1.23
    210     */
    211    public FileTime getAccessFileTime() {
    212        return unixTimeToFileTime(accessTime);
    213    }
    214
    215    /**
    216     * Gets the access time as a java.util.Date
    217     * of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
    218     * The milliseconds are always zeroed out, since the underlying data
    219     * offers only per-second precision.
    220     *
    221     * @return access time as java.util.Date or null.
    222     */
    223    public Date getAccessJavaTime() {
    224        return zipLongToDate(accessTime);
    225    }
    226
    227    /**
    228     * Gets the access time (seconds since epoch) of this ZIP entry
    229     * as a ZipLong object, or null if no such timestamp exists in the
    230     * ZIP entry.
    231     *
    232     * @return access time (seconds since epoch) or null.
    233     */
    234    public ZipLong getAccessTime() {
    235        return accessTime;
    236    }
    237
    238    /**
    239     * Gets the actual data to put into central directory data - without Header-ID
    240     * or length specifier.
    241     *
    242     * @return the central directory data
    243     */
    244    @Override
    245    public byte[] getCentralDirectoryData() {
    246        // Truncate out create & access time (last 8 bytes) from
    247        // the copy of the local data we obtained:
    248        return Arrays.copyOf(getLocalFileDataData(), getCentralDirectoryLength().getValue());
    249    }
    250
    251    /**
    252     * Gets the length of the extra field in the local file data - without
    253     * Header-ID or length specifier.
    254     *
    255     * <p>For X5455 the central length is often smaller than the
    256     * local length, because central cannot contain access or create
    257     * timestamps.</p>
    258     *
    259     * @return a {@code ZipShort} for the length of the data of this extra field
    260     */
    261    @Override
    262    public ZipShort getCentralDirectoryLength() {
    263        return new ZipShort(1 + (bit0_modifyTimePresent ? 4 : 0));
    264    }
    265
    266    /**
    267     * Gets the create time as a {@link FileTime}
    268     * of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
    269     * The milliseconds are always zeroed out, since the underlying data
    270     * offers only per-second precision.
    271     *
    272     * @return modify time as {@link FileTime} or null.
    273     * @since 1.23
    274     */
    275    public FileTime getCreateFileTime() {
    276        return unixTimeToFileTime(createTime);
    277    }
    278
    279    /**
    280     * <p>
    281     * Gets the create time as a java.util.Date
    282     * of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
    283     * The milliseconds are always zeroed out, since the underlying data
    284     * offers only per-second precision.
    285     * </p>
    286     * <p>
    287     * Note: modern Linux file systems (e.g., ext2)
    288     * do not appear to store a "create time" value, and so
    289     * it's usually omitted altogether in the ZIP extra
    290     * field. Perhaps other Unix systems track this.
    291     * </p>
    292     *
    293     * @return create time as java.util.Date or null.
    294     */
    295    public Date getCreateJavaTime() {
    296        return zipLongToDate(createTime);
    297    }
    298
    299    /**
    300     * <p>
    301     * Gets the create time (seconds since epoch) of this ZIP entry
    302     * as a ZipLong object, or null if no such timestamp exists in the
    303     * ZIP entry.
    304     * </p>
    305     * <p>
    306     * Note: modern Linux file systems (e.g., ext2)
    307     * do not appear to store a "create time" value, and so
    308     * it's usually omitted altogether in the ZIP extra
    309     * field. Perhaps other Unix systems track this.
    310     * </p>
    311     *
    312     * @return create time (seconds since epoch) or null.
    313     */
    314    public ZipLong getCreateTime() {
    315        return createTime;
    316    }
    317
    318    /**
    319     * Gets flags byte.  The flags byte tells us which of the
    320     * three datestamp fields are present in the data:
    321     * <pre>
    322     * bit0 - modify time
    323     * bit1 - access time
    324     * bit2 - create time
    325     * </pre>
    326     * Only first 3 bits of flags are used according to the
    327     * latest version of the spec (December 2012).
    328     *
    329     * @return flags byte indicating which of the
    330     *         three datestamp fields are present.
    331     */
    332    public byte getFlags() { return flags; }
    333
    334    /**
    335     * Gets the Header-ID.
    336     *
    337     * @return the value for the header id for this extrafield
    338     */
    339    @Override
    340    public ZipShort getHeaderId() {
    341        return HEADER_ID;
    342    }
    343
    344    /**
    345     * Gets the actual data to put into local file data - without Header-ID
    346     * or length specifier.
    347     *
    348     * @return get the data
    349     */
    350    @Override
    351    public byte[] getLocalFileDataData() {
    352        final byte[] data = new byte[getLocalFileDataLength().getValue()];
    353        int pos = 0;
    354        data[pos++] = 0;
    355        if (bit0_modifyTimePresent) {
    356            data[0] |= MODIFY_TIME_BIT;
    357            System.arraycopy(modifyTime.getBytes(), 0, data, pos, 4);
    358            pos += 4;
    359        }
    360        if (bit1_accessTimePresent && accessTime != null) {
    361            data[0] |= ACCESS_TIME_BIT;
    362            System.arraycopy(accessTime.getBytes(), 0, data, pos, 4);
    363            pos += 4;
    364        }
    365        if (bit2_createTimePresent && createTime != null) {
    366            data[0] |= CREATE_TIME_BIT;
    367            System.arraycopy(createTime.getBytes(), 0, data, pos, 4);
    368            pos += 4; // NOSONAR - assignment as documentation
    369        }
    370        return data;
    371    }
    372
    373    /**
    374     * Gets the length of the extra field in the local file data - without
    375     * Header-ID or length specifier.
    376     *
    377     * @return a {@code ZipShort} for the length of the data of this extra field
    378     */
    379    @Override
    380    public ZipShort getLocalFileDataLength() {
    381        return new ZipShort(1 +
    382                (bit0_modifyTimePresent ? 4 : 0) +
    383                (bit1_accessTimePresent && accessTime != null ? 4 : 0) +
    384                (bit2_createTimePresent && createTime != null ? 4 : 0)
    385        );
    386    }
    387
    388    /**
    389     * Gets the modify time as a {@link FileTime}
    390     * of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
    391     * The milliseconds are always zeroed out, since the underlying data
    392     * offers only per-second precision.
    393     *
    394     * @return modify time as {@link FileTime} or null.
    395     * @since 1.23
    396     */
    397    public FileTime getModifyFileTime() {
    398        return unixTimeToFileTime(modifyTime);
    399    }
    400
    401    /**
    402     * Gets the modify time as a java.util.Date
    403     * of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
    404     * The milliseconds are always zeroed out, since the underlying data
    405     * offers only per-second precision.
    406     *
    407     * @return modify time as java.util.Date or null.
    408     */
    409    public Date getModifyJavaTime() {
    410        return zipLongToDate(modifyTime);
    411    }
    412
    413    /**
    414     * Gets the modify time (seconds since epoch) of this ZIP entry
    415     * as a ZipLong object, or null if no such timestamp exists in the
    416     * ZIP entry.
    417     *
    418     * @return modify time (seconds since epoch) or null.
    419     */
    420    public ZipLong getModifyTime() { return modifyTime; }
    421
    422    @Override
    423    public int hashCode() {
    424        int hc = -123 * (flags & 0x07); // only last 3 bits of flags matter
    425        if (modifyTime != null) {
    426            hc ^= modifyTime.hashCode();
    427        }
    428        if (accessTime != null) {
    429            // Since accessTime is often same as modifyTime,
    430            // this prevents them from XOR negating each other.
    431            hc ^= Integer.rotateLeft(accessTime.hashCode(), 11);
    432        }
    433        if (createTime != null) {
    434            hc ^= Integer.rotateLeft(createTime.hashCode(), 22);
    435        }
    436        return hc;
    437    }
    438
    439    /**
    440     * Tests whether bit0 of the flags byte is set or not,
    441     * which should correspond to the presence or absence of
    442     * a modify timestamp in this particular ZIP entry.
    443     *
    444     * @return true if bit0 of the flags byte is set.
    445     */
    446    public boolean isBit0_modifyTimePresent() {
    447        return bit0_modifyTimePresent;
    448    }
    449
    450    /**
    451     * Tests whether bit1 of the flags byte is set or not,
    452     * which should correspond to the presence or absence of
    453     * a "last access" timestamp in this particular ZIP entry.
    454     *
    455     * @return true if bit1 of the flags byte is set.
    456     */
    457    public boolean isBit1_accessTimePresent() {
    458        return bit1_accessTimePresent;
    459    }
    460
    461    /**
    462     * Tests whether bit2 of the flags byte is set or not,
    463     * which should correspond to the presence or absence of
    464     * a create timestamp in this particular ZIP entry.
    465     *
    466     * @return true if bit2 of the flags byte is set.
    467     */
    468    public boolean isBit2_createTimePresent() {
    469        return bit2_createTimePresent;
    470    }
    471
    472    /**
    473     * Doesn't do anything special since this class always uses the
    474     * same parsing logic for both central directory and local file data.
    475     */
    476    @Override
    477    public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, final int length) throws ZipException {
    478        reset();
    479        parseFromLocalFileData(buffer, offset, length);
    480    }
    481
    482    /**
    483     * Populate data from this array as if it was in local file data.
    484     *
    485     * @param data   an array of bytes
    486     * @param offset the start offset
    487     * @param length the number of bytes in the array from offset
    488     * @throws java.util.zip.ZipException on error
    489     */
    490    @Override
    491    public void parseFromLocalFileData(final byte[] data, int offset, final int length) throws ZipException {
    492        reset();
    493        if (length < 1) {
    494            throw new ZipException("X5455_ExtendedTimestamp too short, only " + length + " bytes");
    495        }
    496        final int len = offset + length;
    497        setFlags(data[offset++]);
    498        if (bit0_modifyTimePresent && offset + 4 <= len) {
    499            modifyTime = new ZipLong(data, offset);
    500            offset += 4;
    501        } else {
    502            bit0_modifyTimePresent = false;
    503        }
    504        if (bit1_accessTimePresent && offset + 4 <= len) {
    505            accessTime = new ZipLong(data, offset);
    506            offset += 4;
    507        } else {
    508            bit1_accessTimePresent = false;
    509        }
    510        if (bit2_createTimePresent && offset + 4 <= len) {
    511            createTime = new ZipLong(data, offset);
    512            offset += 4; // NOSONAR - assignment as documentation
    513        } else {
    514            bit2_createTimePresent = false;
    515        }
    516    }
    517
    518    /**
    519     * Reset state back to newly constructed state.  Helps us make sure
    520     * parse() calls always generate clean results.
    521     */
    522    private void reset() {
    523        setFlags((byte) 0);
    524        this.modifyTime = null;
    525        this.accessTime = null;
    526        this.createTime = null;
    527    }
    528
    529    /**
    530     * <p>
    531     * Sets the acccess time as a {@link FileTime}
    532     * of this ZIP entry. Supplied value is truncated to per-second
    533     * precision (milliseconds zeroed-out).
    534     * </p><p>
    535     * Note: the setters for flags and timestamps are decoupled.
    536     * Even if the timestamp is not-null, it will only be written
    537     * out if the corresponding bit in the flags is also set.
    538     * </p>
    539     *
    540     * @param time access time as {@link FileTime}
    541     * @since 1.23
    542     */
    543    public void setAccessFileTime(final FileTime time) {
    544        setAccessTime(fileTimeToZipLong(time));
    545    }
    546
    547    /**
    548     * <p>
    549     * Sets the access time as a java.util.Date
    550     * of this ZIP entry.  Supplied value is truncated to per-second
    551     * precision (milliseconds zeroed-out).
    552     * </p><p>
    553     * Note: the setters for flags and timestamps are decoupled.
    554     * Even if the timestamp is not-null, it will only be written
    555     * out if the corresponding bit in the flags is also set.
    556     * </p>
    557     *
    558     * @param d access time as java.util.Date
    559     */
    560    public void setAccessJavaTime(final Date d) {
    561        setAccessTime(dateToZipLong(d));
    562    }
    563
    564    /**
    565     * <p>
    566     * Sets the access time (seconds since epoch) of this ZIP entry
    567     * using a ZipLong object
    568     * </p><p>
    569     * Note: the setters for flags and timestamps are decoupled.
    570     * Even if the timestamp is not-null, it will only be written
    571     * out if the corresponding bit in the flags is also set.
    572     * </p>
    573     *
    574     * @param l ZipLong of the access time (seconds per epoch)
    575     */
    576    public void setAccessTime(final ZipLong l) {
    577        bit1_accessTimePresent = l != null;
    578        flags = (byte) (l != null ? flags | ACCESS_TIME_BIT : flags & ~ACCESS_TIME_BIT);
    579        this.accessTime = l;
    580    }
    581
    582    /**
    583     * <p>
    584     * Sets the create time as a {@link FileTime}
    585     * of this ZIP entry. Supplied value is truncated to per-second
    586     * precision (milliseconds zeroed-out).
    587     * </p><p>
    588     * Note: the setters for flags and timestamps are decoupled.
    589     * Even if the timestamp is not-null, it will only be written
    590     * out if the corresponding bit in the flags is also set.
    591     * </p>
    592     *
    593     * @param time create time as {@link FileTime}
    594     * @since 1.23
    595     */
    596    public void setCreateFileTime(final FileTime time) {
    597        setCreateTime(fileTimeToZipLong(time));
    598    }
    599
    600    /**
    601     * <p>
    602     * Sets the create time as a java.util.Date
    603     * of this ZIP entry.  Supplied value is truncated to per-second
    604     * precision (milliseconds zeroed-out).
    605     * </p><p>
    606     * Note: the setters for flags and timestamps are decoupled.
    607     * Even if the timestamp is not-null, it will only be written
    608     * out if the corresponding bit in the flags is also set.
    609     * </p>
    610     *
    611     * @param d create time as java.util.Date
    612     */
    613    public void setCreateJavaTime(final Date d) { setCreateTime(dateToZipLong(d)); }
    614
    615    /**
    616     * <p>
    617     * Sets the create time (seconds since epoch) of this ZIP entry
    618     * using a ZipLong object
    619     * </p><p>
    620     * Note: the setters for flags and timestamps are decoupled.
    621     * Even if the timestamp is not-null, it will only be written
    622     * out if the corresponding bit in the flags is also set.
    623     * </p>
    624     *
    625     * @param l ZipLong of the create time (seconds per epoch)
    626     */
    627    public void setCreateTime(final ZipLong l) {
    628        bit2_createTimePresent = l != null;
    629        flags = (byte) (l != null ? flags | CREATE_TIME_BIT : flags & ~CREATE_TIME_BIT);
    630        this.createTime = l;
    631    }
    632
    633    /**
    634     * Sets flags byte.  The flags byte tells us which of the
    635     * three datestamp fields are present in the data:
    636     * <pre>
    637     * bit0 - modify time
    638     * bit1 - access time
    639     * bit2 - create time
    640     * </pre>
    641     * Only first 3 bits of flags are used according to the
    642     * latest version of the spec (December 2012).
    643     *
    644     * @param flags flags byte indicating which of the
    645     *              three datestamp fields are present.
    646     */
    647    public void setFlags(final byte flags) {
    648        this.flags = flags;
    649        this.bit0_modifyTimePresent = (flags & MODIFY_TIME_BIT) == MODIFY_TIME_BIT;
    650        this.bit1_accessTimePresent = (flags & ACCESS_TIME_BIT) == ACCESS_TIME_BIT;
    651        this.bit2_createTimePresent = (flags & CREATE_TIME_BIT) == CREATE_TIME_BIT;
    652    }
    653
    654    /**
    655     * <p>
    656     * Sets the modify time as a {@link FileTime}
    657     * of this ZIP entry. Supplied value is truncated to per-second
    658     * precision (milliseconds zeroed-out).
    659     * </p><p>
    660     * Note: the setters for flags and timestamps are decoupled.
    661     * Even if the timestamp is not-null, it will only be written
    662     * out if the corresponding bit in the flags is also set.
    663     * </p>
    664     *
    665     * @param time modify time as {@link FileTime}
    666     * @since 1.23
    667     */
    668    public void setModifyFileTime(final FileTime time) {
    669        setModifyTime(fileTimeToZipLong(time));
    670    }
    671
    672    /**
    673     * <p>
    674     * Sets the modify time as a java.util.Date
    675     * of this ZIP entry.  Supplied value is truncated to per-second
    676     * precision (milliseconds zeroed-out).
    677     * </p><p>
    678     * Note: the setters for flags and timestamps are decoupled.
    679     * Even if the timestamp is not-null, it will only be written
    680     * out if the corresponding bit in the flags is also set.
    681     * </p>
    682     *
    683     * @param d modify time as java.util.Date
    684     */
    685    public void setModifyJavaTime(final Date d) {
    686        setModifyTime(dateToZipLong(d));
    687    }
    688
    689    /**
    690     * <p>
    691     * Sets the modify time (seconds since epoch) of this ZIP entry
    692     * using a ZipLong object.
    693     * </p><p>
    694     * Note: the setters for flags and timestamps are decoupled.
    695     * Even if the timestamp is not-null, it will only be written
    696     * out if the corresponding bit in the flags is also set.
    697     * </p>
    698     *
    699     * @param l ZipLong of the modify time (seconds per epoch)
    700     */
    701    public void setModifyTime(final ZipLong l) {
    702        bit0_modifyTimePresent = l != null;
    703        flags = (byte) (l != null ? flags | MODIFY_TIME_BIT : flags & ~MODIFY_TIME_BIT);
    704        this.modifyTime = l;
    705    }
    706
    707    /**
    708     * Returns a String representation of this class useful for
    709     * debugging purposes.
    710     *
    711     * @return A String representation of this class useful for
    712     *         debugging purposes.
    713     */
    714    @Override
    715    public String toString() {
    716        final StringBuilder buf = new StringBuilder();
    717        buf.append("0x5455 Zip Extra Field: Flags=");
    718        buf.append(Integer.toBinaryString(ZipUtil.unsignedIntToSignedByte(flags))).append(" ");
    719        if (bit0_modifyTimePresent && modifyTime != null) {
    720            final Date m = getModifyJavaTime();
    721            buf.append(" Modify:[").append(m).append("] ");
    722        }
    723        if (bit1_accessTimePresent && accessTime != null) {
    724            final Date a = getAccessJavaTime();
    725            buf.append(" Access:[").append(a).append("] ");
    726        }
    727        if (bit2_createTimePresent && createTime != null) {
    728            final Date c = getCreateJavaTime();
    729            buf.append(" Create:[").append(c).append("] ");
    730        }
    731        return buf.toString();
    732    }
    733
    734}