001    package org.fest.assertions.api;
002    
003    import static org.fest.util.Dates.ISO_DATE_FORMAT;
004    
005    import java.text.DateFormat;
006    import java.text.ParseException;
007    import java.util.ArrayList;
008    import java.util.Calendar;
009    import java.util.Collection;
010    import java.util.Comparator;
011    import java.util.Date;
012    import java.util.concurrent.TimeUnit;
013    
014    import org.fest.assertions.core.Assert;
015    import org.fest.assertions.internal.Dates;
016    import org.fest.assertions.internal.Failures;
017    import org.fest.util.ComparatorBasedComparisonStrategy;
018    import org.fest.util.VisibleForTesting;
019    
020    /**
021     * 
022     * Assertions for {@code Date}s.
023     * <p>
024     * To create a new instance of this class invoke <code>{@link Assertions#assertThat(Date)}</code>.
025     * </p>
026     * Note that assertions with date parameter comes with two flavor, one is obviously a {@link Date} and the other is a
027     * String representing a Date.<br>
028     * For the latter, the default format follows ISO 8901 : "yyyy-MM-dd", user can override it with a custom format by
029     * calling {@link #withDateFormat(DateFormat)}.<br>
030     * The user custom format will then be used for all next Date assertions (i.e not limited to the current assertion) in
031     * the test suite.<br>
032     * To turn back to default format, simply call {@link #withIsoDateFormat()}.
033     * 
034     * @author Tomasz Nurkiewicz (thanks for giving assertions idea)
035     * @author Joel Costigliola
036     */
037    public class DateAssert extends AbstractAssert<DateAssert, Date> {
038    
039      @VisibleForTesting
040      Dates dates = Dates.instance();
041    
042      /**
043       * Used in String based Date assertions - like {@link #isAfter(String)} - to convert input date represented as string
044       * to Date.<br>
045       * The format used can be overriden by invoking {@link #withDateFormat(DateFormat)}
046       */
047      @VisibleForTesting
048      static DateFormat dateFormat = ISO_DATE_FORMAT;
049    
050      /**
051       * Creates a new </code>{@link DateAssert}</code>.
052       * @param actual the target to verify.
053       */
054      protected DateAssert(Date actual) {
055        super(actual, DateAssert.class);
056      }
057    
058      /**
059       * Same assertion as {@link AbstractAssert#isEqualTo(Object) isEqualTo(Date date)} but given Date is represented as String either with ISO date format
060       * (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
061       * @param dateAsString the given Date represented as String in default or custom date format.
062       * @return this assertion object.
063       * @throws AssertionError if actual and given Date represented as String are not equal.
064       * @throws AssertionError if the given date as String could not be converted to a Date.
065       */
066      public DateAssert isEqualTo(String dateAsString) {
067        return isEqualTo(parse(dateAsString));
068      }
069    
070      /**
071       * Same assertion as {@link AbstractAssert#isNotEqualTo(Object) isNotEqualTo(Date date)} but given Date is represented as String either with ISO date format
072       * (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
073       * @param dateAsString the given Date represented as String in default or custom date format.
074       * @return this assertion object.
075       * @throws AssertionError if actual and given Date represented as String are equal.
076       * @throws AssertionError if the given date as String could not be converted to a Date.
077       */
078      public DateAssert isNotEqualTo(String dateAsString) {
079        return isNotEqualTo(parse(dateAsString));
080      }
081    
082      /**
083       * Same assertion as {@link Assert#isIn(Object...)} but given Dates are represented as String either with ISO date format
084       * (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
085       * @param datesAsString the given Dates represented as String in default or custom date format.
086       * @return this assertion object.
087       * @throws AssertionError if actual is not in given Dates represented as String.
088       * @throws AssertionError if one of the given date as String could not be converted to a Date.
089       */
090      public DateAssert isIn(String... datesAsString) {
091        Date[] dates = new Date[datesAsString.length];
092        for (int i = 0; i < datesAsString.length; i++) {
093          dates[i] = parse(datesAsString[i]);
094        }
095        return isIn(dates);
096      }
097    
098      /**
099       * Same assertion as {@link Assert#isIn(Iterable)} but given Dates are represented as String either with ISO date format
100       * (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).<br>
101       * Method signature could not be <code>isIn(Collection&lt;String&gt;)</code> because it would be same signature as
102       * <code>isIn(Collection&lt;Date&gt;)</code> since java collection type are erased at runtime.
103       * @param datesAsString the given Dates represented as String in default or custom date format.
104       * @return this assertion object.
105       * @throws AssertionError if actual is not in given Dates represented as String.
106       * @throws AssertionError if one of the given date as String could not be converted to a Date.
107       */
108      public DateAssert isInWithStringDateCollection(Collection<String> datesAsString) {
109        Collection<Date> dates = new ArrayList<Date>(datesAsString.size());
110        for (String dateAsString : datesAsString) {
111          dates.add(parse(dateAsString));
112        }
113        return isIn(dates);
114      }
115    
116      /**
117       * Same assertion as {@link Assert#isNotIn(Object...)} but given Dates are represented as String either with ISO date
118       * format (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
119       * @param datesAsString the given Dates represented as String in default or custom date format.
120       * @return this assertion object.
121       * @throws AssertionError if actual is in given Dates represented as String.
122       * @throws AssertionError if one of the given date as String could not be converted to a Date.
123       */
124      public DateAssert isNotIn(String... datesAsString) {
125        Date[] dates = new Date[datesAsString.length];
126        for (int i = 0; i < datesAsString.length; i++) {
127          dates[i] = parse(datesAsString[i]);
128        }
129        return isNotIn(dates);
130      }
131    
132      /**
133       * Same assertion as {@link Assert#isNotIn(Iterable)} but given Dates are represented as String either with ISO date
134       * format (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).<br>
135       * Method signature could not be <code>isNotIn(Collection&lt;String&gt;)</code> because it would be same signature as
136       * <code>isNotIn(Collection&lt;Date&gt;)</code> since java collection type are erased at runtime.
137       * @param datesAsString the given Dates represented as String in default or custom date format.
138       * @return this assertion object.
139       * @throws AssertionError if actual is in given Dates represented as String.
140       * @throws AssertionError if one of the given date as String could not be converted to a Date.
141       */
142      public DateAssert isNotInWithStringDateCollection(Collection<String> datesAsString) {
143        Collection<Date> dates = new ArrayList<Date>(datesAsString.size());
144        for (String dateAsString : datesAsString) {
145          dates.add(parse(dateAsString));
146        }
147        return isNotIn(dates);
148      }
149    
150      /**
151       * Verifies that the actual {@code Date} is <b>strictly</b> before the given one.
152       * @param other the given Date.
153       * @return this assertion object.
154       * @throws AssertionError if the actual {@code Date} is {@code null}.
155       * @throws NullPointerException if other {@code Date} is {@code null}.
156       * @throws AssertionError if the actual {@code Date} is not strictly before the given one.
157       */
158      public DateAssert isBefore(Date other) {
159        dates.assertIsBefore(info, actual, other);
160        return this;
161      }
162    
163      /**
164       * Same assertion as {@link #isBefore(Date)} but given Date is represented as String either with ISO date format
165       * (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
166       * @param dateAsString the given Date represented as String in default or custom date format.
167       * @return this assertion object.
168       * @throws AssertionError if the actual {@code Date} is {@code null}.
169       * @throws NullPointerException if given date as String is {@code null}.
170       * @throws AssertionError if the actual {@code Date} is not strictly before the given Date represented as String.
171       * @throws AssertionError if the given date as String could not be converted to a Date.
172       */
173      public DateAssert isBefore(String dateAsString) {
174        return isBefore(parse(dateAsString));
175      }
176    
177      /**
178       * Verifies that the actual {@code Date} is before or equals to the given one.
179       * @param other the given Date.
180       * @return this assertion object.
181       * @throws AssertionError if the actual {@code Date} is {@code null}.
182       * @throws NullPointerException if other {@code Date} is {@code null}.
183       * @throws AssertionError if the actual {@code Date} is not before or equals to the given one.
184       */
185      public DateAssert isBeforeOrEqualsTo(Date other) {
186        dates.assertIsBeforeOrEqualsTo(info, actual, other);
187        return this;
188      }
189    
190      /**
191       * Same assertion as {@link #isBeforeOrEqualsTo(Date)} but given Date is represented as String either with ISO date
192       * format (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
193       * @param dateAsString the given Date represented as String in default or custom date format.
194       * @return this assertion object.
195       * @throws AssertionError if the actual {@code Date} is {@code null}.
196       * @throws NullPointerException if given date as String is {@code null}.
197       * @throws AssertionError if the actual {@code Date} is not before or equals to the given Date represented as String.
198       * @throws AssertionError if the given date as String could not be converted to a Date.
199       */
200      public DateAssert isBeforeOrEqualsTo(String dateAsString) {
201        return isBeforeOrEqualsTo(parse(dateAsString));
202      }
203    
204      /**
205       * Verifies that the actual {@code Date} is <b>strictly</b> after the given one.
206       * @param other the given Date.
207       * @return this assertion object.
208       * @throws AssertionError if the actual {@code Date} is {@code null}.
209       * @throws NullPointerException if other {@code Date} is {@code null}.
210       * @throws AssertionError if the actual {@code Date} is not strictly after the given one.
211       */
212      public DateAssert isAfter(Date other) {
213        dates.assertIsAfter(info, actual, other);
214        return this;
215      }
216    
217      /**
218       * Same assertion as {@link #isAfter(Date)} but given Date is represented as String either with ISO date format
219       * (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
220       * @param dateAsString the given Date represented as String in default or custom date format.
221       * @return this assertion object.
222       * @throws AssertionError if the actual {@code Date} is {@code null}.
223       * @throws NullPointerException if given date as String is {@code null}.
224       * @throws AssertionError if the actual {@code Date} is not strictly after the given Date represented as String.
225       * @throws AssertionError if the given date as String could not be converted to a Date.
226       */
227      public DateAssert isAfter(String dateAsString) {
228        return isAfter(parse(dateAsString));
229      }
230    
231      /**
232       * Verifies that the actual {@code Date} is after or equals to the given one.
233       * @param other the given Date.
234       * @return this assertion object.
235       * @throws AssertionError if the actual {@code Date} is {@code null}.
236       * @throws NullPointerException if other {@code Date} is {@code null}.
237       * @throws AssertionError if the actual {@code Date} is not after or equals to the given one.
238       */
239      public DateAssert isAfterOrEqualsTo(Date other) {
240        dates.assertIsAfterOrEqualsTo(info, actual, other);
241        return this;
242      }
243    
244      /**
245       * Same assertion as {@link #isAfterOrEqualsTo(Date)} but given Date is represented as String either with ISO date
246       * format (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
247       * @param dateAsString the given Date represented as String in default or custom date format.
248       * @return this assertion object.
249       * @throws AssertionError if the actual {@code Date} is {@code null}.
250       * @throws NullPointerException if given date as String is {@code null}.
251       * @throws AssertionError if the actual {@code Date} is not after or equals to the given Date represented as String.
252       * @throws AssertionError if the given date as String could not be converted to a Date.
253       */
254      public DateAssert isAfterOrEqualsTo(String dateAsString) {
255        return isAfterOrEqualsTo(parse(dateAsString));
256      }
257    
258      /**
259       * Verifies that the actual {@code Date} is in [start, end[ period (start included, end excluded).
260       * @param start the period start (inclusive), expected not to be null.
261       * @param end the period end (exclusive), expected not to be null.
262       * @return this assertion object.
263       * @throws AssertionError if the actual {@code Date} is {@code null}.
264       * @throws NullPointerException if start {@code Date} is {@code null}.
265       * @throws NullPointerException if end {@code Date} is {@code null}.
266       * @throws AssertionError if the actual {@code Date} is not in [start, end[ period.
267       */
268      public DateAssert isBetween(Date start, Date end) {
269        return isBetween(start, end, true, false);
270      }
271    
272      /**
273       * Same assertion as {@link #isBetween(Date, Date)} but given Dates are represented as String either with ISO date
274       * format (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
275       * @param start the period start (inclusive), expected not to be null.
276       * @param end the period end (exclusive), expected not to be null.
277       * @return this assertion object.
278       * @throws AssertionError if the actual {@code Date} is {@code null}.
279       * @throws NullPointerException if start Date as String is {@code null}.
280       * @throws NullPointerException if end Date as String is {@code null}.
281       * @throws AssertionError if the actual {@code Date} is not in [start, end[ period.
282       * @throws AssertionError if one of the given date as String could not be converted to a Date.
283       */
284      public DateAssert isBetween(String start, String end) {
285        return isBetween(parse(start), parse(end));
286      }
287    
288      /**
289       * Verifies that the actual {@code Date} is in the given period defined by start and end dates.<br>
290       * To include start in the period set inclusiveStart parameter to <code>true</code>.<br>
291       * To include end in the period set inclusiveEnd parameter to <code>true</code>.<br>
292       * @param start the period start, expected not to be null.
293       * @param end the period end, expected not to be null.
294       * @param inclusiveStart wether to include start date in period.
295       * @param inclusiveEnd wether to include end date in period.
296       * @return this assertion object.
297       * @throws AssertionError if {@code actual} is {@code null}.
298       * @throws NullPointerException if start {@code Date} is {@code null}.
299       * @throws NullPointerException if end {@code Date} is {@code null}.
300       * @throws AssertionError if the actual {@code Date} is not in (start, end) period.
301       */
302      public DateAssert isBetween(Date start, Date end, boolean inclusiveStart, boolean inclusiveEnd) {
303        dates.assertIsBetween(info, actual, start, end, inclusiveStart, inclusiveEnd);
304        return this;
305      }
306    
307      /**
308       * Same assertion as {@link #isBetween(Date, Date, boolean, boolean)} but given Dates are represented as String either
309       * with ISO date format (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
310       * @param start the period start, expected not to be null.
311       * @param end the period end, expected not to be null.
312       * @param inclusiveStart wether to include start date in period.
313       * @param inclusiveEnd wether to include end date in period.
314       * @return this assertion object.
315       * @throws AssertionError if {@code actual} is {@code null}.
316       * @throws NullPointerException if start Date as String is {@code null}.
317       * @throws NullPointerException if end Date as String is {@code null}.
318       * @throws AssertionError if the actual {@code Date} is not in (start, end) period.
319       * @throws AssertionError if one of the given date as String could not be converted to a Date.
320       */
321      public DateAssert isBetween(String start, String end, boolean inclusiveStart, boolean inclusiveEnd) {
322        dates.assertIsBetween(info, actual, parse(start), parse(end), inclusiveStart, inclusiveEnd);
323        return this;
324      }
325    
326      /**
327       * Verifies that the actual {@code Date} is not in the given period defined by start and end dates.<br>
328       * To include start in the period set inclusiveStart parameter to <code>true</code>.<br>
329       * To include end in the period set inclusiveEnd parameter to <code>true</code>.<br>
330       * @param start the period start (inclusive), expected not to be null.
331       * @param end the period end (exclusive), expected not to be null.
332       * @param inclusiveStart wether to include start date in period.
333       * @param inclusiveEnd wether to include end date in period.
334       * @return this assertion object.
335       * @throws AssertionError if {@code actual} is {@code null}.
336       * @throws NullPointerException if start {@code Date} is {@code null}.
337       * @throws NullPointerException if end {@code Date} is {@code null}.
338       * @throws AssertionError if the actual {@code Date} is not in (start, end) period.
339       */
340      public DateAssert isNotBetween(Date start, Date end, boolean inclusiveStart, boolean inclusiveEnd) {
341        dates.assertIsNotBetween(info, actual, start, end, inclusiveStart, inclusiveEnd);
342        return this;
343      }
344    
345      /**
346       * Same assertion as {@link #isNotBetween(Date, Date, boolean, boolean)} but given Dates are represented as String
347       * either with ISO date format (yyyy-MM-dd) or user custom date format (set with method
348       * {@link #withDateFormat(DateFormat)}).
349       * @param start the period start (inclusive), expected not to be null.
350       * @param end the period end (exclusive), expected not to be null.
351       * @param inclusiveStart wether to include start date in period.
352       * @param inclusiveEnd wether to include end date in period.
353       * @return this assertion object.
354       * @throws AssertionError if {@code actual} is {@code null}.
355       * @throws NullPointerException if start Date as String is {@code null}.
356       * @throws NullPointerException if end Date as String is {@code null}.
357       * @throws AssertionError if the actual {@code Date} is not in (start, end) period.
358       * @throws AssertionError if one of the given date as String could not be converted to a Date.
359       */
360      public DateAssert isNotBetween(String start, String end, boolean inclusiveStart, boolean inclusiveEnd) {
361        return isNotBetween(parse(start), parse(end), inclusiveStart, inclusiveEnd);
362      }
363    
364      /**
365       * Verifies that the actual {@code Date} is not in [start, end[ period
366       * @param start the period start (inclusive), expected not to be null.
367       * @param end the period end (exclusive), expected not to be null.
368       * @return this assertion object.
369       * @throws AssertionError if the actual {@code Date} is {@code null}.
370       * @throws NullPointerException if start {@code Date} is {@code null}.
371       * @throws NullPointerException if end {@code Date} is {@code null}.
372       * @throws AssertionError if the actual {@code Date} is in [start, end[ period.
373       * @throws AssertionError if one of the given date as String could not be converted to a Date.
374       */
375      public DateAssert isNotBetween(Date start, Date end) {
376        return isNotBetween(start, end, true, false);
377      }
378    
379      /**
380       * Same assertion as {@link #isNotBetween(Date, Date)} but given Dates are represented as String either with ISO date
381       * format (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
382       * @param start the period start (inclusive), expected not to be null.
383       * @param end the period end (exclusive), expected not to be null.
384       * @return this assertion object.
385       * @throws AssertionError if the actual {@code Date} is {@code null}.
386       * @throws NullPointerException if start Date as String is {@code null}.
387       * @throws NullPointerException if end Date as String is {@code null}.
388       * @throws AssertionError if the actual {@code Date} is in [start, end[ period.
389       * @throws AssertionError if one of the given date as String could not be converted to a Date.
390       */
391      public DateAssert isNotBetween(String start, String end) {
392        return isNotBetween(parse(start), parse(end), true, false);
393      }
394    
395      /**
396       * Verifies that the actual {@code Date} is strictly in the past.
397       * @return this assertion object.
398       * @throws AssertionError if the actual {@code Date} is {@code null}.
399       * @throws AssertionError if the actual {@code Date} is not in the past.
400       */
401      public DateAssert isInThePast() {
402        dates.assertIsInThePast(info, actual);
403        return this;
404      }
405    
406      /**
407       * Verifies that the actual {@code Date} is today, that is matching current year, month and day (no check on hour,
408       * minute, second, milliseconds).
409       * @return this assertion object.
410       * @throws AssertionError if the actual {@code Date} is {@code null}.
411       * @throws AssertionError if the actual {@code Date} is not today.
412       */
413      public DateAssert isToday() {
414        dates.assertIsToday(info, actual);
415        return this;
416      }
417    
418      /**
419       * Verifies that the actual {@code Date} is strictly in the future.
420       * @return this assertion object.
421       * @throws AssertionError if the actual {@code Date} is {@code null}.
422       * @throws AssertionError if the actual {@code Date} is not in the future.
423       */
424      public DateAssert isInTheFuture() {
425        dates.assertIsInTheFuture(info, actual);
426        return this;
427      }
428    
429      /**
430       * Verifies that the actual {@code Date} is <b>strictly</b> before the given year.
431       * @param year the year to compare actual year to
432       * @return this assertion object.
433       * @throws AssertionError if the actual {@code Date} is {@code null}.
434       * @throws AssertionError if the actual {@code Date} year is after or equals to the given year.
435       */
436      public DateAssert isBeforeYear(int year) {
437        dates.assertIsBeforeYear(info, actual, year);
438        return this;
439      }
440    
441      /**
442       * Verifies that the actual {@code Date} is <b>strictly</b> after the given year.
443       * @param year the year to compare actual year to
444       * @return this assertion object.
445       * @throws AssertionError if the actual {@code Date} is {@code null}.
446       * @throws AssertionError if the actual {@code Date} year is before or equals to the given year.
447       */
448      public DateAssert isAfterYear(int year) {
449        dates.assertIsAfterYear(info, actual, year);
450        return this;
451      }
452    
453      /**
454       * Verifies that the actual {@code Date} year is equal to the given year.
455       * <p>
456       * Note that using a custom comparator has no effect on this assertion (see {@link #usingComparator(Comparator)}.
457       *  
458       * @param year the year to compare actual year to
459       * @return this assertion object.
460       * @throws AssertionError if the actual {@code Date} is {@code null}.
461       * @throws AssertionError if the actual {@code Date} year is not equal to the given year.
462       */
463      public DateAssert isWithinYear(int year) {
464        dates.assertIsWithinYear(info, actual, year);
465        return this;
466      }
467    
468      /**
469       * Verifies that the actual {@code Date} month is equal to the given month, <b>month value starting at 1</b>
470       * (January=1, February=2, ...).
471       * <p>
472       * Note that using a custom comparator has no effect on this assertion (see {@link #usingComparator(Comparator)}.
473       *  
474       * @param month the month to compare actual month to, <b>month value starting at 1</b> (January=1, February=2, ...).
475       * @return this assertion object.
476       * @throws AssertionError if the actual {@code Date} is {@code null}.
477       * @throws AssertionError if the actual {@code Date} month is not equal to the given month.
478       */
479      public DateAssert isWithinMonth(int month) {
480        dates.assertIsWithinMonth(info, actual, month);
481        return this;
482      }
483    
484      /**
485       * Verifies that the actual {@code Date} day of month is equal to the given day of month.
486       * <p>
487       * Note that using a custom comparator has no effect on this assertion (see {@link #usingComparator(Comparator)}.
488       *  
489       * @param dayOfMonth the day of month to compare actual day of month to
490       * @return this assertion object.
491       * @throws AssertionError if the actual {@code Date} is {@code null}.
492       * @throws AssertionError if the actual {@code Date} month is not equal to the given day of month.
493       */
494      public DateAssert isWithinDayOfMonth(int dayOfMonth) {
495        dates.assertIsWithinDayOfMonth(info, actual, dayOfMonth);
496        return this;
497      }
498    
499      /**
500       * Verifies that the actual {@code Date} day of week is equal to the given day of week (see
501       * {@link Calendar#DAY_OF_WEEK} for valid values).
502       * <p>
503       * Note that using a custom comparator has no effect on this assertion (see {@link #usingComparator(Comparator)}.
504       *  
505       * @param dayOfWeek the day of week to compare actual day of week to, see {@link Calendar#DAY_OF_WEEK} for valid
506       *          values
507       * @return this assertion object.
508       * @throws AssertionError if the actual {@code Date} is {@code null}.
509       * @throws AssertionError if the actual {@code Date} week is not equal to the given day of week.
510       */
511      public DateAssert isWithinDayOfWeek(int dayOfWeek) {
512        dates.assertIsWithinDayOfWeek(info, actual, dayOfWeek);
513        return this;
514      }
515    
516      /**
517       * Verifies that the actual {@code Date} hour od day is equal to the given hour of day (24-hour clock).
518       * <p>
519       * Note that using a custom comparator has no effect on this assertion (see {@link #usingComparator(Comparator)}.
520       *  
521       * @param hourOfDay the hour of day to compare actual hour of day to (24-hour clock)
522       * @return this assertion object.
523       * @throws AssertionError if the actual {@code Date} is {@code null}.
524       * @throws AssertionError if the actual {@code Date} hour is not equal to the given hour.
525       */
526      public DateAssert isWithinHourOfDay(int hourOfDay) {
527        dates.assertIsWithinHourOfDay(info, actual, hourOfDay);
528        return this;
529      }
530    
531      /**
532       * Verifies that the actual {@code Date} minute is equal to the given minute.
533       * <p>
534       * Note that using a custom comparator has no effect on this assertion (see {@link #usingComparator(Comparator)}.
535       *  
536       * @param minute the minute to compare actual minute to
537       * @return this assertion object.
538       * @throws AssertionError if the actual {@code Date} is {@code null}.
539       * @throws AssertionError if the actual {@code Date} minute is not equal to the given minute.
540       */
541      public DateAssert isWithinMinute(int minute) {
542        dates.assertIsWithinMinute(info, actual, minute);
543        return this;
544      }
545    
546      /**
547       * Verifies that the actual {@code Date} second is equal to the given second.
548       * <p>
549       * Note that using a custom comparator has no effect on this assertion (see {@link #usingComparator(Comparator)}.
550       *  
551       * @param second the second to compare actual second to
552       * @return this assertion object.
553       * @throws AssertionError if the actual {@code Date} is {@code null}.
554       * @throws AssertionError if the actual {@code Date} second is not equal to the given second.
555       */
556      public DateAssert isWithinSecond(int second) {
557        dates.assertIsWithinSecond(info, actual, second);
558        return this;
559      }
560    
561      /**
562       * Verifies that the actual {@code Date} millisecond is equal to the given millisecond.
563       * <p>
564       * Note that using a custom comparator has no effect on this assertion (see {@link #usingComparator(Comparator)}.
565       *  
566       * @param millisecond the millisecond to compare actual millisecond to
567       * @return this assertion object.
568       * @throws AssertionError if the actual {@code Date} is {@code null}.
569       * @throws AssertionError if the actual {@code Date} millisecond is not equal to the given millisecond.
570       */
571      public DateAssert isWithinMillisecond(int millisecond) {
572        dates.assertIsWithinMillisecond(info, actual, millisecond);
573        return this;
574      }
575    
576      /**
577       * Verifies that actual and given {@code Date} are in the same year.
578       * <p>
579       * Note that using a custom comparator has no effect on this assertion (see {@link #usingComparator(Comparator)}.
580       *  
581       * @param other the given {@code Date} to compare actual {@code Date} to.
582       * @return this assertion object.
583       * @throws NullPointerException if {@code Date} parameter is {@code null}.
584       * @throws AssertionError if the actual {@code Date} is {@code null}.
585       * @throws AssertionError if actual and given {@code Date} are not in the same year.
586       */
587      public DateAssert isInSameYearAs(Date other) {
588        dates.assertIsInSameYearAs(info, actual, other);
589        return this;
590      }
591    
592      /**
593       * Same assertion as {@link #isInSameYearAs(Date)} but given Date is represented as String either with ISO date format
594       * (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
595       * @param dateAsString the given Date represented as String in default or custom date format.
596       * @return this assertion object.
597       * @throws NullPointerException if dateAsString parameter is {@code null}.
598       * @throws AssertionError if the actual {@code Date} is {@code null}.
599       * @throws AssertionError if actual and given Date represented as String are not in the same year.
600       * @throws AssertionError if the given date as String could not be converted to a Date.
601       */
602      public DateAssert isInSameYearAs(String dateAsString) {
603        return isInSameYearAs(parse(dateAsString));
604      }
605    
606      /**
607       * Verifies that actual and given {@code Date} are chronologically in the same month (and thus in the same year).
608       * <p>
609       * If you want to compare month only (without year), use :
610       * <code>assertThat(myDate).isWithinMonth(monthOf(otherDate))</code><br>
611       * See {@link org.fest.util.Dates#monthOf(Date)} to get the month of a given Date.
612       * <p>
613       * Note that using a custom comparator has no effect on this assertion (see {@link #usingComparator(Comparator)}.
614       *  
615       * @param other the given {@code Date} to compare actual {@code Date} to.
616       * @return this assertion object.
617       * @throws NullPointerException if {@code Date} parameter is {@code null}.
618       * @throws AssertionError if the actual {@code Date} is {@code null}.
619       * @throws AssertionError if actual and given {@code Date} are not in the same month.
620       */
621      public DateAssert isInSameMonthAs(Date other) {
622        dates.assertIsInSameMonthAs(info, actual, other);
623        return this;
624      }
625    
626      /**
627       * Same assertion as {@link #isInSameMonthAs(Date)} but given Date is represented as String either with ISO date
628       * format (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
629       * @param dateAsString the given Date represented as String in default or custom date format.
630       * @return this assertion object.
631       * @throws NullPointerException if dateAsString parameter is {@code null}.
632       * @throws AssertionError if the actual {@code Date} is {@code null}.
633       * @throws AssertionError if actual and given {@code Date} are not in the same month.
634       */
635      public DateAssert isInSameMonthAs(String dateAsString) {
636        return isInSameMonthAs(parse(dateAsString));
637      }
638    
639      /**
640       * Verifies that actual and given {@code Date} are chronologically in the same day of month (and thus in the same
641       * month and year).
642       * <p>
643       * If you want to compare day of month only (without month and year), you could write :
644       * <code>assertThat(myDate).isWithinDayOfMonth(dayOfMonthOf(otherDate))</code><br>
645       * see {@link org.fest.util.Dates#dayOfMonthOf(Date)} to get the day of month of a given Date.
646       * <p>
647       * Note that using a custom comparator has no effect on this assertion (see {@link #usingComparator(Comparator)}. 
648       * 
649       * @param other the given {@code Date} to compare actual {@code Date} to.
650       * @return this assertion object.
651       * @throws NullPointerException if {@code Date} parameter is {@code null}.
652       * @throws AssertionError if the actual {@code Date} is {@code null}.
653       * @throws AssertionError if actual and given {@code Date} are not in the same day of month.
654       */
655      public DateAssert isInSameDayAs(Date other) {
656        dates.assertIsInSameDayAs(info, actual, other);
657        return this;
658      }
659    
660      /**
661       * Same assertion as {@link #isInSameDayAs(Date)} but given Date is represented as String either with ISO date format
662       * (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
663       * @param dateAsString the given Date represented as String in default or custom date format.
664       * @return this assertion object.
665       * @throws NullPointerException if dateAsString parameter is {@code null}.
666       * @throws AssertionError if the actual {@code Date} is {@code null}.
667       * @throws AssertionError if actual and given {@code Date} are not in the same day of month.
668       */
669      public DateAssert isInSameDayAs(String dateAsString) {
670        return isInSameDayAs(parse(dateAsString));
671      }
672    
673      /**
674       * Verifies that actual and given {@code Date} are chronologically in the same hour (and thus in the same day, month
675       * and year).
676       * <p>
677       * If you want to compare hour only (without day, month and year), you could write :
678       * <code>assertThat(myDate).isWithinHour(hourOfDayOf(otherDate))</code><br>
679       * see {@link org.fest.util.Dates#hourOfDay(Date)} to get the hour of a given Date.
680       * <p>
681       * Note that using a custom comparator has no effect on this assertion (see {@link #usingComparator(Comparator)}.
682       *  
683       * @param other the given {@code Date} to compare actual {@code Date} to.
684       * @return this assertion object.
685       * @throws NullPointerException if {@code Date} parameter is {@code null}.
686       * @throws AssertionError if the actual {@code Date} is {@code null}.
687       * @throws AssertionError if actual and given {@code Date} are not in the same hour.
688       */
689      public DateAssert isInSameHourAs(Date other) {
690        dates.assertIsInSameHourAs(info, actual, other);
691        return this;
692      }
693    
694      /**
695       * Same assertion as {@link #isInSameHourAs(Date)} but given Date is represented as String either with ISO date format
696       * (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
697       * @param dateAsString the given Date represented as String in default or custom date format.
698       * @return this assertion object.
699       * @throws NullPointerException if dateAsString parameter is {@code null}.
700       * @throws AssertionError if the actual {@code Date} is {@code null}.
701       * @throws AssertionError if actual and given {@code Date} are not in the same hour.
702       */
703      public DateAssert isInSameHourAs(String dateAsString) {
704        return isInSameHourAs(parse(dateAsString));
705      }
706    
707      /**
708       * Verifies that actual and given {@code Date} are chronologically in the same minute (and thus in the same hour, day,
709       * month and year).
710       * <p>
711       * If you want to compare minute only (without hour, day, month and year), you could write :
712       * <code>assertThat(myDate).isWithinMinute(minuteOf(otherDate))</code><br>
713       * see {@link org.fest.util.Dates#minuteOf(Date)} to get the minute of a given Date.
714       * <p>
715       * Note that using a custom comparator has no effect on this assertion (see {@link #usingComparator(Comparator)}.
716       *  
717       * @param other the given {@code Date} to compare actual {@code Date} to.
718       * @return this assertion object.
719       * @throws NullPointerException if {@code Date} parameter is {@code null}.
720       * @throws AssertionError if the actual {@code Date} is {@code null}.
721       * @throws AssertionError if actual and given {@code Date} are not in the same minute.
722       */
723      public DateAssert isInSameMinuteAs(Date other) {
724        dates.assertIsInSameMinuteAs(info, actual, other);
725        return this;
726      }
727    
728      /**
729       * Same assertion as {@link #isInSameMinuteAs(Date)} but given Date is represented as String either with ISO date
730       * format (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
731       * @param dateAsString the given Date represented as String in default or custom date format.
732       * @return this assertion object.
733       * @throws NullPointerException if dateAsString parameter is {@code null}.
734       * @throws AssertionError if the actual {@code Date} is {@code null}.
735       * @throws AssertionError if actual and given {@code Date} are not in the same minute.
736       */
737      public DateAssert isInSameMinuteAs(String dateAsString) {
738        return isInSameMinuteAs(parse(dateAsString));
739      }
740    
741      /**
742       * Verifies that actual and given {@code Date} are chronologically in the same second (and thus in the same minute,
743       * hour, day, month and year).
744       * <p>
745       * If you want to compare second only (without minute, hour, day, month and year), you could write :
746       * <code>assertThat(myDate).isWithinSecond(secondOf(otherDate))</code><br>
747       * see {@link org.fest.util.Dates#secondOf(Date)} to get the second of a given Date.
748       * <p>
749       * Note that using a custom comparator has no effect on this assertion (see {@link #usingComparator(Comparator)}.
750       *  
751       * @param other the given {@code Date} to compare actual {@code Date} to.
752       * @return this assertion object.
753       * @throws NullPointerException if {@code Date} parameter is {@code null}.
754       * @throws AssertionError if the actual {@code Date} is {@code null}.
755       * @throws AssertionError if actual and given {@code Date} are not in the same second.
756       */
757      public DateAssert isInSameSecondAs(Date other) {
758        dates.assertIsInSameSecondAs(info, actual, other);
759        return this;
760      }
761    
762      /**
763       * Same assertion as {@link #isInSameSecondAs(Date)} but given Date is represented as String either with ISO date
764       * format (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
765       * @param dateAsString the given Date represented as String in default or custom date format.
766       * @return this assertion object.
767       * @throws NullPointerException if dateAsString parameter is {@code null}.
768       * @throws AssertionError if the actual {@code Date} is {@code null}.
769       * @throws AssertionError if actual and given {@code Date} are not in the same second.
770       */
771      public DateAssert isInSameSecondAs(String dateAsString) {
772        return isInSameSecondAs(parse(dateAsString));
773      }
774    
775      /**
776       * Verifies that the actual {@code Date} is close to the other date by less than delta (expressed in milliseconds), if
777       * difference is equals to delta it's ok.
778       * <p>
779       * One can use handy {@link TimeUnit} to convert a duration in milliseconds, for example you can express a delta of 5 seconds with
780       * <code>TimeUnit.SECONDS.toMillis(5)</code>.
781       * <p>
782       * Note that using a custom comparator has no effect on this assertion (see {@link #usingComparator(Comparator)}. 
783       * @param other the date to compare actual to
784       * @param deltaInMilliseconds the delta used for date comparison, expressed in milliseconds
785       * @return this assertion object.
786       * @throws NullPointerException if {@code Date} parameter is {@code null}.
787       * @throws AssertionError if the actual {@code Date} is {@code null}.
788       * @throws AssertionError if the actual {@code Date} week is not close to the given date by less than delta.
789       */
790      public DateAssert isCloseTo(Date other, long deltaInMilliseconds) {
791        dates.assertIsCloseTo(info, actual, other, deltaInMilliseconds);
792        return this;
793      }
794    
795      /**
796       * Same assertion as {@link #isCloseTo(Date, long)} but given Date is represented as String either with ISO date
797       * format (yyyy-MM-dd) or user custom date format (set with method {@link #withDateFormat(DateFormat)}).
798       * @param dateAsString the given Date represented as String in default or custom date format.
799       * @param deltaInMilliseconds the delta used for date comparison, expressed in milliseconds
800       * @return this assertion object.
801       * @throws NullPointerException if dateAsString parameter is {@code null}.
802       * @throws AssertionError if the actual {@code Date} is {@code null}.
803       * @throws AssertionError if the actual {@code Date} week is not close to the given date by less than delta.
804       */
805      public DateAssert isCloseTo(String dateAsString, long deltaInMilliseconds) {
806        return isCloseTo(parse(dateAsString), deltaInMilliseconds);
807      }
808    
809      /**
810       * For String based Date assertions like {@link #isBefore(String)}, given String is expected to follow the default
811       * Date format, that is ISO 8601 format : "yyyy-MM-dd".
812       * <p>
813       * With this method, user can specify its own date format, replacing the current date format for all future Date
814       * assertions in the test suite (i.e. not only the current assertions) since custom DateFormat is stored in a static
815       * field.
816       * <p>
817       * To revert to default format simply call {@link #withIsoDateFormat()}.
818       * 
819       * @param userCustomDateFormat the new Date format used for String based Date assertions.
820       * @return this assertion object.
821       */
822      public DateAssert withDateFormat(DateFormat userCustomDateFormat) {
823        useDateFormat(userCustomDateFormat);
824        return this;
825      }
826    
827      /**
828       * For String based Date assertions like {@link #isBefore(String)}, given String is expected to follow the default
829       * Date format, that is ISO 8601 format : "yyyy-MM-dd".
830       * <p>
831       * With this method, user can specify its own date format, replacing the current date format for all future Date
832       * assertions in the test suite (i.e. not only the current assertions) since custom DateFormat is stored in a static
833       * field.
834       * <p>
835       * To revert to default format simply call {@link #useIsoDateFormat()} (static method) or {@link #withIsoDateFormat()}.
836       * 
837       * @param userCustomDateFormat the new Date format used for String based Date assertions.
838       */
839      public static void useDateFormat(DateFormat userCustomDateFormat) {
840        if (userCustomDateFormat == null) throw new NullPointerException("The given date format should not be null");
841        dateFormat = userCustomDateFormat;
842      }
843    
844      /**
845       * Use ISO 8601 date format ("yyyy-MM-dd") for String based Date assertions.
846       * @return this assertion object.
847       */
848      public DateAssert withIsoDateFormat() {
849        useIsoDateFormat();
850        return this;
851      }
852    
853      /**
854       * Use ISO 8601 date format ("yyyy-MM-dd") for String based Date assertions.
855       */
856      public static void useIsoDateFormat() {
857        dateFormat = ISO_DATE_FORMAT;
858      }
859    
860      /**
861       * Utillity method to parse a Date with {@link #dateFormat}, note that it is thread safe.<br>
862       * Returns <code>null</code> if dateAsString parameter is <code>null</code>.
863       * @param dateAsString the string to parse as a Date with {@link #dateFormat}
864       * @return the corrresponding Date, null if dateAsString parameter is null.
865       * @throws AssertionError if the string can't be parsed as a Date
866       */
867      private static Date parse(String dateAsString) {
868        if (dateAsString == null) { return null; }
869        try {
870          // synchronized is used because SimpleDateFormat which is not thread safe (sigh).
871          synchronized (dateFormat) {
872            return dateFormat.parse(dateAsString);
873          }
874        } catch (ParseException e) {
875          throw Failures.instance().failure("Failed to parse " + dateAsString + " with date format " + dateFormat);
876        }
877      }
878    
879      @Override
880      public DateAssert usingComparator(Comparator<?> customComparator) {
881        super.usingComparator(customComparator);
882        this.dates = new Dates(new ComparatorBasedComparisonStrategy(customComparator));
883        return myself;
884      }
885    
886      @Override
887      public DateAssert usingDefaultComparator() {
888        super.usingDefaultComparator();
889        this.dates = Dates.instance();
890        return myself;
891      }
892    }