GregorianCalendar.java
/*
* Copyright (C) 2020-2023 Dipl.-Inform. Kai Hofmann. All rights reserved!
*/
package de.powerstat.validation.values;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import de.powerstat.validation.interfaces.IValueObject;
/**
* Gregorian calendar.
*
* Not DSGVO relevant.
*
* TODO More country reform dates
*/
public final class GregorianCalendar implements Comparable<GregorianCalendar>, IValueObject
{
/* *
* Cache for singletons.
*/
// private static final Map<Country, GregorianCalendar> CACHE = new WeakHashMap<>();
/**
* Days per month.
*/
private static final int[] DAYS_IN_MONTH = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
/**
* Gregorian calendar reform before and after dates.
*/
private static final Map<Country, Map<String, Map<String, Long>>> REFORM_DATES = new ConcurrentHashMap<>();
/**
* After.
*/
private static final String AFTER = "after"; //$NON-NLS-1$
/**
* Before.
*/
private static final String BEFORE = "before"; //$NON-NLS-1$
/**
* Days.
*/
private static final String DAYS = "days"; //$NON-NLS-1$
/**
* Day.
*/
private static final String DAY = "day"; //$NON-NLS-1$
/**
* Month.
*/
private static final String MONTH = "month"; //$NON-NLS-1$
/**
* Year.
*/
private static final String YEAR = "year"; //$NON-NLS-1$
/**
* Country of gregorian calendar reform.
*/
private final Country country;
/* *
* Static initialization.
*/
static
{
final Map<String, Long> itBefore = new ConcurrentHashMap<>();
itBefore.put(GregorianCalendar.YEAR, Long.valueOf(1582));
itBefore.put(GregorianCalendar.MONTH, Long.valueOf(10));
itBefore.put(GregorianCalendar.DAY, Long.valueOf(4));
itBefore.put(GregorianCalendar.DAYS, Long.valueOf(21));
final Map<String, Long> itAfter = new ConcurrentHashMap<>();
itAfter.put(GregorianCalendar.YEAR, Long.valueOf(1582));
itAfter.put(GregorianCalendar.MONTH, Long.valueOf(10));
itAfter.put(GregorianCalendar.DAY, Long.valueOf(15));
final Map<String, Map<String, Long>> it = new ConcurrentHashMap<>();
it.put(GregorianCalendar.BEFORE, itBefore);
it.put(GregorianCalendar.AFTER, itAfter);
GregorianCalendar.REFORM_DATES.put(Country.of("IT"), it); //$NON-NLS-1$
final Map<String, Long> beBefore = new ConcurrentHashMap<>();
beBefore.put(GregorianCalendar.YEAR, Long.valueOf(1582));
beBefore.put(GregorianCalendar.MONTH, Long.valueOf(12));
beBefore.put(GregorianCalendar.DAY, Long.valueOf(21));
beBefore.put(GregorianCalendar.DAYS, Long.valueOf(21));
final Map<String, Long> beAfter = new ConcurrentHashMap<>();
beAfter.put(GregorianCalendar.YEAR, Long.valueOf(1583));
beAfter.put(GregorianCalendar.MONTH, Long.valueOf(1));
beAfter.put(GregorianCalendar.DAY, Long.valueOf(1));
final Map<String, Map<String, Long>> be = new ConcurrentHashMap<>();
be.put(GregorianCalendar.BEFORE, beBefore);
be.put(GregorianCalendar.AFTER, beAfter);
GregorianCalendar.REFORM_DATES.put(Country.of("BE"), be); //$NON-NLS-1$
final Map<String, Long> deBefore = new ConcurrentHashMap<>();
deBefore.put(GregorianCalendar.YEAR, Long.valueOf(1700));
deBefore.put(GregorianCalendar.MONTH, Long.valueOf(2));
deBefore.put(GregorianCalendar.DAY, Long.valueOf(18));
deBefore.put(GregorianCalendar.DAYS, Long.valueOf(18));
final Map<String, Long> deAfter = new ConcurrentHashMap<>();
deAfter.put(GregorianCalendar.YEAR, Long.valueOf(1700));
deAfter.put(GregorianCalendar.MONTH, Long.valueOf(3));
deAfter.put(GregorianCalendar.DAY, Long.valueOf(1));
final Map<String, Map<String, Long>> de = new ConcurrentHashMap<>();
de.put(GregorianCalendar.BEFORE, deBefore);
de.put(GregorianCalendar.AFTER, deAfter);
GregorianCalendar.REFORM_DATES.put(Country.of("DE"), de); //$NON-NLS-1$
GregorianCalendar.REFORM_DATES.put(Country.of("CH"), de); //$NON-NLS-1$
GregorianCalendar.REFORM_DATES.put(Country.of("DK"), de); //$NON-NLS-1$
final Map<String, Long> usBefore = new ConcurrentHashMap<>();
usBefore.put(GregorianCalendar.YEAR, Long.valueOf(1752));
usBefore.put(GregorianCalendar.MONTH, Long.valueOf(9));
usBefore.put(GregorianCalendar.DAY, Long.valueOf(2));
usBefore.put(GregorianCalendar.DAYS, Long.valueOf(19));
final Map<String, Long> usAfter = new ConcurrentHashMap<>();
usAfter.put(GregorianCalendar.YEAR, Long.valueOf(1752));
usAfter.put(GregorianCalendar.MONTH, Long.valueOf(9));
usAfter.put(GregorianCalendar.DAY, Long.valueOf(14));
final Map<String, Map<String, Long>> us = new ConcurrentHashMap<>();
us.put(GregorianCalendar.BEFORE, usBefore);
us.put(GregorianCalendar.AFTER, usAfter);
GregorianCalendar.REFORM_DATES.put(Country.of("US"), us); //$NON-NLS-1$
GregorianCalendar.REFORM_DATES.put(Country.of("GB"), us); //$NON-NLS-1$
final Map<String, Long> seBefore = new ConcurrentHashMap<>();
seBefore.put(GregorianCalendar.YEAR, Long.valueOf(1753));
seBefore.put(GregorianCalendar.MONTH, Long.valueOf(2));
seBefore.put(GregorianCalendar.DAY, Long.valueOf(17));
seBefore.put(GregorianCalendar.DAYS, Long.valueOf(17));
final Map<String, Long> seAfter = new ConcurrentHashMap<>();
seAfter.put(GregorianCalendar.YEAR, Long.valueOf(1753));
seAfter.put(GregorianCalendar.MONTH, Long.valueOf(3));
seAfter.put(GregorianCalendar.DAY, Long.valueOf(1));
final Map<String, Map<String, Long>> se = new ConcurrentHashMap<>();
se.put(GregorianCalendar.BEFORE, seBefore);
se.put(GregorianCalendar.AFTER, seAfter);
GregorianCalendar.REFORM_DATES.put(Country.of("SE"), se); //$NON-NLS-1$
final Map<String, Long> ruBefore = new ConcurrentHashMap<>();
ruBefore.put(GregorianCalendar.YEAR, Long.valueOf(1918));
ruBefore.put(GregorianCalendar.MONTH, Long.valueOf(1));
ruBefore.put(GregorianCalendar.DAY, Long.valueOf(31));
final Map<String, Long> ruAfter = new ConcurrentHashMap<>();
ruAfter.put(GregorianCalendar.YEAR, Long.valueOf(1918));
ruAfter.put(GregorianCalendar.MONTH, Long.valueOf(2));
ruAfter.put(GregorianCalendar.DAY, Long.valueOf(14));
ruAfter.put(GregorianCalendar.DAYS, Long.valueOf(15));
final Map<String, Map<String, Long>> ru = new ConcurrentHashMap<>();
ru.put(GregorianCalendar.BEFORE, ruBefore);
ru.put(GregorianCalendar.AFTER, ruAfter);
GregorianCalendar.REFORM_DATES.put(Country.of("RU"), ru); //$NON-NLS-1$
}
/*
21.12.1582, 01.01.1583 Netherlands: Holland, Zeeland, Brabant, Limburg, Southern Provinces
28.02.1583, 11.03.1583 Netherlands: Groningen
18.02.1700, 01.03.1700 Netherlands
30.06.1700, 12.07.1700 Netherlands: Gelderland
30.11.1700, 12.12.1700 Netherlands: Utrecht, Overijssel
31.12.1700, 12.01.1701 Netherlands: Friesland, Drenthe, Groningen
*/
/**
* Constructor.
*
* @param country Country of gregorian calendar reform
*/
private GregorianCalendar(final Country country)
{
super();
Objects.requireNonNull(country, "country"); //$NON-NLS-1$
this.country = country;
}
/**
* GregorianClendar factory.
*
* @param country Country of gregorian calendar reform
* @return GregorianDate object
*/
public static GregorianCalendar of(final Country country)
{
/*
synchronized (GregorianCalendar.class)
{
GregorianCalendar obj = GregorianCalendar.CACHE.get(country);
if (obj != null)
{
return obj;
}
obj = new GregorianCalendar(country);
GregorianCalendar.CACHE.put(country, obj);
return obj;
}
*/
return new GregorianCalendar(country);
}
/**
* GregorianClendar factory.
*
* @param value Country alpha-2 code
* @return GregorianDate object
*/
public static GregorianCalendar of(final String value)
{
return of(Country.of(value));
}
/**
* Get country.
*
* @return Country
*/
public Country getCountry()
{
return this.country;
}
/**
* Returns the value of this GregorianCalendar as a string.
*
* @return The text value represented by this object after conversion to type string.
*/
@Override
public String stringValue()
{
return this.country.stringValue();
}
/**
* Calculate hash code.
*
* @return Hash
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
return Objects.hash(this.country);
}
/**
* 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 GregorianCalendar))
{
return false;
}
final GregorianCalendar other = (GregorianCalendar)obj;
return this.country.equals(other.country);
}
/**
* Returns the string representation of this GregorianCalendar.
*
* The exact details of this representation are unspecified and subject to change, but the following may be regarded as typical:
*
* "GregorianCalendar[]"
*
* @return String representation of this GregorianCalendar
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
final var builder = new StringBuilder(27);
builder.append("GregorianCalendar[country=").append(this.country.stringValue()).append(']'); //$NON-NLS-1$
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 GregorianCalendar obj)
{
Objects.requireNonNull(obj, "obj"); //$NON-NLS-1$
int result = GregorianCalendar.REFORM_DATES.get(this.country).get(GregorianCalendar.BEFORE).get(GregorianCalendar.YEAR).compareTo(GregorianCalendar.REFORM_DATES.get(obj.country).get(GregorianCalendar.BEFORE).get(GregorianCalendar.YEAR));
if (result == 0)
{
result = GregorianCalendar.REFORM_DATES.get(this.country).get(GregorianCalendar.BEFORE).get(GregorianCalendar.MONTH).compareTo(GregorianCalendar.REFORM_DATES.get(obj.country).get(GregorianCalendar.BEFORE).get(GregorianCalendar.MONTH));
}
return result;
}
/**
* Is leap year.
*
* @param year Year
* @return true: leap year, false otherwise
*/
public boolean isLeapYear(final Year year)
{
Objects.requireNonNull(year, GregorianCalendar.YEAR);
final String beforeAfter = GregorianCalendar.REFORM_DATES.get(this.country).get(GregorianCalendar.BEFORE).get(GregorianCalendar.DAYS) == null ? GregorianCalendar.AFTER : GregorianCalendar.BEFORE;
final long reformYear = GregorianCalendar.REFORM_DATES.get(this.country).get(beforeAfter).get(GregorianCalendar.YEAR).longValue();
if (year.longValue() > reformYear)
{
return ((year.longValue() % 4) == 0) && (((year.longValue() % 100) != 0) || ((year.longValue() % 400) == 0));
}
return ((year.longValue() % 4) == 0); // TODO JulianCalendar.isLeapYear(year);
}
/**
* Days in month.
*
* @param year Year
* @param month Month (1-12)
* @return Days in month (15,17,18,19,21, 28-31)
*/
public int daysInMonth(final Year year, final Month month)
{
Objects.requireNonNull(year, GregorianCalendar.YEAR);
Objects.requireNonNull(month, GregorianCalendar.MONTH);
final String beforeAfter = GregorianCalendar.REFORM_DATES.get(this.country).get(GregorianCalendar.BEFORE).get(GregorianCalendar.DAYS) == null ? GregorianCalendar.AFTER : GregorianCalendar.BEFORE;
final long reformYear = GregorianCalendar.REFORM_DATES.get(this.country).get(beforeAfter).get(GregorianCalendar.YEAR).longValue();
final int reformMonth = GregorianCalendar.REFORM_DATES.get(this.country).get(beforeAfter).get(GregorianCalendar.MONTH).intValue();
final int restDaysInMonth = GregorianCalendar.REFORM_DATES.get(this.country).get(beforeAfter).get(GregorianCalendar.DAYS).intValue();
if ((year.longValue() == reformYear) && (month.intValue() == reformMonth)) // Depend on country
{
return restDaysInMonth;
}
return GregorianCalendar.DAYS_IN_MONTH[month.intValue()] + (((month.intValue() == 2) && isLeapYear(year)) ? 1 : 0);
}
}