View Javadoc
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 }