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 * Year.
14 *
15 * Not DSGVO relevant.
16 *
17 * TODO Weeks weeksWithin() = (50, 51,) 52, 53 (CalendarSystem, Country dependend ISO vs US)
18 * TODO min, max
19 */
20 public final class Year implements Comparable<Year>, IValueObject
21 {
22 /**
23 * Unsupported calendar system constant.
24 */
25 private static final String UNSUPPORTED_CALENDAR_SYSTEM = "Unsupported calendar system!";
26
27 /* *
28 * Cache for singletons.
29 */
30 // private static final Map<NTuple2<CalendarSystems, Long>, Year> CACHE = new WeakHashMap<>();
31
32 /**
33 * Year of Gregorian calendar reform.
34 *
35 * TODO Country dependend.
36 */
37 private static final long BEFORE_GREGORIAN_YEAR = 1582;
38
39 /**
40 * Calendar system.
41 */
42 private final CalendarSystems calendarSystem;
43
44 /**
45 * Year.
46 */
47 private final long year;
48
49
50 /**
51 * Constructor.
52 *
53 * @param calendarSystem Calendar system
54 * @param year Year != 0
55 * @throws NullPointerException When calendarSystem is null
56 * @throws IndexOutOfBoundsException When the year is 0
57 */
58 private Year(final CalendarSystems calendarSystem, final long year)
59 {
60 super();
61 Objects.requireNonNull(calendarSystem, "calendarSystem"); //$NON-NLS-1$
62 if (year == 0)
63 {
64 throw new IndexOutOfBoundsException("Year 0 does not exist!"); //$NON-NLS-1$
65 }
66 this.calendarSystem = calendarSystem;
67 this.year = year;
68 }
69
70
71 /**
72 * Year factory.
73 *
74 * @param calendarSystem Calendar system
75 * @param year Year != 0
76 * @return Year object
77 */
78 public static Year of(final CalendarSystems calendarSystem, final long year)
79 {
80 /*
81 final NTuple2<CalendarSystems, Long> tuple = NTuple2.of(calendarSystem, year);
82 synchronized (Year.class)
83 {
84 Year obj = Year.CACHE.get(tuple);
85 if (obj != null)
86 {
87 return obj;
88 }
89 obj = new Year(calendarSystem, year);
90 Year.CACHE.put(tuple, obj);
91 return obj;
92 }
93 */
94 return new Year(calendarSystem, year);
95 }
96
97
98 /**
99 * Gregorian calendar year factory.
100 *
101 * @param year Year != 0
102 * @return Year object
103 */
104 public static Year of(final long year)
105 {
106 return of(CalendarSystems.GREGORIAN, year);
107 }
108
109
110 /**
111 * Gregorian calendar year factory.
112 *
113 * @param value Year != 0 string
114 * @return Year object
115 */
116 public static Year of(final String value)
117 {
118 return of(CalendarSystems.GREGORIAN, Long.parseLong(value));
119 }
120
121
122 /**
123 * Returns the value of this Year as an long.
124 *
125 * @return The numeric value represented by this object after conversion to type long.
126 */
127 public long longValue()
128 {
129 return this.year;
130 }
131
132
133 /**
134 * Returns the value of this Year as an String.
135 *
136 * @return The numeric value represented by this object after conversion to type String.
137 */
138 @Override
139 public String stringValue()
140 {
141 return String.valueOf(this.year);
142 }
143
144
145 /**
146 * Months within year.
147 *
148 * @return Months (12) within year
149 */
150 public static Months monthsWithin()
151 {
152 return Months.of(12);
153 }
154
155
156 /**
157 * Is julian calendar leap year.
158 *
159 * @param year Julian calendar year
160 * @return true: is leap year; false otherwise
161 */
162 private static boolean isJulianLeapYear(final long year)
163 {
164 if (year <= 0)
165 {
166 return ((-year) % 4) == 1;
167 }
168 else
169 {
170 return (year % 4) == 0;
171 }
172 }
173
174
175 /**
176 * Is gregorian calendar leap year.
177 *
178 * @param year Gregorian calendar year
179 * @return true: is leap year; false otherwise
180 */
181 private static boolean isGregorianLeapYear(final long year)
182 {
183 return (((year % 4) == 0) && (((year % 100) > 0) || ((year % 400) == 0)));
184 }
185
186
187 /**
188 * Calendar system dependent leap year.
189 *
190 * @return true: is leap year; false otherwise
191 * @throws IllegalStateException When an unsupported calendar system is used
192 */
193 public boolean isLeapYear()
194 {
195 switch (this.calendarSystem)
196 {
197 case JULIAN:
198 return isJulianLeapYear(this.year);
199 case GREGORIAN:
200 if (this.year < BEFORE_GREGORIAN_YEAR) // Country dependend
201 {
202 return isJulianLeapYear(this.year);
203 }
204 else
205 {
206 return isGregorianLeapYear(this.year);
207 }
208 default:
209 throw new IllegalStateException(UNSUPPORTED_CALENDAR_SYSTEM);
210 }
211 }
212
213
214 /**
215 * Leap year dependent days within year.
216 *
217 * @return Days within year
218 * @throws IllegalStateException When an unsupported calendar system is used
219 */
220 public Days daysWithin()
221 {
222 switch (this.calendarSystem)
223 {
224 case JULIAN:
225 return Days.of(365L + (this.isLeapYear() ? 1 : 0));
226 case GREGORIAN:
227 if (this.year == BEFORE_GREGORIAN_YEAR) // Country dependend
228 {
229 return Days.of(365L - 10); // Country dependend
230 }
231 return Days.of(365L + (this.isLeapYear() ? 1 : 0));
232 default:
233 throw new IllegalStateException(UNSUPPORTED_CALENDAR_SYSTEM);
234 }
235 }
236
237
238 /**
239 * Calculate hash code.
240 *
241 * @return Hash
242 * @see java.lang.Object#hashCode()
243 */
244 @Override
245 public int hashCode()
246 {
247 return Objects.hash(this.calendarSystem, this.year);
248 }
249
250
251 /**
252 * Is equal with another object.
253 *
254 * @param obj Object
255 * @return true when equal, false otherwise
256 * @see java.lang.Object#equals(java.lang.Object)
257 */
258 @Override
259 public boolean equals(final Object obj)
260 {
261 if (this == obj)
262 {
263 return true;
264 }
265 if (!(obj instanceof Year))
266 {
267 return false;
268 }
269 final Year other = (Year)obj;
270 return this.year == other.year;
271 }
272
273
274 /**
275 * Returns the string representation of this Year.
276 *
277 * The exact details of this representation are unspecified and subject to change, but the following may be regarded as typical:
278 *
279 * "Year[calendarSystem=GREGORIAN, year=2020]"
280 *
281 * @return String representation of this Year
282 * @see java.lang.Object#toString()
283 */
284 @Override
285 public String toString()
286 {
287 final var builder = new StringBuilder(28);
288 builder.append("Year[calendarSystem=").append(this.calendarSystem).append(", year=").append(this.year).append(']'); //$NON-NLS-1$
289 return builder.toString();
290 }
291
292
293 /**
294 * Compare with another object.
295 *
296 * @param obj Object to compare with
297 * @return 0: equal; 1: greater; -1: smaller
298 * @throws IllegalStateException When the calendarSystems of the two years are not equal
299 * @see java.lang.Comparable#compareTo(java.lang.Object)
300 */
301 @Override
302 public int compareTo(final Year obj)
303 {
304 Objects.requireNonNull(obj, "obj"); //$NON-NLS-1$
305 if (this.calendarSystem.compareTo(obj.calendarSystem) != 0)
306 {
307 throw new IllegalStateException("CalendarSystems are not equal!");
308 }
309 return Long.compare(this.year, obj.year);
310 }
311
312
313 /**
314 * Add years to this year.
315 *
316 * @param years Years to add to this year
317 * @return New year after adding the years to this year with same calendarSystem
318 * @throws ArithmeticException In case of an overflow
319 */
320 public Year add(final Years years)
321 {
322 long newYear = Math.addExact(this.year, years.longValue());
323 if ((this.year < 0) && (newYear >= 0))
324 {
325 newYear = Math.incrementExact(newYear); // Because there is no year 0!
326 }
327 return Year.of(this.calendarSystem, newYear);
328 }
329
330
331 /**
332 * Subtract years from this year.
333 *
334 * @param years Years to subtract from this year
335 * @return New year after subtracting years from this year with same calendarSystem
336 * @throws ArithmeticException In case of an underflow
337 */
338 public Year subtract(final Years years)
339 {
340 long newYear = Math.subtractExact(this.year, years.longValue());
341 if ((this.year > 0) && (newYear <= 0))
342 {
343 newYear = Math.decrementExact(newYear); // Because there is no year 0!
344 }
345 return Year.of(this.calendarSystem, newYear);
346 }
347
348
349 /**
350 * Increment this year.
351 *
352 * @return New year after incrementing this year
353 * @throws ArithmeticException In case of an overflow
354 */
355 public Year increment()
356 {
357 long newYear = Math.incrementExact(this.year);
358 if (this.year == -1)
359 {
360 newYear = Math.incrementExact(newYear); // Because there is no year 0!
361 }
362 return Year.of(newYear);
363 }
364
365
366 /**
367 * Decrement this year.
368 *
369 * @return New year after decrement this year
370 * @throws ArithmeticException In case of an overflow
371 */
372 public Year decrement()
373 {
374 long newYear = Math.decrementExact(this.year);
375 if (this.year == 1)
376 {
377 newYear = Math.decrementExact(newYear); // Because there is no year 0!
378 }
379 return Year.of(newYear);
380 }
381
382 }