diff --git a/pom.xml b/pom.xml index 7623f985..d8810398 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,10 @@ + + Jon Chambers + https://github.com/jchambers + Carlo Dapor https://github.com/catull diff --git a/src/main/java/org/threeten/extra/DayTime.java b/src/main/java/org/threeten/extra/DayTime.java new file mode 100644 index 00000000..7e7ead97 --- /dev/null +++ b/src/main/java/org/threeten/extra/DayTime.java @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of JSR-310 nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.threeten.extra; + +import java.io.Serializable; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.temporal.*; +import java.util.Objects; + +/** + * A day-of-week and time without a time-zone in the ISO-8601 calendar system + * such as {@code Monday at 13:45}. + *

+ * {@code DayTime} is an immutable date-time object that represents the combination + * of a day-of-week and time-of-day. + * Any field that can be derived from those two fields can be obtained. + *

+ * This class does not store or represent a date or time-zone. + *

+ * The ISO-8601 calendar system is the modern civil calendar system used today + * in most of the world. It is equivalent to the proleptic Gregorian calendar + * system, in which today's rules for leap years are applied for all time. + * For most applications written today, the ISO-8601 rules are entirely suitable. + * However, any application that makes use of historical dates, and requires them + * to be accurate will find the ISO-8601 approach unsuitable. + *

+ * ISO-8601 defines the week as always starting with Monday. + * + *

Implementation Requirements:

+ * This class is immutable and thread-safe. + *

+ * This class must be treated as a value type. Do not synchronize, rely on the + * identity hash code or use the distinction between equals() and ==. + */ +public final class DayTime + implements Temporal, TemporalAdjuster, Comparable, Serializable { + + /** + * Serialization version. + */ + private static final long serialVersionUID = -7762817588559947796L; + + private final DayOfWeek dayOfWeek; + private final LocalTime localTime; + + private static final long NANOS_PER_MICROSECOND = 1_000; + private static final long NANOS_PER_MILLISECOND = 1_000_000; + private static final long NANOS_PER_SECOND = 1_000_000_000; + private static final long NANOS_PER_MINUTE = NANOS_PER_SECOND * 60; + private static final long NANOS_PER_HOUR = NANOS_PER_MINUTE * 60; + private static final long NANOS_PER_DAY = NANOS_PER_HOUR * 24; + private static final long NANOS_PER_WEEK = NANOS_PER_DAY * 7; + + private static final long MICROS_PER_DAY = NANOS_PER_DAY / 1000; + private static final long MILLIS_PER_DAY = MICROS_PER_DAY / 1000; + private static final long SECONDS_PER_DAY = MILLIS_PER_DAY / 1000; + private static final long MINUTES_PER_DAY = SECONDS_PER_DAY / 60; + + private DayTime(final DayOfWeek dayOfWeek, final LocalTime localTime) { + this.dayOfWeek = Objects.requireNonNull(dayOfWeek, "Day of week must not be null."); + this.localTime = Objects.requireNonNull(localTime, "Time of day must not be null."); + } + + /** + * Obtains a {@code DayTime} with the given day of the week and local time. + * + * @param dayOfWeek the day of the week to represent; must not be {@code null} + * @param localTime the local time of day to represent; must not be {@code null} + * + * @return a day-time with the given day of the week and local time + */ + public static DayTime of(final DayOfWeek dayOfWeek, final LocalTime localTime) { + return new DayTime(dayOfWeek, localTime); + } + + public static DayTime from(final TemporalAccessor temporalAccessor) { + Objects.requireNonNull(temporalAccessor, "Temporal accessor must not be null."); + + final DayOfWeek dayOfWeek = DayOfWeek.from(temporalAccessor); + final LocalTime localTime = LocalTime.from(temporalAccessor); + + return new DayTime(dayOfWeek, localTime); + } + + /** + * Gets the day-of-week field using the {@link DayOfWeek} enum. + * + * @return the day of the week + */ + public DayOfWeek getDayOfWeek() { + return dayOfWeek; + } + + /** + * Gets the {@code LocalTime} part of this day-time. + * + * @return the time part of this day-time, not null + */ + public LocalTime toLocalTime() { + return localTime; + } + + /** + * Checks if the specified field is supported. Any {@link ChronoField} supported by a {@link DayOfWeek} or + * {@link LocalTime} is supported. + * + * @param field the field to check + * + * @return {@code true} if the field is supported or {@code false} otherwise + */ + @Override + public boolean isSupported(final TemporalField field) { + if (field instanceof ChronoField) { + final ChronoField chronoField = (ChronoField) field; + return chronoField == ChronoField.DAY_OF_WEEK || chronoField.isTimeBased(); + } + + return field != null && field.isSupportedBy(this); + } + + @Override + public boolean isSupported(final TemporalUnit unit) { + if (unit instanceof ChronoUnit) { + return unit.isTimeBased(); + } + + return unit != null && unit.isSupportedBy(this); + } + + @Override + public long getLong(final TemporalField field) { + if (field instanceof ChronoField) { + final ChronoField chronoField = (ChronoField) field; + + if (chronoField == ChronoField.DAY_OF_WEEK) { + return dayOfWeek.getValue(); + } else { + return localTime.getLong(field); + } + } else { + return field.getFrom(this); + } + } + + @Override + public Temporal adjustInto(final Temporal temporal) { + return localTime.adjustInto(dayOfWeek.adjustInto(temporal)); + } + + @Override + public DayTime with(final TemporalField field, final long newValue) { + if (field instanceof ChronoField) { + final ChronoField chronoField = (ChronoField) field; + + final DayOfWeek dayOfWeek; + final LocalTime localTime; + + if (field == ChronoField.DAY_OF_WEEK) { + dayOfWeek = DayOfWeek.of((int) newValue); + localTime = this.localTime; + } else { + dayOfWeek = this.dayOfWeek; + localTime = this.localTime.with(chronoField, newValue); + } + + return new DayTime(dayOfWeek, localTime); + } + + return field.adjustInto(this, newValue); + } + + @Override + public DayTime plus(final long amountToAdd, final TemporalUnit unit) { + final DayTime sum; + + if (unit instanceof ChronoUnit) { + final long daysToAdd; + final long nanosToAdd; + + switch ((ChronoUnit) unit) { + + case NANOS: { + daysToAdd = amountToAdd / NANOS_PER_DAY; + nanosToAdd = amountToAdd % NANOS_PER_DAY; + break; + } + + case MICROS: { + daysToAdd = amountToAdd / MICROS_PER_DAY; + nanosToAdd = (amountToAdd % MICROS_PER_DAY) * NANOS_PER_MICROSECOND; + break; + } + + case MILLIS: { + daysToAdd = amountToAdd / MILLIS_PER_DAY; + nanosToAdd = (amountToAdd % MILLIS_PER_DAY) * NANOS_PER_MILLISECOND; + break; + } + + case SECONDS: { + daysToAdd = amountToAdd / SECONDS_PER_DAY; + nanosToAdd = (amountToAdd % SECONDS_PER_DAY) * NANOS_PER_SECOND; + break; + } + + case MINUTES: { + daysToAdd = amountToAdd / MINUTES_PER_DAY; + nanosToAdd = (amountToAdd % MINUTES_PER_DAY) * NANOS_PER_MINUTE; + break; + } + + case HOURS: { + daysToAdd = amountToAdd / 24; + nanosToAdd = (amountToAdd % 24) * NANOS_PER_HOUR; + break; + } + + case HALF_DAYS: { + daysToAdd = amountToAdd / 2; + nanosToAdd = (amountToAdd % 2) * NANOS_PER_DAY / 2; + break; + } + + case DAYS: { + daysToAdd = amountToAdd; + nanosToAdd = 0; + break; + } + + default: { + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit); + } + } + + final LocalTime adjustedTime = localTime.plusNanos(nanosToAdd); + final long carryoverDay; + + // We may have wrapped around the day boundary and should "carry the one" over to the days to add + if (amountToAdd > 0 && adjustedTime.isBefore(localTime)) { + carryoverDay = 1; + } else if (amountToAdd < 0 && adjustedTime.isAfter(localTime)) { + carryoverDay = -1; + } else { + carryoverDay = 0; + } + + sum = new DayTime(dayOfWeek.plus(daysToAdd + carryoverDay), adjustedTime); + } else { + sum = unit.addTo(this, amountToAdd); + } + + return sum; + } + + @Override + public DayTime minus(final long amountToSubtract, final TemporalUnit unit) { + // Copied from Temporal to provide more convenient typing + return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit)); + } + + @Override + public long until(final Temporal endExclusive, final TemporalUnit unit) { + final DayTime endDayTime = DayTime.from(endExclusive); + final long timeUntil; + + if (unit instanceof ChronoUnit) { + final long nanosUntil = ((((endDayTime.dayOfWeek.getValue() - dayOfWeek.getValue()) * NANOS_PER_DAY) + + endDayTime.localTime.toNanoOfDay() - localTime.toNanoOfDay()) + NANOS_PER_WEEK) % NANOS_PER_WEEK; + + switch ((ChronoUnit) unit) { + case NANOS: { + timeUntil = nanosUntil; + break; + } + + case MICROS: { + timeUntil = nanosUntil / 1000; + break; + } + + case MILLIS: { + timeUntil = nanosUntil / 1_000_000; + break; + } + + case SECONDS: { + timeUntil = nanosUntil / NANOS_PER_SECOND; + break; + } + + case MINUTES: { + timeUntil = nanosUntil / NANOS_PER_MINUTE; + break; + } + + case HOURS: { + timeUntil = nanosUntil / NANOS_PER_HOUR; + break; + } + + case HALF_DAYS: { + timeUntil = nanosUntil / (12 * NANOS_PER_HOUR); + break; + } + + case DAYS: { + timeUntil = nanosUntil / NANOS_PER_DAY; + break; + } + + default: { + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit); + } + } + } else { + timeUntil = unit.between(this, endDayTime); + } + + return timeUntil; + } + + @Override + public int compareTo(final DayTime other) { + final int dayOfWeekComparison = dayOfWeek.compareTo(other.dayOfWeek); + return dayOfWeekComparison == 0 ? localTime.compareTo(other.localTime) : dayOfWeekComparison; + } + + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } + + if (other == null || getClass() != other.getClass()) { + return false; + } + + final DayTime that = (DayTime) other; + return dayOfWeek == that.dayOfWeek && Objects.equals(localTime, that.localTime); + } + + @Override + public int hashCode() { + return Objects.hash(dayOfWeek, localTime); + } + + @Override + public String toString() { + return dayOfWeek + "@" + localTime; + } +} diff --git a/src/test/java/org/threeten/extra/DayTimeTest.java b/src/test/java/org/threeten/extra/DayTimeTest.java new file mode 100644 index 00000000..9cc97402 --- /dev/null +++ b/src/test/java/org/threeten/extra/DayTimeTest.java @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of JSR-310 nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.threeten.extra; + +import org.junit.Before; +import org.junit.Test; + +import java.time.*; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.UnsupportedTemporalTypeException; + +import static org.junit.Assert.*; + +public class DayTimeTest { + + private static final DayOfWeek DAY_OF_WEEK = DayOfWeek.WEDNESDAY; + + private static final int HOUR = 13; + private static final int MINUTE = 10; + private static final int SECOND = 22; + private static final LocalTime LOCAL_TIME = LocalTime.of(HOUR, MINUTE, SECOND); + + private DayTime dayTime; + + @Before + public void setUp() { + dayTime = DayTime.of(DAY_OF_WEEK, LOCAL_TIME); + } + + @Test + public void testGetDayOfWeek() { + assertEquals(DAY_OF_WEEK, dayTime.getDayOfWeek()); + } + + @Test + public void testFrom() { + final DayTime dayTime = DayTime.from(LocalDateTime.parse("2017-11-17T10:15:30")); + + assertEquals(DayOfWeek.FRIDAY, dayTime.getDayOfWeek()); + assertEquals(10, dayTime.toLocalTime().getHour()); + assertEquals(15, dayTime.toLocalTime().getMinute()); + assertEquals(30, dayTime.toLocalTime().getSecond()); + } + + @Test + public void testWith() { + assertEquals(DayTime.of(DAY_OF_WEEK.plus(1), LOCAL_TIME), + dayTime.with(ChronoField.DAY_OF_WEEK, DAY_OF_WEEK.plus(1).getValue())); + + assertEquals(DayTime.of(DAY_OF_WEEK, LocalTime.of(HOUR + 1, MINUTE, SECOND)), + dayTime.with(ChronoField.HOUR_OF_DAY, HOUR + 1)); + + assertEquals(DayTime.of(DAY_OF_WEEK, LocalTime.of(HOUR, MINUTE + 1, SECOND)), + dayTime.with(ChronoField.MINUTE_OF_HOUR, MINUTE + 1)); + + assertEquals(DayTime.of(DAY_OF_WEEK, LocalTime.of(HOUR, MINUTE, SECOND + 1)), + dayTime.with(ChronoField.SECOND_OF_MINUTE, SECOND + 1)); + } + + @Test + public void testPlusTemporalUnit() { + final DayTime earlyTuesdayMorning = DayTime.of(DayOfWeek.TUESDAY, LocalTime.of(1, 0)); + final DayTime lateMondayEvening = DayTime.of(DayOfWeek.MONDAY, LocalTime.of(23, 0)); + + assertEquals(DayOfWeek.WEDNESDAY, earlyTuesdayMorning.plus(1, ChronoUnit.DAYS).getDayOfWeek()); + assertEquals(1, earlyTuesdayMorning.plus(1, ChronoUnit.DAYS).toLocalTime().getHour()); + assertEquals(0, earlyTuesdayMorning.plus(1, ChronoUnit.DAYS).toLocalTime().getMinute()); + + assertEquals(DayOfWeek.TUESDAY, earlyTuesdayMorning.plus(77, ChronoUnit.MINUTES).getDayOfWeek()); + assertEquals(2, earlyTuesdayMorning.plus(77, ChronoUnit.MINUTES).toLocalTime().getHour()); + assertEquals(17, earlyTuesdayMorning.plus(77, ChronoUnit.MINUTES).toLocalTime().getMinute()); + + assertEquals(DayOfWeek.TUESDAY, lateMondayEvening.plus(97, ChronoUnit.MINUTES).getDayOfWeek()); + } + + @Test + public void testMinus() { + final DayTime earlyTuesdayMorning = DayTime.of(DayOfWeek.TUESDAY, LocalTime.of(1, 0)); + + assertEquals(DayOfWeek.MONDAY, earlyTuesdayMorning.minus(1, ChronoUnit.DAYS).getDayOfWeek()); + assertEquals(1, earlyTuesdayMorning.minus(1, ChronoUnit.DAYS).toLocalTime().getHour()); + assertEquals(0, earlyTuesdayMorning.minus(1, ChronoUnit.DAYS).toLocalTime().getMinute()); + + assertEquals(DayOfWeek.MONDAY, earlyTuesdayMorning.minus(77, ChronoUnit.MINUTES).getDayOfWeek()); + assertEquals(23, earlyTuesdayMorning.minus(77, ChronoUnit.MINUTES).toLocalTime().getHour()); + assertEquals(43, earlyTuesdayMorning.minus(77, ChronoUnit.MINUTES).toLocalTime().getMinute()); + } + + @Test(expected = UnsupportedTemporalTypeException.class) + public void testPlusUnsupportedUnit() { + dayTime.plus(1, ChronoUnit.DECADES); + } + + @Test + public void testUntil() { + final DayTime wednesdayAtNoon = DayTime.of(DayOfWeek.WEDNESDAY, LocalTime.NOON); + final DayTime fridayAtNoon = DayTime.of(DayOfWeek.FRIDAY, LocalTime.NOON); + final DayTime fridayMorning = DayTime.of(DayOfWeek.FRIDAY, LocalTime.of(9, 0)); + + final DayTime lateMondayNight = DayTime.of(DayOfWeek.MONDAY, LocalTime.of(23, 0)); + final DayTime earlyTuesdayMorning = DayTime.of(DayOfWeek.TUESDAY, LocalTime.of(1, 0)); + + assertEquals(0, wednesdayAtNoon.until(wednesdayAtNoon, ChronoUnit.DAYS)); + assertEquals(0, wednesdayAtNoon.until(wednesdayAtNoon, ChronoUnit.HOURS)); + + assertEquals(2, wednesdayAtNoon.until(fridayAtNoon, ChronoUnit.DAYS)); + assertEquals(1, wednesdayAtNoon.until(fridayMorning, ChronoUnit.DAYS)); + assertEquals(5, fridayAtNoon.until(wednesdayAtNoon, ChronoUnit.DAYS)); + assertEquals(5, fridayMorning.until(wednesdayAtNoon, ChronoUnit.DAYS)); + assertEquals(0, lateMondayNight.until(earlyTuesdayMorning, ChronoUnit.DAYS)); + assertEquals(6, earlyTuesdayMorning.until(lateMondayNight, ChronoUnit.DAYS)); + + assertEquals(48, wednesdayAtNoon.until(fridayAtNoon, ChronoUnit.HOURS)); + assertEquals(120, fridayAtNoon.until(wednesdayAtNoon, ChronoUnit.HOURS)); + assertEquals(3, fridayMorning.until(fridayAtNoon, ChronoUnit.HOURS)); + assertEquals(165, fridayAtNoon.until(fridayMorning, ChronoUnit.HOURS)); + assertEquals(2, lateMondayNight.until(earlyTuesdayMorning, ChronoUnit.HOURS)); + assertEquals(166, earlyTuesdayMorning.until(lateMondayNight, ChronoUnit.HOURS)); + } + + @Test(expected = UnsupportedTemporalTypeException.class) + public void testUntilUnsupportedUnit() { + dayTime.until(dayTime, ChronoUnit.DECADES); + } + + @Test(expected = UnsupportedTemporalTypeException.class) + public void testWithUnsupportedField() { + dayTime.with(ChronoField.ALIGNED_WEEK_OF_MONTH, 1); + } + + @Test + public void testIsSupported() { + for (final ChronoField chronoField : ChronoField.values()) { + final boolean expectSupport = (chronoField == ChronoField.DAY_OF_WEEK || chronoField.isTimeBased()); + assertEquals(expectSupport, dayTime.isSupported(chronoField)); + } + } + + @Test + public void testGetLong() { + assertEquals(DAY_OF_WEEK.getValue(), dayTime.getLong(ChronoField.DAY_OF_WEEK)); + assertEquals(LOCAL_TIME.toNanoOfDay(), dayTime.getLong(ChronoField.NANO_OF_DAY)); + } + + @Test(expected = UnsupportedTemporalTypeException.class) + public void testGetLongUnsupportedField() { + dayTime.getLong(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH); + } + + @Test + public void testAdjustInto() { + final LocalDateTime localDateTime = LocalDateTime.of(2017, 11, 14, 9, 14); + final LocalDateTime adjustedLocalDateTime = (LocalDateTime) dayTime.adjustInto(localDateTime); + + final ChronoField[] chronoFields = new ChronoField[] { + ChronoField.DAY_OF_WEEK, + ChronoField.HOUR_OF_DAY, + ChronoField.MINUTE_OF_HOUR + }; + + for (final ChronoField chronoField : chronoFields) { + assertEquals(dayTime.get(chronoField), adjustedLocalDateTime.get(chronoField)); + } + } + + @Test + public void testCompareTo() { + final DayTime tuesdayAtNoon = DayTime.of(DayOfWeek.TUESDAY, LocalTime.NOON); + final DayTime tuesdayAfternoon = DayTime.of(DayOfWeek.TUESDAY, LocalTime.of(15, 0)); + + assertEquals(0, tuesdayAtNoon.compareTo(tuesdayAtNoon)); + assertTrue(tuesdayAtNoon.compareTo(tuesdayAfternoon) < 0); + assertTrue(tuesdayAfternoon.compareTo(tuesdayAtNoon) > 0); + + final DayTime wednesdayAtNoon = DayTime.of(DayOfWeek.WEDNESDAY, LocalTime.NOON); + + assertTrue(tuesdayAtNoon.compareTo(wednesdayAtNoon) < 0); + assertTrue(wednesdayAtNoon.compareTo(tuesdayAtNoon) > 0); + } + + @Test + public void testEquals() { + final DayTime tuesdayAtNoon = DayTime.of(DayOfWeek.TUESDAY, LocalTime.NOON); + final DayTime tuesdayAfternoon = DayTime.of(DayOfWeek.TUESDAY, LocalTime.of(15, 0)); + + assertEquals(tuesdayAtNoon, tuesdayAtNoon); + assertFalse((tuesdayAtNoon.equals(tuesdayAfternoon))); + + final DayTime wednesdayAtNoon = DayTime.of(DayOfWeek.WEDNESDAY, LocalTime.NOON); + + assertNotEquals(tuesdayAtNoon, wednesdayAtNoon); + + assertFalse(tuesdayAtNoon.equals(4)); + assertFalse(tuesdayAtNoon.equals(null)); + } +}