View Javadoc
1   /*
2    * Copyright (C) 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   * DayMonth.
14   *
15   * Not DSGVO relevant.
16   *
17   * TODO LeapYear support
18   * TODO min, max
19   */
20  public final class MonthDay implements Comparable<MonthDay>, IValueObject
21   {
22    /**
23     * Overflow constant.
24     */
25    private static final String OVERFLOW = "Overflow"; //$NON-NLS-1$
26  
27    /**
28     * Underflow constant.
29     */
30    private static final String UNDERFLOW = "Underflow"; //$NON-NLS-1$
31  
32    /**
33     * Date separator.
34     */
35    private static final String DATE_SEP = "-"; //$NON-NLS-1$
36  
37    /* *
38     * Cache for singletons.
39     */
40    // private static final Map<Integer, MonthDay> CACHE = new WeakHashMap<>();
41  
42    /**
43     * Month.
44     */
45    private final Month month;
46  
47    /**
48     * Day.
49     */
50    private final Day day;
51  
52  
53    /**
54     * Constructor.
55     *
56     * @param month Month
57     * @param day Day
58     * @throws NullPointerException When month or day is null
59     * @throws IndexOutOfBoundsException When the day is less than 1 or greater than 31 or the day is to large for the month.
60     * @throws IllegalStateException When the month is in an illegal state
61     */
62    private MonthDay(final Month month, final Day day)
63     {
64      super();
65      Objects.requireNonNull(month, "month"); //$NON-NLS-1$
66      Objects.requireNonNull(day, "day"); //$NON-NLS-1$
67      switch (month.intValue())
68       {
69        case 1:
70        case 3:
71        case 5:
72        case 7:
73        case 8:
74        case 10:
75        case 12:
76          break;
77  
78        case 4:
79        case 6:
80        case 9:
81        case 11:
82          if (day.intValue() > 30)
83           {
84            throw new IndexOutOfBoundsException("Day number out of range for the month (31)!"); //$NON-NLS-1$
85           }
86          break;
87  
88        case 2:
89          if (day.intValue() > 29)
90           {
91            throw new IndexOutOfBoundsException("Day number out of range for the month (30-31)!"); //$NON-NLS-1$
92           }
93          break;
94  
95        default:
96          throw new IllegalStateException("Illegal month!"); //$NON-NLS-1$
97       }
98      this.month = month;
99      this.day = day;
100    }
101 
102 
103   /**
104    * DayMonth factory.
105    *
106    * @param month Month 1-12
107    * @param day Day 1-31
108    * @return DayMonth object
109    */
110   public static MonthDay of(final Month month, final Day day)
111    {
112     /*
113     synchronized (MonthDay.class)
114      {
115       MonthDay obj = MonthDay.CACHE.get((month.intValue() * 100) + day.intValue());
116       if (obj != null)
117        {
118         return obj;
119        }
120       obj = new MonthDay(month, day);
121       MonthDay.CACHE.put(Integer.valueOf((month.intValue() * 100) + day.intValue()), obj);
122       return obj;
123      }
124     */
125     return new MonthDay(month, day);
126    }
127 
128 
129   /**
130    * DayMonth factory.
131    *
132    * @param value ISO8601 format [m]m-[d]d
133    * @return DayMonth object
134    */
135   public static MonthDay of(final String value)
136    {
137     final String[] values = value.split(DATE_SEP);
138     if (values.length != 2)
139      {
140       throw new IllegalArgumentException("value not of required format");
141      }
142     return of(Month.of(values[0]), Day.of(values[1]));
143    }
144 
145 
146   /**
147    * Returns the month value of this DayMonth.
148    *
149    * @return The month value represented by this object.
150    */
151   public Month monthValue()
152    {
153     return this.month;
154    }
155 
156 
157   /**
158    * Returns the day value of this DayMonth.
159    *
160    * @return The day value represented by this object.
161    */
162   public Day dayValue()
163    {
164     return this.day;
165    }
166 
167 
168   /**
169    * Returns the String value of this DayMonth in ISO8601 format.
170    *
171    * @return The String value represented by this object ([m]m-[d]d).
172    */
173   @Override
174   public String stringValue()
175    {
176     return this.month.stringValue() + DATE_SEP + this.day.stringValue();
177    }
178 
179 
180   /**
181    * Calculate hash code.
182    *
183    * @return Hash
184    * @see java.lang.Object#hashCode()
185    */
186   @Override
187   public int hashCode()
188    {
189     return Objects.hash(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 == null) || (this.getClass() != obj.getClass()))
208     if (!(obj instanceof MonthDay))
209      {
210       return false;
211      }
212     final MonthDay other = (MonthDay)obj;
213     boolean result = this.month.equals(other.month);
214     if (result)
215      {
216       result = this.day.equals(other.day);
217      }
218     return result;
219    }
220 
221 
222   /**
223    * Returns the string representation of this DayMonth.
224    *
225    * The exact details of this representation are unspecified and subject to change, but the following may be regarded as typical:
226    *
227    * "DayMonth[month=1, day=1]"
228    *
229    * @return String representation of this DayMonth
230    * @see java.lang.Object#toString()
231    */
232   @Override
233   public String toString()
234    {
235     final var builder = new StringBuilder(22);
236     builder.append("MonthDay[month=").append(this.month).append(", day=").append(this.day).append(']'); //$NON-NLS-1$
237     return builder.toString();
238    }
239 
240 
241   /**
242    * Compare with another object.
243    *
244    * @param obj Object to compare with
245    * @return 0: equal; 1: greater; -1: smaller
246    * @see java.lang.Comparable#compareTo(java.lang.Object)
247    */
248   @Override
249   public int compareTo(final MonthDay obj)
250    {
251     Objects.requireNonNull(obj, "obj"); //$NON-NLS-1$
252     int result = this.month.compareTo(obj.month);
253     if (result == 0)
254      {
255       result = this.day.compareTo(obj.day);
256      }
257     return result;
258    }
259 
260 
261   /**
262    * Add months to this MonthDay.
263    *
264    * @param months Months to add to this MonthDay
265    * @return New MonthDay after adding the months to this MonthDay
266    * @throws ArithmeticException In case of an overflow
267    */
268   public MonthDay add(final Months months)
269    {
270     final long newMonth = Math.toIntExact(Math.addExact(this.month.intValue(), months.longValue()));
271     if (newMonth > 12) // while (newMonth > 12)
272      {
273       // TODO Listener
274       // newMonthDay -= 12;
275       // incrementYear();
276       throw new ArithmeticException(MonthDay.OVERFLOW);
277      }
278     return MonthDay.of(Month.of(Math.toIntExact(newMonth)), this.day);
279    }
280 
281 
282   /**
283    * Subtract months from this MonthDay.
284    *
285    * @param months Months to subtract from this MonthDay
286    * @return New MonthDay after subtracting months from this MonthDay
287    * @throws ArithmeticException In case of an underflow
288    */
289   public MonthDay subtract(final Months months)
290    {
291     final long newMonth = Math.toIntExact(Math.subtractExact(this.month.intValue(), months.longValue()));
292     if (newMonth <= 0) // while (newMonth <= 0)
293      {
294       // TODO Listener
295       // newMonth += 12;
296       // decrementYear();
297       throw new ArithmeticException(MonthDay.UNDERFLOW);
298      }
299     return MonthDay.of(Month.of(Math.toIntExact(newMonth)), this.day);
300    }
301 
302 
303   /**
304    * Increment this MonthDay by one month.
305    *
306    * @return New MonthDay after incrementing this MonthDay by one month
307    * @throws ArithmeticException In case of an overflow
308    */
309   public MonthDay incrementMonth()
310    {
311     final int newMonth = Math.incrementExact(this.month.intValue());
312     if (newMonth == 13)
313      {
314       // TODO Listener
315       // newMonth = 1;
316       // incrementYear();
317       throw new ArithmeticException(MonthDay.OVERFLOW);
318      }
319     return MonthDay.of(Month.of(newMonth), this.day);
320    }
321 
322 
323   /**
324    * Decrement this MonthDay by one month.
325    *
326    * @return New MonthDay after decrement this MonthDay by one month
327    * @throws ArithmeticException In case of an overflow
328    */
329   public MonthDay decrementMonth()
330    {
331     final int newMonth = Math.decrementExact(this.month.intValue());
332     if (newMonth == 0)
333      {
334       // TODO Listener
335       // newMonth = 12;
336       // decrementYear();
337       throw new ArithmeticException(MonthDay.UNDERFLOW);
338      }
339     return MonthDay.of(Month.of(newMonth), this.day);
340    }
341 
342 
343   // TODO add days (leap year)
344   // TODO subtract days (leap year)
345   // TODO increment day (leap year)
346   // TODO decrement day (leap year)
347 
348  }