View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.datamodel.common;
20  
21  import static java.time.temporal.ChronoField.DAY_OF_MONTH;
22  import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
23  import static java.time.temporal.ChronoField.YEAR;
24  
25  import java.time.ZoneOffset;
26  import java.time.chrono.IsoChronology;
27  import java.time.format.DateTimeFormatter;
28  import java.time.format.DateTimeFormatterBuilder;
29  import java.time.format.ResolverStyle;
30  import java.time.temporal.ChronoField;
31  import java.util.Locale;
32  import java.util.regex.Matcher;
33  import java.util.regex.Pattern;
34  
35  /**
36   * is a helper class for MCRMetaISO8601Date. Please be aware that this class is not supported. It may disappear some day
37   * or methods get removed.
38   *
39   * @author Thomas Scheffler (yagee)
40   * @version $Revision: 18729 $ $Date: 2010-09-21 12:33:45 +0200 (Di, 21. Sep 2010) $
41   * @since 1.3
42   */
43  public final class MCRISO8601FormatChooser {
44  
45      public static final DateTimeFormatter YEAR_FORMAT = ISODateTimeFormat.year();
46  
47      public static final DateTimeFormatter YEAR_MONTH_FORMAT = ISODateTimeFormat.yearMonth();
48  
49      public static final DateTimeFormatter COMPLETE_FORMAT = ISODateTimeFormat.date();
50  
51      public static final DateTimeFormatter COMPLETE_HH_MM_FORMAT = ISODateTimeFormat.dateHourMinute();
52  
53      public static final DateTimeFormatter COMPLETE_HH_MM_SS_FORMAT = ISODateTimeFormat.dateTimeNoMillis();
54  
55      public static final DateTimeFormatter COMPLETE_HH_MM_SS_SSS_FORMAT = ISODateTimeFormat.dateTime();
56  
57      private static final Pattern MILLI_CHECK_PATTERN = Pattern.compile("\\.\\d{4,}\\+");
58  
59      private static final boolean USE_UTC = true;
60  
61      /**
62       * returns a DateTimeFormatter for the given isoString or format. This method prefers the format parameter. So if
63       * it's not null or not zero length this method will interpret the format string. You can also get a formatter for e
64       * specific iso String. In either case if the underlying algorithm can not determine an exact matching formatter it
65       * will allway fall back to a default. So this method will never return null.
66       *
67       * @param isoString
68       *            an ISO 8601 formatted time String, or null
69       * @param isoFormat
70       *            a valid format String, or null
71       * @return returns a specific DateTimeFormatter
72       */
73      public static DateTimeFormatter getFormatter(String isoString, MCRISO8601Format isoFormat) {
74          DateTimeFormatter df;
75          if (isoFormat != null) {
76              df = getFormatterForFormat(isoFormat);
77          } else if (isoString != null && isoString.length() != 0) {
78              df = getFormatterForDuration(isoString);
79          } else {
80              df = COMPLETE_HH_MM_SS_SSS_FORMAT;
81          }
82          if (USE_UTC) {
83              df = df.withZone(ZoneOffset.UTC);
84          }
85          return df;
86      }
87  
88      private static DateTimeFormatter getFormatterForFormat(MCRISO8601Format isoFormat) {
89          switch (isoFormat) {
90          case YEAR:
91              return YEAR_FORMAT;
92          case YEAR_MONTH:
93              return YEAR_MONTH_FORMAT;
94          case COMPLETE:
95              return COMPLETE_FORMAT;
96          case COMPLETE_HH_MM:
97              return COMPLETE_HH_MM_FORMAT;
98          case COMPLETE_HH_MM_SS:
99              return COMPLETE_HH_MM_SS_FORMAT;
100         case COMPLETE_HH_MM_SS_SSS:
101             return COMPLETE_HH_MM_SS_SSS_FORMAT;
102         case YEAR_ERA:
103             return YEAR_FORMAT;
104         case YEAR_MONTH_ERA:
105             return YEAR_MONTH_FORMAT;
106         case COMPLETE_ERA:
107             return COMPLETE_FORMAT;
108         case COMPLETE_HH_MM_ERA:
109             return COMPLETE_HH_MM_FORMAT;
110         case COMPLETE_HH_MM_SS_ERA:
111             return COMPLETE_HH_MM_SS_FORMAT;
112         case COMPLETE_HH_MM_SS_SSS_ERA:
113             return COMPLETE_HH_MM_SS_SSS_FORMAT;
114         default:
115             return COMPLETE_HH_MM_SS_SSS_FORMAT;
116         }
117     }
118 
119     private static DateTimeFormatter getFormatterForDuration(String isoString) {
120         boolean test = false;
121         switch (isoString.length()) {
122         case 1:
123         case 2:
124         case 3:
125         case 4:
126         case 5:
127             return YEAR_FORMAT;
128         case 6:
129         case 7:
130         case 8:
131             return YEAR_MONTH_FORMAT;
132         case 10:
133         case 11:
134             return COMPLETE_FORMAT;
135         case 17: // YYYY-MM-DDThh:mm'Z'
136             test = true;
137         case 22:
138             if (test || !isoString.endsWith("Z")) {
139                 // YYYY-MM-DDThh:mm[+-]hh:mm
140                 return COMPLETE_HH_MM_FORMAT;
141             }
142             // YYYY-MM-DDThh:mm:ss.s'Z'
143             return COMPLETE_HH_MM_SS_SSS_FORMAT;
144         case 20: // YYYY-MM-DDThh:mm:ss'Z'
145         case 25: // YYYY-MM-DDThh:mm:ss[+-]hh:mm
146             return COMPLETE_HH_MM_SS_FORMAT;
147         case 23: // YYYY-MM-DDThh:mm:ss.ss'Z'
148         case 24: // YYYY-MM-DDThh:mm:ss.sss'Z'
149         case 27: // YYYY-MM-DDThh:mm:ss.s[+-]hh:mm
150         case 28: // YYYY-MM-DDThh:mm:ss.ss[+-]hh:mm
151         case 29: // YYYY-MM-DDThh:mm:ss.ss[+-]hh:mm
152             return COMPLETE_HH_MM_SS_SSS_FORMAT;
153         default:
154             return COMPLETE_HH_MM_SS_SSS_FORMAT;
155         }
156     }
157 
158     /**
159      * returns a String that has not more than 3 digits representing "fractions of a second". If isoString has no or not
160      * more than 3 digits this method simply returns isoString.
161      *
162      * @param isoString
163      *            an ISO 8601 formatted time String
164      * @return an ISO 8601 formatted time String with at max 3 digits for fractions of a second
165      */
166     public static String cropSecondFractions(String isoString) {
167         Matcher matcher = MILLI_CHECK_PATTERN.matcher(isoString);
168         boolean result = matcher.find();
169         if (result) {
170             return matcher.replaceFirst(isoString.substring(matcher.start(), matcher.start() + 4) + "+");
171         }
172         return isoString;
173     }
174 
175     private static class ISODateTimeFormat {
176 
177         public static DateTimeFormatter year() {
178             return DateTimeFormatter.ofPattern("uuuu", Locale.ROOT);
179         }
180 
181         public static DateTimeFormatter yearMonth() {
182             return DateTimeFormatter.ofPattern("uuuu-MM", Locale.ROOT);
183         }
184 
185         public static DateTimeFormatter date() {
186             return DateTimeFormatter.ISO_LOCAL_DATE;
187         }
188 
189         public static DateTimeFormatter dateHourMinute() {
190             return new DateTimeFormatterBuilder().parseCaseInsensitive()
191                 .appendValue(YEAR, 4)
192                 .appendLiteral('-')
193                 .appendValue(MONTH_OF_YEAR, 2)
194                 .appendLiteral('-')
195                 .appendValue(DAY_OF_MONTH, 2)
196                 .appendLiteral('T')
197                 .appendValue(ChronoField.HOUR_OF_DAY, 2)
198                 .appendLiteral(':')
199                 .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
200                 .appendOffset("+HH:MM", "Z")
201                 .toFormatter(Locale.ROOT)
202                 .withChronology(IsoChronology.INSTANCE)
203                 .withResolverStyle(ResolverStyle.STRICT);
204         }
205 
206         public static DateTimeFormatter dateTimeNoMillis() {
207             return new DateTimeFormatterBuilder().parseCaseInsensitive()
208                 .appendValue(YEAR, 4)
209                 .appendLiteral('-')
210                 .appendValue(MONTH_OF_YEAR, 2)
211                 .appendLiteral('-')
212                 .appendValue(DAY_OF_MONTH, 2)
213                 .appendLiteral('T')
214                 .appendValue(ChronoField.HOUR_OF_DAY, 2)
215                 .appendLiteral(':')
216                 .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
217                 .appendLiteral(':')
218                 .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
219                 .appendOffset("+HH:MM", "Z")
220                 .toFormatter(Locale.ROOT)
221                 .withChronology(IsoChronology.INSTANCE)
222                 .withResolverStyle(ResolverStyle.STRICT);
223         }
224 
225         public static DateTimeFormatter dateTime() {
226             return new DateTimeFormatterBuilder().parseCaseInsensitive()
227                 .appendValue(YEAR, 4)
228                 .appendLiteral('-')
229                 .appendValue(MONTH_OF_YEAR, 2)
230                 .appendLiteral('-')
231                 .appendValue(DAY_OF_MONTH, 2)
232                 .appendLiteral('T')
233                 .appendValue(ChronoField.HOUR_OF_DAY, 2)
234                 .appendLiteral(':')
235                 .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
236                 .appendLiteral(':')
237                 .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
238                 .appendFraction(ChronoField.NANO_OF_SECOND, 3, 3, true)
239                 .appendOffset("+HH:MM", "Z")
240                 .toFormatter(Locale.ROOT)
241                 .withChronology(IsoChronology.INSTANCE)
242                 .withResolverStyle(ResolverStyle.STRICT);
243         }
244     }
245 
246 }