/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.isis.applib.fixtures;
import java.util.Calendar;
import java.util.TimeZone;
import org.apache.isis.applib.clock.Clock;
/**
* This clock, for use by fixtures, can be set to specific time.
*
* <p>
* If not set it will provide the time provided by the system clock.
*
* <p>
* Note that - by design - it does not provide any mechanism to advance the time
* (eg automatic ticking of the clock). That is, the time returned is always
* explicitly under the control of the programmer (it can be moved forward or
* back as required).
*/
public class FixtureClock extends Clock {
private static final TimeZone UTC_TIME_ZONE;
static {
TimeZone tempTimeZone = TimeZone.getTimeZone("Etc/UTC");
if (tempTimeZone == null) {
tempTimeZone = TimeZone.getTimeZone("UTC");
}
UTC_TIME_ZONE = tempTimeZone;
}
/**
* Configures the system to use a FixtureClock rather than the in-built
* system clock. Can be called multiple times.
*
* <p>
* Must call before any other call to {@link Clock#getInstance()}.
*
* @throws IllegalStateException
* if Clock singleton already initialized with some other
* implementation.
*/
public synchronized static FixtureClock initialize() {
if (!isInitialized() || !(getInstance() instanceof FixtureClock)) {
// installs the FixtureClock as the Clock singleton via the Clock's
// constructor
// if was initialized, then will replace.
// (if non-replaceable, then superclass will throw exception for us.
new FixtureClock();
}
return (FixtureClock) getInstance();
}
/**
* Makes {@link Clock#remove()} visible.
*/
public static boolean remove() {
return Clock.remove();
}
// //////////////////////////////////////////////////
// Constructor
// //////////////////////////////////////////////////
// if non-null, then indicates that the time has been explicitly set.
// Otherwise returns the system time.
private Calendar calendar = null;
private FixtureClock() {
}
// //////////////////////////////////////////////////
// hook
// //////////////////////////////////////////////////
/**
* Access via {@link Clock#getTime()}.
*
* <p>
* Will just return the system time until {@link #setDate(int, int, int)} or
* {@link #setTime(int, int)} (or one of the overloads) has been called.
*/
@Override
protected long time() {
if (calendar == null) {
return System.currentTimeMillis();
}
return calendar.getTime().getTime();
}
// //////////////////////////////////////////////////
// setting/adjusting time
// //////////////////////////////////////////////////
/**
* Sets the clock to epoch, that is midnight, 1 Jan 1970 UTC.
*
* <p>
* This is typically called before either {@link #setDate(int, int, int)}
* (so that time is set to midnight) and/or {@link #setTime(int, int)} (so
* that date is set to a well known value).
*/
public void clear() {
setupCalendarIfRequired();
calendar.clear();
}
/**
* Sets the hours and minutes as specified, and sets the seconds and
* milliseconds to zero, but the date portion is left unchanged.
*
* @see #setDate(int, int, int)
* @see #addTime(int, int)
*/
public void setTime(final int hour, final int min) {
setupCalendarIfRequired();
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, min);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
}
/**
* Sets the date, but the time portion is left unchanged.
*
* @see #setTime(int, int)
* @see #addDate(int, int, int)
*/
public void setDate(final int year, final int month, final int day) {
setupCalendarIfRequired();
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month - 1);
calendar.set(Calendar.DAY_OF_MONTH, day);
}
/**
* Adjusts the time by the specified number of hours and minutes.
*
* <p>
* Typically called after {@link #setTime(int, int)}, to move the clock
* forward or perhaps back.
*
* @see #addDate(int, int, int)
*/
public void addTime(final int hours, final int minutes) {
setupCalendarIfRequired();
calendar.add(Calendar.HOUR_OF_DAY, hours);
calendar.add(Calendar.MINUTE, minutes);
}
/**
* Adjusts the time by the specified number of years, months or days.
*
* <p>
* Typically called after {@link #setDate(int, int, int)}, to move the clock
* forward or perhaps back.
*
* @see #addTime(int, int)
*/
public void addDate(final int years, final int months, final int days) {
setupCalendarIfRequired();
calendar.add(Calendar.YEAR, years);
calendar.add(Calendar.MONTH, months);
calendar.add(Calendar.DAY_OF_MONTH, days);
}
private void setupCalendarIfRequired() {
if (calendar != null) {
return;
}
calendar = Calendar.getInstance();
calendar.setTimeZone(UTC_TIME_ZONE);
}
// //////////////////////////////////////////////////
// reset
// //////////////////////////////////////////////////
/**
* Go back to just returning the system's time.
*/
public void reset() {
calendar = null;
}
// //////////////////////////////////////////////////
// toString
// //////////////////////////////////////////////////
@Override
public String toString() {
return (calendar == null ? "System" : "Explicitly set") + ": " + Clock.getTimeAsDateTime().toString();
}
}