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.Objects;
8   
9   import de.powerstat.validation.interfaces.IValueObject;
10  
11  
12  /**
13   * Gregorian calendar date.
14   *
15   * Not DSGVO relevant.
16   *
17   * TODO next day
18   * TODO previous day
19   * TODO add x days
20   * TODO subtract x days
21   * TODO add days, months, years
22   * TODO subtract days, months, years
23   * TODO getJD
24   * TODO getMJD
25   * TODO getWeekday
26   * TODO date - date = days
27   * TODO date - date = days, months, years
28   * TODO get WeekNr
29   * TODO format date
30   * TODO parse date
31   * TODO min, max
32   */
33  public final class GregorianDate implements Comparable<GregorianDate>, IValueObject
34   {
35    /* *
36     * Cache for singletons.
37     */
38    // private static final Map<NTuple4<GregorianCalendar, Year, Month, Day>, GregorianDate> CACHE = new WeakHashMap<>();
39  
40    /**
41     * Output format.
42     */
43    private static final String FORMAT_TWODIGIT = "%02d"; //$NON-NLS-1$
44  
45    /**
46     * Year format.
47     */
48    private static final String FORMAT_FOURDIGIT = "%04d"; //$NON-NLS-1$
49  
50    /**
51     * ISO8601 separator.
52     */
53    private static final String DATE_SEP = "-"; //$NON-NLS-1$
54  
55    /**
56     * IT - italy constant.
57     */
58    private static final String IT = "IT"; //$NON-NLS-1$
59  
60    /**
61     * Gregorian calendar.
62     */
63    private final GregorianCalendar calendar;
64  
65    /**
66     * Year.
67     */
68    private final Year year;
69  
70    /**
71     * Month.
72     */
73    private final Month month;
74  
75    /**
76     * Day.
77     */
78    private final Day day;
79  
80  
81    /**
82     * Constructor.
83     *
84     * @param calendar Gregorian calendar
85     * @param year Year
86     * @param month Month
87     * @param day Day
88     */
89    private GregorianDate(final GregorianCalendar calendar, final Year year, final Month month, final Day day)
90     {
91      super();
92      Objects.requireNonNull(calendar, "calendar"); //$NON-NLS-1$
93      Objects.requireNonNull(year, "year"); //$NON-NLS-1$
94      Objects.requireNonNull(month, "month"); //$NON-NLS-1$
95      Objects.requireNonNull(day, "day"); //$NON-NLS-1$
96      if (day.intValue() > calendar.daysInMonth(year, month)) // TODO Does not work for gregorian reform month
97       {
98        throw new IllegalArgumentException("Day does not exists in month"); //$NON-NLS-1$
99       }
100     this.calendar = calendar;
101     this.year = year;
102     this.month = month;
103     this.day = day;
104    }
105 
106 
107   /**
108    * GregorianDate factory.
109    *
110    * @param calendar Gregorian calendar
111    * @param year Year
112    * @param month Month
113    * @param day Day
114    * @return GregorianDate object
115    */
116   public static GregorianDate of(final GregorianCalendar calendar, final Year year, final Month month, final Day day)
117    {
118     /*
119     final NTuple4<GregorianCalendar, Year, Month, Day> tuple = NTuple4.of(calendar, year, month, day);
120     synchronized (GregorianDate.class)
121      {
122       GregorianDate obj = GregorianDate.CACHE.get(tuple);
123       if (obj != null)
124        {
125         return obj;
126        }
127       obj = new GregorianDate(calendar, year, month, day);
128       GregorianDate.CACHE.put(tuple, obj);
129       return obj;
130      }
131     */
132     return new GregorianDate(calendar, year, month, day);
133    }
134 
135 
136   /**
137    * GregorianDate factory for country=IT.
138    *
139    * @param year Year
140    * @param month Month
141    * @param day Day
142    * @return GregorianDate object
143    */
144   public static GregorianDate of(final Year year, final Month month, final Day day)
145    {
146     return GregorianDate.of(GregorianCalendar.of(Country.of(IT)), year, month, day);
147    }
148 
149 
150   /**
151    * GregorianDate factory for country=IT.
152    *
153    * @param value String value of ISO8601 type yyyy-mm-dd
154    * @return GregorianDate object
155    */
156   public static GregorianDate of(final String value)
157    {
158     final String[] values = value.split(DATE_SEP);
159     if (values.length != 3)
160      {
161       throw new IllegalArgumentException("Format not as expected: yyyy-mm-dd");
162      }
163     return GregorianDate.of(GregorianCalendar.of(Country.of(IT)), Year.of(values[0]), Month.of(values[1]), Day.of(values[2]));
164    }
165 
166 
167   /**
168    * Returns the value of this GregorianDate as a string.
169    *
170    * @return The text value represented by this object after conversion to type string in ISO8601 format with - as separator.
171    */
172   @Override
173   public String stringValue()
174    {
175     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());
176    }
177 
178 
179   /**
180    * Calculate hash code.
181    *
182    * @return Hash
183    * @see java.lang.Object#hashCode()
184    */
185   @Override
186   public int hashCode()
187    {
188     // TODO calendar
189     return Objects.hash(this.year, this.month, this.day);
190    }
191 
192 
193   /**
194    * Is equal with another object.
195    *
196    * @param obj Object
197    * @return true when equal, false otherwise
198    * @see java.lang.Object#equals(java.lang.Object)
199    */
200   @Override
201   public boolean equals(final Object obj)
202    {
203     if (this == obj)
204      {
205       return true;
206      }
207     if (!(obj instanceof GregorianDate))
208      {
209       return false;
210      }
211     final GregorianDate other = (GregorianDate)obj;
212     // TODO calendar
213     boolean result = this.year.equals(other.year);
214     if (result)
215      {
216       result = this.month.equals(other.month);
217       if (result)
218        {
219         result = this.day.equals(other.day);
220        }
221      }
222     return result;
223    }
224 
225 
226   /**
227    * Returns the string representation of this GregorianDate.
228    *
229    * The exact details of this representation are unspecified and subject to change, but the following may be regarded as typical:
230    *
231    * "GregorianDate[country=IT, date=2020-07-06]"
232    *
233    * @return String representation of this GregorianDate
234    * @see java.lang.Object#toString()
235    */
236   @Override
237   public String toString()
238    {
239     final var builder = new StringBuilder(30);
240     builder.append("GregorianDate[country=").append(this.calendar.getCountry().stringValue()).append(", date=").append(stringValue()).append(']'); //$NON-NLS-1$ //$NON-NLS-2$
241     return builder.toString();
242    }
243 
244 
245   /**
246    * Compare with another object.
247    *
248    * @param obj Object to compare with
249    * @return 0: equal; 1: greater; -1: smaller
250    * @see java.lang.Comparable#compareTo(java.lang.Object)
251    */
252   @Override
253   public int compareTo(final GregorianDate obj)
254    {
255     Objects.requireNonNull(obj, "obj"); //$NON-NLS-1$
256     // TODO calendar
257     int result = this.year.compareTo(obj.year);
258     if (result == 0)
259      {
260       result = this.month.compareTo(obj.month);
261       if (result == 0)
262        {
263         result = this.day.compareTo(obj.day);
264        }
265      }
266     return result;
267    }
268 
269 
270   /**
271    * Calculate easter date for year.
272    *
273    * @param calendar Gregorian calendar
274    * @param year Year
275    * @return GregorianDate of easter for given year
276    */
277   @SuppressWarnings("PMD.ShortVariable")
278   public static GregorianDate easter(final GregorianCalendar calendar, final Year year)
279    {
280     final long a = year.longValue() % 19;
281     final long b = year.longValue() / 100;
282     final long c = year.longValue() % 100;
283     final long d = ((((19 * a) + b) - (b / 4) - (((b - ((b + 8) / 25)) + 1) / 3)) + 15) % 30;
284     final long e = ((32 + (2 * (b % 4)) + (2 * (c / 4))) - d - (c % 4)) % 7;
285     final long f = ((d + e) - (7 * ((a + (11 * d) + (22 * e)) / 451))) + 114;
286     final var day = Day.of(((int)f % 31) + 1);
287     final var month = Month.of((int)(f / 31));
288     return GregorianDate.of(calendar, year, month, day);
289    }
290 
291  }