@@ -43,6 +43,8 @@ import java.text.NumberFormat
43
43
import java.text.ParsePosition
44
44
import java.time.format.DateTimeFormatter
45
45
import java.time.format.DateTimeFormatterBuilder
46
+ import java.time.temporal.Temporal
47
+ import java.time.temporal.TemporalQuery
46
48
import java.util.Locale
47
49
import kotlin.reflect.KClass
48
50
import kotlin.reflect.KType
@@ -152,46 +154,60 @@ internal object Parsers : GlobalParserOptions {
152
154
resetToDefault()
153
155
}
154
156
155
- private fun String.toJavaLocalDateTimeOrNull (formatter : DateTimeFormatter ? ): JavaLocalDateTime ? {
156
- if (formatter != null ) {
157
- return catchSilent { JavaLocalDateTime .parse(this , formatter) }
158
- } else {
159
- catchSilent { JavaLocalDateTime .parse(this ) }?.let { return it }
160
- for (format in formatters) {
161
- catchSilent { JavaLocalDateTime .parse(this , format) }?.let { return it }
157
+ /* *
158
+ * Parses a [string][str] using the given [java formatter][DateTimeFormatter] and [query]
159
+ * while avoiding exceptions. This avoidance is achieved by first trying to parse the string _unresovled_.
160
+ * If this is unsuccessful, we can simply return `null` without throwing an exception. Only if the string can
161
+ * successfully be parsed unresolved, we try to parse it _resolved_.
162
+ *
163
+ * See more about resolved and unresolved parsing in the [DateTimeFormatter] documentation.
164
+ */
165
+ private fun <T : Temporal > DateTimeFormatter.parseOrNull (str : String , query : TemporalQuery <T >): T ? =
166
+ catchSilent {
167
+ // first try to parse unresolved, since it doesn't throw exceptions on invalid values
168
+ val parsePosition = ParsePosition (0 )
169
+ if (parseUnresolved(str, parsePosition) != null && parsePosition.errorIndex == - 1 ) {
170
+ // do the parsing again, but now resolved, since the chance of exception is low
171
+ parse(str, query)
172
+ } else {
173
+ null
162
174
}
163
175
}
164
- return null
165
- }
166
176
167
- private fun String.toInstantOrNull (): Instant ? {
168
- // Default format used by Instant.parse
169
- val format = DateTimeComponents .Formats .ISO_DATE_TIME_OFFSET
170
- return catchSilent {
171
- // low chance throwing exception, thanks to using parseOrNull instead of parse
172
- format.parseOrNull(this )?.toInstantUsingOffset()
177
+ private fun String.toInstantOrNull (): Instant ? =
178
+ // low chance throwing exception, thanks to using parseOrNull instead of parse
179
+ catchSilent {
180
+ // Default format used by Instant.parse
181
+ DateTimeComponents .Formats .ISO_DATE_TIME_OFFSET
182
+ .parseOrNull(this )
183
+ ?.toInstantUsingOffset()
173
184
}
174
185
// fallback on the java instant to catch things like "2022-01-23T04:29:60", a.k.a. leap seconds
175
186
? : toJavaInstantOrNull()?.toKotlinInstant()
176
- }
177
187
178
- private fun String.toJavaInstantOrNull (): JavaInstant ? {
188
+ private fun String.toJavaInstantOrNull (): JavaInstant ? =
179
189
// Default format used by java.time.Instant.parse
180
- val format = DateTimeFormatter .ISO_INSTANT
181
- return catchSilent {
182
- // low chance throwing exception, thanks to using parseUnresolved instead of parse
183
- val parsePosition = ParsePosition (0 )
184
- val accessor = format.parseUnresolved(this , parsePosition)
185
- if (accessor != null && parsePosition.errorIndex == - 1 ) {
186
- JavaInstant .from(accessor)
187
- } else {
188
- null
190
+ DateTimeFormatter .ISO_INSTANT
191
+ .parseOrNull(this , JavaInstant ::from)
192
+
193
+ private fun String.toJavaLocalDateTimeOrNull (formatter : DateTimeFormatter ? ): JavaLocalDateTime ? {
194
+ if (formatter != null ) {
195
+ return formatter.parseOrNull(this , JavaLocalDateTime ::from)
196
+ } else {
197
+ DateTimeFormatter .ISO_LOCAL_DATE_TIME
198
+ .parseOrNull(this , JavaLocalDateTime ::from)
199
+ ?.let { return it }
200
+ for (format in formatters) {
201
+ format.parseOrNull(this , JavaLocalDateTime ::from)
202
+ ?.let { return it }
189
203
}
190
204
}
205
+ return null
191
206
}
192
207
193
208
private fun String.toLocalDateTimeOrNull (formatter : DateTimeFormatter ? ): LocalDateTime ? =
194
- toJavaLocalDateTimeOrNull(formatter)?.toKotlinLocalDateTime()
209
+ toJavaLocalDateTimeOrNull(formatter) // since we accept a Java DateTimeFormatter
210
+ ?.toKotlinLocalDateTime()
195
211
196
212
private fun String.toUrlOrNull (): URL ? = if (isURL(this )) catchSilent { URL (this ) } else null
197
213
@@ -208,33 +224,41 @@ internal object Parsers : GlobalParserOptions {
208
224
209
225
private fun String.toJavaLocalDateOrNull (formatter : DateTimeFormatter ? ): JavaLocalDate ? {
210
226
if (formatter != null ) {
211
- return catchSilent { JavaLocalDate .parse (this , formatter) }
227
+ return formatter.parseOrNull (this , JavaLocalDate ::from)
212
228
} else {
213
- catchSilent { JavaLocalDate .parse(this ) }?.let { return it }
229
+ DateTimeFormatter .ISO_LOCAL_DATE
230
+ .parseOrNull(this , JavaLocalDate ::from)
231
+ ?.let { return it }
214
232
for (format in formatters) {
215
- catchSilent { JavaLocalDate .parse(this , format) }?.let { return it }
233
+ format.parseOrNull(this , JavaLocalDate ::from)
234
+ ?.let { return it }
216
235
}
217
236
}
218
237
return null
219
238
}
220
239
221
240
private fun String.toLocalDateOrNull (formatter : DateTimeFormatter ? ): LocalDate ? =
222
- toJavaLocalDateOrNull(formatter)?.toKotlinLocalDate()
241
+ toJavaLocalDateOrNull(formatter) // since we accept a Java DateTimeFormatter
242
+ ?.toKotlinLocalDate()
223
243
224
244
private fun String.toJavaLocalTimeOrNull (formatter : DateTimeFormatter ? ): JavaLocalTime ? {
225
245
if (formatter != null ) {
226
- return catchSilent { JavaLocalTime .parse (this , formatter) }
246
+ return formatter.parseOrNull (this , JavaLocalTime ::from)
227
247
} else {
228
- catchSilent { JavaLocalTime .parse(this ) }?.let { return it }
248
+ DateTimeFormatter .ISO_LOCAL_TIME
249
+ .parseOrNull(this , JavaLocalTime ::from)
250
+ ?.let { return it }
229
251
for (format in formatters) {
230
- catchSilent { JavaLocalTime .parse(this , format) }?.let { return it }
252
+ format.parseOrNull(this , JavaLocalTime ::from)
253
+ ?.let { return it }
231
254
}
232
255
}
233
256
return null
234
257
}
235
258
236
259
private fun String.toLocalTimeOrNull (formatter : DateTimeFormatter ? ): LocalTime ? =
237
- toJavaLocalTimeOrNull(formatter)?.toKotlinLocalTime()
260
+ toJavaLocalTimeOrNull(formatter) // since we accept a Java DateTimeFormatter
261
+ ?.toKotlinLocalTime()
238
262
239
263
private fun String.toJavaDurationOrNull (): JavaDuration ? =
240
264
if (javaDurationCanParse(this )) {
0 commit comments