/*
* Copyright 2001-2012 Stephen Colebourne
*
* 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 org.joda.time;
import java.lang.reflect.Method;
import java.text.DateFormatSymbols;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import org.joda.time.chrono.ISOChronology;
/**
* DateTimeUtils provide public utility methods for the date-time library.
* <p>
* DateTimeUtils is thread-safe although shared static variables are used.
*
* @author Stephen Colebourne
* @since 1.0
*/
public class DateTimeUtils {
/** The singleton instance of the system millisecond provider. */
private static final SystemMillisProvider SYSTEM_MILLIS_PROVIDER = new SystemMillisProvider();
/** The millisecond provider currently in use. */
private static volatile MillisProvider cMillisProvider = SYSTEM_MILLIS_PROVIDER;
/** The millisecond provider currently in use. */
private static volatile Map<String, DateTimeZone> cZoneNames;
static {
// names from RFC-822 / JDK
// this is all very US-centric and dubious, but perhaps it will help some
Map<String, DateTimeZone> map = new LinkedHashMap<String, DateTimeZone>();
map.put("UT", DateTimeZone.UTC);
map.put("UTC", DateTimeZone.UTC);
map.put("GMT", DateTimeZone.UTC);
put(map, "EST", "America/New_York");
put(map, "EDT", "America/New_York");
put(map, "CST", "America/Chicago");
put(map, "CDT", "America/Chicago");
put(map, "MST", "America/Denver");
put(map, "MDT", "America/Denver");
put(map, "PST", "America/Los_Angeles");
put(map, "PDT", "America/Los_Angeles");
cZoneNames = Collections.unmodifiableMap(map);
}
private static void put(Map<String, DateTimeZone> map, String name, String id) {
try {
map.put(name, DateTimeZone.forID(id));
} catch (RuntimeException ex) {
// ignore
}
}
/**
* Restrictive constructor
*/
protected DateTimeUtils() {
super();
}
//-----------------------------------------------------------------------
/**
* Gets the current time in milliseconds.
* <p>
* By default this returns <code>System.currentTimeMillis()</code>.
* This may be changed using other methods in this class.
*
* @return the current time in milliseconds from 1970-01-01T00:00:00Z
*/
public static final long currentTimeMillis() {
return cMillisProvider.getMillis();
}
/**
* Resets the current time to return the system time.
* <p>
* This method changes the behaviour of {@link #currentTimeMillis()}.
* Whenever the current time is queried, {@link System#currentTimeMillis()} is used.
*
* @throws SecurityException if the application does not have sufficient security rights
*/
public static final void setCurrentMillisSystem() throws SecurityException {
checkPermission();
cMillisProvider = SYSTEM_MILLIS_PROVIDER;
}
/**
* Sets the current time to return a fixed millisecond time.
* <p>
* This method changes the behaviour of {@link #currentTimeMillis()}.
* Whenever the current time is queried, the same millisecond time will be returned.
*
* @param fixedMillis the fixed millisecond time to use
* @throws SecurityException if the application does not have sufficient security rights
*/
public static final void setCurrentMillisFixed(long fixedMillis) throws SecurityException {
checkPermission();
cMillisProvider = new FixedMillisProvider(fixedMillis);
}
/**
* Sets the current time to return the system time plus an offset.
* <p>
* This method changes the behaviour of {@link #currentTimeMillis()}.
* Whenever the current time is queried, {@link System#currentTimeMillis()} is used
* and then offset by adding the millisecond value specified here.
*
* @param offsetMillis the fixed millisecond time to use
* @throws SecurityException if the application does not have sufficient security rights
*/
public static final void setCurrentMillisOffset(long offsetMillis) throws SecurityException {
checkPermission();
if (offsetMillis == 0) {
cMillisProvider = SYSTEM_MILLIS_PROVIDER;
} else {
cMillisProvider = new OffsetMillisProvider(offsetMillis);
}
}
/**
* Sets the provider of the current time to class specified.
* <p>
* This method changes the behaviour of {@link #currentTimeMillis()}.
* Whenever the current time is queried, the specified class will be called.
*
* @param millisProvider the provider of the current time to use, not null
* @throws SecurityException if the application does not have sufficient security rights
* @since 2.0
*/
public static final void setCurrentMillisProvider(MillisProvider millisProvider) throws SecurityException {
if (millisProvider == null) {
throw new IllegalArgumentException("The MillisProvider must not be null");
}
checkPermission();
cMillisProvider = millisProvider;
}
/**
* Checks whether the provider may be changed using permission 'CurrentTime.setProvider'.
*
* @throws SecurityException if the provider may not be changed
*/
private static void checkPermission() throws SecurityException {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new JodaTimePermission("CurrentTime.setProvider"));
}
}
//-----------------------------------------------------------------------
/**
* Gets the millisecond instant from the specified instant object handling null.
* <p>
* If the instant object is <code>null</code>, the {@link #currentTimeMillis()}
* will be returned. Otherwise, the millis from the object are returned.
*
* @param instant the instant to examine, null means now
* @return the time in milliseconds from 1970-01-01T00:00:00Z
*/
public static final long getInstantMillis(ReadableInstant instant) {
if (instant == null) {
return DateTimeUtils.currentTimeMillis();
}
return instant.getMillis();
}
//-----------------------------------------------------------------------
/**
* Gets the chronology from the specified instant object handling null.
* <p>
* If the instant object is <code>null</code>, or the instant's chronology is
* <code>null</code>, {@link ISOChronology#getInstance()} will be returned.
* Otherwise, the chronology from the object is returned.
*
* @param instant the instant to examine, null means ISO in the default zone
* @return the chronology, never null
*/
public static final Chronology getInstantChronology(ReadableInstant instant) {
if (instant == null) {
return ISOChronology.getInstance();
}
Chronology chrono = instant.getChronology();
if (chrono == null) {
return ISOChronology.getInstance();
}
return chrono;
}
//-----------------------------------------------------------------------
/**
* Gets the chronology from the specified instant based interval handling null.
* <p>
* The chronology is obtained from the start if that is not null, or from the
* end if the start is null. The result is additionally checked, and if still
* null then {@link ISOChronology#getInstance()} will be returned.
*
* @param start the instant to examine and use as the primary source of the chronology
* @param end the instant to examine and use as the secondary source of the chronology
* @return the chronology, never null
*/
public static final Chronology getIntervalChronology(ReadableInstant start, ReadableInstant end) {
Chronology chrono = null;
if (start != null) {
chrono = start.getChronology();
} else if (end != null) {
chrono = end.getChronology();
}
if (chrono == null) {
chrono = ISOChronology.getInstance();
}
return chrono;
}
//-----------------------------------------------------------------------
/**
* Gets the chronology from the specified interval object handling null.
* <p>
* If the interval object is <code>null</code>, or the interval's chronology is
* <code>null</code>, {@link ISOChronology#getInstance()} will be returned.
* Otherwise, the chronology from the object is returned.
*
* @param interval the interval to examine, null means ISO in the default zone
* @return the chronology, never null
*/
public static final Chronology getIntervalChronology(ReadableInterval interval) {
if (interval == null) {
return ISOChronology.getInstance();
}
Chronology chrono = interval.getChronology();
if (chrono == null) {
return ISOChronology.getInstance();
}
return chrono;
}
//-----------------------------------------------------------------------
/**
* Gets the interval handling null.
* <p>
* If the interval is <code>null</code>, an interval representing now
* to now in the {@link ISOChronology#getInstance() ISOChronology}
* will be returned. Otherwise, the interval specified is returned.
*
* @param interval the interval to use, null means now to now
* @return the interval, never null
* @since 1.1
*/
public static final ReadableInterval getReadableInterval(ReadableInterval interval) {
if (interval == null) {
long now = DateTimeUtils.currentTimeMillis();
interval = new Interval(now, now);
}
return interval;
}
//-----------------------------------------------------------------------
/**
* Gets the chronology handling null.
* <p>
* If the chronology is <code>null</code>, {@link ISOChronology#getInstance()}
* will be returned. Otherwise, the chronology is returned.
*
* @param chrono the chronology to use, null means ISO in the default zone
* @return the chronology, never null
*/
public static final Chronology getChronology(Chronology chrono) {
if (chrono == null) {
return ISOChronology.getInstance();
}
return chrono;
}
//-----------------------------------------------------------------------
/**
* Gets the zone handling null.
* <p>
* If the zone is <code>null</code>, {@link DateTimeZone#getDefault()}
* will be returned. Otherwise, the zone specified is returned.
*
* @param zone the time zone to use, null means the default zone
* @return the time zone, never null
*/
public static final DateTimeZone getZone(DateTimeZone zone) {
if (zone == null) {
return DateTimeZone.getDefault();
}
return zone;
}
//-----------------------------------------------------------------------
/**
* Gets the period type handling null.
* <p>
* If the zone is <code>null</code>, {@link PeriodType#standard()}
* will be returned. Otherwise, the type specified is returned.
*
* @param type the time zone to use, null means the standard type
* @return the type to use, never null
*/
public static final PeriodType getPeriodType(PeriodType type) {
if (type == null) {
return PeriodType.standard();
}
return type;
}
//-----------------------------------------------------------------------
/**
* Gets the millisecond duration from the specified duration object handling null.
* <p>
* If the duration object is <code>null</code>, zero will be returned.
* Otherwise, the millis from the object are returned.
*
* @param duration the duration to examine, null means zero
* @return the duration in milliseconds
*/
public static final long getDurationMillis(ReadableDuration duration) {
if (duration == null) {
return 0L;
}
return duration.getMillis();
}
//-----------------------------------------------------------------------
/**
* Checks whether the partial is contiguous.
* <p>
* A partial is contiguous if one field starts where another ends.
* <p>
* For example <code>LocalDate</code> is contiguous because DayOfMonth has
* the same range (Month) as the unit of the next field (MonthOfYear), and
* MonthOfYear has the same range (Year) as the unit of the next field (Year).
* <p>
* Similarly, <code>LocalTime</code> is contiguous, as it consists of
* MillisOfSecond, SecondOfMinute, MinuteOfHour and HourOfDay (note how
* the names of each field 'join up').
* <p>
* However, a Year/HourOfDay partial is not contiguous because the range
* field Day is not equal to the next field Year.
* Similarly, a DayOfWeek/DayOfMonth partial is not contiguous because
* the range Month is not equal to the next field Day.
*
* @param partial the partial to check
* @return true if the partial is contiguous
* @throws IllegalArgumentException if the partial is null
* @since 1.1
*/
public static final boolean isContiguous(ReadablePartial partial) {
if (partial == null) {
throw new IllegalArgumentException("Partial must not be null");
}
DurationFieldType lastType = null;
for (int i = 0; i < partial.size(); i++) {
DateTimeField loopField = partial.getField(i);
if (i > 0) {
if (loopField.getRangeDurationField().getType() != lastType) {
return false;
}
}
lastType = loopField.getDurationField().getType();
}
return true;
}
//-----------------------------------------------------------------------
/**
* Gets the {@link DateFormatSymbols} based on the given locale.
* <p>
* If JDK 6 or newer is being used, DateFormatSymbols.getInstance(locale) will
* be used in order to allow the use of locales defined as extensions.
* Otherwise, new DateFormatSymbols(locale) will be used.
* See JDK 6 {@link DateFormatSymbols} for further information.
*
* @param locale the {@link Locale} used to get the correct {@link DateFormatSymbols}
* @return the symbols
* @since 2.0
*/
public static final DateFormatSymbols getDateFormatSymbols(Locale locale) {
try {
Method method = DateFormatSymbols.class.getMethod("getInstance", new Class[] {Locale.class});
return (DateFormatSymbols) method.invoke(null, new Object[] {locale});
} catch (Exception ex) {
return new DateFormatSymbols(locale);
}
}
//-----------------------------------------------------------------------
/**
* Gets the default map of time zone names.
* <p>
* This can be changed by {@link #setDefaultTimeZoneNames}.
* <p>
* The default set of short time zone names is as follows:
* <ul>
* <li>UT - UTC
* <li>UTC - UTC
* <li>GMT - UTC
* <li>EST - America/New_York
* <li>EDT - America/New_York
* <li>CST - America/Chicago
* <li>CDT - America/Chicago
* <li>MST - America/Denver
* <li>MDT - America/Denver
* <li>PST - America/Los_Angeles
* <li>PDT - America/Los_Angeles
* </ul>
*
* @return the unmodifiable map of abbreviations to zones, not null
* @since 2.2
*/
public static final Map<String, DateTimeZone> getDefaultTimeZoneNames() {
return cZoneNames;
}
/**
* Sets the default map of time zone names.
* <p>
* The map is copied before storage.
*
* @param names the map of abbreviations to zones, not null
* @since 2.2
*/
public static final void setDefaultTimeZoneNames(Map<String, DateTimeZone> names) {
cZoneNames = Collections.unmodifiableMap(new HashMap<String, DateTimeZone>(names));
}
//-------------------------------------------------------------------------
/**
* Calculates the astronomical Julian Day for an instant.
* <p>
* The <a href="http://en.wikipedia.org/wiki/Julian_day">Julian day</a> is a well-known
* system of time measurement for scientific use by the astronomy community.
* It expresses the interval of time in days and fractions of a day since
* January 1, 4713 BC (Julian) Greenwich noon.
* <p>
* Each day starts at midday (not midnight) and time is expressed as a fraction.
* Thus the fraction 0.25 is 18:00. equal to one quarter of the day from midday to midday.
* <p>
* Note that this method has nothing to do with the day-of-year.
*
* @param epochMillis the epoch millis from 1970-01-01Z
* @return the astronomical Julian Day represented by the specified instant
* @since 2.2
*/
public static final double toJulianDay(long epochMillis) {
// useful links
// http://en.wikipedia.org/wiki/Julian_day#cite_note-13 - Wikipedia
// http://aa.usno.navy.mil/data/docs/JulianDate.php" - USNO
// http://users.zoominternet.net/~matto/Java/Julian%20Date%20Converter.htm - Julian Date Converter by Matt Oltersdorf
// http://ssd.jpl.nasa.gov/tc.cgi#top - CalTech
double epochDay = epochMillis / 86400000d;
return epochDay + 2440587.5d;
}
/**
* Calculates the astronomical Julian Day Number for an instant.
* <p>
* The {@link #toJulianDay(long)} method calculates the astronomical Julian Day
* with a fraction based on days starting at midday.
* This method calculates the variant where days start at midnight.
* JDN 0 is used for the date equivalent to Monday January 1, 4713 BC (Julian).
* Thus these days start 12 hours before those of the fractional Julian Day.
* <p>
* Note that this method has nothing to do with the day-of-year.
*
* @param epochMillis the epoch millis from 1970-01-01Z
* @return the astronomical Julian Day represented by the specified instant
* @since 2.2
*/
public static final long toJulianDayNumber(long epochMillis) {
return (long) Math.floor(toJulianDay(epochMillis) + 0.5d);
}
/**
* Creates a date-time from a Julian Day.
* <p>
* Returns the {@code DateTime} object equal to the specified Julian Day.
*
* @param julianDay the Julian Day
* @return the epoch millis from 1970-01-01Z
* @since 2.2
*/
public static final long fromJulianDay(double julianDay) {
double epochDay = julianDay - 2440587.5d;
return (long) (epochDay * 86400000d);
}
//-----------------------------------------------------------------------
/**
* A millisecond provider, allowing control of the system clock.
*
* @author Stephen Colebourne
* @since 2.0 (previously private)
*/
public static interface MillisProvider {
/**
* Gets the current time.
* <p>
* Implementations of this method must be thread-safe.
*
* @return the current time in milliseconds
*/
long getMillis();
}
/**
* System millis provider.
*/
static class SystemMillisProvider implements MillisProvider {
/**
* Gets the current time.
* @return the current time in millis
*/
public long getMillis() {
return System.currentTimeMillis();
}
}
/**
* Fixed millisecond provider.
*/
static class FixedMillisProvider implements MillisProvider {
/** The fixed millis value. */
private final long iMillis;
/**
* Constructor.
* @param offsetMillis the millis offset
*/
FixedMillisProvider(long fixedMillis) {
iMillis = fixedMillis;
}
/**
* Gets the current time.
* @return the current time in millis
*/
public long getMillis() {
return iMillis;
}
}
/**
* Offset from system millis provider.
*/
static class OffsetMillisProvider implements MillisProvider {
/** The millis offset. */
private final long iMillis;
/**
* Constructor.
* @param offsetMillis the millis offset
*/
OffsetMillisProvider(long offsetMillis) {
iMillis = offsetMillis;
}
/**
* Gets the current time.
* @return the current time in millis
*/
public long getMillis() {
return System.currentTimeMillis() + iMillis;
}
}
}