GregorianDate.java
/*
* Copyright (C) 2020-2023 Dipl.-Inform. Kai Hofmann. All rights reserved!
*/
package de.powerstat.validation.values;
import java.util.Objects;
import de.powerstat.validation.interfaces.IValueObject;
/**
* Gregorian calendar date.
*
* Not DSGVO relevant.
*
* TODO next day
* TODO previous day
* TODO add x days
* TODO subtract x days
* TODO add days, months, years
* TODO subtract days, months, years
* TODO getJD
* TODO getMJD
* TODO getWeekday
* TODO date - date = days
* TODO date - date = days, months, years
* TODO get WeekNr
* TODO format date
* TODO parse date
* TODO min, max
*/
public final class GregorianDate implements Comparable<GregorianDate>, IValueObject
{
/* *
* Cache for singletons.
*/
// private static final Map<NTuple4<GregorianCalendar, Year, Month, Day>, GregorianDate> CACHE = new WeakHashMap<>();
/**
* Output format.
*/
private static final String FORMAT_TWODIGIT = "%02d"; //$NON-NLS-1$
/**
* Year format.
*/
private static final String FORMAT_FOURDIGIT = "%04d"; //$NON-NLS-1$
/**
* ISO8601 separator.
*/
private static final String DATE_SEP = "-"; //$NON-NLS-1$
/**
* IT - italy constant.
*/
private static final String IT = "IT"; //$NON-NLS-1$
/**
* Gregorian calendar.
*/
private final GregorianCalendar calendar;
/**
* Year.
*/
private final Year year;
/**
* Month.
*/
private final Month month;
/**
* Day.
*/
private final Day day;
/**
* Constructor.
*
* @param calendar Gregorian calendar
* @param year Year
* @param month Month
* @param day Day
*/
private GregorianDate(final GregorianCalendar calendar, final Year year, final Month month, final Day day)
{
super();
Objects.requireNonNull(calendar, "calendar"); //$NON-NLS-1$
Objects.requireNonNull(year, "year"); //$NON-NLS-1$
Objects.requireNonNull(month, "month"); //$NON-NLS-1$
Objects.requireNonNull(day, "day"); //$NON-NLS-1$
if (day.intValue() > calendar.daysInMonth(year, month)) // TODO Does not work for gregorian reform month
{
throw new IllegalArgumentException("Day does not exists in month"); //$NON-NLS-1$
}
this.calendar = calendar;
this.year = year;
this.month = month;
this.day = day;
}
/**
* GregorianDate factory.
*
* @param calendar Gregorian calendar
* @param year Year
* @param month Month
* @param day Day
* @return GregorianDate object
*/
public static GregorianDate of(final GregorianCalendar calendar, final Year year, final Month month, final Day day)
{
/*
final NTuple4<GregorianCalendar, Year, Month, Day> tuple = NTuple4.of(calendar, year, month, day);
synchronized (GregorianDate.class)
{
GregorianDate obj = GregorianDate.CACHE.get(tuple);
if (obj != null)
{
return obj;
}
obj = new GregorianDate(calendar, year, month, day);
GregorianDate.CACHE.put(tuple, obj);
return obj;
}
*/
return new GregorianDate(calendar, year, month, day);
}
/**
* GregorianDate factory for country=IT.
*
* @param year Year
* @param month Month
* @param day Day
* @return GregorianDate object
*/
public static GregorianDate of(final Year year, final Month month, final Day day)
{
return GregorianDate.of(GregorianCalendar.of(Country.of(IT)), year, month, day);
}
/**
* GregorianDate factory for country=IT.
*
* @param value String value of ISO8601 type yyyy-mm-dd
* @return GregorianDate object
*/
public static GregorianDate of(final String value)
{
final String[] values = value.split(DATE_SEP);
if (values.length != 3)
{
throw new IllegalArgumentException("Format not as expected: yyyy-mm-dd");
}
return GregorianDate.of(GregorianCalendar.of(Country.of(IT)), Year.of(values[0]), Month.of(values[1]), Day.of(values[2]));
}
/**
* Returns the value of this GregorianDate as a string.
*
* @return The text value represented by this object after conversion to type string in ISO8601 format with - as separator.
*/
@Override
public String stringValue()
{
return String.format(GregorianDate.FORMAT_FOURDIGIT, this.year.longValue()) + GregorianDate.DATE_SEP + String.format(GregorianDate.FORMAT_TWODIGIT, this.month.intValue()) + GregorianDate.DATE_SEP + String.format(GregorianDate.FORMAT_TWODIGIT, this.day.intValue());
}
/**
* Calculate hash code.
*
* @return Hash
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
// TODO calendar
return Objects.hash(this.year, this.month, this.day);
}
/**
* Is equal with another object.
*
* @param obj Object
* @return true when equal, false otherwise
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(final Object obj)
{
if (this == obj)
{
return true;
}
if (!(obj instanceof GregorianDate))
{
return false;
}
final GregorianDate other = (GregorianDate)obj;
// TODO calendar
boolean result = this.year.equals(other.year);
if (result)
{
result = this.month.equals(other.month);
if (result)
{
result = this.day.equals(other.day);
}
}
return result;
}
/**
* Returns the string representation of this GregorianDate.
*
* The exact details of this representation are unspecified and subject to change, but the following may be regarded as typical:
*
* "GregorianDate[country=IT, date=2020-07-06]"
*
* @return String representation of this GregorianDate
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
final var builder = new StringBuilder(30);
builder.append("GregorianDate[country=").append(this.calendar.getCountry().stringValue()).append(", date=").append(stringValue()).append(']'); //$NON-NLS-1$ //$NON-NLS-2$
return builder.toString();
}
/**
* Compare with another object.
*
* @param obj Object to compare with
* @return 0: equal; 1: greater; -1: smaller
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(final GregorianDate obj)
{
Objects.requireNonNull(obj, "obj"); //$NON-NLS-1$
// TODO calendar
int result = this.year.compareTo(obj.year);
if (result == 0)
{
result = this.month.compareTo(obj.month);
if (result == 0)
{
result = this.day.compareTo(obj.day);
}
}
return result;
}
/**
* Calculate easter date for year.
*
* @param calendar Gregorian calendar
* @param year Year
* @return GregorianDate of easter for given year
*/
@SuppressWarnings("PMD.ShortVariable")
public static GregorianDate easter(final GregorianCalendar calendar, final Year year)
{
final long a = year.longValue() % 19;
final long b = year.longValue() / 100;
final long c = year.longValue() % 100;
final long d = ((((19 * a) + b) - (b / 4) - (((b - ((b + 8) / 25)) + 1) / 3)) + 15) % 30;
final long e = ((32 + (2 * (b % 4)) + (2 * (c / 4))) - d - (c % 4)) % 7;
final long f = ((d + e) - (7 * ((a + (11 * d) + (22 * e)) / 451))) + 114;
final var day = Day.of(((int)f % 31) + 1);
final var month = Month.of((int)(f / 31));
return GregorianDate.of(calendar, year, month, day);
}
}