8075526: Need a way to read and write ZipEntry timestamp using local date/time without tz conversion

To add a pair of set/getTimeLocal()

Reviewed-by: ksrini, rriggs
This commit is contained in:
Xueming Shen 2015-07-22 21:43:33 +00:00
parent ef917cec15
commit 47dbbc7b72
4 changed files with 210 additions and 4 deletions

View File

@ -29,6 +29,9 @@ import static java.util.zip.ZipUtils.*;
import java.nio.file.attribute.FileTime;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import static java.util.zip.ZipConstants64.*;
@ -194,6 +197,85 @@ class ZipEntry implements ZipConstants, Cloneable {
return (xdostime != -1) ? extendedDosToJavaTime(xdostime) : -1;
}
/**
* Sets the last modification time of the entry in local date-time.
*
* <p> If the entry is output to a ZIP file or ZIP file formatted
* output stream the last modification time set by this method will
* be stored into the {@code date and time fields} of the zip file
* entry and encoded in standard {@code MS-DOS date and time format}.
* If the date-time set is out of the range of the standard {@code
* MS-DOS date and time format}, the time will also be stored into
* zip file entry's extended timestamp fields in {@code optional
* extra data} in UTC time. The {@link java.time.ZoneId#systemDefault()
* system default TimeZone} is used to convert the local date-time
* to UTC time.
*
* <p> {@code LocalDateTime} uses a precision of nanoseconds, whereas
* this class uses a precision of milliseconds. The conversion will
* truncate any excess precision information as though the amount in
* nanoseconds was subject to integer division by one million.
*
* @param time
* The last modification time of the entry in local date-time
*
* @see #getTimeLocal()
* @since 1.9
*/
public void setTimeLocal(LocalDateTime time) {
int year = time.getYear() - 1980;
if (year < 0) {
this.xdostime = DOSTIME_BEFORE_1980;
} else {
this.xdostime = (year << 25 |
time.getMonthValue() << 21 |
time.getDayOfMonth() << 16 |
time.getHour() << 11 |
time.getMinute() << 5 |
time.getSecond() >> 1)
+ ((long)(((time.getSecond() & 0x1) * 1000) +
time.getNano() / 1000_000) << 32);
}
if (xdostime != DOSTIME_BEFORE_1980 && year <= 0x7f) {
this.mtime = null;
} else {
this.mtime = FileTime.from(
ZonedDateTime.of(time, ZoneId.systemDefault()).toInstant());
}
}
/**
* Returns the last modification time of the entry in local date-time.
*
* <p> If the entry is read from a ZIP file or ZIP file formatted
* input stream, this is the last modification time from the zip
* file entry's {@code optional extra data} if the extended timestamp
* fields are present. Otherwise, the last modification time is read
* from entry's standard MS-DOS formatted {@code date and time fields}.
*
* <p> The {@link java.time.ZoneId#systemDefault() system default TimeZone}
* is used to convert the UTC time to local date-time.
*
* @return The last modification time of the entry in local date-time
*
* @see #setTimeLocal(LocalDateTime)
* @since 1.9
*/
public LocalDateTime getTimeLocal() {
if (mtime != null) {
return LocalDateTime.ofInstant(mtime.toInstant(), ZoneId.systemDefault());
}
int ms = (int)(xdostime >> 32);
return LocalDateTime.of((int)(((xdostime >> 25) & 0x7f) + 1980),
(int)((xdostime >> 21) & 0x0f),
(int)((xdostime >> 16) & 0x1f),
(int)((xdostime >> 11) & 0x1f),
(int)((xdostime >> 5) & 0x3f),
(int)((xdostime << 1) & 0x3e) + ms / 1000,
(ms % 1000) * 1000_000);
}
/**
* Sets the last modification time of the entry.
*
@ -498,15 +580,15 @@ class ZipEntry implements ZipConstants, Cloneable {
// flag its presence or absence. But if mtime is present
// in LOC it must be present in CEN as well.
if ((flag & 0x1) != 0 && (sz0 + 4) <= sz) {
mtime = unixTimeToFileTime(get32(extra, off + sz0));
mtime = unixTimeToFileTime(get32S(extra, off + sz0));
sz0 += 4;
}
if ((flag & 0x2) != 0 && (sz0 + 4) <= sz) {
atime = unixTimeToFileTime(get32(extra, off + sz0));
atime = unixTimeToFileTime(get32S(extra, off + sz0));
sz0 += 4;
}
if ((flag & 0x4) != 0 && (sz0 + 4) <= sz) {
ctime = unixTimeToFileTime(get32(extra, off + sz0));
ctime = unixTimeToFileTime(get32S(extra, off + sz0));
sz0 += 4;
}
break;

View File

@ -144,4 +144,13 @@ class ZipUtils {
public static final long get64(byte b[], int off) {
return get32(b, off) | (get32(b, off+4) << 32);
}
/**
* Fetches signed 32-bit value from byte array at specified offset.
* The bytes are assumed to be in Intel (little-endian) byte order.
*
*/
public static final int get32S(byte b[], int off) {
return (get16(b, off) | (get16(b, off+2) << 16));
}
}

View File

@ -23,7 +23,7 @@
/**
* @test
* @bug 4759491 6303183 7012868 8015666 8023713 8068790 8076641
* @bug 4759491 6303183 7012868 8015666 8023713 8068790 8076641 8075526
* @summary Test ZOS and ZIS timestamp in extra field correctly
*/
@ -54,8 +54,12 @@ public class TestExtraTime {
for (byte[] extra : new byte[][] { null, new byte[] {1, 2, 3}}) {
test(mtime, null, null, null, extra);
// ms-dos 1980 epoch problem
test(FileTime.from(10, TimeUnit.MILLISECONDS), null, null, null, extra);
// negative epoch time
test(FileTime.from(-100, TimeUnit.DAYS), null, null, null, extra);
// non-default tz
test(mtime, null, null, tz, extra);

View File

@ -0,0 +1,111 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8075526
* @summary Test timestamp via ZipEntry.get/setTimeLocal()
*/
import java.io.*;
import java.nio.file.*;
import java.time.*;
import java.util.*;
import java.util.zip.*;
public class TestLocalTime {
private static TimeZone tz0 = TimeZone.getDefault();
public static void main(String[] args) throws Throwable{
try {
LocalDateTime ldt = LocalDateTime.now();
test(getBytes(ldt), ldt); // now
ldt = ldt.withYear(1968); test(getBytes(ldt), ldt);
ldt = ldt.withYear(1970); test(getBytes(ldt), ldt);
ldt = ldt.withYear(1982); test(getBytes(ldt), ldt);
ldt = ldt.withYear(2037); test(getBytes(ldt), ldt);
ldt = ldt.withYear(2100); test(getBytes(ldt), ldt);
ldt = ldt.withYear(2106); test(getBytes(ldt), ldt);
TimeZone tz = TimeZone.getTimeZone("Asia/Shanghai");
// dos time does not support < 1980, have to use
// utc in mtime.
testWithTZ(tz, ldt.withYear(1982));
testWithTZ(tz, ldt.withYear(2037));
testWithTZ(tz, ldt.withYear(2100));
testWithTZ(tz, ldt.withYear(2106));
} finally {
TimeZone.setDefault(tz0);
}
}
static byte[] getBytes(LocalDateTime mtime) throws Throwable {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos);
ZipEntry ze = new ZipEntry("TestLocalTime.java");
ze.setTimeLocal(mtime);
check(ze, mtime);
zos.putNextEntry(ze);
zos.write(new byte[] { 1, 2, 3, 4});
zos.close();
return baos.toByteArray();
}
static void testWithTZ(TimeZone tz, LocalDateTime ldt) throws Throwable {
TimeZone.setDefault(tz);
byte[] zbytes = getBytes(ldt);
TimeZone.setDefault(tz0);
test(zbytes, ldt);
}
static void test(byte[] zbytes, LocalDateTime expected) throws Throwable {
System.out.printf("--------------------%nTesting: [%s]%n", expected);
// ZipInputStream
ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zbytes));
ZipEntry ze = zis.getNextEntry();
zis.close();
check(ze, expected);
// ZipFile
Path zpath = Paths.get(System.getProperty("test.dir", "."),
"TestLocalTime.zip");
try {
Files.copy(new ByteArrayInputStream(zbytes), zpath);
ZipFile zf = new ZipFile(zpath.toFile());
ze = zf.getEntry("TestLocalTime.java");
check(ze, expected);
zf.close();
} finally {
Files.deleteIfExists(zpath);
}
}
static void check(ZipEntry ze, LocalDateTime expected) {
LocalDateTime ldt = ze.getTimeLocal();
if (ldt.atOffset(ZoneOffset.UTC).toEpochSecond() >> 1
!= expected.atOffset(ZoneOffset.UTC).toEpochSecond() >> 1) {
throw new RuntimeException("Timestamp: storing mtime failed!");
}
}
}