1 /*
2 * Oceanus: Java Utilities
3 * Copyright 2012-2026. Tony Washer
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6 * use this file except in compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 * License for the specific language governing permissions and limitations under
15 * the License.
16 */
17 package io.github.tonywasher.joceanus.oceanus.date;
18
19 import io.github.tonywasher.joceanus.oceanus.event.OceanusEventManager;
20 import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar;
21 import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar.OceanusEventProvider;
22
23 import java.time.LocalDate;
24 import java.util.Locale;
25 import java.util.function.Predicate;
26
27 /**
28 * Date Configuration.
29 */
30 public class OceanusDateConfig
31 implements OceanusEventProvider<OceanusDateEvent> {
32 /**
33 * The Event Manager.
34 */
35 private final OceanusEventManager<OceanusDateEvent> theEventManager;
36
37 /**
38 * The formatter.
39 */
40 private final OceanusDateFormatter theFormatter;
41
42 /**
43 * The Locale.
44 */
45 private Locale theLocale;
46
47 /**
48 * Show Narrow days.
49 */
50 private boolean doShowNarrowDays;
51
52 /**
53 * Allow null date selection.
54 */
55 private boolean allowNullDateSelection;
56
57 /**
58 * The selected date.
59 */
60 private OceanusDate theSelected;
61
62 /**
63 * The earliest select-able date (or null if no lower bound).
64 */
65 private OceanusDate theEarliest;
66
67 /**
68 * The latest select-able date (or null if no upper bound).
69 */
70 private OceanusDate theLatest;
71
72 /**
73 * The list of disallowed dates.
74 */
75 private Predicate<OceanusDate> theAllowed;
76
77 /**
78 * The active display month.
79 */
80 private OceanusDate theMonth;
81
82 /**
83 * Constructor.
84 *
85 * @param pFormatter the date formatter
86 */
87 public OceanusDateConfig(final OceanusDateFormatter pFormatter) {
88 /* Create resources */
89 theFormatter = pFormatter;
90 theEventManager = new OceanusEventManager<>();
91 setLocale(pFormatter.getLocale());
92
93 /* Initialise the allowed predicate */
94 theAllowed = d -> true;
95
96 /* Listen to locale changes */
97 theFormatter.getEventRegistrar().addEventListener(e -> setLocale(theFormatter.getLocale()));
98 }
99
100 @Override
101 public OceanusEventRegistrar<OceanusDateEvent> getEventRegistrar() {
102 return theEventManager.getEventRegistrar();
103 }
104
105 /**
106 * Get the locale.
107 *
108 * @return the locale
109 */
110 public Locale getLocale() {
111 return theLocale;
112 }
113
114 /**
115 * Get the selected date.
116 *
117 * @return the Selected date
118 */
119 public OceanusDate getSelectedDate() {
120 return theSelected;
121 }
122
123 /**
124 * Get the earliest select-able date.
125 *
126 * @return the Earliest date
127 */
128 public OceanusDate getEarliestDate() {
129 return theEarliest;
130 }
131
132 /**
133 * Get the latest select-able date.
134 *
135 * @return the Latest date
136 */
137 public OceanusDate getLatestDate() {
138 return theLatest;
139 }
140
141 /**
142 * Get the current month.
143 *
144 * @return the current month
145 */
146 public OceanusDate getCurrentMonth() {
147 return theMonth;
148 }
149
150 /**
151 * Allow Null Date selection.
152 *
153 * @return true/false
154 */
155 public boolean allowNullDateSelection() {
156 return allowNullDateSelection;
157 }
158
159 /**
160 * Show Narrow Days.
161 *
162 * @return true/false
163 */
164 public boolean showNarrowDays() {
165 return doShowNarrowDays;
166 }
167
168 /**
169 * Set the Allowed predicate.
170 *
171 * @param pAllowed the predicate
172 */
173 public void setAllowed(final Predicate<OceanusDate> pAllowed) {
174 theAllowed = pAllowed;
175 }
176
177 /**
178 * Determine whether the day in the month is allowed.
179 *
180 * @param pDay the day of the current month
181 * @return true/false
182 */
183 public boolean isAllowed(final int pDay) {
184 final OceanusDate myDate = new OceanusDate(theMonth);
185 myDate.adjustDay(pDay - 1);
186 return theAllowed.test(myDate);
187 }
188
189 /**
190 * Set the Locale.
191 *
192 * @param pLocale the Locale
193 */
194 public final void setLocale(final Locale pLocale) {
195 /* Store locale */
196 theLocale = pLocale;
197
198 /* Request rebuild of names */
199 rebuildNames();
200 }
201
202 /**
203 * Allow null date selection. If this flag is set an additional button will be displayed
204 * allowing the user to explicitly select no date, thus setting the SelectedDate to null.
205 *
206 * @param pAllowNullDateSelection true/false
207 */
208 public void setAllowNullDateSelection(final boolean pAllowNullDateSelection) {
209 allowNullDateSelection = pAllowNullDateSelection;
210 }
211
212 /**
213 * Show Narrow Days. If this flag is set Days are show in narrow rather than short form.
214 *
215 * @param pShowNarrowDays true/false
216 */
217 public void setShowNarrowDays(final boolean pShowNarrowDays) {
218 /* Set options */
219 doShowNarrowDays = pShowNarrowDays;
220
221 /* Request rebuild of names */
222 rebuildNames();
223 }
224
225 /**
226 * Rebuild names.
227 */
228 protected final void rebuildNames() {
229 /* Fire event */
230 theEventManager.fireEvent(OceanusDateEvent.FORMATCHANGED);
231 }
232
233 /**
234 * Set the earliest date. This is the earliest date that may be selected. If the configured
235 * latest date is earlier than this date, it will be set to this date to ensure a valid range.
236 *
237 * @param pEarliest the Earliest select-able date (or null if unlimited)
238 */
239 public final void setEarliestDate(final OceanusDate pEarliest) {
240 /* Default the field to null */
241 theEarliest = null;
242
243 /* If we have an earliest */
244 if (pEarliest != null) {
245 /* Store the date */
246 theEarliest = pEarliest;
247
248 /* If we have a latest date, reset if necessary */
249 if (theLatest != null
250 && theLatest.compareTo(theEarliest) < 0) {
251 theLatest = theEarliest;
252 }
253 }
254 }
255
256 /**
257 * Set the latest date. This is the latest date that may be selected. If the configured earliest
258 * date is later than this date, it will be set to this date to ensure a valid range.
259 *
260 * @param pLatest the Latest select-able date (or null if unlimited)
261 */
262 public final void setLatestDate(final OceanusDate pLatest) {
263 /* Null the field */
264 theLatest = null;
265
266 /* If we have an earliest */
267 if (pLatest != null) {
268 /* Store the date */
269 theLatest = pLatest;
270
271 /* If we have an earliest date, reset if necessary */
272 if (theEarliest != null
273 && theLatest.compareTo(theEarliest) < 0) {
274 theEarliest = theLatest;
275 }
276 }
277 }
278
279 /**
280 * Format a date according to configured rules.
281 *
282 * @param pDate the date to format
283 * @return the formatted date
284 */
285 public String formatDate(final LocalDate pDate) {
286 /* Handle null */
287 if (pDate == null) {
288 return null;
289 }
290
291 /* Format the date */
292 return theFormatter.formatLocalDate(pDate);
293 }
294
295 /**
296 * Capitalise first letter of string.
297 *
298 * @param pValue the string to capitalise the first letter of
299 * @return the capitalised string
300 */
301 public String capitaliseString(final String pValue) {
302 String myValue = pValue;
303
304 /* If the first UniCode item is lowerCase */
305 if (Character.isLowerCase(pValue.codePointAt(0))) {
306 /* Locate the length of the first character */
307 final int iCharLen = pValue.offsetByCodePoints(0, 1);
308
309 /* UpperCase the first iCharLen letters */
310 myValue = pValue.substring(0, iCharLen).toUpperCase(theLocale)
311 + pValue.substring(iCharLen);
312 }
313
314 /* Return the capitalised value */
315 return myValue;
316 }
317
318 /**
319 * Obtain current date.
320 *
321 * @return the current date
322 */
323 public OceanusDate currentDate() {
324 return new OceanusDate();
325 }
326
327 /**
328 * Obtain current day of month or zero if not current month.
329 *
330 * @return the current month day
331 */
332 public int getCurrentDay() {
333 final OceanusDate myDate = currentDate();
334 return isSameMonth(myDate, theMonth)
335 ? myDate.getDay()
336 : 0;
337 }
338
339 /**
340 * Obtain Selected day of month or zero if not current month.
341 *
342 * @return the selected month day
343 */
344 public int getSelectedDay() {
345 final OceanusDate myDate = getSelectedDate();
346 return isSameMonth(myDate, theMonth)
347 ? myDate.getDay()
348 : 0;
349 }
350
351 /**
352 * Obtain Earliest day of month or zero if not current month.
353 *
354 * @return the earliest month day
355 */
356 public int getEarliestDay() {
357 return isSameMonth(theEarliest, theMonth)
358 ? theEarliest.getDay()
359 : 0;
360 }
361
362 /**
363 * Obtain Latest day of month or zero if not current month.
364 *
365 * @return the latest month day
366 */
367 public int getLatestDay() {
368 return isSameMonth(theLatest, theMonth)
369 ? theLatest.getDay()
370 : 0;
371 }
372
373 /**
374 * Adjust current month to previous month.
375 */
376 public void previousMonth() {
377 theMonth.adjustMonth(-1);
378 }
379
380 /**
381 * Adjust current month to next month.
382 */
383 public void nextMonth() {
384 theMonth.adjustMonth(1);
385 }
386
387 /**
388 * Adjust current month to previous year.
389 */
390 public void previousYear() {
391 theMonth.adjustYear(-1);
392 if (theEarliest != null
393 && theMonth.compareTo(theEarliest) < 0) {
394 theMonth = new OceanusDate(theEarliest);
395 theMonth.startCalendarMonth();
396 }
397 }
398
399 /**
400 * Adjust current month to next year.
401 */
402 public void nextYear() {
403 theMonth.adjustYear(1);
404 if (theLatest != null
405 && theMonth.compareTo(theLatest) > 0) {
406 theMonth = new OceanusDate(theLatest);
407 theMonth.startCalendarMonth();
408 }
409 }
410
411 /**
412 * Set the selected date.
413 *
414 * @param pDate the Selected date
415 */
416 public final void setSelectedDate(final OceanusDate pDate) {
417 /* Store the date */
418 theSelected = pDate;
419 }
420
421 /**
422 * Set selected day in current month (called from dialog).
423 *
424 * @param pDay the selected day
425 */
426 public void setSelectedDay(final int pDay) {
427 final OceanusDate myOld = getSelectedDate();
428 OceanusDate myNew = null;
429
430 /* If we are selecting a proper date */
431 if (pDay > 0) {
432 /* Build the new selected date */
433 myNew = new OceanusDate(theMonth);
434 myNew.adjustDay(pDay - 1);
435 }
436
437 /* Ignore if there is no change */
438 if (!isDateChanged(myOld, myNew)) {
439 return;
440 }
441
442 /* Store the explicitly selected date */
443 theSelected = myNew;
444 }
445
446 /**
447 * Initialise the current month.
448 */
449 public void initialiseCurrent() {
450 /* Access Selected Date */
451 OceanusDate myDate = theSelected;
452 if (myDate == null) {
453 myDate = currentDate();
454 }
455
456 /* Move to start date if we are earlier */
457 if (theEarliest != null
458 && myDate.compareTo(theEarliest) < 0) {
459 myDate = theEarliest;
460 }
461
462 /* Move to end date if we are later */
463 if (theLatest != null
464 && myDate.compareTo(theLatest) > 0) {
465 myDate = theLatest;
466 }
467
468 /* Set to 1st of month and record it */
469 theMonth = new OceanusDate(myDate);
470 theMonth.startCalendarMonth();
471 }
472
473 /**
474 * Has the date changed?
475 *
476 * @param pFirst the first date
477 * @param pSecond the second date
478 * @return <code>true/false</code>
479 */
480 public static boolean isDateChanged(final OceanusDate pFirst,
481 final OceanusDate pSecond) {
482 if (pFirst == null) {
483 return pSecond != null;
484 } else {
485 return !pFirst.equals(pSecond);
486 }
487 }
488
489 /**
490 * Are the dates in the same month.
491 *
492 * @param pFirst the first date (maybe null)
493 * @param pSecond the second date
494 * @return true/false
495 */
496 public static boolean isSameMonth(final OceanusDate pFirst,
497 final OceanusDate pSecond) {
498 if (!isSameYear(pFirst, pSecond)) {
499 return false;
500 } else {
501 return pFirst.getMonth() == pSecond.getMonth();
502 }
503 }
504
505 /**
506 * Are the dates in the same year.
507 *
508 * @param pFirst the first date (maybe null)
509 * @param pSecond the second date
510 * @return true/false
511 */
512 public static boolean isSameYear(final OceanusDate pFirst,
513 final OceanusDate pSecond) {
514 if (pFirst == null) {
515 return false;
516 }
517 return pFirst.getYear() == pSecond.getYear();
518 }
519 }