001 /*
002 * Java GPX Library (jpx-3.1.0).
003 * Copyright (c) 2016-2023 Franz Wilhelmstötter
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 *
017 * Author:
018 * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com)
019 */
020 package io.jenetics.jpx;
021
022 import static java.time.ZoneOffset.UTC;
023 import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME;
024 import static java.util.Objects.requireNonNull;
025
026 import java.time.Instant;
027 import java.time.ZonedDateTime;
028 import java.time.format.DateTimeFormatter;
029 import java.time.format.DateTimeFormatterBuilder;
030 import java.time.format.DateTimeParseException;
031 import java.time.format.ResolverStyle;
032 import java.util.Optional;
033 import java.util.regex.Pattern;
034 import java.util.stream.Stream;
035
036 /**
037 * Enumeration of the valid date time formats.
038 *
039 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
040 * @version 3.0
041 * @since 1.0
042 */
043 enum TimeFormat {
044
045 // http://books.xmlschemata.org/relaxng/ch19-77049.html
046
047 ISO_DATE_TIME_UTC(
048 new DateTimeFormatterBuilder()
049 .append(ISO_LOCAL_DATE_TIME)
050 .optionalStart()
051 .appendOffsetId()
052 .toFormatter()
053 .withResolverStyle(ResolverStyle.LENIENT)
054 .withZone(UTC),
055 "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})*+Z*+$"
056 ),
057
058 ISO_DATE_TIME_OFFSET(
059 new DateTimeFormatterBuilder()
060 .append(ISO_LOCAL_DATE_TIME)
061 .optionalStart()
062 .appendOffsetId()
063 .toFormatter(),
064 "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})*+([+-]\\d{2}:\\d{2})"
065 );
066
067 // Default formatter used for formatting time strings.
068 private static final DateTimeFormatter FORMATTER =
069 DateTimeFormatter.ISO_DATE_TIME.withZone(UTC);
070
071 private final DateTimeFormatter _formatter;
072 private final Pattern[] _patterns;
073
074 TimeFormat(final DateTimeFormatter formatter, final String... patterns) {
075 _formatter = requireNonNull(formatter);
076 _patterns = Stream.of(patterns)
077 .map(Pattern::compile)
078 .toArray(Pattern[]::new);
079 }
080
081 private boolean matches(final String time) {
082 for (var pattern : _patterns) {
083 if (pattern.matcher(time).matches()) {
084 return true;
085 }
086 }
087 return false;
088 }
089
090 /**
091 * Parses the given time string with the current formatter.
092 *
093 * @param time the time string to pare
094 * @return the parsed zoned date time
095 * @throws DateTimeParseException if the text cannot be parsed
096 */
097 public Instant formatParse(final String time) {
098 return time != null
099 ? ZonedDateTime.parse(time, _formatter).toInstant()
100 : null;
101 }
102
103 /**
104 * Return the default format of the given {@code ZonedDateTime}.
105 *
106 * @param time the {@code ZonedDateTime} to format
107 * @return the time string of the given zoned date time
108 */
109 public static String format(final Instant time) {
110 return time != null ? FORMATTER.format(time) : null;
111 }
112
113 static Optional<Instant> parseOptional(final String time) {
114 final var format = findFormat(time);
115 if (format != null) {
116 return Optional.of(format.formatParse(time));
117 } else {
118 return Optional.empty();
119 }
120 }
121
122 /**
123 * Finds the formatter which fits the given {@code time} string.
124 *
125 * @param time the time string
126 * @return the formatter which fits to the given {@code time} string, or
127 * {@code Optional.empty()} of no formatter is found
128 */
129 static TimeFormat findFormat(final String time) {
130 if (ISO_DATE_TIME_UTC.matches(time)) {
131 return ISO_DATE_TIME_UTC;
132 } else if (ISO_DATE_TIME_OFFSET.matches(time)) {
133 return ISO_DATE_TIME_OFFSET;
134 } else {
135 return null;
136 }
137 }
138
139 /**
140 * Parses the given object to a zoned data time object.
141 *
142 * @param value the string to parse
143 * @return the parsed object
144 */
145 static Instant parse(final String value) {
146 final String time = Strings.trim(value);
147
148 if (time != null) {
149 final var format = findFormat(time);
150 if (format != null) {
151 return format.formatParse(time);
152 } else {
153 throw new IllegalArgumentException(
154 String.format("Can't parse time: '%s'", time)
155 );
156 }
157 } else {
158 return null;
159 }
160 }
161
162 }
|