View Javadoc
1   /*
2    * Copyright (C) 2018 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.units;
17  
18  import java.text.FieldPosition;
19  import java.text.MessageFormat;
20  import java.text.NumberFormat;
21  import java.text.ParseException;
22  import java.util.Locale;
23  import java.util.regex.Pattern;
24  
25  /**
26   * Formats and parses time value and unit.
27   */
28  @SuppressWarnings("DuplicatedCode")
29  public class TimeFormatter extends MeasureFormatter<Time, TimeUnit> {
30  
31      /**
32       * Flag indicating whether nanoseconds must be included when formatting a time
33       * instance.
34       */
35      public static final int FORMAT_NANOSECONDS = 1;
36  
37      /**
38       * Flag indicating whether microseconds must be included when formatting a time
39       * instance.
40       */
41      public static final int FORMAT_MICROSECONDS = 1 << 1;
42  
43      /**
44       * Flag indicating whether milliseconds must be included when formatting a time
45       * instance.
46       */
47      public static final int FORMAT_MILLISECONDS = 1 << 2;
48  
49      /**
50       * Flag indicating whether seconds must be included when formatting a time
51       * instance.
52       */
53      public static final int FORMAT_SECONDS = 1 << 3;
54  
55      /**
56       * Flag indicating whether minutes must be included when formatting a time
57       * instance.
58       */
59      public static final int FORMAT_MINUTES = 1 << 4;
60  
61      /**
62       * Flag indicating whether hours must be included when formatting a time
63       * instance.
64       */
65      public static final int FORMAT_HOURS = 1 << 5;
66  
67      /**
68       * Flag indicating whether days must be included when formatting a time
69       * instance.
70       */
71      public static final int FORMAT_DAYS = 1 << 6;
72  
73      /**
74       * Flag indicating whether weeks must be included when formatting a time
75       * instance.
76       */
77      public static final int FORMAT_WEEKS = 1 << 7;
78  
79      /**
80       * Flag indicating whether months must be included when formatting a time
81       * instance.
82       */
83      public static final int FORMAT_MONTHS = 1 << 8;
84  
85      /**
86       * Flag indicating whether years must be included when formatting a time
87       * instance.
88       */
89      public static final int FORMAT_YEARS = 1 << 9;
90  
91      /**
92       * Flag indicating whether centuries must be included when formatting a time
93       * instance.
94       */
95      public static final int FORMAT_CENTURIES = 1 << 10;
96  
97      /**
98       * Flag indicating that all units must be included when formatting a time
99       * instance.
100      */
101     public static final int FORMAT_ALL = FORMAT_NANOSECONDS | FORMAT_MICROSECONDS | FORMAT_MILLISECONDS
102             | FORMAT_SECONDS | FORMAT_MINUTES | FORMAT_HOURS | FORMAT_DAYS | FORMAT_WEEKS | FORMAT_MONTHS
103             | FORMAT_YEARS | FORMAT_CENTURIES;
104 
105     /**
106      * Flag indicating that all time units (smaller than a day) must be used when
107      * formatting a time instance.
108      */
109     public static final int FORMAT_TIME_ALL = FORMAT_NANOSECONDS | FORMAT_MICROSECONDS | FORMAT_MILLISECONDS
110             | FORMAT_SECONDS | FORMAT_MINUTES | FORMAT_HOURS;
111 
112     /**
113      * Flag indicating that standard time units (hours, minutes and seconds) must be
114      * used when formatting a time instance.
115      */
116     public static final int FORMAT_TIME_STANDARD = FORMAT_SECONDS | FORMAT_MINUTES | FORMAT_HOURS;
117 
118     /**
119      * Flag indicating that all date units (greater than hours) must be used when
120      * formatting a time instance.
121      */
122     public static final int FORMAT_DATE_ALL = FORMAT_DAYS | FORMAT_WEEKS | FORMAT_MONTHS | FORMAT_YEARS
123             | FORMAT_CENTURIES;
124 
125     /**
126      * Flag indicating that standard date units (days, months and years) must be
127      * used when formatting a time instance.
128      */
129     public static final int FORMAT_DATE_STANDARD = FORMAT_DAYS | FORMAT_MONTHS | FORMAT_YEARS;
130 
131     /**
132      * Nanosecond symbol.
133      */
134     public static final String NANOSECOND_SYMBOL = "ns";
135 
136     /**
137      * Microsecond symbol.
138      */
139     public static final String MICROSECOND_SYMBOL = "µs";
140 
141     /**
142      * Millisecond symbol.
143      */
144     public static final String MILLISECOND_SYMBOL = "ms";
145 
146     /**
147      * Second symbol.
148      */
149     public static final String SECOND_SYMBOL = "s";
150 
151     /**
152      * Minute symbol.
153      */
154     public static final String MINUTE_SYMBOL = "min";
155 
156     /**
157      * Hour symbol.
158      */
159     public static final String HOUR_SYMBOL = "h";
160 
161     /**
162      * Day symbol.
163      */
164     public static final String DAY_SYMBOL = "d";
165 
166     /**
167      * Week symbol.
168      */
169     public static final String WEEK_SYMBOL = "wk";
170 
171     /**
172      * Month symbol.
173      */
174     public static final String MONTH_SYMBOL = "mon";
175 
176     /**
177      * Year symbol.
178      */
179     public static final String YEAR_SYMBOL = "yr";
180 
181     /**
182      * n-th century symbol.
183      */
184     public static final String CENTURY_SYMBOL = "th c.";
185 
186     /**
187      * First century symbol.
188      */
189     private static final String FIRST_CENTURY_SYMBOL = "st c.";
190 
191     /**
192      * Second century symbol.
193      */
194     private static final String SECOND_CENTURY_SYMBOL = "nd c.";
195 
196     /**
197      * Third century symbol.
198      */
199     private static final String THIRD_CENTURY_SIMBOL = "rd c.";
200 
201     /**
202      * Default pattern to format centuries.
203      * {0} corresponds to the value, {1} corresponds to the unit part.
204      */
205     private static final String CENTURY_FORMAT_PATTERN = "{0}{1}";
206 
207     /**
208      * Minimum number of digits for hours and minutes.
209      */
210     private static final int INTEGER_DIGITS = 2;
211 
212     /**
213      * Pattern to format hour and minutes (hh:mm.s).
214      */
215     private static final String HOUR_MINUTE_PATTERN = "^(\\d+):(\\d{2,})$";
216 
217 
218     /**
219      * Pattern to format hour, minutes and seconds (hh:mm:ss.ms).
220      */
221     private static final String HOUR_MINUTE_SECOND_PATTERN = "^(\\d+):(\\d{2}):(\\d{2,})$";
222 
223     /**
224      * Pattern to parse 1st century format.
225      */
226     private static final String FIRST_CENTURY_PATTERN = "(.*)(\\s+)(\\d+)(\\s?st c.)(\\s+)(.*)";
227 
228     /**
229      * Pattern to parse 2nd century format.
230      */
231     private static final String SECOND_CENTURY_PATTERN = "(.*)(\\s+)(\\d+)(\\s?nd c.)(\\s+)(.*)";
232 
233     /**
234      * Pattern to parse 3rd century format.
235      */
236     private static final String THIRD_CENTURY_PATTERN = "(.*)(\\s+)(\\d+)(\\s?rd c.)(\\s+)(.*)";
237 
238     /**
239      * Pattern to parse n-th century format.
240      */
241     private static final String CENTURY_PATTERN = "(.*)(\\s+)(\\d+)(\\s?th c.)(\\s+)(.*)";
242 
243     /**
244      * Pattern to parse year format.
245      */
246     private static final String YEAR_PATTERN = "(.*)(\\s+)(\\d+)(\\s?yr)(\\s+)(.*)";
247 
248     /**
249      * Pattern to parse month format.
250      */
251     private static final String MONTH_PATTERN = "(.*)(\\s+)(\\d+)(\\s?mon)(\\s+)(.*)";
252 
253     /**
254      * Pattern to parse week format.
255      */
256     private static final String WEEK_PATTERN = "(.*)(\\s+)(\\d+)(\\s?wk)(\\s+)(.*)";
257 
258     /**
259      * Pattern to parse day format.
260      */
261     private static final String DAY_PATTERN = "(.*)(\\s+)(\\d+)(\\s?d)(\\s+)(.*)";
262 
263     /**
264      * Pattern to parse hour format.
265      */
266     private static final String HOUR_PATTERN = "(.*)(\\s+)(\\d+)(\\s?h)(\\s+)(.*)";
267 
268     /**
269      * Pattern to parse minute format.
270      */
271     private static final String MINUTE_PATTERN = "(.*)(\\s+)(\\d+)(\\s?min)(\\s+)(.*)";
272 
273     /**
274      * Pattern to parse second format.
275      */
276     private static final String SECOND_PATTERN = "(.*)(\\s+)(\\d+)(\\s?s)(\\s+)(.*)";
277 
278     /**
279      * Pattern to parse millisecond format.
280      */
281     private static final String MILLISECOND_PATTERN = "(.*)(\\s+)(\\d+)(\\s?ms)(\\s+)(.*)";
282 
283     /**
284      * Pattern to parse microsecond format.
285      */
286     private static final String MICROSECOND_PATTERN = "(.*)(\\s+)(\\d+)(\\s?µs)(\\s+)(.*)";
287 
288     /**
289      * Pattern to parse nanosecond format.
290      */
291     private static final String NANOSECOND_PATTERN = "(.*)(\\s+)(\\d+)(\\s?ns)(\\s+)(.*)";
292 
293     /**
294      * Defines a space.
295      */
296     private static final String SPACE = " ";
297 
298     /**
299      * Pattern to parse time in hour and minute format (hh:mm.s)
300      */
301     private Pattern hourMinutePattern;
302 
303     /**
304      * Pattern to parse time in hour, minute and second format (hh:mm:ss.ms).
305      */
306     private Pattern hourMinuteSecondPattern;
307 
308     /**
309      * Pattern to parse 1st century format.
310      */
311     private Pattern firstCenturyPattern;
312 
313     /**
314      * Pattern to parse 2nd century format.
315      */
316     private Pattern secondCenturyPattern;
317 
318     /**
319      * Pattern to parse 3rd century format.
320      */
321     private Pattern thirdCenturyPattern;
322 
323     /**
324      * Pattern to parse n-th century format.
325      */
326     private Pattern centuryPattern;
327 
328     /**
329      * Pattern to parse year format.
330      */
331     private Pattern yearPattern;
332 
333     /**
334      * Pattern to parse month format.
335      */
336     private Pattern monthPattern;
337 
338     /**
339      * Pattern to parse week format.
340      */
341     private Pattern weekPattern;
342 
343     /**
344      * Pattern to parse day format.
345      */
346     private Pattern dayPattern;
347 
348     /**
349      * Pattern to parse hour format.
350      */
351     private Pattern hourPattern;
352 
353     /**
354      * Pattern to parse minute format.
355      */
356     private Pattern minutePattern;
357 
358     /**
359      * Pattern to parse second format.
360      */
361     private Pattern secondPattern;
362 
363     /**
364      * Pattern to parse millisecond format.
365      */
366     private Pattern millisecondPattern;
367 
368     /**
369      * Pattern to parse microsecond format.
370      */
371     private Pattern microsecondPattern;
372 
373     /**
374      * Pattern to parse nanosecond format.
375      */
376     private Pattern nanosecondPattern;
377 
378     /**
379      * Constructor.
380      */
381     @SuppressWarnings("WeakerAccess")
382     public TimeFormatter() {
383         super();
384     }
385 
386     /**
387      * Constructor with locale.
388      *
389      * @param locale locale.
390      * @throws IllegalArgumentException if locale is null.
391      */
392     @SuppressWarnings("WeakerAccess")
393     public TimeFormatter(final Locale locale) {
394         super(locale);
395     }
396 
397     /**
398      * Copy constructor.
399      *
400      * @param formatter input instance to copy from.
401      * @throws NullPointerException if provided formatter is null.
402      */
403     public TimeFormatter(final TimeFormatter formatter) {
404         this(formatter.getLocale());
405         hourMinutePattern = formatter.hourMinutePattern;
406         hourMinuteSecondPattern = formatter.hourMinuteSecondPattern;
407         firstCenturyPattern = formatter.firstCenturyPattern;
408         secondCenturyPattern = formatter.secondCenturyPattern;
409         thirdCenturyPattern = formatter.thirdCenturyPattern;
410         centuryPattern = formatter.centuryPattern;
411         yearPattern = formatter.yearPattern;
412         monthPattern = formatter.monthPattern;
413         weekPattern = formatter.weekPattern;
414         dayPattern = formatter.dayPattern;
415         hourPattern = formatter.hourPattern;
416         minutePattern = formatter.minutePattern;
417         secondPattern = formatter.secondPattern;
418         millisecondPattern = formatter.millisecondPattern;
419         microsecondPattern = formatter.microsecondPattern;
420         nanosecondPattern = formatter.nanosecondPattern;
421     }
422 
423     /**
424      * Determines if two time formatters are equal by comparing all of its internal parameters.
425      *
426      * @param obj another object to compare.
427      * @return true if provided object is assumed to be equal to this instance.
428      */
429     @Override
430     public boolean equals(final Object obj) {
431         final var equals = super.equals(obj);
432         return (obj instanceof TimeFormatter) && equals;
433     }
434 
435     /**
436      * Hash code generated for this instance.
437      * Hash codes can be internally used by some collections to coarsely compare objects.
438      * This implementation only calls parent implementation to avoid static analyzer warning.
439      *
440      * @return hash code.
441      */
442     @Override
443     public int hashCode() {
444         return super.hashCode();
445     }
446 
447     /**
448      * Gets unit system for detected unit into provided string representation
449      * of a measurement.
450      *
451      * @param source a measurement string representation to be checked.
452      * @return returns metric system only for units belonging to the International
453      * System of units.
454      */
455     @Override
456     public UnitSystem getUnitSystem(final String source) {
457         final var unit = findUnit(source);
458         try {
459             return unit != null ? TimeUnit.getUnitSystem(unit) : null;
460         } catch (final IllegalArgumentException e) {
461             return null;
462         }
463     }
464 
465     /**
466      * Formats provided time value and unit into a string representation.
467      *
468      * @param value a time value.
469      * @param unit  a time unit.
470      * @return string representation of provided measurement value and unit.
471      */
472     @Override
473     public String format(final Number value, final TimeUnit unit) {
474         if (unit == TimeUnit.CENTURY) {
475             final var v = value.doubleValue();
476 
477             final String symbol;
478             if (Math.abs(v) <= 1.0) {
479                 symbol = FIRST_CENTURY_SYMBOL;
480             } else if (Math.abs(v) <= 2.0) {
481                 symbol = SECOND_CENTURY_SYMBOL;
482             } else if (Math.abs(v) <= 3.0) {
483                 symbol = THIRD_CENTURY_SIMBOL;
484             } else {
485                 symbol = CENTURY_SYMBOL;
486             }
487             return MessageFormat.format(CENTURY_FORMAT_PATTERN, numberFormat.format(value), symbol);
488         } else {
489             return super.format(value, unit);
490         }
491     }
492 
493     /**
494      * Formats provided time value and unit into a string representation
495      * and appends the result into provided string buffer.
496      *
497      * @param value      a time value.
498      * @param unit       a time unit.
499      * @param toAppendTo buffer to append the result to.
500      * @param pos        field position where result will be appended.
501      * @return provided string buffer where result is appended.
502      */
503     @Override
504     public StringBuffer format(
505             final Number value, final TimeUnit unit, final StringBuffer toAppendTo, final FieldPosition pos) {
506         if (unit == TimeUnit.CENTURY) {
507             final var v = value.doubleValue();
508 
509             final String symbol;
510             if (Math.abs(v) <= 1.0) {
511                 symbol = FIRST_CENTURY_SYMBOL;
512             } else if (Math.abs(v) <= 2.0) {
513                 symbol = SECOND_CENTURY_SYMBOL;
514             } else if (Math.abs(v) <= 3.0) {
515                 symbol = THIRD_CENTURY_SIMBOL;
516             } else {
517                 symbol = CENTURY_SYMBOL;
518             }
519 
520             final var format = new MessageFormat(CENTURY_FORMAT_PATTERN);
521             return format.format(new Object[]{numberFormat.format(value), symbol}, toAppendTo, pos);
522         } else {
523             return super.format(value, unit, toAppendTo, pos);
524         }
525     }
526 
527     /**
528      * Formats and converts provided time value and unit.
529      * Unit system is ignored since time is always expressed in metric system,
530      * but some units might not belong to the international system of units.
531      * If provided value is too large for provided unit, this method will
532      * convert it to a more appropriate unit.
533      *
534      * @param value  a measurement value.
535      * @param unit   a measurement unit.
536      * @param system it is ignored.
537      * @return a string representation of measurement value and unit.
538      */
539     @Override
540     public String formatAndConvert(
541             final Number value, final TimeUnit unit, final UnitSystem system) {
542         final var v = value.doubleValue();
543 
544         final var nanoseconds = TimeConverter.convert(v, unit, TimeUnit.NANOSECOND);
545         if (Math.abs(nanoseconds) < (TimeConverter.SECONDS_PER_MICROSECOND / TimeConverter.SECONDS_PER_NANOSECOND)) {
546             return format(nanoseconds, TimeUnit.NANOSECOND);
547         }
548 
549         final var microseconds = TimeConverter.convert(v, unit, TimeUnit.MICROSECOND);
550         if (Math.abs(microseconds) < (TimeConverter.SECONDS_PER_MILLISECOND / TimeConverter.SECONDS_PER_MICROSECOND)) {
551             return format(microseconds, TimeUnit.MICROSECOND);
552         }
553 
554         final var milliseconds = TimeConverter.convert(v, unit, TimeUnit.MILLISECOND);
555         if (Math.abs(milliseconds) < (1.0 / TimeConverter.SECONDS_PER_MILLISECOND)) {
556             return format(milliseconds, TimeUnit.MILLISECOND);
557         }
558 
559         final var seconds = TimeConverter.convert(v, unit, TimeUnit.SECOND);
560         if (Math.abs(seconds) < TimeConverter.SECONDS_PER_MINUTE) {
561             return format(seconds, TimeUnit.SECOND);
562         }
563 
564         final var minutes = TimeConverter.convert(v, unit, TimeUnit.MINUTE);
565         if (Math.abs(minutes) < (TimeConverter.SECONDS_PER_HOUR / TimeConverter.SECONDS_PER_MINUTE)) {
566             return format(minutes, TimeUnit.MINUTE);
567         }
568 
569         final var hours = TimeConverter.convert(v, unit, TimeUnit.HOUR);
570         if (Math.abs(hours) < (TimeConverter.SECONDS_PER_DAY / TimeConverter.SECONDS_PER_HOUR)) {
571             return format(hours, TimeUnit.HOUR);
572         }
573 
574         final var days = TimeConverter.convert(v, unit, TimeUnit.DAY);
575         if (Math.abs(days) < (TimeConverter.SECONDS_PER_WEEK / TimeConverter.SECONDS_PER_DAY)) {
576             return format(days, TimeUnit.DAY);
577         }
578 
579         final var weeks = TimeConverter.convert(v, unit, TimeUnit.WEEK);
580         if (Math.abs(weeks) < (TimeConverter.SECONDS_PER_MONTH / TimeConverter.SECONDS_PER_WEEK)) {
581             return format(weeks, TimeUnit.WEEK);
582         }
583 
584         final var months = TimeConverter.convert(v, unit, TimeUnit.MONTH);
585         if (Math.abs(months) < (TimeConverter.SECONDS_PER_YEAR / TimeConverter.SECONDS_PER_MONTH)) {
586             return format(months, TimeUnit.MONTH);
587         }
588 
589         final var years = TimeConverter.convert(v, unit, TimeUnit.YEAR);
590         if (Math.abs(years) < (TimeConverter.SECONDS_PER_CENTURY / TimeConverter.SECONDS_PER_YEAR)) {
591             return format(years, TimeUnit.YEAR);
592         }
593 
594         final var centuries = TimeConverter.convert(v, unit, TimeUnit.CENTURY);
595         return format(centuries, TimeUnit.CENTURY);
596     }
597 
598     /**
599      * Returns unit system this instance will use based on its assigned locale.
600      *
601      * @return always returns metric system
602      */
603     @Override
604     public UnitSystem getUnitSystem() {
605         return UnitSystem.METRIC;
606     }
607 
608 
609     /**
610      * Parses provided string and tries to determine a time value and unit.
611      *
612      * @param source text to be parsed.
613      * @return time containing a value and unit.
614      * @throws ParseException       if provided string cannot be parsed.
615      * @throws UnknownUnitException if unit cannot be determined.
616      */
617     @Override
618     public Time parse(final String source) throws ParseException, UnknownUnitException {
619         return internalParse(source, new Time());
620     }
621 
622     /**
623      * Attempts to determine a time unit within a measurement string
624      * representation.
625      *
626      * @param source a measurement string representation.
627      * @return a time unit, or null if none can be determined.
628      */
629     @Override
630     public TimeUnit findUnit(final String source) {
631         if (source.contains(NANOSECOND_SYMBOL + " ") || source.endsWith(NANOSECOND_SYMBOL)) {
632             return TimeUnit.NANOSECOND;
633         }
634         if (source.contains(MICROSECOND_SYMBOL + " ") || source.endsWith(MICROSECOND_SYMBOL)) {
635             return TimeUnit.MICROSECOND;
636         }
637         if (source.contains(MILLISECOND_SYMBOL + " ") || source.endsWith(MILLISECOND_SYMBOL)) {
638             return TimeUnit.MILLISECOND;
639         }
640         if (source.contains(SECOND_SYMBOL + " ") || source.endsWith(SECOND_SYMBOL)) {
641             return TimeUnit.SECOND;
642         }
643         if (source.contains(MINUTE_SYMBOL + " ") || source.endsWith(MINUTE_SYMBOL)) {
644             return TimeUnit.MINUTE;
645         }
646         if (source.contains(WEEK_SYMBOL + " ") || source.endsWith(WEEK_SYMBOL)) {
647             return TimeUnit.WEEK;
648         }
649         if (source.contains(MONTH_SYMBOL + " ") || source.endsWith(MONTH_SYMBOL)) {
650             return TimeUnit.MONTH;
651         }
652         if (source.contains(YEAR_SYMBOL + " ") || source.endsWith(YEAR_SYMBOL)) {
653             return TimeUnit.YEAR;
654         }
655         if (source.contains(CENTURY_SYMBOL) || source.contains(FIRST_CENTURY_SYMBOL)
656                 || source.contains(SECOND_CENTURY_SYMBOL) || source.contains(THIRD_CENTURY_SIMBOL)) {
657             return TimeUnit.CENTURY;
658         }
659 
660         if (source.contains(DAY_SYMBOL + " ") || source.endsWith(DAY_SYMBOL)) {
661             return TimeUnit.DAY;
662         }
663         if (source.contains(HOUR_SYMBOL + " ") || source.endsWith(HOUR_SYMBOL)) {
664             return TimeUnit.HOUR;
665         }
666 
667         return null;
668     }
669 
670     /**
671      * Returns unit string representation.
672      *
673      * @param unit a measure unit.
674      * @return its string representation.
675      */
676     @Override
677     public String getUnitSymbol(final TimeUnit unit) {
678         return switch (unit) {
679             case NANOSECOND -> NANOSECOND_SYMBOL;
680             case MICROSECOND -> MICROSECOND_SYMBOL;
681             case MILLISECOND -> MILLISECOND_SYMBOL;
682             case MINUTE -> MINUTE_SYMBOL;
683             case HOUR -> HOUR_SYMBOL;
684             case DAY -> DAY_SYMBOL;
685             case WEEK -> WEEK_SYMBOL;
686             case MONTH -> MONTH_SYMBOL;
687             case YEAR -> YEAR_SYMBOL;
688             case CENTURY -> CENTURY_SYMBOL;
689             default -> SECOND_SYMBOL;
690         };
691     }
692 
693     /**
694      * Formats time instance using hour and minute format (hh:mm.ms).
695      *
696      * @param time time to be formatted.
697      * @return a string representation of provided time instance using hour and
698      * minute format (hh:mm.ms).
699      */
700     public String formatHourMinute(final Time time) {
701         final var exactHours = TimeConverter.convert(time.getValue().doubleValue(), time.getUnit(), TimeUnit.HOUR);
702         final var hours = Math.floor(exactHours);
703         final var diffHours = exactHours - hours;
704 
705         final var minutes = TimeConverter.convert(diffHours, TimeUnit.HOUR, TimeUnit.MINUTE);
706 
707         final var hourFormat = NumberFormat.getInstance(getLocale());
708         hourFormat.setMinimumIntegerDigits(INTEGER_DIGITS);
709         hourFormat.setMinimumFractionDigits(0);
710         hourFormat.setMaximumFractionDigits(0);
711 
712         final var minuteFormat = NumberFormat.getInstance(getLocale());
713         minuteFormat.setMinimumIntegerDigits(INTEGER_DIGITS);
714         minuteFormat.setMaximumIntegerDigits(INTEGER_DIGITS);
715 
716         if (numberFormat != null) {
717             hourFormat.setMaximumIntegerDigits(numberFormat.getMaximumIntegerDigits());
718             hourFormat.setRoundingMode(numberFormat.getRoundingMode());
719             hourFormat.setGroupingUsed(numberFormat.isGroupingUsed());
720 
721             minuteFormat.setMinimumFractionDigits(numberFormat.getMinimumFractionDigits());
722             minuteFormat.setMaximumFractionDigits(numberFormat.getMaximumFractionDigits());
723             minuteFormat.setRoundingMode(numberFormat.getRoundingMode());
724             minuteFormat.setGroupingUsed(numberFormat.isGroupingUsed());
725         }
726         return hourFormat.format(hours) + ":" + minuteFormat.format(minutes);
727     }
728 
729     /**
730      * Parses provided string representation using hour and minute format (hh:mm).
731      * Note: decimals are not accepted on either hour or minute values.
732      *
733      * @param source string to be parsed.
734      * @return parsed time.
735      * @throws ParseException       if parsing fails.
736      * @throws UnknownUnitException if format is not recognized.
737      */
738     public Time parseHourMinute(final CharSequence source)
739             throws ParseException, UnknownUnitException {
740         if (hourMinutePattern == null) {
741             hourMinutePattern = Pattern.compile(HOUR_MINUTE_PATTERN);
742         }
743 
744         final var matcher = hourMinutePattern.matcher(source);
745         if (!matcher.matches()) {
746             throw new UnknownUnitException();
747         }
748 
749         final var hourString = matcher.group(1);
750         final var minuteString = matcher.group(2);
751 
752         final var hour = numberFormat.parse(hourString);
753         final var minute = numberFormat.parse(minuteString);
754 
755         final var result = new Time(hour, TimeUnit.HOUR);
756         TimeConverter.convert(result, TimeUnit.MINUTE);
757         result.add(new Time(minute, TimeUnit.MINUTE));
758         return result;
759     }
760 
761     /**
762      * Formats this instance using hour, minute and second format (hh:mm::ss.ms).
763      *
764      * @param time time to be formatted.
765      * @return a string representation of provided time instance using hour, minute
766      * and second format (hh:mm:ss.ms).
767      */
768     public String formatHourMinuteSecond(final Time time) {
769         final var exactHours = TimeConverter.convert(time.getValue().doubleValue(), time.getUnit(), TimeUnit.HOUR);
770         final var hours = Math.floor(exactHours);
771         final var diffHours = exactHours - hours;
772 
773         final var exactMinutes = TimeConverter.convert(diffHours, TimeUnit.HOUR, TimeUnit.MINUTE);
774         final var minutes = Math.floor(exactMinutes);
775         final var diffMinutes = exactMinutes - minutes;
776 
777         final var seconds = TimeConverter.convert(diffMinutes, TimeUnit.MINUTE, TimeUnit.SECOND);
778 
779         final var hourFormat = NumberFormat.getInstance(getLocale());
780         hourFormat.setMinimumIntegerDigits(INTEGER_DIGITS);
781         hourFormat.setMinimumFractionDigits(0);
782         hourFormat.setMaximumFractionDigits(0);
783 
784         final var minuteFormat = NumberFormat.getInstance(getLocale());
785         minuteFormat.setMinimumIntegerDigits(INTEGER_DIGITS);
786         minuteFormat.setMinimumFractionDigits(0);
787         minuteFormat.setMaximumFractionDigits(0);
788 
789         final var secondFormat = NumberFormat.getInstance(getLocale());
790         secondFormat.setMinimumIntegerDigits(INTEGER_DIGITS);
791         secondFormat.setMaximumIntegerDigits(INTEGER_DIGITS);
792 
793         if (numberFormat != null) {
794             hourFormat.setMaximumIntegerDigits(numberFormat.getMaximumIntegerDigits());
795             hourFormat.setRoundingMode(numberFormat.getRoundingMode());
796             hourFormat.setGroupingUsed(numberFormat.isGroupingUsed());
797 
798             minuteFormat.setMaximumIntegerDigits(numberFormat.getMaximumIntegerDigits());
799             minuteFormat.setRoundingMode(numberFormat.getRoundingMode());
800             minuteFormat.setGroupingUsed(numberFormat.isGroupingUsed());
801 
802             secondFormat.setMinimumFractionDigits(numberFormat.getMinimumFractionDigits());
803             secondFormat.setMaximumFractionDigits(numberFormat.getMaximumFractionDigits());
804             secondFormat.setRoundingMode(numberFormat.getRoundingMode());
805             secondFormat.setGroupingUsed(numberFormat.isGroupingUsed());
806         }
807         return hourFormat.format(hours) + ":" + minuteFormat.format(minutes) + ":" + secondFormat.format(seconds);
808     }
809 
810     /**
811      * Parses provided string representation using hour, minute and second format (hh:mm:ss).
812      * Note: decimals are not accepted on either hour, minute or seconds values
813      *
814      * @param source string to be parsed.
815      * @return parsed time.
816      * @throws ParseException       if parsing fails.
817      * @throws UnknownUnitException if format is not recognized.
818      */
819     public Time parseHourMinuteSecond(final CharSequence source) throws ParseException, UnknownUnitException {
820         if (hourMinuteSecondPattern == null) {
821             hourMinuteSecondPattern = Pattern.compile(HOUR_MINUTE_SECOND_PATTERN);
822         }
823 
824         final var matcher = hourMinuteSecondPattern.matcher(source);
825         if (!matcher.matches()) {
826             throw new UnknownUnitException();
827         }
828 
829         final var hourString = matcher.group(1);
830         final var minuteString = matcher.group(2);
831         final var secondString = matcher.group(3);
832 
833         final var hour = numberFormat.parse(hourString);
834         final var minute = numberFormat.parse(minuteString);
835         final var second = numberFormat.parse(secondString);
836 
837         final var result = new Time(hour, TimeUnit.HOUR);
838         TimeConverter.convert(result, TimeUnit.MINUTE);
839         result.add(new Time(minute, TimeUnit.MINUTE));
840         TimeConverter.convert(result, TimeUnit.SECOND);
841         result.add(new Time(second, TimeUnit.SECOND));
842         return result;
843     }
844 
845     /**
846      * Formats provided time instance using required units as flags.
847      * Flags can be provided as bitwise combinations of FORMAT constants.
848      * Only non zero units will be included.
849      *
850      * @param time  time to be formatted.
851      * @param flags flags indicating units to include.
852      * @return formatted time.
853      */
854     public String formatMultiple(final Time time, final int flags) {
855         return formatMultiple(time, flags, true);
856     }
857 
858     /**
859      * Formats provided time instance using required units as flags.
860      * Flags can be provided as bitwise combinations of FORMAT constants.
861      *
862      * @param time        time to be formatted.
863      * @param flags       flags indicating units to include.
864      * @param onlyNonZero true indicates to include only non zero units, false to
865      *                    include all selected units even if they are zero.
866      * @return formatted time.
867      */
868     public String formatMultiple(final Time time, final int flags, final boolean onlyNonZero) {
869 
870         // centuries
871         final var exactCenturies = TimeConverter.convert(time.getValue().doubleValue(), time.getUnit(),
872                 TimeUnit.CENTURY);
873         var centuries = 0.0;
874         double diffCenturies;
875         if ((flags & FORMAT_CENTURIES) != 0) {
876             if ((flags & (FORMAT_YEARS | FORMAT_MONTHS | FORMAT_WEEKS | FORMAT_DAYS | FORMAT_HOURS | FORMAT_MINUTES
877                     | FORMAT_SECONDS | FORMAT_MILLISECONDS | FORMAT_MICROSECONDS | FORMAT_NANOSECONDS)) != 0) {
878                 // there are smaller units
879                 centuries = Math.floor(exactCenturies);
880             } else {
881                 centuries = exactCenturies;
882             }
883             diffCenturies = exactCenturies - centuries;
884         } else {
885             diffCenturies = exactCenturies;
886         }
887 
888         // years
889         final var exactYears = TimeConverter.convert(diffCenturies, TimeUnit.CENTURY, TimeUnit.YEAR);
890         var years = 0.0;
891         double diffYears;
892         if ((flags & FORMAT_YEARS) != 0) {
893             if ((flags & (FORMAT_MONTHS | FORMAT_WEEKS | FORMAT_DAYS | FORMAT_HOURS | FORMAT_MINUTES | FORMAT_SECONDS
894                     | FORMAT_MILLISECONDS | FORMAT_MICROSECONDS | FORMAT_NANOSECONDS)) != 0) {
895                 // there are smaller units
896                 years = Math.floor(exactYears);
897             } else {
898                 years = exactYears;
899             }
900             diffYears = exactYears - years;
901         } else {
902             diffYears = exactYears;
903         }
904 
905         // months
906         final var exactMonths = TimeConverter.convert(diffYears, TimeUnit.YEAR, TimeUnit.MONTH);
907         var months = 0.0;
908         double diffMonths;
909         if ((flags & FORMAT_MONTHS) != 0) {
910             if ((flags & (FORMAT_WEEKS | FORMAT_DAYS | FORMAT_HOURS | FORMAT_MINUTES | FORMAT_SECONDS
911                     | FORMAT_MILLISECONDS | FORMAT_MICROSECONDS | FORMAT_NANOSECONDS)) != 0) {
912                 // there are smaller units
913                 months = Math.floor(exactMonths);
914             } else {
915                 months = exactMonths;
916             }
917             diffMonths = exactMonths - months;
918         } else {
919             diffMonths = exactMonths;
920         }
921 
922         // weeks
923         final var exactWeeks = TimeConverter.convert(diffMonths, TimeUnit.MONTH, TimeUnit.WEEK);
924         var weeks = 0.0;
925         double diffWeeks;
926         if ((flags & FORMAT_WEEKS) != 0) {
927             if ((flags & (FORMAT_DAYS | FORMAT_HOURS | FORMAT_MINUTES | FORMAT_SECONDS | FORMAT_MILLISECONDS
928                     | FORMAT_MICROSECONDS | FORMAT_NANOSECONDS)) != 0) {
929                 // there are smaller units
930                 weeks = Math.floor(exactWeeks);
931             } else {
932                 weeks = exactWeeks;
933             }
934             diffWeeks = exactWeeks - weeks;
935         } else {
936             diffWeeks = exactWeeks;
937         }
938 
939         // days
940         final var exactDays = TimeConverter.convert(diffWeeks, TimeUnit.WEEK, TimeUnit.DAY);
941         var days = 0.0;
942         double diffDays;
943         if ((flags & FORMAT_DAYS) != 0) {
944             if ((flags & (FORMAT_HOURS | FORMAT_MINUTES | FORMAT_SECONDS | FORMAT_MILLISECONDS | FORMAT_MICROSECONDS
945                     | FORMAT_NANOSECONDS)) != 0) {
946                 // there are smaller units
947                 days = Math.floor(exactDays);
948             } else {
949                 days = exactDays;
950             }
951             diffDays = exactDays - days;
952         } else {
953             diffDays = exactDays;
954         }
955 
956         // hours
957         final var exactHours = TimeConverter.convert(diffDays, TimeUnit.DAY, TimeUnit.HOUR);
958         var hours = 0.0;
959         double diffHours;
960         if ((flags & FORMAT_HOURS) != 0) {
961             if ((flags & (FORMAT_MINUTES | FORMAT_SECONDS | FORMAT_MILLISECONDS | FORMAT_MICROSECONDS
962                     | FORMAT_NANOSECONDS)) != 0) {
963                 // there are smaller units
964                 hours = Math.floor(exactHours);
965             } else {
966                 hours = exactHours;
967             }
968             diffHours = exactHours - hours;
969         } else {
970             diffHours = exactHours;
971         }
972 
973         // minutes
974         final var exactMinutes = TimeConverter.convert(diffHours, TimeUnit.HOUR, TimeUnit.MINUTE);
975         var minutes = 0.0;
976         double diffMinutes;
977         if ((flags & FORMAT_MINUTES) != 0) {
978             if ((flags & (FORMAT_SECONDS | FORMAT_MILLISECONDS | FORMAT_MICROSECONDS | FORMAT_NANOSECONDS)) != 0) {
979                 // there are smaller units
980                 minutes = Math.floor(exactMinutes);
981             } else {
982                 minutes = exactMinutes;
983             }
984             diffMinutes = exactMinutes - minutes;
985         } else {
986             diffMinutes = exactMinutes;
987         }
988 
989         // seconds
990         final var exactSeconds = TimeConverter.convert(diffMinutes, TimeUnit.MINUTE, TimeUnit.SECOND);
991         var seconds = 0.0;
992         double diffSeconds;
993         if ((flags & FORMAT_SECONDS) != 0) {
994             if ((flags & (FORMAT_MILLISECONDS | FORMAT_MICROSECONDS | FORMAT_NANOSECONDS)) != 0) {
995                 // there are smaller units
996                 seconds = Math.floor(exactSeconds);
997             } else {
998                 seconds = exactSeconds;
999             }
1000             diffSeconds = exactSeconds - seconds;
1001         } else {
1002             diffSeconds = exactSeconds;
1003         }
1004 
1005         // milliseconds
1006         final var exactMilliseconds = TimeConverter.convert(diffSeconds, TimeUnit.SECOND, TimeUnit.MILLISECOND);
1007         var milliseconds = 0.0;
1008         double diffMilliseconds;
1009         if ((flags & FORMAT_MILLISECONDS) != 0) {
1010             if ((flags & (FORMAT_MICROSECONDS | FORMAT_NANOSECONDS)) != 0) {
1011                 // there are smaller units
1012                 milliseconds = Math.floor(exactMilliseconds);
1013             } else {
1014                 milliseconds = exactMilliseconds;
1015             }
1016             diffMilliseconds = exactMilliseconds - milliseconds;
1017         } else {
1018             diffMilliseconds = exactMilliseconds;
1019         }
1020 
1021         // microseconds
1022         final var exactMicroseconds = TimeConverter.convert(diffMilliseconds, TimeUnit.MILLISECOND,
1023                 TimeUnit.MICROSECOND);
1024         var microseconds = 0.0;
1025         double diffMicroseconds;
1026         if ((flags & FORMAT_MICROSECONDS) != 0) {
1027             if ((flags & (FORMAT_NANOSECONDS)) != 0) {
1028                 microseconds = Math.floor(exactMicroseconds);
1029             } else {
1030                 microseconds = exactMicroseconds;
1031             }
1032             diffMicroseconds = exactMicroseconds - microseconds;
1033         } else {
1034             diffMicroseconds = exactMicroseconds;
1035         }
1036 
1037         // nanoseconds
1038         var nanoseconds = 0.0;
1039         if ((flags & FORMAT_NANOSECONDS) != 0) {
1040             nanoseconds = TimeConverter.convert(diffMicroseconds, TimeUnit.MICROSECOND, TimeUnit.NANOSECOND);
1041         }
1042 
1043         // format result
1044         final var builder = new StringBuilder();
1045         if (((flags & FORMAT_CENTURIES) != 0) && (!onlyNonZero || centuries != 0.0)) {
1046             builder.append(format(centuries, TimeUnit.CENTURY));
1047         }
1048         if (((flags & FORMAT_YEARS) != 0) && (!onlyNonZero || years != 0.0)) {
1049             appendSpaceIfNeeded(builder).append(format(years, TimeUnit.YEAR));
1050         }
1051         if (((flags & FORMAT_MONTHS) != 0) && (!onlyNonZero || months != 0.0)) {
1052             appendSpaceIfNeeded(builder).append(format(months, TimeUnit.MONTH));
1053         }
1054         if (((flags & FORMAT_WEEKS) != 0) && (!onlyNonZero || weeks != 0.0)) {
1055             appendSpaceIfNeeded(builder).append(format(weeks, TimeUnit.WEEK));
1056         }
1057         if (((flags & FORMAT_DAYS) != 0) && (!onlyNonZero || days != 0.0)) {
1058             appendSpaceIfNeeded(builder).append(
1059                     format(days, TimeUnit.DAY));
1060         }
1061         if (((flags & FORMAT_HOURS) != 0) && (!onlyNonZero || hours != 0.0)) {
1062             appendSpaceIfNeeded(builder).append(format(hours, TimeUnit.HOUR));
1063         }
1064         if (((flags & FORMAT_MINUTES) != 0) && (!onlyNonZero || minutes != 0.0)) {
1065             appendSpaceIfNeeded(builder).append(format(minutes, TimeUnit.MINUTE));
1066         }
1067         if (((flags & FORMAT_SECONDS) != 0) && (!onlyNonZero || seconds != 0.0)) {
1068             appendSpaceIfNeeded(builder).append(format(seconds, TimeUnit.SECOND));
1069         }
1070         if (((flags & FORMAT_MILLISECONDS) != 0) && (!onlyNonZero || milliseconds != 0.0)) {
1071             appendSpaceIfNeeded(builder).append(format(milliseconds, TimeUnit.MILLISECOND));
1072         }
1073         if (((flags & FORMAT_MICROSECONDS) != 0) && (!onlyNonZero || microseconds != 0.0)) {
1074             appendSpaceIfNeeded(builder).append(format(microseconds, TimeUnit.MICROSECOND));
1075         }
1076         if (((flags & FORMAT_NANOSECONDS) != 0) && (!onlyNonZero || nanoseconds != 0.0)) {
1077             appendSpaceIfNeeded(builder).append(format(nanoseconds, TimeUnit.NANOSECOND));
1078         }
1079         return builder.toString();
1080     }
1081 
1082     /**
1083      * Parses a string containing multiple units and returns the summation of all
1084      * found values as a single Time instance.
1085      * This method does not allow decimal values with fractions or thousand
1086      * separators.
1087      *
1088      * @param source string to be parsed.
1089      * @return summation of all time values that have been parsed.
1090      * @throws ParseException       if parsing fails.
1091      * @throws UnknownUnitException if format is not recognized.
1092      */
1093     public Time parseMultiple(final CharSequence source) throws ParseException, UnknownUnitException {
1094         final var wrapped = " " + source + " ";
1095         Time result = null;
1096 
1097         final var firstCentury = parse1stCentury(wrapped);
1098         final var secondCentury = parse2ndCentury(wrapped);
1099         final var thirdCentury = parse3rdCentury(wrapped);
1100         final var century = parseCentury(wrapped);
1101         final var year = parseYear(wrapped);
1102         final var month = parseMonth(wrapped);
1103         final var week = parseWeek(wrapped);
1104         final var day = parseDay(wrapped);
1105         final var hour = parseHour(wrapped);
1106         final var minute = parseMinute(wrapped);
1107         final var second = parseSecond(wrapped);
1108         final var millisecond = parseMillisecond(wrapped);
1109         final var microsecond = parseMicrosecond(wrapped);
1110         final var nanosecond = parseNanosecond(wrapped);
1111 
1112         // century
1113         if (firstCentury != null) {
1114             result = firstCentury;
1115         }
1116         if (secondCentury != null) {
1117             result = secondCentury;
1118         }
1119         if (thirdCentury != null) {
1120             result = thirdCentury;
1121         }
1122         if (century != null) {
1123             result = century;
1124         }
1125 
1126         // year
1127         if (year != null) {
1128             if (result == null) {
1129                 result = year;
1130             } else {
1131                 result.add(year);
1132             }
1133         }
1134 
1135         // month
1136         if (month != null) {
1137             if (result == null) {
1138                 result = month;
1139             } else {
1140                 result.add(month);
1141             }
1142         }
1143 
1144         // week
1145         if (week != null) {
1146             if (result == null) {
1147                 result = week;
1148             } else {
1149                 result.add(week);
1150             }
1151         }
1152 
1153         // day
1154         if (day != null) {
1155             if (result == null) {
1156                 result = day;
1157             } else {
1158                 result.add(day);
1159             }
1160         }
1161 
1162         // hour
1163         if (hour != null) {
1164             if (result == null) {
1165                 result = hour;
1166             } else {
1167                 result.add(hour);
1168             }
1169         }
1170 
1171         // minute
1172         if (minute != null) {
1173             if (result == null) {
1174                 result = minute;
1175             } else {
1176                 result.add(minute);
1177             }
1178         }
1179 
1180         // second
1181         if (second != null) {
1182             if (result == null) {
1183                 result = second;
1184             } else {
1185                 result.add(second);
1186             }
1187         }
1188 
1189         // millisecond
1190         if (millisecond != null) {
1191             if (result == null) {
1192                 result = millisecond;
1193             } else {
1194                 result.add(millisecond);
1195             }
1196         }
1197 
1198         // microsecond
1199         if (microsecond != null) {
1200             if (result == null) {
1201                 result = microsecond;
1202             } else {
1203                 result.add(microsecond);
1204             }
1205         }
1206 
1207         // nanosecond
1208         if (nanosecond != null) {
1209             if (result == null) {
1210                 result = nanosecond;
1211             } else {
1212                 result.add(nanosecond);
1213             }
1214         }
1215 
1216         if (result == null) {
1217             // no valid unit was found
1218             throw new UnknownUnitException();
1219         }
1220 
1221         return result;
1222     }
1223 
1224     /**
1225      * Parses string as 1st century format.
1226      * This method does not allow decimal values with fractions or thousand
1227      * separators.
1228      *
1229      * @param source string to be parsed.
1230      * @return parsed time in century units.
1231      * @throws ParseException if parsing fails.
1232      */
1233     private Time parse1stCentury(final CharSequence source) throws ParseException {
1234         if (firstCenturyPattern == null) {
1235             firstCenturyPattern = Pattern.compile(FIRST_CENTURY_PATTERN);
1236         }
1237 
1238         final var matcher = firstCenturyPattern.matcher(source);
1239         if (!matcher.matches()) {
1240             return null;
1241         }
1242 
1243         return new Time(numberFormat.parse(matcher.group(3)), TimeUnit.CENTURY);
1244     }
1245 
1246     /**
1247      * Parses string as 2nd century format.
1248      * This method does not allow decimal values with fractions or thousand
1249      * separators.
1250      *
1251      * @param source string to be parsed.
1252      * @return parsed time in century units.
1253      * @throws ParseException if parsing fails.
1254      */
1255     private Time parse2ndCentury(final CharSequence source) throws ParseException {
1256         if (secondCenturyPattern == null) {
1257             secondCenturyPattern = Pattern.compile(SECOND_CENTURY_PATTERN);
1258         }
1259 
1260         final var matcher = secondCenturyPattern.matcher(source);
1261         if (!matcher.matches()) {
1262             return null;
1263         }
1264 
1265         return new Time(numberFormat.parse(matcher.group(3)), TimeUnit.CENTURY);
1266     }
1267 
1268     /**
1269      * Parses string as 3rd century format.
1270      * This method does not allow decimal values with fractions or thousand
1271      * separators.
1272      *
1273      * @param source string to be parsed.
1274      * @return parsed time in century units.
1275      * @throws ParseException if parsing fails.
1276      */
1277     private Time parse3rdCentury(final CharSequence source) throws ParseException {
1278         if (thirdCenturyPattern == null) {
1279             thirdCenturyPattern = Pattern.compile(THIRD_CENTURY_PATTERN);
1280         }
1281 
1282         final var matcher = thirdCenturyPattern.matcher(source);
1283         if (!matcher.matches()) {
1284             return null;
1285         }
1286 
1287         return new Time(numberFormat.parse(matcher.group(3)), TimeUnit.CENTURY);
1288     }
1289 
1290     /**
1291      * Parses string as n-th century format.
1292      * This method does not allow decimal values with fractions or thousand
1293      * separators.
1294      *
1295      * @param source string to be parsed.
1296      * @return parsed time in century units.
1297      * @throws ParseException if parsing fails.
1298      */
1299     private Time parseCentury(final CharSequence source) throws ParseException {
1300         if (centuryPattern == null) {
1301             centuryPattern = Pattern.compile(CENTURY_PATTERN);
1302         }
1303 
1304         final var matcher = centuryPattern.matcher(source);
1305         if (!matcher.matches()) {
1306             return null;
1307         }
1308 
1309         return new Time(numberFormat.parse(matcher.group(3)), TimeUnit.CENTURY);
1310     }
1311 
1312     /**
1313      * Parses string as year format.
1314      * This method does not allow decimal values with fractions or thousand
1315      * separators.
1316      *
1317      * @param source string to be parsed.
1318      * @return parsed time in year units.
1319      * @throws ParseException if parsing fails.
1320      */
1321     private Time parseYear(final CharSequence source) throws ParseException {
1322         if (yearPattern == null) {
1323             yearPattern = Pattern.compile(YEAR_PATTERN);
1324         }
1325 
1326         final var matcher = yearPattern.matcher(source);
1327         if (!matcher.matches()) {
1328             return null;
1329         }
1330 
1331         return new Time(numberFormat.parse(matcher.group(3)), TimeUnit.YEAR);
1332     }
1333 
1334     /**
1335      * Parses string as month format.
1336      * This method does not allow decimal values with fractions or thousand
1337      * separators.
1338      *
1339      * @param source string to be parsed.
1340      * @return parsed time in month units.
1341      * @throws ParseException if parsing fails.
1342      */
1343     private Time parseMonth(final CharSequence source) throws ParseException {
1344         if (monthPattern == null) {
1345             monthPattern = Pattern.compile(MONTH_PATTERN);
1346         }
1347 
1348         final var matcher = monthPattern.matcher(source);
1349         if (!matcher.matches()) {
1350             return null;
1351         }
1352 
1353         return new Time(numberFormat.parse(matcher.group(3)), TimeUnit.MONTH);
1354     }
1355 
1356     /**
1357      * Parses string as week format.
1358      * This method does not allow decimal values with fractions or thousand
1359      * separators.
1360      *
1361      * @param source string to be parsed.
1362      * @return parsed time in week units.
1363      * @throws ParseException if parsing fails.
1364      */
1365     private Time parseWeek(final CharSequence source) throws ParseException {
1366         if (weekPattern == null) {
1367             weekPattern = Pattern.compile(WEEK_PATTERN);
1368         }
1369 
1370         final var matcher = weekPattern.matcher(source);
1371         if (!matcher.matches()) {
1372             return null;
1373         }
1374 
1375         return new Time(numberFormat.parse(matcher.group(3)), TimeUnit.WEEK);
1376     }
1377 
1378     /**
1379      * Parses string as day format.
1380      * This method does not allow decimal values with fractions or thousand
1381      * separators.
1382      *
1383      * @param source string to be parsed.
1384      * @return parsed time in day units.
1385      * @throws ParseException if parsing fails.
1386      */
1387     private Time parseDay(final CharSequence source) throws ParseException {
1388         if (dayPattern == null) {
1389             dayPattern = Pattern.compile(DAY_PATTERN);
1390         }
1391 
1392         final var matcher = dayPattern.matcher(source);
1393         if (!matcher.matches()) {
1394             return null;
1395         }
1396 
1397         return new Time(numberFormat.parse(matcher.group(3)), TimeUnit.DAY);
1398     }
1399 
1400     /**
1401      * Parses string as hour format.
1402      * This method does not allow decimal values with fractions or thousand
1403      * separators.
1404      *
1405      * @param source string to be parsed.
1406      * @return parsed time in hour units.
1407      * @throws ParseException if parsing fails.
1408      */
1409     private Time parseHour(final CharSequence source) throws ParseException {
1410         if (hourPattern == null) {
1411             hourPattern = Pattern.compile(HOUR_PATTERN);
1412         }
1413 
1414         final var matcher = hourPattern.matcher(source);
1415         if (!matcher.matches()) {
1416             return null;
1417         }
1418 
1419         return new Time(numberFormat.parse(matcher.group(3)), TimeUnit.HOUR);
1420     }
1421 
1422     /**
1423      * Parses string as minute format.
1424      * This method does not allow decimal values with fractions or thousand
1425      * separators.
1426      *
1427      * @param source string to be parsed.
1428      * @return parsed time in minute units.
1429      * @throws ParseException if parsing fails.
1430      */
1431     private Time parseMinute(final CharSequence source) throws ParseException {
1432         if (minutePattern == null) {
1433             minutePattern = Pattern.compile(MINUTE_PATTERN);
1434         }
1435 
1436         final var matcher = minutePattern.matcher(source);
1437         if (!matcher.matches()) {
1438             return null;
1439         }
1440 
1441         return new Time(numberFormat.parse(matcher.group(3)), TimeUnit.MINUTE);
1442     }
1443 
1444     /**
1445      * Parses string as second format.
1446      * This method does not allow decimal values with fractions or thousand
1447      * separators.
1448      *
1449      * @param source string to be parsed.
1450      * @return parsed time in second units.
1451      * @throws ParseException if parsing fails.
1452      */
1453     private Time parseSecond(final CharSequence source) throws ParseException {
1454         if (secondPattern == null) {
1455             secondPattern = Pattern.compile(SECOND_PATTERN);
1456         }
1457 
1458         final var matcher = secondPattern.matcher(source);
1459         if (!matcher.matches()) {
1460             return null;
1461         }
1462 
1463         return new Time(numberFormat.parse(matcher.group(3)), TimeUnit.SECOND);
1464     }
1465 
1466     /**
1467      * Parses string as millisecond format.
1468      * This method does not allow decimal values with fractions or thousand
1469      * separators.
1470      *
1471      * @param source string to be parsed.
1472      * @return parsed time in millisecond units.
1473      * @throws ParseException if parsing fails.
1474      */
1475     private Time parseMillisecond(final CharSequence source) throws ParseException {
1476         if (millisecondPattern == null) {
1477             millisecondPattern = Pattern.compile(MILLISECOND_PATTERN);
1478         }
1479 
1480         final var matcher = millisecondPattern.matcher(source);
1481         if (!matcher.matches()) {
1482             return null;
1483         }
1484 
1485         return new Time(numberFormat.parse(matcher.group(3)), TimeUnit.MILLISECOND);
1486     }
1487 
1488     /**
1489      * Parses string as microsecond format.
1490      * This method does not allow decimal values with fractions or thousand
1491      * separators.
1492      *
1493      * @param source string to be parsed.
1494      * @return parsed time in microsecond units.
1495      * @throws ParseException if parsing fails.
1496      */
1497     private Time parseMicrosecond(final CharSequence source) throws ParseException {
1498         if (microsecondPattern == null) {
1499             microsecondPattern = Pattern.compile(MICROSECOND_PATTERN);
1500         }
1501 
1502         final var matcher = microsecondPattern.matcher(source);
1503         if (!matcher.matches()) {
1504             return null;
1505         }
1506 
1507         return new Time(numberFormat.parse(matcher.group(3)), TimeUnit.MICROSECOND);
1508     }
1509 
1510     /**
1511      * Parses string as nanosecond format.
1512      * This method does not allow decimal values with fractions or thousand
1513      * separators.
1514      *
1515      * @param source string to be parsed.
1516      * @return parsed time in nanosecond units.
1517      * @throws ParseException if parsing fails.
1518      */
1519     private Time parseNanosecond(final CharSequence source) throws ParseException {
1520         if (nanosecondPattern == null) {
1521             nanosecondPattern = Pattern.compile(NANOSECOND_PATTERN);
1522         }
1523 
1524         final var matcher = nanosecondPattern.matcher(source);
1525         if (!matcher.matches()) {
1526             return null;
1527         }
1528 
1529         return new Time(numberFormat.parse(matcher.group(3)), TimeUnit.NANOSECOND);
1530     }
1531 
1532     /**
1533      * Appends a space if builder is not empty.
1534      *
1535      * @param builder builder to add space to.
1536      * @return same instance provided as parameter.
1537      */
1538     private StringBuilder appendSpaceIfNeeded(final StringBuilder builder) {
1539         if (!builder.isEmpty()) {
1540             builder.append(SPACE);
1541         }
1542         return builder;
1543     }
1544 }