com.helger.commons.datetime.XMLOffsetDate Maven / Gradle / Ivy
Show all versions of ph-commons Show documentation
/*
* Copyright (C) 2014-2024 Philip Helger (www.helger.com)
* philip[at]helger[dot]com
*
* Licensed 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.
*/
/*
* 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 com.helger.commons.datetime;
import static java.time.temporal.ChronoField.EPOCH_DAY;
import static java.time.temporal.ChronoField.OFFSET_SECONDS;
import static java.time.temporal.ChronoUnit.DAYS;
import java.io.Serializable;
import java.time.Clock;
import java.time.DateTimeException;
import java.time.DayOfWeek;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Month;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.chrono.IsoChronology;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.TemporalAmount;
import java.time.temporal.TemporalField;
import java.time.temporal.TemporalQueries;
import java.time.temporal.TemporalQuery;
import java.time.temporal.TemporalUnit;
import java.time.temporal.UnsupportedTemporalTypeException;
import java.time.temporal.ValueRange;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.equals.EqualsHelper;
import com.helger.commons.hashcode.HashCodeGenerator;
/**
* Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento
* Santos.
* This class is based on the class OffsetDate but the Timezone offset is
* optional - it may behave like a regular LocalDate
*
* A date with an offset from UTC/Greenwich in the ISO-8601 calendar system,
* such as {@code 2007-12-03+01:00}.
*
* {@code XMLOffsetDate} is an immutable date-time object that represents a
* date, often viewed as year-month-day-offset. This object can also access
* other date fields such as day-of-year, day-of-week and week-of-year.
*
* This class does not store or represent a time. For example, the value "2nd
* October 2007 +02:00" can be stored in an {@code XMLOffsetDate}.
* 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 ==.
*
* @since v10.1
* @see OffsetDate
*/
@Immutable
public class XMLOffsetDate implements Temporal, TemporalAdjuster, Comparable , Serializable
{
/**
* The minimum supported {@code XMLOffsetDate}, '-999999999-01-01+18:00'. This
* is the minimum local date in the maximum offset (larger offsets are earlier
* on the time-line). This combines {@link LocalDate#MIN} and
* {@link ZoneOffset#MAX}. This could be used by an application as a "far
* past" date.
*/
public static final XMLOffsetDate MIN = XMLOffsetDate.of (LocalDate.MIN, ZoneOffset.MAX);
/**
* The maximum supported {@code XMLOffsetDate}, '+999999999-12-31-18:00'. This
* is the maximum local date in the minimum offset (larger negative offsets
* are later on the time-line). This combines {@link LocalDate#MAX} and
* {@link ZoneOffset#MIN}. This could be used by an application as a "far
* future" date.
*/
public static final XMLOffsetDate MAX = XMLOffsetDate.of (LocalDate.MAX, ZoneOffset.MIN);
/**
* The number of seconds per day.
*/
private static final long SECONDS_PER_DAY = 86400;
/**
* The local date.
*/
private final LocalDate m_aDate;
/**
* The offset from UTC/Greenwich.
*/
private final ZoneOffset m_aOffset;
/**
* Obtains the current date from the system clock in the default time-zone.
*
* This will query the {@link Clock#systemDefaultZone() system clock} in the
* default time-zone to obtain the current date. The offset will be calculated
* from the time-zone in the clock.
*
* Using this method will prevent the ability to use an alternate clock for
* testing because the clock is hard-coded.
*
* @return the current date using the system clock, not null
*/
@Nonnull
public static XMLOffsetDate now ()
{
return now (Clock.systemDefaultZone ());
}
/**
* Obtains the current date from the system clock in the specified time-zone.
*
* This will query the {@link Clock#system(ZoneId) system clock} to obtain the
* current date. Specifying the time-zone avoids dependence on the default
* time-zone. The offset will be calculated from the specified time-zone.
*
* Using this method will prevent the ability to use an alternate clock for
* testing because the clock is hard-coded.
*
* @param zone
* the zone ID to use, not null
* @return the current date using the system clock, not null
*/
@Nonnull
public static XMLOffsetDate now (@Nonnull final ZoneId zone)
{
return now (Clock.system (zone));
}
/**
* Obtains the current date from the specified clock.
*
* This will query the specified clock to obtain the current date - today. The
* offset will be calculated from the time-zone in the clock.
*
* Using this method allows the use of an alternate clock for testing. The
* alternate clock may be introduced using {@link Clock dependency injection}.
*
* @param clock
* the clock to use, not null
* @return the current date, not null
*/
@Nonnull
public static XMLOffsetDate now (@Nonnull final Clock clock)
{
ValueEnforcer.notNull (clock, "clock");
final Instant now = clock.instant (); // called once
return ofInstant (now, clock.getZone ().getRules ().getOffset (now));
}
/**
* Obtains an instance of {@code XMLOffsetDate} from a local date and not zone
* offset.
*
* @param date
* the local date, not null
* @return the offset date, not null
* @since 11.0.0
*/
@Nonnull
public static XMLOffsetDate of (@Nonnull final LocalDate date)
{
return new XMLOffsetDate (date, null);
}
/**
* Obtains an instance of {@code XMLOffsetDate} from a local date and an
* offset.
*
* @param date
* the local date, not null
* @param offset
* the zone offset, may be null
* @return the offset date, not null
*/
@Nonnull
public static XMLOffsetDate of (@Nonnull final LocalDate date, @Nullable final ZoneOffset offset)
{
return new XMLOffsetDate (date, offset);
}
@Nonnull
public static XMLOffsetDate of (@Nonnull final OffsetDate ofsDate)
{
return new XMLOffsetDate (ofsDate.toLocalDate (), ofsDate.getOffset ());
}
/**
* Obtains an instance of {@code XMLOffsetDate} from a year, month, day and
* offset.
*
* This creates an offset date with the four specified fields.
*
* This method exists primarily for writing test cases. Non test-code will
* typically use other methods to create an offset time.
*
* @param year
* the year to represent, from MIN_YEAR to MAX_YEAR
* @param month
* the month-of-year to represent, from 1 (January) to 12 (December)
* @param dayOfMonth
* the day-of-month to represent, from 1 to 31
* @param offset
* the zone offset, may be null
* @return the offset date, not null
* @throws DateTimeException
* if the value of any field is out of range, or if the day-of-month
* is invalid for the month-year
*/
@Nonnull
public static XMLOffsetDate of (final int year,
final int month,
final int dayOfMonth,
@Nullable final ZoneOffset offset)
{
final LocalDate d = LocalDate.of (year, month, dayOfMonth);
return new XMLOffsetDate (d, offset);
}
/**
* Obtains an instance of {@code XMLOffsetDate} from a year, month, day and
* offset.
*
* This creates an offset date with the four specified fields.
*
* This method exists primarily for writing test cases. Non test-code will
* typically use other methods to create an offset time.
*
* @param year
* the year to represent, from MIN_YEAR to MAX_YEAR
* @param month
* the month-of-year to represent, from 1 (January) to 12 (December)
* @param dayOfMonth
* the day-of-month to represent, from 1 to 31
* @param offset
* the zone offset, may be null
* @return the offset date, not null
* @throws DateTimeException
* if the value of any field is out of range, or if the day-of-month
* is invalid for the month-year
*/
@Nonnull
public static XMLOffsetDate of (final int year,
final Month month,
final int dayOfMonth,
@Nullable final ZoneOffset offset)
{
final LocalDate d = LocalDate.of (year, month, dayOfMonth);
return new XMLOffsetDate (d, offset);
}
/**
* Obtains an instance of {@code XMLOffsetDate} from an {@code Instant} and
* zone ID.
*
* This creates an offset date with the same instant as midnight at the start
* of day of the instant specified. Finding the offset from UTC/Greenwich is
* simple as there is only one valid offset for each instant.
*
* @param instant
* the instant to create the time from, not null
* @param zone
* the time-zone, which may be an offset, not null
* @return the offset time, not null
*/
@Nonnull
public static XMLOffsetDate ofInstant (@Nonnull final Instant instant, @Nonnull final ZoneId zone)
{
ValueEnforcer.notNull (instant, "instant");
ValueEnforcer.notNull (zone, "zone");
final ZoneOffset offset = zone.getRules ().getOffset (instant);
// overflow caught later
final long epochSec = instant.getEpochSecond () + offset.getTotalSeconds ();
final long epochDay = Math.floorDiv (epochSec, SECONDS_PER_DAY);
final LocalDate date = LocalDate.ofEpochDay (epochDay);
return new XMLOffsetDate (date, offset);
}
/**
* Obtains an instance of {@code XMLOffsetDate} from a temporal object.
*
* A {@code TemporalAccessor} represents some form of date and time
* information. This factory converts the arbitrary temporal object to an
* instance of {@code XMLOffsetDate}.
*
* The conversion extracts and combines {@code LocalDate} and
* {@code ZoneOffset}.
*
* This method matches the signature of the functional interface
* {@link TemporalQuery} allowing it to be used in queries via method
* reference, {@code XMLOffsetDate::from}.
*
* @param temporal
* the temporal object to convert, not null
* @return the offset date, not null
* @throws DateTimeException
* if unable to convert to an {@code XMLOffsetDate}
*/
@Nonnull
public static XMLOffsetDate from (@Nonnull final TemporalAccessor temporal)
{
if (temporal instanceof XMLOffsetDate)
return (XMLOffsetDate) temporal;
try
{
final LocalDate date = LocalDate.from (temporal);
// Optional offset
ZoneOffset offset;
try
{
offset = ZoneOffset.from (temporal);
}
catch (final DateTimeException ex)
{
offset = null;
}
return new XMLOffsetDate (date, offset);
}
catch (final DateTimeException ex)
{
throw new DateTimeException ("Unable to obtain XMLOffsetDate from TemporalAccessor: " + temporal.getClass (), ex);
}
}
/**
* Obtains an instance of {@code XMLOffsetDate} from a text string such as
* {@code 2007-12-03+01:00}.
*
* The string must represent a valid date and is parsed using
* {@link DateTimeFormatter#ISO_DATE}.
*
* @param text
* the text to parse such as "2007-12-03+01:00", not null
* @return the parsed offset date, not null
* @throws DateTimeParseException
* if the text cannot be parsed
*/
@Nonnull
public static XMLOffsetDate parse (@Nonnull final CharSequence text)
{
return parse (text, DateTimeFormatter.ISO_DATE);
}
/**
* Obtains an instance of {@code XMLOffsetDate} from a text string using a
* specific formatter.
*
* The text is parsed using the formatter, returning a date.
*
* @param sText
* the text to parse, not null
.
* @param aFormatter
* the formatter to use, not null
.
* @return the parsed offset date, not null
.
* @throws DateTimeParseException
* if the text cannot be parsed
*/
@Nonnull
public static XMLOffsetDate parse (@Nonnull final CharSequence sText, @Nonnull final DateTimeFormatter aFormatter)
{
ValueEnforcer.notNull (aFormatter, "formatter");
return aFormatter.parse (sText, XMLOffsetDate::from);
}
/**
* Constructor.
*
* @param date
* the local date, not null
* @param offset
* the zone offset, not null
*/
protected XMLOffsetDate (@Nonnull final LocalDate date, @Nullable final ZoneOffset offset)
{
ValueEnforcer.notNull (date, "date");
m_aDate = date;
m_aOffset = offset;
}
/**
* Returns a new date based on this one, returning {@code this} where
* possible.
*
* @param date
* the date to create with, not null
* @param offset
* the zone offset to create with, may be null
*/
@Nonnull
private XMLOffsetDate with (@Nonnull final LocalDate date, @Nullable final ZoneOffset offset)
{
if (this.m_aDate == date && EqualsHelper.equals (m_aOffset, offset))
return this;
return new XMLOffsetDate (date, offset);
}
/**
* Checks if the specified field is supported.
*
* This checks if this date can be queried for the specified field. If false,
* then calling the {@link #range(TemporalField) range},
* {@link #get(TemporalField) get} and {@link #with(TemporalField, long)}
* methods will throw an exception.
*
* If the field is a {@link ChronoField} then the query is implemented here.
* The supported fields are:
*
* - {@code DAY_OF_WEEK}
*
- {@code ALIGNED_DAY_OF_WEEK_IN_MONTH}
*
- {@code ALIGNED_DAY_OF_WEEK_IN_YEAR}
*
- {@code DAY_OF_MONTH}
*
- {@code DAY_OF_YEAR}
*
- {@code EPOCH_DAY}
*
- {@code ALIGNED_WEEK_OF_MONTH}
*
- {@code ALIGNED_WEEK_OF_YEAR}
*
- {@code MONTH_OF_YEAR}
*
- {@code PROLEPTIC_MONTH}
*
- {@code YEAR_OF_ERA}
*
- {@code YEAR}
*
- {@code ERA}
*
- {@code OFFSET_SECONDS}
*
* All other {@code ChronoField} instances will return false.
*
* If the field is not a {@code ChronoField}, then the result of this method
* is obtained by invoking
* {@code TemporalField.isSupportedBy(TemporalAccessor)} passing {@code this}
* as the argument. Whether the field is supported is determined by the field.
*
* @param field
* the field to check, null returns false
* @return true if the field is supported on this date, false if not
*/
@Override
public boolean isSupported (@Nullable final TemporalField field)
{
if (field instanceof ChronoField)
return field.isDateBased () || field == OFFSET_SECONDS;
return field != null && field.isSupportedBy (this);
}
/**
* Checks if the specified unit is supported.
*
* This checks if the specified unit can be added to, or subtracted from, this
* date. If false, then calling the {@link #plus(long, TemporalUnit)} and
* {@link #minus(long, TemporalUnit) minus} methods will throw an exception.
*
* If the unit is a {@link ChronoUnit} then the query is implemented here. The
* supported units are:
*
* - {@code DAYS}
*
- {@code WEEKS}
*
- {@code MONTHS}
*
- {@code YEARS}
*
- {@code DECADES}
*
- {@code CENTURIES}
*
- {@code MILLENNIA}
*
- {@code ERAS}
*
* All other {@code ChronoUnit} instances will return false.
*
* If the unit is not a {@code ChronoUnit}, then the result of this method is
* obtained by invoking {@code TemporalUnit.isSupportedBy(Temporal)} passing
* {@code this} as the argument. Whether the unit is supported is determined
* by the unit.
*
* @param unit
* the unit to check, null returns false
* @return true if the unit can be added/subtracted, false if not
*/
@Override
public boolean isSupported (@Nullable final TemporalUnit unit)
{
if (unit instanceof ChronoUnit)
return unit.isDateBased ();
return unit != null && unit.isSupportedBy (this);
}
/**
* Gets the range of valid values for the specified field.
*
* The range object expresses the minimum and maximum valid values for a
* field. This date is used to enhance the accuracy of the returned range. If
* it is not possible to return the range, because the field is not supported
* or for some other reason, an exception is thrown.
*
* If the field is a {@link ChronoField} then the query is implemented here.
* The {@link #isSupported(TemporalField) supported fields} will return
* appropriate range instances. All other {@code ChronoField} instances will
* throw an {@code UnsupportedTemporalTypeException}.
*
* If the field is not a {@code ChronoField}, then the result of this method
* is obtained by invoking
* {@code TemporalField.rangeRefinedBy(TemporalAccessor)} passing {@code this}
* as the argument. Whether the range can be obtained is determined by the
* field.
*
* @param field
* the field to query the range for, not null
* @return the range of valid values for the field, not null
* @throws DateTimeException
* if the range for the field cannot be obtained
* @throws UnsupportedTemporalTypeException
* if the field is not supported
*/
@Override
public ValueRange range (@Nonnull final TemporalField field)
{
if (field instanceof ChronoField)
{
if (field == OFFSET_SECONDS)
return field.range ();
return m_aDate.range (field);
}
return field.rangeRefinedBy (this);
}
/**
* Gets the value of the specified field from this date as an {@code int}.
*
* This queries this date for the value for the specified field. The returned
* value will always be within the valid range of values for the field. If it
* is not possible to return the value, because the field is not supported or
* for some other reason, an exception is thrown.
*
* If the field is a {@link ChronoField} then the query is implemented here.
* The {@link #isSupported(TemporalField) supported fields} will return valid
* values based on this date, except {@code EPOCH_DAY} and
* {@code PROLEPTIC_MONTH} which are too large to fit in an {@code int} and
* throw a {@code DateTimeException}. All other {@code ChronoField} instances
* will throw a {@code DateTimeException}.
*
* If the field is not a {@code ChronoField}, then the result of this method
* is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}
* passing {@code this} as the argument. Whether the value can be obtained,
* and what the value represents, is determined by the field.
*
* @param field
* the field to get, not null
* @return the value for the field
* @throws DateTimeException
* if a value for the field cannot be obtained or the value is outside
* the range of valid values for the field
* @throws UnsupportedTemporalTypeException
* if the field is not supported or the range of values exceeds an
* {@code int}
* @throws ArithmeticException
* if numeric overflow occurs
*/
@Override // override for Javadoc
public int get (@Nonnull final TemporalField field)
{
return Temporal.super.get (field);
}
/**
* Gets the value of the specified field from this date as a {@code long}.
*
* This queries this date for the value for the specified field. If it is not
* possible to return the value, because the field is not supported or for
* some other reason, an exception is thrown.
*
* If the field is a {@link ChronoField} then the query is implemented here.
* The {@link #isSupported(TemporalField) supported fields} will return valid
* values based on this date. All other {@code ChronoField} instances will
* throw an {@code UnsupportedTemporalTypeException}.
*
* If the field is not a {@code ChronoField}, then the result of this method
* is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}
* passing {@code this} as the argument. Whether the value can be obtained,
* and what the value represents, is determined by the field.
*
* @param field
* the field to get, not null
* @return the value for the field
* @throws DateTimeException
* if a value for the field cannot be obtained
* @throws UnsupportedTemporalTypeException
* if the field is not supported
* @throws ArithmeticException
* if numeric overflow occurs
*/
@Override
public long getLong (@Nonnull final TemporalField field)
{
if (field instanceof ChronoField)
{
if (field == OFFSET_SECONDS)
{
if (m_aOffset != null)
return m_aOffset.getTotalSeconds ();
throw new DateTimeException ("No offset present");
}
return m_aDate.getLong (field);
}
return field.getFrom (this);
}
/**
* Gets the zone offset, such as '+01:00'.
*
* This is the offset of the local date from UTC/Greenwich.
*
* @return the zone offset, maybe null
*/
@Nullable
public ZoneOffset getOffset ()
{
return m_aOffset;
}
/**
* @return true
if this date has a zone offset,
* false
if not.
*/
public boolean hasOffset ()
{
return m_aOffset != null;
}
/**
* Returns a copy of this {@code XMLOffsetDate} with the specified offset
* ensuring that the result has the same local date.
*
* This method returns an object with the same {@code LocalDate} and the
* specified {@code ZoneOffset}. No calculation is needed or performed. For
* example, if this time represents {@code 2007-12-03+02:00} and the offset
* specified is {@code +03:00}, then this method will return
* {@code 2007-12-03+03:00}.
*
* This instance is immutable and unaffected by this method call.
*
* @param offset
* the zone offset to change to, may be null
* @return an {@code XMLOffsetDate} based on this date with the requested
* offset, not null
*/
@Nonnull
public XMLOffsetDate withOffsetSameLocal (@Nullable final ZoneOffset offset)
{
return with (m_aDate, offset);
}
/**
* Gets the {@code LocalDate} part of this date.
*
* This returns a {@code LocalDate} with the same year, month and day as this
* date.
*
* @return the date part of this date, not null
*/
@Nonnull
public LocalDate toLocalDate ()
{
return m_aDate;
}
/**
* Gets the year field.
*
* This method returns the primitive {@code int} value for the year.
*
* The year returned by this method is proleptic as per {@code get(YEAR)}. To
* obtain the year-of-era, use {@code get(YEAR_OF_ERA)}.
*
* @return the year, from MIN_YEAR to MAX_YEAR
*/
public int getYear ()
{
return m_aDate.getYear ();
}
/**
* Gets the month-of-year field from 1 to 12.
*
* This method returns the month as an {@code int} from 1 to 12. Application
* code is frequently clearer if the enum {@link Month} is used by calling
* {@link #getMonth()}.
*
* @return the month-of-year, from 1 to 12
* @see #getMonth()
*/
public int getMonthValue ()
{
return m_aDate.getMonthValue ();
}
/**
* Gets the month-of-year field using the {@code Month} enum.
*
* This method returns the enum {@link Month} for the month. This avoids
* confusion as to what {@code int} values mean. If you need access to the
* primitive {@code int} value then the enum provides the
* {@link Month#getValue() int value}.
*
* @return the month-of-year, not null
* @see #getMonthValue()
*/
@Nonnull
public Month getMonth ()
{
return m_aDate.getMonth ();
}
/**
* Gets the day-of-month field.
*
* This method returns the primitive {@code int} value for the day-of-month.
*
* @return the day-of-month, from 1 to 31
*/
public int getDayOfMonth ()
{
return m_aDate.getDayOfMonth ();
}
/**
* Gets the day-of-year field.
*
* This method returns the primitive {@code int} value for the day-of-year.
*
* @return the day-of-year, from 1 to 365, or 366 in a leap year
*/
public int getDayOfYear ()
{
return m_aDate.getDayOfYear ();
}
/**
* Gets the day-of-week field, which is an enum {@code DayOfWeek}.
*
* This method returns the enum {@link DayOfWeek} for the day-of-week. This
* avoids confusion as to what {@code int} values mean. If you need access to
* the primitive {@code int} value then the enum provides the
* {@link DayOfWeek#getValue() int value}.
*
* Additional information can be obtained from the {@code DayOfWeek}. This
* includes textual names of the values.
*
* @return the day-of-week, not null
*/
@Nonnull
public DayOfWeek getDayOfWeek ()
{
return m_aDate.getDayOfWeek ();
}
/**
* Returns an adjusted copy of this date.
*
* This returns an {@code XMLOffsetDate} based on this one, with the date
* adjusted. The adjustment takes place using the specified adjuster strategy
* object. Read the documentation of the adjuster to understand what
* adjustment will be made.
*
* A simple adjuster might simply set the one of the fields, such as the year
* field. A more complex adjuster might set the date to the last day of the
* month. A selection of common adjustments is provided in
* {@link TemporalAdjusters}. These include finding the "last day of the
* month" and "next Wednesday". Key date-time classes also implement the
* {@code TemporalAdjuster} interface, such as {@link Month} and
* {@link MonthDay MonthDay}. The adjuster is responsible for handling special
* cases, such as the varying lengths of month and leap years.
*
* For example this code returns a date on the last day of July:
*
*
* import static java.time.Month.*;
* import static java.time.temporal.TemporalAdjusters.*;
*
* result = offsetDate.with(JULY).with(lastDayOfMonth());
*
*
* The classes {@link LocalDate} and {@link ZoneOffset} implement
* {@code TemporalAdjuster}, thus this method can be used to change the date
* or offset:
*
*
* result = offsetDate.with (date);
* result = offsetDate.with (offset);
*
*
* The result of this method is obtained by invoking the
* {@link TemporalAdjuster#adjustInto(Temporal)} method on the specified
* adjuster passing {@code this} as the argument.
*
* This instance is immutable and unaffected by this method call.
*
* @param adjuster
* the adjuster to use, not null
* @return an {@code XMLOffsetDate} based on {@code this} with the adjustment
* made, not null
* @throws DateTimeException
* if the adjustment cannot be made
* @throws ArithmeticException
* if numeric overflow occurs
*/
@Override
@Nonnull
public XMLOffsetDate with (@Nonnull final TemporalAdjuster adjuster)
{
// optimizations
if (adjuster instanceof LocalDate)
return with ((LocalDate) adjuster, m_aOffset);
if (adjuster instanceof ZoneOffset)
return with (m_aDate, (ZoneOffset) adjuster);
if (adjuster instanceof XMLOffsetDate)
return (XMLOffsetDate) adjuster;
return (XMLOffsetDate) adjuster.adjustInto (this);
}
/**
* Returns a copy of this date with the specified field set to a new value.
*
* This returns an {@code XMLOffsetDate} based on this one, with the value for
* the specified field changed. This can be used to change any supported
* field, such as the year, month or day-of-month. If it is not possible to
* set the value, because the field is not supported or for some other reason,
* an exception is thrown.
*
* In some cases, changing the specified field can cause the resulting date to
* become invalid, such as changing the month from 31st January to February
* would make the day-of-month invalid. In cases like this, the field is
* responsible for resolving the date. Typically it will choose the previous
* valid date, which would be the last valid day of February in this example.
*
* If the field is a {@link ChronoField} then the adjustment is implemented
* here.
*
* The {@code OFFSET_SECONDS} field will return a date with the specified
* offset. The local date is unaltered. If the new offset value is outside the
* valid range then a {@code DateTimeException} will be thrown.
*
* The other {@link #isSupported(TemporalField) supported fields} will behave
* as per the matching method on {@link LocalDate#with(TemporalField, long)}
* LocalDate}. In this case, the offset is not part of the calculation and
* will be unchanged.
*
* All other {@code ChronoField} instances will throw an
* {@code UnsupportedTemporalTypeException}.
*
* If the field is not a {@code ChronoField}, then the result of this method
* is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)}
* passing {@code this} as the argument. In this case, the field determines
* whether and how to adjust the instant.
*
* This instance is immutable and unaffected by this method call.
*
* @param field
* the field to set in the result, not null
* @param newValue
* the new value of the field in the result
* @return an {@code XMLOffsetDate} based on {@code this} with the specified
* field set, not null
* @throws DateTimeException
* if the field cannot be set
* @throws UnsupportedTemporalTypeException
* if the field is not supported
* @throws ArithmeticException
* if numeric overflow occurs
*/
@Override
@Nonnull
public XMLOffsetDate with (@Nonnull final TemporalField field, final long newValue)
{
if (field instanceof ChronoField)
{
if (field == OFFSET_SECONDS)
{
final ChronoField f = (ChronoField) field;
return with (m_aDate, ZoneOffset.ofTotalSeconds (f.checkValidIntValue (newValue)));
}
return with (m_aDate.with (field, newValue), m_aOffset);
}
return field.adjustInto (this, newValue);
}
/**
* Returns a copy of this {@code XMLOffsetDate} with the year altered.
*
* The offset does not affect the calculation and will be the same in the
* result. If the day-of-month is invalid for the year, it will be changed to
* the last valid day of the month.
*
* This instance is immutable and unaffected by this method call.
*
* @param year
* the year to set in the result, from MIN_YEAR to MAX_YEAR
* @return an {@code XMLOffsetDate} based on this date with the requested
* year, not null
* @throws DateTimeException
* if the year value is invalid
*/
@Nonnull
public XMLOffsetDate withYear (final int year)
{
return with (m_aDate.withYear (year), m_aOffset);
}
/**
* Returns a copy of this {@code XMLOffsetDate} with the month-of-year
* altered.
*
* The offset does not affect the calculation and will be the same in the
* result. If the day-of-month is invalid for the year, it will be changed to
* the last valid day of the month.
*
* This instance is immutable and unaffected by this method call.
*
* @param month
* the month-of-year to set in the result, from 1 (January) to 12
* (December)
* @return an {@code XMLOffsetDate} based on this date with the requested
* month, not null
* @throws DateTimeException
* if the month-of-year value is invalid
*/
@Nonnull
public XMLOffsetDate withMonth (final int month)
{
return with (m_aDate.withMonth (month), m_aOffset);
}
/**
* Returns a copy of this {@code XMLOffsetDate} with the day-of-month altered.
*
* If the resulting date is invalid, an exception is thrown. The offset does
* not affect the calculation and will be the same in the result.
*
* This instance is immutable and unaffected by this method call.
*
* @param dayOfMonth
* the day-of-month to set in the result, from 1 to 28-31
* @return an {@code XMLOffsetDate} based on this date with the requested day,
* not null
* @throws DateTimeException
* if the day-of-month value is invalid, or if the day-of-month is
* invalid for the month-year
*/
@Nonnull
public XMLOffsetDate withDayOfMonth (final int dayOfMonth)
{
return with (m_aDate.withDayOfMonth (dayOfMonth), m_aOffset);
}
/**
* Returns a copy of this {@code XMLOffsetDate} with the day-of-year altered.
*
* If the resulting date is invalid, an exception is thrown.
*
* This instance is immutable and unaffected by this method call.
*
* @param dayOfYear
* the day-of-year to set in the result, from 1 to 365-366
* @return an {@code XMLOffsetDate} based on this date with the requested day,
* not null
* @throws DateTimeException
* if the day-of-year value is invalid, or if the day-of-year is
* invalid for the year
*/
@Nonnull
public XMLOffsetDate withDayOfYear (final int dayOfYear)
{
return with (m_aDate.withDayOfYear (dayOfYear), m_aOffset);
}
/**
* Returns a copy of this date with the specified period added.
*
* This returns an {@code XMLOffsetDate} based on this one, with the specified
* amount added. The amount is typically {@link Period} but may be any other
* type implementing the {@link TemporalAmount} interface.
*
* This uses {@link TemporalAmount#addTo(Temporal)} to perform the
* calculation.
*
* This instance is immutable and unaffected by this method call.
*
* @param amountToAdd
* the amount to add, not null
* @return an {@code XMLOffsetDate} based on this date with the addition made,
* not null
* @throws DateTimeException
* if the addition cannot be made
* @throws ArithmeticException
* if numeric overflow occurs
*/
@Override
@Nonnull
public XMLOffsetDate plus (@Nonnull final TemporalAmount amountToAdd)
{
return (XMLOffsetDate) amountToAdd.addTo (this);
}
/**
* Returns a copy of this date with the specified amount added.
*
* This returns an {@code XMLOffsetDate} based on this one, with the amount in
* terms of the unit added. If it is not possible to add the amount, because
* the unit is not supported or for some other reason, an exception is thrown.
*
* If the field is a {@link ChronoUnit} then the addition is implemented by
* {@link LocalDate#plus(long, TemporalUnit)}. The offset is not part of the
* calculation and will be unchanged in the result.
*
* If the field is not a {@code ChronoUnit}, then the result of this method is
* obtained by invoking {@code TemporalUnit.addTo(Temporal, long)} passing
* {@code this} as the argument. In this case, the unit determines whether and
* how to perform the addition.
*
* This instance is immutable and unaffected by this method call.
*
* @param amountToAdd
* the amount of the unit to add to the result, may be negative
* @param unit
* the unit of the amount to add, not null
* @return an {@code XMLOffsetDate} based on this date with the specified
* amount added, not null
* @throws DateTimeException
* if the addition cannot be made
* @throws UnsupportedTemporalTypeException
* if the unit is not supported
* @throws ArithmeticException
* if numeric overflow occurs
*/
@Override
@Nonnull
public XMLOffsetDate plus (final long amountToAdd, @Nonnull final TemporalUnit unit)
{
if (unit instanceof ChronoUnit)
return with (m_aDate.plus (amountToAdd, unit), m_aOffset);
return unit.addTo (this, amountToAdd);
}
/**
* Returns a copy of this {@code XMLOffsetDate} with the specified number of
* years added.
*
* This uses {@link LocalDate#plusYears(long)} to add the years. The offset
* does not affect the calculation and will be the same in the result.
*
* This instance is immutable and unaffected by this method call.
*
* @param years
* the years to add, may be negative
* @return an {@code XMLOffsetDate} based on this date with the years added,
* not null
* @throws DateTimeException
* if the result exceeds the supported date range
*/
@Nonnull
public XMLOffsetDate plusYears (final long years)
{
return with (m_aDate.plusYears (years), m_aOffset);
}
/**
* Returns a copy of this {@code XMLOffsetDate} with the specified number of
* months added.
*
* This uses {@link LocalDate#plusMonths(long)} to add the months. The offset
* does not affect the calculation and will be the same in the result.
*
* This instance is immutable and unaffected by this method call.
*
* @param months
* the months to add, may be negative
* @return an {@code XMLOffsetDate} based on this date with the months added,
* not null
* @throws DateTimeException
* if the result exceeds the supported date range
*/
@Nonnull
public XMLOffsetDate plusMonths (final long months)
{
return with (m_aDate.plusMonths (months), m_aOffset);
}
/**
* Returns a copy of this {@code XMLOffsetDate} with the specified number of
* weeks added.
*
* This uses {@link LocalDate#plusWeeks(long)} to add the weeks. The offset
* does not affect the calculation and will be the same in the result.
*
* This instance is immutable and unaffected by this method call.
*
* @param weeks
* the weeks to add, may be negative
* @return an {@code XMLOffsetDate} based on this date with the weeks added,
* not null
* @throws DateTimeException
* if the result exceeds the supported date range
*/
@Nonnull
public XMLOffsetDate plusWeeks (final long weeks)
{
return with (m_aDate.plusWeeks (weeks), m_aOffset);
}
/**
* Returns a copy of this {@code XMLOffsetDate} with the specified number of
* days added.
*
* This uses {@link LocalDate#plusDays(long)} to add the days. The offset does
* not affect the calculation and will be the same in the result.
*
* This instance is immutable and unaffected by this method call.
*
* @param days
* the days to add, may be negative
* @return an {@code XMLOffsetDate} based on this date with the days added,
* not null
* @throws DateTimeException
* if the result exceeds the supported date range
*/
@Nonnull
public XMLOffsetDate plusDays (final long days)
{
return with (m_aDate.plusDays (days), m_aOffset);
}
/**
* Returns a copy of this date with the specified amount subtracted.
*
* This returns am {@code XMLOffsetDate} based on this one, with the specified
* amount subtracted. The amount is typically {@link Period} but may be any
* other type implementing the {@link TemporalAmount} interface.
*
* This uses {@link TemporalAmount#subtractFrom(Temporal)} to perform the
* calculation.
*
* This instance is immutable and unaffected by this method call.
*
* @param amountToSubtract
* the amount to subtract, not null
* @return an {@code XMLOffsetDate} based on this date with the subtraction
* made, not null
* @throws DateTimeException
* if the subtraction cannot be made
* @throws ArithmeticException
* if numeric overflow occurs
*/
@Override
@Nonnull
public XMLOffsetDate minus (@Nonnull final TemporalAmount amountToSubtract)
{
return (XMLOffsetDate) amountToSubtract.subtractFrom (this);
}
/**
* Returns a copy of this date with the specified amount subtracted.
*
* This returns an {@code XMLOffsetDate} based on this one, with the amount in
* terms of the unit subtracted. If it is not possible to subtract the amount,
* because the unit is not supported or for some other reason, an exception is
* thrown.
*
* This method is equivalent to {@link #plus(long, TemporalUnit)} with the
* amount negated. See that method for a full description of how addition, and
* thus subtraction, works.
*
* This instance is immutable and unaffected by this method call.
*
* @param amountToSubtract
* the amount of the unit to subtract from the result, may be negative
* @param unit
* the unit of the amount to subtract, not null
* @return an {@code XMLOffsetDate} based on this date with the specified
* amount subtracted, not null
* @throws DateTimeException
* if the subtraction cannot be made
* @throws UnsupportedTemporalTypeException
* if the unit is not supported
* @throws ArithmeticException
* if numeric overflow occurs
*/
@Override
@Nonnull
public XMLOffsetDate minus (final long amountToSubtract, @Nonnull final TemporalUnit unit)
{
return amountToSubtract == Long.MIN_VALUE ? plus (Long.MAX_VALUE, unit).plus (1, unit) : plus (-amountToSubtract,
unit);
}
/**
* Returns a copy of this {@code XMLOffsetDate} with the specified number of
* years subtracted.
*
* This uses {@link LocalDate#minusYears(long)} to subtract the years. The
* offset does not affect the calculation and will be the same in the result.
*
* This instance is immutable and unaffected by this method call.
*
* @param years
* the years to subtract, may be negative
* @return an {@code XMLOffsetDate} based on this date with the years
* subtracted, not null
* @throws DateTimeException
* if the result exceeds the supported date range
*/
@Nonnull
public XMLOffsetDate minusYears (final long years)
{
return with (m_aDate.minusYears (years), m_aOffset);
}
/**
* Returns a copy of this {@code XMLOffsetDate} with the specified number of
* months subtracted.
*
* This uses {@link LocalDate#minusMonths(long)} to subtract the months. The
* offset does not affect the calculation and will be the same in the result.
*
* This instance is immutable and unaffected by this method call.
*
* @param months
* the months to subtract, may be negative
* @return an {@code XMLOffsetDate} based on this date with the months
* subtracted, not null
* @throws DateTimeException
* if the result exceeds the supported date range
*/
@Nonnull
public XMLOffsetDate minusMonths (final long months)
{
return with (m_aDate.minusMonths (months), m_aOffset);
}
/**
* Returns a copy of this {@code XMLOffsetDate} with the specified number of
* weeks subtracted.
*
* This uses {@link LocalDate#minusWeeks(long)} to subtract the weeks. The
* offset does not affect the calculation and will be the same in the result.
*
* This instance is immutable and unaffected by this method call.
*
* @param weeks
* the weeks to subtract, may be negative
* @return an {@code XMLOffsetDate} based on this date with the weeks
* subtracted, not null
* @throws DateTimeException
* if the result exceeds the supported date range
*/
@Nonnull
public XMLOffsetDate minusWeeks (final long weeks)
{
return with (m_aDate.minusWeeks (weeks), m_aOffset);
}
/**
* Returns a copy of this {@code XMLOffsetDate} with the specified number of
* days subtracted.
*
* This uses {@link LocalDate#minusDays(long)} to subtract the days. The
* offset does not affect the calculation and will be the same in the result.
*
* This instance is immutable and unaffected by this method call.
*
* @param days
* the days to subtract, may be negative
* @return an {@code XMLOffsetDate} based on this date with the days
* subtracted, not null
* @throws DateTimeException
* if the result exceeds the supported date range
*/
@Nonnull
public XMLOffsetDate minusDays (final long days)
{
return with (m_aDate.minusDays (days), m_aOffset);
}
/**
* Queries this date using the specified query.
*
* This queries this date using the specified query strategy object. The
* {@code TemporalQuery} object defines the logic to be used to obtain the
* result. Read the documentation of the query to understand what the result
* of this method will be.
*
* The result of this method is obtained by invoking the
* {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the specified
* query passing {@code this} as the argument.
*
* @param
* the type of the result
* @param query
* the query to invoke, not null
* @return the query result, null may be returned (defined by the query)
* @throws DateTimeException
* if unable to query (defined by the query)
* @throws ArithmeticException
* if numeric overflow occurs (defined by the query)
*/
@SuppressWarnings ("unchecked")
@Override
@Nonnull
public R query (@Nonnull final TemporalQuery query)
{
if (query == TemporalQueries.chronology ())
return (R) IsoChronology.INSTANCE;
if (query == TemporalQueries.precision ())
return (R) DAYS;
if (query == TemporalQueries.offset () || query == TemporalQueries.zone ())
return (R) getOffsetOrDefault ();
return Temporal.super.query (query);
}
/**
* Adjusts the specified temporal object to have the same offset and date as
* this object.
*
* This returns a temporal object of the same observable type as the input
* with the offset and date changed to be the same as this.
*
* The adjustment is equivalent to using
* {@link Temporal#with(TemporalField, long)} twice, passing
* {@link ChronoField#OFFSET_SECONDS} and {@link ChronoField#EPOCH_DAY} as the
* fields.
*
* In most cases, it is clearer to reverse the calling pattern by using
* {@link Temporal#with(TemporalAdjuster)}:
*
*
* // these two lines are equivalent, but the second approach is recommended
* temporal = thisOffsetDate.adjustInto (temporal);
* temporal = temporal.with (thisOffsetDate);
*
*
* This instance is immutable and unaffected by this method call.
*
* @param temporal
* the target object to be adjusted, not null
* @return the adjusted object, not null
* @throws DateTimeException
* if unable to make the adjustment
* @throws ArithmeticException
* if numeric overflow occurs
*/
@Override
public Temporal adjustInto (@Nonnull final Temporal temporal)
{
return temporal.with (EPOCH_DAY, toLocalDate ().toEpochDay ())
.with (OFFSET_SECONDS, m_aOffset != null ? m_aOffset.getTotalSeconds () : 0);
}
/**
* Calculates the period between this date and another date in terms of the
* specified unit.
*
* This calculates the period between two dates in terms of a single unit. The
* start and end points are {@code this} and the specified date. The result
* will be negative if the end is before the start. For example, the period in
* days between two dates can be calculated using
* {@code startDate.until(endDate, DAYS)}.
*
* The {@code Temporal} passed to this method is converted to a
* {@code XMLOffsetDate} using {@link #from(TemporalAccessor)}. If the offset
* differs between the two times, then the specified end time is normalized to
* have the same offset as this time.
*
* The calculation returns a whole number, representing the number of complete
* units between the two dates. For example, the period in months between
* 2012-06-15Z and 2012-08-14Z will only be one month as it is one day short
* of two months.
*
* There are two equivalent ways of using this method. The first is to invoke
* this method. The second is to use
* {@link TemporalUnit#between(Temporal, Temporal)}:
*
*
* // these two lines are equivalent
* amount = start.until (end, DAYS);
* amount = DAYS.between (start, end);
*
*
* The choice should be made based on which makes the code more readable.
*
* The calculation is implemented in this method for {@link ChronoUnit}. The
* units {@code DAYS}, {@code WEEKS}, {@code MONTHS}, {@code YEARS},
* {@code DECADES}, {@code CENTURIES}, {@code MILLENNIA} and {@code ERAS} are
* supported. Other {@code ChronoUnit} values will throw an exception.
*
* If the unit is not a {@code ChronoUnit}, then the result of this method is
* obtained by invoking {@code TemporalUnit.between(Temporal, Temporal)}
* passing {@code this} as the first argument and the converted input temporal
* as the second argument.
*
* This instance is immutable and unaffected by this method call.
*
* @param endExclusive
* the end time, exclusive, which is converted to an
* {@code XMLOffsetDate}, not null
* @param unit
* the unit to measure the amount in, not null
* @return the amount of time between this date and the end date
* @throws DateTimeException
* if the amount cannot be calculated, or the end temporal cannot be
* converted to an {@code XMLOffsetDate}
* @throws UnsupportedTemporalTypeException
* if the unit is not supported
* @throws ArithmeticException
* if numeric overflow occurs
*/
@Override
public long until (@Nonnull final Temporal endExclusive, @Nonnull final TemporalUnit unit)
{
final XMLOffsetDate end = XMLOffsetDate.from (endExclusive);
if (unit instanceof ChronoUnit)
{
final long offsetDiff = (long) (end.m_aOffset != null ? end.m_aOffset.getTotalSeconds () : 0) -
(m_aOffset != null ? m_aOffset.getTotalSeconds () : 0);
final LocalDate endLocal = end.m_aDate.plusDays (Math.floorDiv (-offsetDiff, SECONDS_PER_DAY));
return m_aDate.until (endLocal, unit);
}
return unit.between (this, end);
}
/**
* Formats this date using the specified formatter.
*
* This date will be passed to the formatter to produce a string.
*
* @param formatter
* the formatter to use, not null
* @return the formatted date string, not null
* @throws DateTimeException
* if an error occurs during printing
*/
@Nonnull
public String format (@Nonnull final DateTimeFormatter formatter)
{
ValueEnforcer.notNull (formatter, "formatter");
return formatter.format (this);
}
@Nonnull
protected ZoneOffset getOffsetOrDefault ()
{
ZoneOffset ret = m_aOffset;
if (ret == null)
{
ret = PDTConfig.getDefaultZoneId ().getRules ().getOffset (m_aDate.atStartOfDay ());
}
return ret;
}
/**
* Returns an offset date-time formed from this date at the specified time.
*
* This combines this date with the specified time to form an
* {@code OffsetDateTime}. All possible combinations of date and time are
* valid.
*
* This instance is immutable and unaffected by this method call.
*
* @param aTime
* the time to combine with, not null
* @return the offset date-time formed from this date and the specified time,
* not null
*/
@Nonnull
public OffsetDateTime atTime (@Nonnull final LocalTime aTime)
{
return OffsetDateTime.of (m_aDate, aTime, getOffsetOrDefault ());
}
/**
* Returns an offset date-time formed from this date at the specified time.
*
* This combines this date with the specified time to form an
* {@code OffsetDateTime}. All possible combinations of date and time are
* valid.
*
* This instance is immutable and unaffected by this method call.
*
* @param aTime
* the time to combine with, not null
* @return the offset date-time formed from this date and the specified time,
* not null
*/
@Nonnull
public XMLOffsetDateTime atXMLTime (@Nonnull final LocalTime aTime)
{
return XMLOffsetDateTime.of (m_aDate, aTime, m_aOffset);
}
/**
* Converts this date to midnight at the start of day in epoch seconds.
*
* @return the epoch seconds value
*/
protected long toEpochSecond ()
{
final long epochDay = m_aDate.toEpochDay ();
final long secs = epochDay * SECONDS_PER_DAY;
return secs - (m_aOffset != null ? m_aOffset.getTotalSeconds () : 0);
}
/**
* Converts this {@code XMLOffsetDate} to the number of seconds since the
* epoch of 1970-01-01T00:00:00Z.
*
* This combines this offset date with the specified time to calculate the
* epoch-second value, which is the number of elapsed seconds from
* 1970-01-01T00:00:00Z. Instants on the time-line after the epoch are
* positive, earlier are negative.
*
* @param time
* the local time, not null
* @return the number of seconds since the epoch of 1970-01-01T00:00:00Z, may
* be negative
*/
public long toEpochSecond (@Nonnull final LocalTime time)
{
ValueEnforcer.notNull (time, "time");
return toEpochSecond () + time.toSecondOfDay ();
}
/**
* Compares this {@code XMLOffsetDate} to another date.
*
* The comparison is based first on the UTC equivalent instant, then on the
* local date. It is "consistent with equals", as defined by
* {@link Comparable}.
*
* For example, the following is the comparator order:
*
* - 2008-06-29-11:00
* - 2008-06-29-12:00
* - 2008-06-30+12:00
* - 2008-06-29-13:00
*
* Values #2 and #3 represent the same instant on the time-line. When two
* values represent the same instant, the local date is compared to
* distinguish them. This step is needed to make the ordering consistent with
* {@code equals()}.
*
* To compare the underlying local date of two {@code TemporalAccessor}
* instances, use {@link ChronoField#EPOCH_DAY} as a comparator.
*
* @param o
* the other date to compare to, not null
* @return the comparator value, negative if less, positive if greater
*/
@Override
public int compareTo (@Nonnull final XMLOffsetDate o)
{
if (EqualsHelper.equals (m_aOffset, o.m_aOffset))
return m_aDate.compareTo (o.m_aDate);
int ret = Long.compare (toEpochSecond (), o.toEpochSecond ());
if (ret == 0)
ret = m_aDate.compareTo (o.m_aDate);
return ret;
}
/**
* Checks if the instant of midnight at the start of this
* {@code XMLOffsetDate} is after midnight at the start of the specified date.
*
* This method differs from the comparison in {@link #compareTo} in that it
* only compares the instant of the date. This is equivalent to using
* {@code date1.toEpochSecond().isAfter(date2.toEpochSecond())}.
*
* @param other
* the other date to compare to, not null
* @return true if this is after the instant of the specified date
*/
public boolean isAfter (@Nonnull final XMLOffsetDate other)
{
return toEpochSecond () > other.toEpochSecond ();
}
/**
* Checks if the instant of midnight at the start of this
* {@code XMLOffsetDate} is before midnight at the start of the specified
* date.
*
* This method differs from the comparison in {@link #compareTo} in that it
* only compares the instant of the date. This is equivalent to using
* {@code date1.toEpochSecond().isBefore(date2.toEpochSecond())}.
*
* @param other
* the other date to compare to, not null
* @return true if this is before the instant of the specified date
*/
public boolean isBefore (@Nonnull final XMLOffsetDate other)
{
return toEpochSecond () < other.toEpochSecond ();
}
/**
* Checks if the instant of midnight at the start of this
* {@code XMLOffsetDate} equals midnight at the start of the specified date.
*
* This method differs from the comparison in {@link #compareTo} and
* {@link #equals} in that it only compares the instant of the date. This is
* equivalent to using
* {@code date1.toEpochSecond().equals(date2.toEpochSecond())}.
*
* @param other
* the other date to compare to, not null
* @return true if the instant equals the instant of the specified date
*/
public boolean isEqual (@Nonnull final XMLOffsetDate other)
{
return toEpochSecond () == other.toEpochSecond ();
}
@Nullable
public OffsetDate toOffsetDate ()
{
return OffsetDate.of (m_aDate, getOffsetOrDefault ());
}
/**
* Checks if this date is equal to another date.
*
* The comparison is based on the local-date and the offset. To compare for
* the same instant on the time-line, use {@link #isEqual(XMLOffsetDate)}.
*
* Only objects of type {@code XMLOffsetDate} are compared, other types return
* false. To compare the underlying local date of two {@code TemporalAccessor}
* instances, use {@link ChronoField#EPOCH_DAY} as a comparator.
*
* @param o
* the object to check, null returns false
* @return true if this is equal to the other date
*/
@Override
public boolean equals (final Object o)
{
if (this == o)
return true;
if (o == null || !getClass ().equals (o.getClass ()))
return false;
final XMLOffsetDate other = (XMLOffsetDate) o;
return m_aDate.equals (other.m_aDate) && EqualsHelper.equals (m_aOffset, other.m_aOffset);
}
/**
* A hash code for this date.
*
* @return a suitable hash code
*/
@Override
public int hashCode ()
{
return new HashCodeGenerator (this).append (m_aDate).append (m_aOffset).getHashCode ();
}
// Don't use "getAsString" for compatibility with the rest of the Java DT API
@Nonnull
@Nonempty
@Deprecated (forRemoval = false)
public String getAsString ()
{
return toString ();
}
/**
* Outputs this date as a {@code String}, such as {@code 2007-12-03+01:00}.
*
* The output will be in the ISO-8601 format {@code yyyy-MM-dd} or
* {@code yyyy-MM-ddXXXXX}.
*
* @return a string representation of this date, not null
*/
@Override
public String toString ()
{
return m_aDate.toString () + (m_aOffset != null ? m_aOffset.toString () : "");
}
}