View Javadoc
1   /*
2    * Copyright (C) 2020-2023 Dipl.-Inform. Kai Hofmann. All rights reserved!
3    */
4   package de.powerstat.validation.values;
5   
6   
7   import java.util.Map;
8   import java.util.Objects;
9   import java.util.concurrent.ConcurrentHashMap;
10  
11  import de.powerstat.validation.interfaces.IValueObject;
12  
13  
14  /**
15   * Gregorian calendar.
16   *
17   * Not DSGVO relevant.
18   *
19   * TODO More country reform dates
20   */
21  public final class GregorianCalendar implements Comparable<GregorianCalendar>, IValueObject
22   {
23    /* *
24     * Cache for singletons.
25     */
26    // private static final Map<Country, GregorianCalendar> CACHE = new WeakHashMap<>();
27  
28    /**
29     * Days per month.
30     */
31    private static final int[] DAYS_IN_MONTH = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
32  
33    /**
34     * Gregorian calendar reform before and after dates.
35     */
36    private static final Map<Country, Map<String, Map<String, Long>>> REFORM_DATES = new ConcurrentHashMap<>();
37  
38    /**
39     * After.
40     */
41    private static final String AFTER = "after"; //$NON-NLS-1$
42  
43    /**
44     * Before.
45     */
46    private static final String BEFORE = "before"; //$NON-NLS-1$
47  
48    /**
49     * Days.
50     */
51    private static final String DAYS = "days"; //$NON-NLS-1$
52  
53    /**
54     * Day.
55     */
56    private static final String DAY = "day"; //$NON-NLS-1$
57  
58    /**
59     * Month.
60     */
61    private static final String MONTH = "month"; //$NON-NLS-1$
62  
63    /**
64     * Year.
65     */
66    private static final String YEAR = "year"; //$NON-NLS-1$
67  
68    /**
69     * Country of gregorian calendar reform.
70     */
71    private final Country country;
72  
73  
74    /* *
75     * Static initialization.
76     */
77    static
78     {
79      final Map<String, Long> itBefore = new ConcurrentHashMap<>();
80      itBefore.put(GregorianCalendar.YEAR, Long.valueOf(1582));
81      itBefore.put(GregorianCalendar.MONTH, Long.valueOf(10));
82      itBefore.put(GregorianCalendar.DAY, Long.valueOf(4));
83      itBefore.put(GregorianCalendar.DAYS, Long.valueOf(21));
84      final Map<String, Long> itAfter = new ConcurrentHashMap<>();
85      itAfter.put(GregorianCalendar.YEAR, Long.valueOf(1582));
86      itAfter.put(GregorianCalendar.MONTH, Long.valueOf(10));
87      itAfter.put(GregorianCalendar.DAY, Long.valueOf(15));
88      final Map<String, Map<String, Long>> it = new ConcurrentHashMap<>();
89      it.put(GregorianCalendar.BEFORE, itBefore);
90      it.put(GregorianCalendar.AFTER, itAfter);
91      GregorianCalendar.REFORM_DATES.put(Country.of("IT"), it); //$NON-NLS-1$
92  
93      final Map<String, Long> beBefore = new ConcurrentHashMap<>();
94      beBefore.put(GregorianCalendar.YEAR, Long.valueOf(1582));
95      beBefore.put(GregorianCalendar.MONTH, Long.valueOf(12));
96      beBefore.put(GregorianCalendar.DAY, Long.valueOf(21));
97      beBefore.put(GregorianCalendar.DAYS, Long.valueOf(21));
98      final Map<String, Long> beAfter = new ConcurrentHashMap<>();
99      beAfter.put(GregorianCalendar.YEAR, Long.valueOf(1583));
100     beAfter.put(GregorianCalendar.MONTH, Long.valueOf(1));
101     beAfter.put(GregorianCalendar.DAY, Long.valueOf(1));
102     final Map<String, Map<String, Long>> be = new ConcurrentHashMap<>();
103     be.put(GregorianCalendar.BEFORE, beBefore);
104     be.put(GregorianCalendar.AFTER, beAfter);
105     GregorianCalendar.REFORM_DATES.put(Country.of("BE"), be); //$NON-NLS-1$
106 
107     final Map<String, Long> deBefore = new ConcurrentHashMap<>();
108     deBefore.put(GregorianCalendar.YEAR, Long.valueOf(1700));
109     deBefore.put(GregorianCalendar.MONTH, Long.valueOf(2));
110     deBefore.put(GregorianCalendar.DAY, Long.valueOf(18));
111     deBefore.put(GregorianCalendar.DAYS, Long.valueOf(18));
112     final Map<String, Long> deAfter = new ConcurrentHashMap<>();
113     deAfter.put(GregorianCalendar.YEAR, Long.valueOf(1700));
114     deAfter.put(GregorianCalendar.MONTH, Long.valueOf(3));
115     deAfter.put(GregorianCalendar.DAY, Long.valueOf(1));
116     final Map<String, Map<String, Long>> de = new ConcurrentHashMap<>();
117     de.put(GregorianCalendar.BEFORE, deBefore);
118     de.put(GregorianCalendar.AFTER, deAfter);
119     GregorianCalendar.REFORM_DATES.put(Country.of("DE"), de); //$NON-NLS-1$
120     GregorianCalendar.REFORM_DATES.put(Country.of("CH"), de); //$NON-NLS-1$
121     GregorianCalendar.REFORM_DATES.put(Country.of("DK"), de); //$NON-NLS-1$
122 
123     final Map<String, Long> usBefore = new ConcurrentHashMap<>();
124     usBefore.put(GregorianCalendar.YEAR, Long.valueOf(1752));
125     usBefore.put(GregorianCalendar.MONTH, Long.valueOf(9));
126     usBefore.put(GregorianCalendar.DAY, Long.valueOf(2));
127     usBefore.put(GregorianCalendar.DAYS, Long.valueOf(19));
128     final Map<String, Long> usAfter = new ConcurrentHashMap<>();
129     usAfter.put(GregorianCalendar.YEAR, Long.valueOf(1752));
130     usAfter.put(GregorianCalendar.MONTH, Long.valueOf(9));
131     usAfter.put(GregorianCalendar.DAY, Long.valueOf(14));
132     final Map<String, Map<String, Long>> us = new ConcurrentHashMap<>();
133     us.put(GregorianCalendar.BEFORE, usBefore);
134     us.put(GregorianCalendar.AFTER, usAfter);
135     GregorianCalendar.REFORM_DATES.put(Country.of("US"), us); //$NON-NLS-1$
136     GregorianCalendar.REFORM_DATES.put(Country.of("GB"), us); //$NON-NLS-1$
137 
138     final Map<String, Long> seBefore = new ConcurrentHashMap<>();
139     seBefore.put(GregorianCalendar.YEAR, Long.valueOf(1753));
140     seBefore.put(GregorianCalendar.MONTH, Long.valueOf(2));
141     seBefore.put(GregorianCalendar.DAY, Long.valueOf(17));
142     seBefore.put(GregorianCalendar.DAYS, Long.valueOf(17));
143     final Map<String, Long> seAfter = new ConcurrentHashMap<>();
144     seAfter.put(GregorianCalendar.YEAR, Long.valueOf(1753));
145     seAfter.put(GregorianCalendar.MONTH, Long.valueOf(3));
146     seAfter.put(GregorianCalendar.DAY, Long.valueOf(1));
147     final Map<String, Map<String, Long>> se = new ConcurrentHashMap<>();
148     se.put(GregorianCalendar.BEFORE, seBefore);
149     se.put(GregorianCalendar.AFTER, seAfter);
150     GregorianCalendar.REFORM_DATES.put(Country.of("SE"), se); //$NON-NLS-1$
151 
152     final Map<String, Long> ruBefore = new ConcurrentHashMap<>();
153     ruBefore.put(GregorianCalendar.YEAR, Long.valueOf(1918));
154     ruBefore.put(GregorianCalendar.MONTH, Long.valueOf(1));
155     ruBefore.put(GregorianCalendar.DAY, Long.valueOf(31));
156     final Map<String, Long> ruAfter = new ConcurrentHashMap<>();
157     ruAfter.put(GregorianCalendar.YEAR, Long.valueOf(1918));
158     ruAfter.put(GregorianCalendar.MONTH, Long.valueOf(2));
159     ruAfter.put(GregorianCalendar.DAY, Long.valueOf(14));
160     ruAfter.put(GregorianCalendar.DAYS, Long.valueOf(15));
161     final Map<String, Map<String, Long>> ru = new ConcurrentHashMap<>();
162     ru.put(GregorianCalendar.BEFORE, ruBefore);
163     ru.put(GregorianCalendar.AFTER, ruAfter);
164     GregorianCalendar.REFORM_DATES.put(Country.of("RU"), ru); //$NON-NLS-1$
165   }
166 
167   /*
168   21.12.1582, 01.01.1583 Netherlands: Holland, Zeeland, Brabant, Limburg, Southern Provinces
169   28.02.1583, 11.03.1583 Netherlands: Groningen
170   18.02.1700, 01.03.1700 Netherlands
171   30.06.1700, 12.07.1700 Netherlands: Gelderland
172   30.11.1700, 12.12.1700 Netherlands: Utrecht, Overijssel
173   31.12.1700, 12.01.1701 Netherlands: Friesland, Drenthe, Groningen
174   */
175 
176 
177   /**
178    * Constructor.
179    *
180    * @param country Country of gregorian calendar reform
181    */
182   private GregorianCalendar(final Country country)
183    {
184     super();
185     Objects.requireNonNull(country, "country"); //$NON-NLS-1$
186     this.country = country;
187    }
188 
189 
190   /**
191    * GregorianClendar factory.
192    *
193    * @param country Country of gregorian calendar reform
194    * @return GregorianDate object
195    */
196   public static GregorianCalendar of(final Country country)
197    {
198     /*
199     synchronized (GregorianCalendar.class)
200      {
201       GregorianCalendar obj = GregorianCalendar.CACHE.get(country);
202       if (obj != null)
203        {
204         return obj;
205        }
206       obj = new GregorianCalendar(country);
207       GregorianCalendar.CACHE.put(country, obj);
208       return obj;
209      }
210     */
211     return new GregorianCalendar(country);
212    }
213 
214 
215   /**
216    * GregorianClendar factory.
217    *
218    * @param value Country alpha-2 code
219    * @return GregorianDate object
220    */
221   public static GregorianCalendar of(final String value)
222    {
223     return of(Country.of(value));
224    }
225 
226 
227   /**
228    * Get country.
229    *
230    * @return Country
231    */
232   public Country getCountry()
233    {
234     return this.country;
235    }
236 
237 
238   /**
239    * Returns the value of this GregorianCalendar as a string.
240    *
241    * @return The text value represented by this object after conversion to type string.
242    */
243   @Override
244   public String stringValue()
245    {
246     return this.country.stringValue();
247    }
248 
249 
250   /**
251    * Calculate hash code.
252    *
253    * @return Hash
254    * @see java.lang.Object#hashCode()
255    */
256   @Override
257   public int hashCode()
258    {
259     return Objects.hash(this.country);
260    }
261 
262 
263   /**
264    * Is equal with another object.
265    *
266    * @param obj Object
267    * @return true when equal, false otherwise
268    * @see java.lang.Object#equals(java.lang.Object)
269    */
270   @Override
271   public boolean equals(final Object obj)
272    {
273     if (this == obj)
274      {
275       return true;
276      }
277     if (!(obj instanceof GregorianCalendar))
278      {
279       return false;
280      }
281     final GregorianCalendar other = (GregorianCalendar)obj;
282     return this.country.equals(other.country);
283    }
284 
285 
286   /**
287    * Returns the string representation of this GregorianCalendar.
288    *
289    * The exact details of this representation are unspecified and subject to change, but the following may be regarded as typical:
290    *
291    * "GregorianCalendar[]"
292    *
293    * @return String representation of this GregorianCalendar
294    * @see java.lang.Object#toString()
295    */
296   @Override
297   public String toString()
298    {
299     final var builder = new StringBuilder(27);
300     builder.append("GregorianCalendar[country=").append(this.country.stringValue()).append(']'); //$NON-NLS-1$
301     return builder.toString();
302    }
303 
304 
305   /**
306    * Compare with another object.
307    *
308    * @param obj Object to compare with
309    * @return 0: equal; 1: greater; -1: smaller
310    * @see java.lang.Comparable#compareTo(java.lang.Object)
311    */
312   @Override
313   public int compareTo(final GregorianCalendar obj)
314    {
315     Objects.requireNonNull(obj, "obj"); //$NON-NLS-1$
316     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));
317     if (result == 0)
318      {
319       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));
320      }
321     return result;
322    }
323 
324 
325   /**
326    * Is leap year.
327    *
328    * @param year Year
329    * @return true: leap year, false otherwise
330    */
331   public boolean isLeapYear(final Year year)
332    {
333     Objects.requireNonNull(year, GregorianCalendar.YEAR);
334 
335     final String beforeAfter = GregorianCalendar.REFORM_DATES.get(this.country).get(GregorianCalendar.BEFORE).get(GregorianCalendar.DAYS) == null ? GregorianCalendar.AFTER : GregorianCalendar.BEFORE;
336     final long reformYear = GregorianCalendar.REFORM_DATES.get(this.country).get(beforeAfter).get(GregorianCalendar.YEAR).longValue();
337     if (year.longValue() > reformYear)
338      {
339       return ((year.longValue() % 4) == 0) && (((year.longValue() % 100) != 0) || ((year.longValue() % 400) == 0));
340      }
341     return ((year.longValue() % 4) == 0); // TODO JulianCalendar.isLeapYear(year);
342    }
343 
344 
345   /**
346    * Days in month.
347    *
348    * @param year Year
349    * @param month Month (1-12)
350    * @return Days in month (15,17,18,19,21, 28-31)
351    */
352   public int daysInMonth(final Year year, final Month month)
353    {
354     Objects.requireNonNull(year, GregorianCalendar.YEAR);
355     Objects.requireNonNull(month, GregorianCalendar.MONTH);
356 
357     final String beforeAfter = GregorianCalendar.REFORM_DATES.get(this.country).get(GregorianCalendar.BEFORE).get(GregorianCalendar.DAYS) == null ? GregorianCalendar.AFTER : GregorianCalendar.BEFORE;
358     final long reformYear = GregorianCalendar.REFORM_DATES.get(this.country).get(beforeAfter).get(GregorianCalendar.YEAR).longValue();
359     final int reformMonth = GregorianCalendar.REFORM_DATES.get(this.country).get(beforeAfter).get(GregorianCalendar.MONTH).intValue();
360     final int restDaysInMonth = GregorianCalendar.REFORM_DATES.get(this.country).get(beforeAfter).get(GregorianCalendar.DAYS).intValue();
361     if ((year.longValue() == reformYear) && (month.intValue() == reformMonth)) // Depend on country
362      {
363       return restDaysInMonth;
364      }
365     return GregorianCalendar.DAYS_IN_MONTH[month.intValue()] + (((month.intValue() == 2) && isLeapYear(year)) ? 1 : 0);
366    }
367 
368  }