/*
* -----------------------------------------------------------------------
* Copyright © 2013-2015 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (AdjustableClock.java) is part of project Time4J.
*
* Time4J is free software: You can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* Time4J 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Time4J. If not, see <http://www.gnu.org/licenses/>.
* -----------------------------------------------------------------------
*/
package net.time4j.clock;
import net.time4j.Moment;
import net.time4j.SystemClock;
import net.time4j.base.MathUtils;
import net.time4j.base.TimeSource;
import net.time4j.scale.TimeScale;
import java.util.concurrent.TimeUnit;
/**
* <p>Allows miscellaneous adjustments to any clock. </p>
*
* @author Meno Hochschild
* @since 2.1
* @doctags.concurrency This class is immutable as long as the underlying time source is immutable.
*/
/*[deutsch]
* <p>Erlaubt verschiedene Verstellungen irgendeiner beliebigen Uhr. </p>
*
* @author Meno Hochschild
* @since 2.1
* @doctags.concurrency This class is immutable as long as the underlying time source is immutable.
*/
public final class AdjustableClock
extends AbstractClock {
//~ Statische Felder/Initialisierungen --------------------------------
private static final int MIO = 1000000;
private static final AdjustableClock SYSTEM_CLOCK_WRAPPER =
new AdjustableClock(SystemClock.INSTANCE);
//~ Instanzvariablen --------------------------------------------------
private final TimeSource<?> source;
private final TimeUnit pulse;
private final int offsetAmount;
private final TimeUnit offsetUnit;
//~ Konstruktoren -----------------------------------------------------
private AdjustableClock(TimeSource<?> source) {
this(source, TimeUnit.NANOSECONDS, 0, TimeUnit.NANOSECONDS);
}
private AdjustableClock(
TimeSource<?> source,
TimeUnit pulse,
int offsetAmount,
TimeUnit offsetUnit
) {
super();
this.source = source;
this.pulse = pulse;
this.offsetAmount = offsetAmount;
this.offsetUnit = offsetUnit;
}
//~ Methoden ----------------------------------------------------------
/**
* <p>Yields an adjustable clock around the standard system clock. </p>
*
* <p>The clock can be adjusted using the {@code with()}-methods. </p>
*
* @return adjustable clock wrapper
* @see SystemClock#INSTANCE
* @since 2.1
*/
/*[deutsch]
* <p>Liefert eine verstellbare Uhr, die auf der Standard-Systemuhr basiert. </p>
*
* <p>Die Uhr kann mit Hilfe der {@code with()}-Methoden verstellt
* werden. </p>
*
* @return adjustable clock wrapper
* @see SystemClock#INSTANCE
* @since 2.1
*/
public static AdjustableClock ofSystem() {
return SYSTEM_CLOCK_WRAPPER;
}
/**
* <p>Creates a new adjustable clock for given time source. </p>
*
* <p>After construction, the clock can be adjusted using the
* {@code with()}-methods. </p>
*
* @param source time source which shall be adjusted
* @return new adjustable clock wrapper
* @since 2.1
*/
/*[deutsch]
* <p>Erzeugt eine neue verstellbare Uhr für die angegebene
* Zeitquelle. </p>
*
* <p>Nach der Konstruktion kann die Uhr mit Hilfe der
* {@code with()}-Methoden verstellt werden. </p>
*
* @param source time source which shall be adjusted
* @return new adjustable clock wrapper
* @since 2.1
*/
public static AdjustableClock of(TimeSource<?> source) {
if (source.equals(SystemClock.INSTANCE)) {
return SYSTEM_CLOCK_WRAPPER;
} else if (source instanceof AdjustableClock) {
return AdjustableClock.class.cast(source);
}
return new AdjustableClock(source);
}
/**
* <p>Creates a pulsed clock which only displays the current time in
* given pulse unit. </p>
*
* @param pulse new pulse
* @return adjusted clock with given pulse
* @since 2.1
*/
/*[deutsch]
* <p>Erzeugt eine getaktete Uhr, die die aktuelle Uhrzeit nur in der
* angegebenen Zeiteinheit anzeigt. </p>
*
* @param pulse new pulse
* @return adjusted clock with given pulse
* @since 2.1
*/
public AdjustableClock withPulse(TimeUnit pulse) {
if (pulse == null) {
throw new NullPointerException("Missing pulse.");
} else if (pulse == this.pulse) {
return this;
}
return new AdjustableClock(
this.source,
pulse,
this.offsetAmount,
this.offsetUnit);
}
/**
* <p>Creates an adjusted clock which displays the current time with
* given time shift. </p>
*
* @param offset amount of shift
* @param unit unit of shift
* @return adjusted clock with given shift/offset
* @since 2.1
*/
/*[deutsch]
* <p>Erzeugt eine getaktete Uhr, die die aktuelle Uhrzeit mit dem
* angegebenen Versatz anzeigt. </p>
*
* @param offset amount of shift
* @param unit unit of shift
* @return adjusted clock with given shift/offset
* @since 2.1
*/
public AdjustableClock withOffset(
int offset,
TimeUnit unit
) {
if (unit == null) {
throw new NullPointerException("Missing offset unit.");
} else if (
(this.offsetAmount == offset)
&& (this.offsetUnit == unit)
) {
return this;
}
return new AdjustableClock(
this.source,
this.pulse,
offset,
unit);
}
@Override
public Moment currentTime() {
Moment result = Moment.from(this.source.currentTime());
result = result.plus(this.offsetAmount, this.offsetUnit);
switch (this.pulse) {
case DAYS:
result = applyPulse(result, 86400);
break;
case HOURS:
result = applyPulse(result, 3600);
break;
case MINUTES:
result = applyPulse(result, 60);
break;
case SECONDS:
result = Moment.of(result.getPosixTime(), TimeScale.POSIX);
break;
case MILLISECONDS:
result =
Moment.of(
result.getPosixTime(),
(result.getNanosecond() / MIO) * MIO,
TimeScale.POSIX);
break;
case MICROSECONDS:
result =
Moment.of(
result.getPosixTime(),
(result.getNanosecond() / 1000) * 1000,
TimeScale.POSIX);
break;
case NANOSECONDS:
break; // no-op
default:
throw new UnsupportedOperationException(this.pulse.name());
}
return result;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (obj instanceof AdjustableClock) {
AdjustableClock that = (AdjustableClock) obj;
return (
this.source.equals(that.source)
&& (this.offsetAmount == that.offsetAmount)
&& (this.offsetUnit == that.offsetUnit)
&& (this.pulse == that.pulse)
);
} else {
return false;
}
}
@Override
public int hashCode() {
int hash = 3;
hash = 37 * hash + this.source.hashCode();
hash = 37 * hash + this.pulse.hashCode();
hash = 37 * hash + this.offsetAmount;
return 37 * hash + this.offsetUnit.hashCode();
}
/**
* <p>For debugging purposes. </p>
*
* @return description of clock state
*/
/*[deutsch]
* <p>Für Debugging-Zwecke. </p>
*
* @return description of clock state
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("AdjustableClock[");
sb.append("source=");
sb.append(this.source);
sb.append(",offset-amount=");
sb.append(this.offsetAmount);
sb.append(",offset-unit=");
sb.append(this.offsetUnit);
sb.append(",pulse=");
sb.append(this.pulse);
sb.append(']');
return sb.toString();
}
private static Moment applyPulse(
Moment moment,
int factor
) {
return Moment.of(
MathUtils.floorDivide(
moment.getPosixTime(), factor
) * factor,
TimeScale.POSIX);
}
}