001 package org.nakedobjects.applib.value;
002
003 import java.lang.reflect.InvocationTargetException;
004 import java.lang.reflect.Method;
005 import java.text.DateFormat;
006 import java.util.Calendar;
007 import java.util.TimeZone;
008
009 import org.nakedobjects.applib.annotation.Value;
010 import org.nakedobjects.applib.clock.Clock;
011
012
013 /**
014 * Value object representing a date (not time) value.
015 *
016 * <p>
017 * TODO: other methods to implement comparison methods:
018 * <ul>
019 * <li>sameDateAs() day == day & month == month & year == year</li>
020 * <li>withinNextDatePeriod(int days, int months, int years)</li>
021 * <li>withinDatePeriod(int days, int months, int years)</li>
022 * <li>withinPreviousDatePeriod(int days, int months, int years)</li>
023 * </ul>
024 */
025 @Value(semanticsProviderName = "org.nakedobjects.metamodel.value.DateValueSemanticsProvider")
026 public class Date extends Magnitude {
027 private static final long serialVersionUID = 1L;
028 private static final TimeZone UTC_TIME_ZONE;
029 private static final DateFormat MEDIUM_FORMAT = DateFormat.getDateInstance(DateFormat.MEDIUM);
030 private final java.util.Date date;
031
032 static {
033 // for dotnet compatibility -
034 TimeZone timeZone = TimeZone.getTimeZone("Etc/UTC");
035 if (timeZone == null) {
036 // for dotnet compatibility - "Etc/UTC fails in dotnet
037 timeZone = TimeZone.getTimeZone("UTC");
038 }
039 UTC_TIME_ZONE = timeZone;
040 }
041
042 /**
043 * Create a Date object for today's date.
044 */
045 public Date() {
046 final Calendar cal = Calendar.getInstance();
047 cal.setTimeZone(UTC_TIME_ZONE);
048 final java.util.Date d = new java.util.Date(Clock.getTime());
049 cal.setTime(d);
050 clearTime(cal);
051 date = cal.getTime();
052 }
053
054 /**
055 * Create a Date object set to the specified day, month and year.
056 */
057 public Date(final int year, final int month, final int day) {
058 checkDate(year, month, day);
059 final Calendar cal = Calendar.getInstance();
060 cal.setTimeZone(UTC_TIME_ZONE);
061 clearTime(cal);
062 cal.set(year, month - 1, day);
063 date = cal.getTime();
064 }
065
066 /**
067 * Create a Date object based on the specified Java date object. The time portion of the Java date is
068 * disposed of.
069 */
070 public Date(final java.util.Date date) {
071 final Calendar cal = Calendar.getInstance();
072 cal.setTimeZone(UTC_TIME_ZONE);
073
074 cal.setTime(date);
075 cal.set(Calendar.HOUR, 0);
076 cal.set(Calendar.MINUTE, 0);
077 cal.set(Calendar.SECOND, 0);
078 cal.set(Calendar.MILLISECOND, 0);
079 this.date = cal.getTime();
080 }
081
082 protected Date createDate(final java.util.Date time) {
083 return new Date(time);
084 }
085
086 /**
087 * Add the specified days, years and months to this date value and return a new date object containing the
088 * result.
089 */
090 public Date add(final int years, final int months, final int days) {
091 final Calendar cal = Calendar.getInstance();
092 cal.setTimeZone(UTC_TIME_ZONE);
093 cal.setTime(date);
094 cal.add(Calendar.DAY_OF_MONTH, days);
095 cal.add(Calendar.MONTH, months);
096 cal.add(Calendar.YEAR, years);
097 return createDate(cal.getTime());
098 }
099
100 private void checkDate(final int year, final int month, final int day) {
101 if ((month < 1) || (month > 12)) {
102 throw new IllegalArgumentException("Month must be in the range 1 - 12 inclusive");
103 }
104 final Calendar cal = Calendar.getInstance();
105 cal.setTimeZone(UTC_TIME_ZONE);
106 cal.set(year, month - 1, 0);
107 final int lastDayOfMonth = cal.getMaximum(Calendar.DAY_OF_MONTH);
108 if ((day < 1) || (day > lastDayOfMonth)) {
109 throw new IllegalArgumentException("Day must be in the range 1 - " + lastDayOfMonth + " inclusive: " + day);
110 }
111 }
112
113 /**
114 * clear all aspects of the time that are not used
115 */
116 private void clearTime(final Calendar cal) {
117 cal.set(Calendar.HOUR, 0);
118 cal.set(Calendar.HOUR_OF_DAY, 0);
119 cal.set(Calendar.MINUTE, 0);
120 cal.set(Calendar.SECOND, 0);
121 cal.set(Calendar.AM_PM, 0);
122 cal.set(Calendar.MILLISECOND, 0);
123 }
124
125 /**
126 * Return this date value as a Java Date object.
127 *
128 * @see java.util.Date
129 */
130 public java.util.Date dateValue() {
131 return new java.util.Date(date.getTime());
132 }
133
134 private int getEndDayOfMonthOneDotOne(final Calendar originalCalendar) {
135 final Calendar newCalendar = Calendar.getInstance();
136 newCalendar.setTimeZone(originalCalendar.getTimeZone());
137 newCalendar.setTime(originalCalendar.getTime());
138
139 final int firstPossibleDay = originalCalendar.getLeastMaximum(Calendar.DAY_OF_MONTH);
140 final int lastPossibleDay = originalCalendar.getMaximum(Calendar.DAY_OF_MONTH);
141 int lastValidDay = firstPossibleDay;
142
143 for (int day = firstPossibleDay + 1; day < lastPossibleDay; day++) {
144 newCalendar.set(Calendar.DAY_OF_MONTH, day);
145 if (newCalendar.get(Calendar.MONTH) != originalCalendar.get(Calendar.MONTH)) {
146 return lastValidDay;
147 }
148 lastValidDay = day;
149 }
150 return lastPossibleDay;
151 }
152
153 private int getEndDayOfMonth(final Calendar originalCalendar) {
154
155 final Class<?> cls = originalCalendar.getClass();
156 try {
157 final Method getActualMaximum = cls.getMethod("getActualMaximum", new Class[] { int.class });
158 return ((Integer) getActualMaximum.invoke(originalCalendar, new Object[] { new Integer(Calendar.DAY_OF_MONTH) }))
159 .intValue();
160 } catch (final NoSuchMethodException ignore) {
161 // expected if pre java 1.2 - fall through
162 } catch (final IllegalAccessException e) {
163 throw new RuntimeException(e.getMessage());
164 } catch (final InvocationTargetException e) {
165 throw new RuntimeException(e.getMessage());
166 }
167 return getEndDayOfMonthOneDotOne(originalCalendar);
168 }
169
170 /**
171 * Calculates, and returns, a date representing the last day of the month relative to the current date.
172 *
173 * @author Joshua Cassidy
174 */
175 public Date endOfMonth() {
176 final Calendar c = Calendar.getInstance();
177 c.setTimeZone(UTC_TIME_ZONE);
178 c.setTime(date);
179 c.set(Calendar.DAY_OF_MONTH, getEndDayOfMonth(c));
180 return createDate(c.getTime());
181 }
182
183 @Override
184 public boolean equals(final Object obj) {
185 if (obj instanceof Date) {
186 final Date d = (Date) obj;
187 return d.date.equals(date);
188 }
189 return super.equals(obj);
190 }
191
192 /**
193 * Return the day from this date, in the range 1 - 31.
194 */
195 public int getDay() {
196 final Calendar c = Calendar.getInstance();
197 c.setTimeZone(UTC_TIME_ZONE);
198 c.setTime(date);
199 return c.get(Calendar.DAY_OF_MONTH);
200 }
201
202 /**
203 * Calculates, and returns, an int representing the day of the week relative to the current date. With Mon =
204 * 0 through to Sun = 6
205 *
206 * @author Joshua Cassidy
207 */
208 public int getDayOfWeek() {
209 final Calendar c = Calendar.getInstance();
210 c.setTime(date);
211 final int day = c.get(Calendar.DAY_OF_WEEK);
212 // Calendar day is 1 - 7 for sun - sat
213 if (day == 1) {
214 return 6;
215 } else {
216 return day - 2;
217 }
218 }
219
220 /**
221 * Return the month from this date, in the range 1 - 12.
222 */
223 public int getMonth() {
224 final Calendar c = Calendar.getInstance();
225 c.setTimeZone(UTC_TIME_ZONE);
226 c.setTime(date);
227 return c.get(Calendar.MONTH) + 1;
228 }
229
230 /**
231 * Return the year from this date.
232 */
233 public int getYear() {
234 final Calendar c = Calendar.getInstance();
235 c.setTimeZone(UTC_TIME_ZONE);
236 c.setTime(date);
237 return c.get(Calendar.YEAR);
238 }
239
240 /**
241 * Returns true if the date of this object has the same value as the specified date
242 */
243 @Override
244 public boolean isEqualTo(final Magnitude date) {
245 if (date instanceof Date) {
246 return this.date.equals(((Date) date).date);
247 } else {
248 throw new IllegalArgumentException("Parameter must be of type Time");
249 }
250 }
251
252 /**
253 * Returns true if the time of this object is earlier than the specified time
254 */
255 @Override
256 public boolean isLessThan(final Magnitude date) {
257 if (date instanceof Date) {
258 return this.date.before(((Date) date).date);
259 } else {
260 throw new IllegalArgumentException("Parameter must be of type Time");
261 }
262 }
263
264 private boolean sameAs(final Date as, final int field) {
265 final Calendar c = Calendar.getInstance();
266 c.setTimeZone(UTC_TIME_ZONE);
267 c.setTime(date);
268
269 final Calendar c2 = Calendar.getInstance();
270 c2.setTimeZone(UTC_TIME_ZONE);
271 c2.setTime(as.date);
272
273 return c.get(field) == c2.get(field);
274 }
275
276 /**
277 * Determines if this date and the specified date represent the same day of the month, eg both dates are
278 * for the 3rd.
279 */
280 public boolean sameDayOfMonthAs(final Date as) {
281 return sameAs(as, Calendar.DAY_OF_MONTH);
282 }
283
284 /**
285 * Determines if this date and the specified date represent the same day of the week, eg both dates are on
286 * a Tuesday.
287 */
288 public boolean sameDayOfWeekAs(final Date as) {
289 return sameAs(as, Calendar.DAY_OF_WEEK);
290 }
291
292 /**
293 * Determines if this date and the specified date represent the same day of the year, eg both dates are
294 * for the 108th day of the year.
295 */
296 public boolean sameDayOfYearAs(final Date as) {
297 return sameAs(as, Calendar.DAY_OF_YEAR);
298 }
299
300 /**
301 * Determines if this date and the specified date represent the same month, eg both dates are for the
302 * March.
303 */
304 public boolean sameMonthAs(final Date as) {
305 return sameAs(as, Calendar.MONTH);
306 }
307
308 /**
309 * Determines if this date and the specified date represent the same week in the year, eg both dates are
310 * the for the 18th week of the year.
311 */
312 public boolean sameWeekAs(final Date as) {
313 return sameAs(as, Calendar.WEEK_OF_YEAR);
314 }
315
316 /**
317 * Determines if this date and the specified date represent the same year.
318 */
319 public boolean sameYearAs(final Date as) {
320 return sameAs(as, Calendar.YEAR);
321 }
322
323 /**
324 * Calculates, and returns, a date representing the first day of the month relative to the current date.
325 */
326 public Date startOfMonth() {
327 final Calendar c = Calendar.getInstance();
328 c.setTimeZone(UTC_TIME_ZONE);
329 c.setTime(date);
330 c.set(Calendar.DAY_OF_MONTH, c.getMinimum(Calendar.DAY_OF_MONTH));
331 return createDate(c.getTime());
332 }
333
334 /**
335 * Calculates, and returns, a date representing the first day of the week relative to the current date.
336 */
337 public Date startOfWeek() {
338 final Calendar c = Calendar.getInstance();
339 c.setTimeZone(UTC_TIME_ZONE);
340 c.setTime(date);
341 c.set(Calendar.DAY_OF_WEEK, c.getFirstDayOfWeek());
342 return createDate(c.getTime());
343 }
344
345 /**
346 * Calculates, and returns, a date representing the first day of the year relative to the current date.
347 */
348 public Date startOfYear() {
349 final Calendar c = Calendar.getInstance();
350 c.setTimeZone(UTC_TIME_ZONE);
351 c.setTime(date);
352 c.set(Calendar.DAY_OF_YEAR, c.getMinimum(Calendar.DAY_OF_YEAR));
353 return createDate(c.getTime());
354 }
355
356 public String title() {
357 return MEDIUM_FORMAT.format(date);
358 }
359
360 @Override
361 public String toString() {
362 return getYear() + "-" + getMonth() + "-" + getDay();
363 }
364 }
365 // Copyright (c) Naked Objects Group Ltd.