001 package org.nakedobjects.applib.fixtures;
002
003 import java.util.Calendar;
004 import java.util.TimeZone;
005
006 import org.nakedobjects.applib.clock.Clock;
007
008
009 /**
010 * This clock, for use by fixtures, can be set to specific time.
011 *
012 * <p>
013 * If not set it will provide the time provided by the system clock.
014 *
015 * <p>
016 * Note that - by design - it does not provide any mechanism to advance the time (eg automatic ticking of the
017 * clock). That is, the time returned is always explicitly under the control of the programmer (it can be
018 * moved forward or back as required).
019 */
020 public class FixtureClock extends Clock {
021 private static final TimeZone UTC_TIME_ZONE;
022
023 static {
024 TimeZone tempTimeZone = TimeZone.getTimeZone("Etc/UTC");
025 if (tempTimeZone == null) {
026 tempTimeZone = TimeZone.getTimeZone("UTC");
027 }
028 UTC_TIME_ZONE = tempTimeZone;
029 }
030
031
032 /**
033 * Configures the system to use a FixtureClock rather than the in-built system clock.
034 * Can be called multiple times.
035 *
036 * <p>
037 * Must call before any other call to {@link Clock#getInstance()}.
038 *
039 * @throws IllegalStateException if Clock singleton already initialized with some other implementation.
040 */
041 public synchronized static FixtureClock initialize() {
042 if (!isInitialized() ||
043 !(getInstance() instanceof FixtureClock)) {
044 // installs the FixtureClock as the Clock singleton via the Clock's constructor
045 // if was initialized, then will replace.
046 // (if non-replaceable, then superclass will throw exception for us.
047 new FixtureClock();
048 }
049 return (FixtureClock) getInstance();
050 }
051
052 /**
053 * Makes {@link Clock#remove()} visible.
054 */
055 public static boolean remove() {
056 return Clock.remove();
057 }
058
059
060
061 ////////////////////////////////////////////////////
062 // Constructor
063 ////////////////////////////////////////////////////
064
065 // if non-null, then indicates that the time has been explicitly set.
066 // Otherwise returns the system time.
067 private Calendar calendar = null;
068
069
070 private FixtureClock() {}
071
072
073 ////////////////////////////////////////////////////
074 // hook
075 ////////////////////////////////////////////////////
076
077 /**
078 * Access via {@link Clock#getTime()}.
079 *
080 * <p>
081 * Will just return the system time until {@link #setDate(int, int, int)} or
082 * {@link #setTime(int, int)} (or one of the overloads) has been called.
083 */
084 @Override
085 protected long time() {
086 if (calendar == null) {
087 return System.currentTimeMillis();
088 }
089 return calendar.getTime().getTime();
090 }
091
092
093 ////////////////////////////////////////////////////
094 // setting/adjusting time
095 ////////////////////////////////////////////////////
096
097 /**
098 * Sets the clock to epoch, that is midnight, 1 Jan 1970 UTC.
099 *
100 * <p>
101 * This is typically called before either {@link #setDate(int, int, int)}
102 * (so that time is set to midnight) and/or {@link #setTime(int, int)}
103 * (so that date is set to a well known value).
104 */
105 public void clear() {
106 setupCalendarIfRequired();
107 calendar.clear();
108 }
109
110 /**
111 * Sets the hours and minutes as specified, and sets the seconds and
112 * milliseconds to zero, but the date portion is left unchanged.
113 *
114 * @see #setDate(int, int, int)
115 * @see #addTime(int, int)
116 */
117 public void setTime(final int hour, final int min) {
118 setupCalendarIfRequired();
119 calendar.set(Calendar.HOUR_OF_DAY, hour);
120 calendar.set(Calendar.MINUTE, min);
121 calendar.set(Calendar.SECOND, 0);
122 calendar.set(Calendar.MILLISECOND, 0);
123 }
124
125 /**
126 * Sets the date, but the time portion is left unchanged.
127 *
128 * @see #setTime(int, int)
129 * @see #addDate(int, int, int)
130 */
131 public void setDate(final int year, final int month, final int day) {
132 setupCalendarIfRequired();
133 calendar.set(Calendar.YEAR, year);
134 calendar.set(Calendar.MONTH, month - 1);
135 calendar.set(Calendar.DAY_OF_MONTH, day);
136 }
137
138 /**
139 * Adjusts the time by the specified number of hours and minutes.
140 *
141 * <p>
142 * Typically called after {@link #setTime(int, int)}, to move the clock
143 * forward or perhaps back.
144 *
145 * @see #addDate(int, int, int)
146 */
147 public void addTime(final int hours, final int minutes) {
148 setupCalendarIfRequired();
149 calendar.add(Calendar.HOUR_OF_DAY, hours);
150 calendar.add(Calendar.MINUTE, minutes);
151 }
152
153 /**
154 * Adjusts the time by the specified number of years, months or days.
155 *
156 * <p>
157 * Typically called after {@link #setDate(int, int, int)}, to move the clock
158 * forward or perhaps back.
159 *
160 * @see #addTime(int, int)
161 */
162 public void addDate(final int years, final int months, final int days) {
163 setupCalendarIfRequired();
164 calendar.add(Calendar.YEAR, years);
165 calendar.add(Calendar.MONTH, months);
166 calendar.add(Calendar.DAY_OF_MONTH, days);
167 }
168
169 private void setupCalendarIfRequired() {
170 if (calendar != null) {
171 return;
172 }
173 calendar = Calendar.getInstance();
174 calendar.setTimeZone(UTC_TIME_ZONE);
175 }
176
177 ////////////////////////////////////////////////////
178 // reset
179 ////////////////////////////////////////////////////
180
181 /**
182 * Go back to just returning the system's time.
183 */
184 public void reset() {
185 calendar = null;
186 }
187
188 ////////////////////////////////////////////////////
189 // toString
190 ////////////////////////////////////////////////////
191
192 @Override
193 public String toString() {
194 return (calendar==null?"System":"Explicitly set") +
195 ": " + Clock.getTimeAsCalendar().getTime().toString();
196 }
197 }
198 // Copyright (c) Naked Objects Group Ltd.