Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DateTime: Support throwing without adjusting for invalid dates #1447

Merged
merged 1 commit into from
Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions klock/src/commonMain/kotlin/com/soywiz/klock/DateFormat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.soywiz.klock
/** Allows to [format] and [parse] instances of [Date], [DateTime] and [DateTimeTz] */
interface DateFormat {
fun format(dd: DateTimeTz): String
fun tryParse(str: String, doThrow: Boolean = false): DateTimeTz?
fun tryParse(str: String, doThrow: Boolean = false, doAdjust: Boolean = true): DateTimeTz?

companion object {
val DEFAULT_FORMAT = DateFormat("EEE, dd MMM yyyy HH:mm:ss z")
Expand All @@ -29,8 +29,8 @@ interface DateFormat {
}
}

fun DateFormat.parse(str: String): DateTimeTz =
tryParse(str, doThrow = true) ?: throw DateException("Not a valid format: '$str' for '$this'")
fun DateFormat.parse(str: String, doAdjust: Boolean = true): DateTimeTz =
tryParse(str, doThrow = true, doAdjust = doAdjust) ?: throw DateException("Not a valid format: '$str' for '$this'")
fun DateFormat.parseDate(str: String): Date = parse(str).local.date

fun DateFormat.parseUtc(str: String): DateTime = parse(str).utc
Expand Down
32 changes: 16 additions & 16 deletions klock/src/commonMain/kotlin/com/soywiz/klock/ISO8601.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ object ISO8601 {

override fun format(dd: TimeSpan): String = dateTimeFormat.format(ref + dd)

override fun tryParse(str: String, doThrow: Boolean): TimeSpan? =
dateTimeFormat.tryParse(str, doThrow)?.let { it.utc - ref }
override fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean): TimeSpan? =
dateTimeFormat.tryParse(str, doThrow, doAdjust)?.let { it.utc - ref }
}

data class BaseIsoDateTimeFormat(val format: String, val twoDigitBaseYear: Int = 1900) : DateFormat {
Expand Down Expand Up @@ -87,8 +87,8 @@ object ISO8601 {
}
}

override fun tryParse(str: String, doThrow: Boolean): DateTimeTz? {
return tryParse(str).also {
override fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean): DateTimeTz? {
return _tryParse(str, doAdjust).also {
if (doThrow && it == null) throw DateException("Can't parse $str with $format")
}
}
Expand All @@ -98,7 +98,7 @@ object ISO8601 {
return null
}

private fun tryParse(str: String): DateTimeTz? {
private fun _tryParse(str: String, doAdjust: Boolean): DateTimeTz? {
var sign = +1
var tzOffset: TimeSpan? = null
var year = twoDigitBaseYear
Expand Down Expand Up @@ -263,8 +263,8 @@ object ISO8601 {
val extended = BaseIsoTimeFormat(extendedFormat ?: basicFormat ?: TODO())

override fun format(dd: TimeSpan): String = extended.format(dd)
override fun tryParse(str: String, doThrow: Boolean): TimeSpan? =
basic.tryParse(str, false) ?: extended.tryParse(str, false)
override fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean): TimeSpan? =
basic.tryParse(str, false, doAdjust) ?: extended.tryParse(str, false, doAdjust)
?: (if (doThrow) throw DateException("Invalid format $str") else null)
}

Expand All @@ -273,9 +273,9 @@ object ISO8601 {
val extended = BaseIsoDateTimeFormat(extendedFormat ?: basicFormat ?: TODO())

override fun format(dd: DateTimeTz): String = extended.format(dd)
override fun tryParse(str: String, doThrow: Boolean): DateTimeTz? = null
?: basic.tryParse(str, false)
?: extended.tryParse(str, false)
override fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean): DateTimeTz? = null
?: basic.tryParse(str, false, doAdjust)
?: extended.tryParse(str, false, doAdjust)
?: (if (doThrow) throw DateException("Invalid format $str") else null)
}

Expand Down Expand Up @@ -383,13 +383,13 @@ object ISO8601 {
val DATE = object : DateFormat {
override fun format(dd: DateTimeTz): String = DATE_CALENDAR_COMPLETE.format(dd)

override fun tryParse(str: String, doThrow: Boolean): DateTimeTz? {
override fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean): DateTimeTz? {
DATE_ALL.fastForEach { format ->
val result = format.extended.tryParse(str, false)
val result = format.extended.tryParse(str, false, doAdjust)
if (result != null) return result
}
DATE_ALL.fastForEach { format ->
val result = format.basic.tryParse(str, false)
val result = format.basic.tryParse(str, false, doAdjust)
if (result != null) return result
}
return if (doThrow) throw DateException("Invalid format") else null
Expand All @@ -398,13 +398,13 @@ object ISO8601 {
val TIME = object : TimeFormat {
override fun format(dd: TimeSpan): String = TIME_LOCAL_FRACTION0.format(dd)

override fun tryParse(str: String, doThrow: Boolean): TimeSpan? {
override fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean): TimeSpan? {
TIME_ALL.fastForEach { format ->
val result = format.extended.tryParse(str, false)
val result = format.extended.tryParse(str, false, doAdjust)
if (result != null) return result
}
TIME_ALL.fastForEach { format ->
val result = format.basic.tryParse(str, false)
val result = format.basic.tryParse(str, false, doAdjust)
if (result != null) return result
}
return if (doThrow) throw DateException("Invalid format") else null
Expand Down
16 changes: 12 additions & 4 deletions klock/src/commonMain/kotlin/com/soywiz/klock/PatternDateFormat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ data class PatternDateFormat @JvmOverloads constructor(
return out
}

override fun tryParse(str: String, doThrow: Boolean): DateTimeTz? {
override fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean): DateTimeTz? {
var millisecond = 0
var second = 0
var minute = 0
Expand All @@ -228,9 +228,9 @@ data class PatternDateFormat @JvmOverloads constructor(
"y", "yyyy", "YYYY" -> fullYear = value.toInt()
"yy" -> if (doThrow) throw RuntimeException("Not guessing years from two digits.") else return null
"yyy" -> fullYear = value.toInt() + if (value.toInt() < 800) 2000 else 1000 // guessing year...
"H", "HH", "k", "kk" -> hour = value.toInt() umod 24
"H", "HH", "k", "kk" -> hour = value.toInt()
"h", "hh", "K", "KK" -> {
hour = value.toInt() umod 24
hour = value.toInt()
is12HourFormat = true
}
"m", "mm" -> minute = value.toInt()
Expand Down Expand Up @@ -285,7 +285,15 @@ data class PatternDateFormat @JvmOverloads constructor(
}
}
}
val dateTime = DateTime.createAdjusted(fullYear, month, day, hour, minute, second, millisecond)
if (!doAdjust) {
if (month !in 1..12) if (doThrow) error("Invalid month $month") else return null
if (day !in 1..32) if (doThrow) error("Invalid day $day") else return null
if (hour !in 0..24) if (doThrow) error("Invalid hour $hour") else return null
if (minute !in 0..59) if (doThrow) error("Invalid minute $minute") else return null
if (second !in 0..59) if (doThrow) error("Invalid second $second") else return null
if (millisecond !in 0..999) if (doThrow) error("Invalid millisecond $millisecond") else return null
}
val dateTime = DateTime.createAdjusted(fullYear, month, day, hour umod 24, minute, second, millisecond)
return dateTime.toOffsetUnadjusted(offset ?: 0.hours)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ data class PatternTimeFormat(
return out
}

override fun tryParse(str: String, doThrow: Boolean): TimeSpan? {
override fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean): TimeSpan? {
var millisecond = 0
var second = 0
var minute = 0
Expand Down
6 changes: 3 additions & 3 deletions klock/src/commonMain/kotlin/com/soywiz/klock/TimeFormat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.soywiz.klock

interface TimeFormat {
fun format(dd: TimeSpan): String
fun tryParse(str: String, doThrow: Boolean): TimeSpan?
fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean = true): TimeSpan?

companion object {
val DEFAULT_FORMAT = TimeFormat("HH:mm:ss.SSS")
Expand All @@ -26,8 +26,8 @@ interface TimeFormat {
}
}

fun TimeFormat.parse(str: String): TimeSpan =
tryParse(str, doThrow = true) ?: throw DateException("Not a valid format: '$str' for '$this'")
fun TimeFormat.parse(str: String, doAdjust: Boolean = true): TimeSpan =
tryParse(str, doThrow = true, doAdjust = doAdjust) ?: throw DateException("Not a valid format: '$str' for '$this'")
fun TimeFormat.parseTime(str: String): Time = Time(parse(str))

fun TimeFormat.format(time: Double): String = format(time.milliseconds)
Expand Down
15 changes: 10 additions & 5 deletions klock/src/commonTest/kotlin/com/soywiz/klock/DateTimeTest.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package com.soywiz.klock

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import kotlin.test.*

class DateTimeTest {
val HttpDate by lazy { DateFormat("EEE, dd MMM yyyy HH:mm:ss z") }
Expand Down Expand Up @@ -523,4 +519,13 @@ class DateTimeTest {
assertEquals(dtmilli, fmt.parseLong(match.groups[1]!!.value), message = "datestamp parsed from log line has correct value")
assertEquals("Example log message", msg.drop(match.value.length), message = "total match length for composed regex")
}

@Test
fun testThrowOnInvalid() {
assertEquals("Invalid hour 25", assertFailsWith<RuntimeException> {
DateFormat("yyyy-MM-dd HH:mm:ss").parse("2023-02-01 25:00:00", doAdjust = false)
}.message)

DateFormat("yyyy-MM-dd HH:mm:ss").parse("2023-02-01 25:00:00", doAdjust = true)
}
}