All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.schlichtherle.util.zip.DateTimeConverter Maven / Gradle / Ivy

/*
 * Copyright (C) 2009-2010 Schlichtherle IT Services
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.schlichtherle.util.zip;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.SimpleTimeZone;
import java.util.TimeZone;

/**
 * Converts Java time values to DOS date/time values and vice versa.
 * This class has been introduced in order to enhance interoperability
 * between different flavours of the ZIP file format specification when
 * converting date/time from the serialized DOS format in a ZIP file to
 * the local system time, which is represented by a UNIX-like encoding
 * by the Java API.
 * 

* This class is thread-safe. * * @author Christian Schlichtherle * @version $Id: DateTimeConverter.java,v 1.4 2010/08/20 13:09:39 christian_schlichtherle Exp $ * @since TrueZIP 6.7 */ public abstract class DateTimeConverter { /** * Smallest supported DOS date/time value in a ZIP file, * which is January 1st, 1980 AD 00:00:00 local time. */ static final long MIN_DOS_TIME = (1 << 21) | (1 << 16); // 0x210000; /** * This instance applies the schedule for Daylight Saving Time (DST), * i.e. all time conversions will apply DST where appropriate to a * particular date. *

* This behaviour provides best interoperability with: *

    *
  • Java SE: {@code jar} utility * and {@code java.util.zip} package
  • *
  • Info-ZIP: {@code unzip}
  • *
*/ public static final DateTimeConverter JAR = new DateTimeConverter() { protected TimeZone createTimeZone() { return TimeZone.getDefault(); } protected boolean roundUp(long jTime) { return false; } }; /** * This instance ignores the schedule for Daylight Saving Time (DST), * i.e. all time conversions will use the same raw offset and current * DST savings, regardless of whether DST savings should be applied to * a particular date or not. *

* This behavior provides best interoperability with: *

    *
  • Windows Vista Explorer (as of June 30th, 2009)
  • *
  • WinZip 12.0
  • *
  • 7-Zip 4.65
  • *
*/ public static final DateTimeConverter ZIP = new DateTimeConverter() { protected TimeZone createTimeZone() { TimeZone tz = TimeZone.getDefault(); tz = new SimpleTimeZone( tz.getRawOffset() + tz.getDSTSavings(), tz.getID()); assert !tz.useDaylightTime(); return tz; } protected boolean roundUp(long jTime) { return true; } }; /** * Converts a Java time value to a DOS date/time value. * The returned value is rounded up or down to even seconds, * depending on {@link #roundUp}. * If the Java time value is earlier than January 1st, * 1980 AD 00:00:00 local time, then this value is returned instead. *

* This method uses a {@link Calendar} for the date/time conversion * which has its timezone set to the return value of * {@link #createTimeZone()}. * * @param jTime The number of milliseconds since midnight, January 1st, * 1970 AD UTC (called the epoch alias Java time). * @return A DOS date/time value reflecting the local time zone and * rounded down to even seconds which is minimum * January 1st, 1980 AD 00:00:00. * @throws RuntimeException If {@code jTime} is negative * or later than 2107 AD. * @see #toJavaTime(long) * @see #createTimeZone() */ final long toDosTime(final long jTime) { if (jTime < 0) throw new IllegalArgumentException("Java time is negative: " + Long.toHexString(jTime)); final Calendar cal = getCalendar(); cal.setTimeInMillis(roundUp(jTime) ? jTime + 1999 : jTime); final int year = cal.get(Calendar.YEAR) - 1980; if (year < 0) return MIN_DOS_TIME; if (year > 0x7f) throw new IllegalArgumentException( "Year of Java time is later than 2107 AD: " + (1980 + year)); final long dTime = (year << 25) | ((cal.get(Calendar.MONTH) + 1) << 21) | (cal.get(Calendar.DAY_OF_MONTH) << 16) | (cal.get(Calendar.HOUR_OF_DAY) << 11) | (cal.get(Calendar.MINUTE) << 5) | (cal.get(Calendar.SECOND) >> 1); assert dTime >= MIN_DOS_TIME; return dTime; } /** * Converts a DOS date/time value to a Java time value. * DOS date/time values are encoded using 32 bit integers. * However, not all 32 bit integers make a valid DOS date/time value. * If an illegal 32 bit integer is provided, * the behaviour of this method depends on the assertion status: *

* This method uses a {@link Calendar} for the date/time conversion * which has its timezone set to the return value of * {@link #createTimeZone()}. *

* If assertions are enabled, * {@link Calendar#setLenient(boolean) Calendard.setLenient(false)} * is called in order to throw a {@link RuntimeException} * when parsing illegal DOS date/time field values. * This can be used in order to detect bogus ZIP archive files created * by third party tools. *

* If assertions are disabled however, * {@link Calendar#setLenient(boolean) Calendard.setLenient(true)} * is called in order to adjust illegal DOS date/time field values * by overflowing them into their adjacent fields. * This can be used in order to read bogus ZIP archive files created * by third party tools. * However, the returned Java time may differ from its intended value at * the time of the creation of the ZIP archive file and when converting * it back again, the resulting DOS date/time will not be the same as * {@code dTime}. * Hence, interoperability is negatively affected in this case. * * @param dTime The DOS date/time value. * @return The number of milliseconds since midnight, January 1st, * 1970 AD UTC (called the epoch alias Java time). * @throws RuntimeException If {@code dTime} is earlier * than 1980 AD * or greater than {@code 0xffffffffL} * or holds an illegal DOS date/time field combination * and assertions are enabled. * @see #toDosTime(long) * @see #createTimeZone() */ final long toJavaTime(final long dTime) { if (dTime < MIN_DOS_TIME) throw new IllegalArgumentException( "DOS date/time is earlier than 1980 AD: " + Long.toHexString(dTime)); if (dTime > UInt.MAX_VALUE) throw new IllegalArgumentException( "DOS date/time value is greater than " + Long.toHexString(UInt.MAX_VALUE) + ": " + Long.toHexString(dTime)); final int time = (int) dTime; final Calendar cal = getCalendar(); cal.set(Calendar.YEAR, 1980 + ((time >> 25) & 0x7f)); cal.set(Calendar.MONTH, ((time >> 21) & 0x0f) - 1); cal.set(Calendar.DAY_OF_MONTH, (time >> 16) & 0x1f); cal.set(Calendar.HOUR_OF_DAY, (time >> 11) & 0x1f); cal.set(Calendar.MINUTE, (time >> 5) & 0x3f); cal.set(Calendar.SECOND, (time << 1) & 0x3e); // DOS date/time has only two seconds granularity. // Make calendar return only total seconds in order to make this // work correctly. cal.set(Calendar.MILLISECOND, 0); /*if (cal.get(Calendar.YEAR) > 1980 + 0x7f) { assert cal.isLenient(); assert 2108 == cal.get(Calendar.YEAR); throw new IllegalArgumentException( "An illegal DOS date/time field combination caused the calendar to overflow beyond year 2107 AD: " + Long.toHexString(dTime)); }*/ return cal.getTimeInMillis(); } /** * Returns a thread local {@link Calendar} instance for the * date/time conversion which has its timezone set to the return value * of {@link #createTimeZone()}. * * @return A {@link Calendar} instance. */ private Calendar getCalendar() { return (Calendar) calendar.get(); } /** @see #getCalendar() */ private final ThreadLocal calendar = new ThreadLocal() { protected Object initialValue() { final Calendar cal = new GregorianCalendar(createTimeZone()); boolean ea = false; assert ea = true; // NOT ea == true ! cal.setLenient(!ea); return cal; } }; /** * Returns a new timezone to use for date/time conversion. * All returned instances must have the same * {@link TimeZone#hasSameRules(TimeZone) rules}. * * @return A new timezone for date/time conversion - never {@code null}. */ protected abstract TimeZone createTimeZone(); /** * Returns whether a Java time should be rounded up or down to the next * two second interval when converting it to a DOS date/time. * * @param jTime The number of milliseconds since midnight, January 1st, * 1970 AD UTC (called the epoch alias Java time). * @return {@code yes} for round-up, {@code no} for round-down. */ protected abstract boolean roundUp(long jTime); /*protected boolean isWritingPreciseTime() { return false; }*/ }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy