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 }