Last active
October 30, 2021 09:05
-
-
Save OndraZizka/5fd56479ed2f6175703eb8a2e1bb1088 to your computer and use it in GitHub Desktop.
Parse various date-time formats: Year only to full LocalDateTime, or relative from now (to the past). Kotlin.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ch.zizka.time | |
import java.time.Duration | |
import java.time.LocalDateTime | |
import java.time.format.DateTimeFormatter | |
import java.time.format.DateTimeParseException | |
import java.time.temporal.ChronoField.DAY_OF_MONTH | |
import java.time.temporal.ChronoField.HOUR_OF_DAY | |
import java.time.temporal.ChronoField.MINUTE_OF_HOUR | |
import java.time.temporal.ChronoField.MONTH_OF_YEAR | |
import java.time.temporal.ChronoField.SECOND_OF_MINUTE | |
import java.time.temporal.ChronoField.YEAR | |
import java.time.temporal.ChronoUnit | |
import java.time.temporal.UnsupportedTemporalTypeException | |
object FlexibleTemporalInputParser { | |
val RELAXED_FORMATTER = DateTimeFormatter.ofPattern("yyyy[-MM[-dd[' 'HH:mm[:ss[.SSS]]]]]") | |
fun parseTemporalInput(input: String): LocalDateTime? { | |
when (input.lowercase()) { | |
"now" -> return LocalDateTime.now() | |
"midnight", "today" -> return LocalDateTime.now().truncatedTo(ChronoUnit.DAYS) | |
} | |
try { | |
val parsed = RELAXED_FORMATTER.parse(input) | |
var result = LocalDateTime.MAX.withNano(0) | |
for (field in listOf(YEAR, MONTH_OF_YEAR, DAY_OF_MONTH, HOUR_OF_DAY, MINUTE_OF_HOUR, SECOND_OF_MINUTE)) { | |
try { | |
result = result.with(field, parsed.getLong(field)) | |
} catch (ex: UnsupportedTemporalTypeException) { | |
result = result.with(field, if (field.isDateBased) 1 else 0) | |
} | |
} | |
return result | |
} | |
catch (parseEx: DateTimeParseException) { | |
try { | |
val inputToIso8601 = "P" + input.uppercase().replace("-","").replace(" ", "").replace("D", "DT").removeSuffix("T") | |
// Expected format: "PnDTnHnMn.nS" | |
val duration = Duration.parse(inputToIso8601) | |
val base = | |
if (inputToIso8601.contains("D")) | |
LocalDateTime.now().truncatedTo(ChronoUnit.DAYS) | |
else | |
LocalDateTime.now() | |
return base.minus(duration) | |
} | |
catch (ex: DateTimeParseException) { | |
return null | |
} | |
} | |
return null | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ch.zizka.time | |
import org.assertj.core.api.Assertions.assertThat | |
import org.junit.jupiter.api.Test | |
import java.time.LocalDateTime | |
import java.time.temporal.ChronoUnit | |
class FlexibleTemporalInputParserTest { | |
@Test fun testDateTime() { | |
val dateTime = FlexibleTemporalInputParser.parseTemporalInput("1983-02-17 16:32:19") | |
assertThat(dateTime).isEqualTo(LocalDateTime.parse("1983-02-17T16:32:19")) | |
} | |
@Test fun testDateTime_Minutes() { | |
val dateTime = FlexibleTemporalInputParser.parseTemporalInput("1983-02-17 16:32") | |
assertThat(dateTime).isEqualTo(LocalDateTime.parse("1983-02-17T16:32:00")) | |
} | |
@Test fun testDate() { | |
val dateTime = FlexibleTemporalInputParser.parseTemporalInput("1983-02-17") | |
assertThat(dateTime).isEqualTo(LocalDateTime.parse("1983-02-17T00:00:00")) | |
} | |
@Test fun testDateRelative_1d_minus() { | |
val dateTime = FlexibleTemporalInputParser.parseTemporalInput("-1d") | |
assertThat(dateTime).isEqualTo(LocalDateTime.now().truncatedTo(ChronoUnit.DAYS).minusDays(1)) | |
} | |
@Test fun testDateRelative_1d() { | |
val dateTime = FlexibleTemporalInputParser.parseTemporalInput("1d") | |
assertThat(dateTime).isEqualTo(LocalDateTime.now().truncatedTo(ChronoUnit.DAYS).minusDays(1)) | |
} | |
@Test fun testDateRelative_0d() { | |
val dateTime = FlexibleTemporalInputParser.parseTemporalInput("0d") | |
assertThat(dateTime).isEqualTo(LocalDateTime.now().truncatedTo(ChronoUnit.DAYS)) | |
} | |
@Test fun testDateRelative_now() { | |
val dateTime = FlexibleTemporalInputParser.parseTemporalInput("now") | |
assertThat(dateTime?.truncatedTo(ChronoUnit.SECONDS)).isEqualTo(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS)) | |
} | |
@Test fun testDateRelative_midnight() { | |
val dateTime = FlexibleTemporalInputParser.parseTemporalInput("midnight") | |
assertThat(dateTime).isEqualTo(LocalDateTime.now().truncatedTo(ChronoUnit.DAYS)) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ch.zizka.utils | |
import org.slf4j.Logger | |
import org.slf4j.LoggerFactory | |
/** | |
* Creates a logger for the containing class. | |
* Usage: | |
* companion object { | |
* private val log = logger() | |
* } | |
*/ | |
fun Any.logger(): Logger { | |
val clazz = if (this::class.isCompanion) this::class.java.enclosingClass else this::class.java | |
return LoggerFactory.getLogger(clazz) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment