/******************************************************************************** * CruiseControl, a Continuous Integration Toolkit * Copyright (c) 2001-2003, ThoughtWorks, Inc. * 200 E. Randolph, 25th Floor * Chicago, IL 60601 USA * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * + Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * + Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ********************************************************************************/ package net.sourceforge.cruisecontrol.util; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; import java.lang.ref.SoftReference; import net.sourceforge.cruisecontrol.CruiseControlException; // @todo Find alternative "logging" approach for classes where log4j may not be available //import org.apache.log4j.Logger; import org.apache.tools.ant.util.DateUtils; public final class DateUtil { // Can't use log4j since DateUtil is now used in reporting/jsp // @todo Find alternative "logging" approach for classes where log4j may not be available //private static final Logger LOG = Logger.getLogger(DateUtil.class); public static final long ONE_SECOND = 1000; public static final long ONE_MINUTE = 60 * ONE_SECOND; static final long ONE_HOUR = 60 * ONE_MINUTE; public static final String SIMPLE_DATE_FORMAT = "yyyyMMddHHmmss"; private static final String GMT = "GMT -0:00"; private DateUtil() { } private static final ThreadLocal<SoftReference<SimpleDateFormat>> TL = new ThreadLocal<SoftReference<SimpleDateFormat>>(); /** * Store a ThreadLocal instance of an ISO8601 dateFormat that can be safely re-used by a single thread. * Avoids creating new dateFormat objects for calls from the same thread. * SoftReference is used to keep a thread from holding onto dateFormat instance forever. * Only package visible to allow for unit testing. * @return an instance of an ISO8601 dateFormat that can be safely re-used by a single thread. */ static SimpleDateFormat getThreadLocal8601Format() { final SoftReference<SimpleDateFormat> ref = TL.get(); if (ref != null) { final SimpleDateFormat result = ref.get(); if (result != null) { return result; } } final SimpleDateFormat result = new SimpleDateFormat(DateUtils.ISO8601_DATETIME_PATTERN); final SoftReference<SimpleDateFormat> newRef = new SoftReference<SimpleDateFormat>(result); TL.set(newRef); return result; } /** * SimpleDateFormat is not thread-safe, see http://jira.public.thoughtworks.org/browse/CC-906. * @return a new date format (for use by one thread - not thread safe) */ private static DateFormat createIso8601Format() { final SimpleDateFormat format = getThreadLocal8601Format(); format.setTimeZone(TimeZone.getTimeZone(GMT)); return format; } private static DateFormat createSimpleFormat() { return new SimpleDateFormat(SIMPLE_DATE_FORMAT); } /** * Create an integer time from a <code>Date</code> object. * * @param date * The date to get the timestamp from. * @return The time as an integer formatted as "HHmm". */ public static int getTimeFromDate(final Date date) { final Calendar calendar = Calendar.getInstance(); calendar.setTime(date); final int hour = calendar.get(Calendar.HOUR_OF_DAY) * 100; final int minute = calendar.get(Calendar.MINUTE); return hour + minute; } /** * finds the difference in milliseconds between two integer time values of the format "HHmm". * * @param earlier * integer time value of format "HHmm" * @param later * integer time value of format "HHmm" * @return long millisecond time difference */ public static long milliTimeDifference(final int earlier, final int later) { final long earlierMillis = convertToMillis(earlier); final long laterMillis = convertToMillis(later); return laterMillis - earlierMillis; } /** * Convert a time represented by the format "HHmm" into milliseconds. * * @param hhmm * where hh are hours and mm are minutes * @return hhmm in milliseconds */ public static long convertToMillis(final int hhmm) { final int minutes = hhmm % 100; final int hours = (hhmm - minutes) / 100; return hours * ONE_HOUR + minutes * ONE_MINUTE; } /** * @param time * time in milliseconds * @return Time formatted as X hours Y minutes Z seconds */ public static String formatTime(final long time) { long seconds = time / 1000; final long hours = seconds / 3600; final long minutes = (seconds % 3600) / 60; seconds = seconds % 60; final StringBuilder sb = new StringBuilder(); if (hours != 0) { sb.append(hours).append(" hours "); } if (minutes != 0) { sb.append(minutes).append(" minutes "); } if (seconds != 0) { sb.append(seconds).append(" seconds"); } return sb.toString(); } /** * @return midnight on today's date */ public static Date getMidnight() { final Calendar midnight = Calendar.getInstance(); midnight.set(Calendar.HOUR_OF_DAY, 0); midnight.set(Calendar.MINUTE, 0); midnight.set(Calendar.SECOND, 0); midnight.set(Calendar.MILLISECOND, 0); return midnight.getTime(); } public static String getFormattedTime(final Date date) { if (date == null) { return null; } return createSimpleFormat().format(date); } public static Date parseFormattedTime(final String timeString, final String description) throws CruiseControlException { if (timeString == null) { throw new IllegalArgumentException("Null date string for " + description); } final Date date; try { date = createSimpleFormat().parse(timeString); } catch (ParseException e) { // @todo Find alternative "logging" approach where log4j may not be available //LOG.error("Error parsing timestamp for [" + description + "]", e); throw new CruiseControlException("Cannot parse string for " + description + ":" + timeString); } return date; } /** * @param buildLength the length of a build in millis. * @return a String representation of a duration specified in milliseconds. */ public static String getDurationAsString(final long buildLength) { long timeSeconds = buildLength / 1000; long minutes = (timeSeconds / 60); long seconds = timeSeconds - (minutes * 60); return minutes + " minute(s) " + seconds + " second(s)"; } public static Date parseIso8601(final String timestamp) throws ParseException { return createIso8601Format().parse(timestamp); } public static String formatIso8601(final Date date) { if (date == null) { return null; } return DateUtils.format(date, DateUtils.ISO8601_DATETIME_PATTERN); } }