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.Locale;
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$ * @since TrueZIP 6.7 */ public abstract class DateTimeConverter { /** * 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; } }; /** * 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; /** * Largest supported DOS date/time value in a ZIP file, * which is December 31st, 2107 AD 23:59:58 local time. */ static final long MAX_DOS_TIME = ((long) (2107 - 1980) << 25) | (12 << 21) | (31 << 16) | (23 << 11) | (59 << 5) | (58 >> 1); /** * A thread local lenient gregorian calendar for date/time * conversion which has its timezone set to the return value of * {@link #newTimeZone()}. */ private final ThreadLocal calendar = new ThreadLocalGregorianCalendar(); /** * 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 the given 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 epoch alias Java time). * @return {@code true} for round-up, {@code false} for round-down. */ protected abstract boolean roundUp(long jTime); /** * Returns a thread local lenient gregorian calendar for date/time * conversion which has its timezone set to the return value of * {@link #newTimeZone()}. * * @return A thread local lenient gregorian calendar. */ private GregorianCalendar getGregorianCalendar() { return (GregorianCalendar) calendar.get(); } /** * 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 lenient {@link GregorianCalendar} for the date/time * conversion which has its timezone set to the return value of * {@link #newTimeZone()}. * * @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 #newTimeZone() */ final long toDosTime(final long jTime) { if (jTime < 0) throw new IllegalArgumentException("Java time is negative: 0x" + Long.toHexString(jTime).toUpperCase(Locale.ENGLISH)); final GregorianCalendar cal = getGregorianCalendar(); cal.setTimeInMillis(roundUp(jTime) ? jTime + 1999 : jTime); long dTime = cal.get(Calendar.YEAR) - 1980; if (dTime < 0) return MIN_DOS_TIME; dTime = (dTime << 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); if (MAX_DOS_TIME < dTime) throw new IllegalArgumentException( "Java time is later than 2107 AD: 0x" + Long.toHexString(jTime).toUpperCase(Locale.ENGLISH)); assert MIN_DOS_TIME <= dTime && dTime <= MAX_DOS_TIME; return dTime; } /** * Converts a 32 bit integer encoded DOS date/time value to a Java time * value. *

* Note that not all 32 bit integers are valid DOS date/time values. * If an invalid DOS date/time value is provided, it gets adjusted by * overflowing the respective field value as if using a * {@link Calendar#setLenient lenient calendar}. * This feature is provided 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 value will not be the same as * {@code dTime}. *

* This method uses a lenient {@link GregorianCalendar} for the date/time * conversion which has its timezone set to the return value of * {@link #newTimeZone()}. * * @param dTime The DOS date/time value. * @return The number of milliseconds since midnight, January 1st, * 1970 AD UTC (called epoch alias Java time). * @throws IllegalArgumentException If {@code dTime} is earlier * than 1980 AD or greater than {@code 0xffffffffL}. * @see #toDosTime(long) * @see #newTimeZone() */ final long toJavaTime(final long dTime) { if (dTime < MIN_DOS_TIME) throw new IllegalArgumentException( "DOS date/time is earlier than 1980 AD: 0x" + Long.toHexString(dTime).toUpperCase(Locale.ENGLISH)); if (MAX_DOS_TIME < dTime) throw new IllegalArgumentException( "DOS date/time is later than 2107 AD: 0x" + Long.toHexString(dTime).toUpperCase(Locale.ENGLISH)); final int time = (int) dTime; final GregorianCalendar cal = getGregorianCalendar(); 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); return cal.getTimeInMillis(); } /** @see #getGregorianCalendar() */ private final class ThreadLocalGregorianCalendar extends ThreadLocal { protected Object initialValue() { return new GregorianCalendar(createTimeZone()); } }; }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy