Kotlin - how to parse a string that is either in a LocalDateTime or LocalDate format into LocalDate - kotlin

I have a mapper where I am getting a string that is either in this format:
"2021-06-08" or "2021-06-08T15:00"
in my mapper I should convert this string always into a LocalDate:
ExcelColumn.CHANGED_DATE -> rowData
.nullLocalDate(column.dbColumnName)
?.format(excelV2DateFormat)
nullLocalDate looks like this:
fun Entity.nullLocalDate(key: String): LocalDate? = if (this[key] == null) null else localDate(key)
fun Entity.localDate(key: String): LocalDate = when (val v = this[key]) {
is LocalDateTime -> v.toLocalDate()
is Instant -> LocalDate.ofInstant(v, ZoneId.of("UTC"))
is String -> LocalDate.parse(v)
else -> v as LocalDate
}
I also have a function that checks nullLocalDateTime:
fun Entity.nullLocalDateTime(key: String): LocalDateTime? = if (this[key] == null) null else localDateTime(key)
fun Entity.localDateTime(key: String, zoneId: ZoneId? = null): LocalDateTime = when (val v = this[key]) {
is Instant -> LocalDateTime.ofInstant(v, zoneId ?: ZoneId.of("UTC"))
is String -> LocalDateTime.parse(v)
else -> v as LocalDateTime
}
I can use one or the other, but how can I combine this two to check if it is either in localDate or localDateTime format and convert it into localDate in both cases?
As of now it works fine if the value is in format "2021-06-08", but it fails if the string is in the format of "2021-06-08T15:00":
java.time.format.DateTimeParseException: Text '2021-06-08T15:00' could not be parsed, unparsed text found at index 10
How can I accommodate both cases in one function?

You can define optional parts in the pattern of a DateTimeFormatter. In your case, you'd want:
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd['T'HH:mm]")
Where the ['T'HH:mm] defines the optional time part. You can then use this formatter when parsing your strings into LocalDate instances. There are a few ways this can be done, but the easiest and most readable is to use the LocalDate#parse(CharSequence,DateTimeFormatter) method.
Runnable example:
import java.time.LocalDate
import java.time.format.DateTimeFormatter
fun main() {
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd['T'HH:mm]")
val dateOnlyString = "2021-06-08"
val dateAndTimeString = "2021-06-08T15:00"
val parsedDateOnly = LocalDate.parse(dateOnlyString, formatter)
val parsedDateAndTime = LocalDate.parse(dateAndTimeString, formatter)
println("parse(\"$dateOnlyString\") --> $parsedDateOnly")
println("parse(\"$dateAndTimeString\") --> $parsedDateAndTime")
}
Output:
parse("2021-06-08") --> 2021-06-08
parse("2021-06-08T15:00") --> 2021-06-08

Related

Kotlin: How to use higher order functions with OpenEndRange<LocalDate>

I want to use higher order functions like map for open end ranges.
val from = LocalDate.now().minusDays(10)
val to = LocalDate.now()
(from ..< to).forEach(::println)
I tried to copy an example for ClosedRange<LocalDate> but it does not work.
package de.otto.di.extensions
import java.time.LocalDate
class OpenEndRangeLocalDateIterator(
startDate: LocalDate,
private val endExclusive: LocalDate,
private val stepDays: Long
) : Iterator<LocalDate> {
private var currentDate = startDate
override fun hasNext() = currentDate.plusDays(stepDays) <= endExclusive
override fun next(): LocalDate {
val next = currentDate
currentDate = currentDate.plusDays(stepDays)
return next
}
}
#OptIn(ExperimentalStdlibApi::class)
class OpenEndLocalDateRange(
override val start: LocalDate,
override val endExclusive: LocalDate,
private val stepDays: Long = 1
) : Iterable<LocalDate>, OpenEndRange<LocalDate> {
override fun iterator(): Iterator<LocalDate> =
OpenEndRangeLocalDateIterator(start, endExclusive, stepDays)
infix fun step(days: Long) = OpenEndLocalDateRange(start, endExclusive, days)
}
infix operator fun LocalDate.rangeUntil(to: LocalDate): OpenEndLocalDateRange =
OpenEndLocalDateRange(this, to)
It is implemented for Int so I assume it must be possible somehow. How can I achieve this?
The issue here is that you've defined the operator function to return OpenEndRange<LocalDate> rather than OpenEndedLocalDateRange. If you change the return type of your operator function that should fix the issue.
The reason why it isn't working as is is because OpenEndRange doesn't have the higher order functions defined for it (ClosedRange doesn't have them defined as well). Int has it because the operators return an IntRange which indirectly extends Iterable<Int> via IntProgression and Iterable has these higher order functions defined, so, the only missing piece is failing to return the correct type from your operator function.

How to remove ZoneId?

Thank you always for your help.
I got a problem in my project.
I want to delete Zoned Id and leave only time.
How to remove zoned Id?
It is written using kotlin in Spring Boot Framework.
data class AvailableScheduleRequest (
val address: String,
val date: LocalDate,
val people: Long,
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "Asia/Seoul")
val hour: ZonedDateTime
)
#RestController
class Controller(
val newBookingElasticSearchService: NewBookingElasticSearchService
) {
#RequestMapping("/")
#ResponseBody
fun get(#RequestBody param: Map<String, Any>): List<AvailableRestaurant> {
val json = JacksonUtils.om.writeValueAsString(param)
val availableScheduleRequest =
JacksonUtils.om.readValue(json, AvailableScheduleRequest::class.java)
println(availableScheduleRequest.hour)
}
}
object JacksonUtils {
val om = ObjectMapper()
init {
val module = SimpleModule()
module.addDeserializer(String::class.java, JsonRawValueDeserializer.INSTANCE)
om.registerModule(JavaTimeModule())
om.registerModule(KotlinModule())
om.registerModule(module)
om.dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX")
om.setTimeZone(TimeZone.getDefault())
om.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
om.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true)
om.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true)
om.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
}
This code outputs:
2019-10-02T07:00+09:00[Asia/Seoul]
But I wanna get this:
2019-10-02T07:00+09:00
How to get this?
Thank you for your efforts all the time!!
Instead of ZonedDateTime, you could use OffsetDateTime.

Kotlin DSL - union structure

I am designing a DSL and run into a requirement where I have a variable which could be assigned to different ways. Greatly simplified, I would like to set value property either by an integer or by an expression in String. (The real need is even more complex.)
I would like to write in my DSL:
value = 42
or
value = "6*7"
Behind the scene, the value will be stored in a DynamicValue<Int> structure which contains either an integer or the expression.
class DynamicValue<T>(dv : T?, expr : String) {
val directValue : T? = dv
val script : String? = expr
...
}
I tried several ways (delegate, class, etc), but none of them provided these syntax.
Is there a way to declare this union like structure?
What do you think about the following syntax:
value(42)
value("6*7")
//or
value+=42
value+="6*7"
You can do this with operator functions:
class DynamicValue<T>() {
var dv: T? = null
var expr: String? = null
operator fun invoke(dv : T) {
this.dv = dv
this.expr = null
}
operator fun invoke(expr: String) {
this.dv = null
this.expr = expr
}
operator fun plusAssign(dv : T) {
this.dv = dv
this.expr = null
}
operator fun plusAssign(expr: String) {
this.dv = null
this.expr = expr
}
}
You can't redefine the assign operator in Kotlin, therefor the pure syntax value=42 is not possible.
But I wouldn't go with operator functions, it's to magical. I would do this:
val value = DynamicValue<Int>()
value.simple=42
value.expr="6*7"
class DynamicValue2<T>() {
private var _dv: T? = null
private var _expr: String? = null
var simple: T?
get() = _dv
set(value) {
_dv = value
_expr = null
}
var expr: String?
get() = _expr
set(value) {
_expr = value
_dv = null
}
}
Rene's answer gave me the lead and finally I turned up with this solution.
In this solution I took all my requirements in (the ones I dropped out in my original question) so this became much more complicated than my original question would have required.
My whole requirement was to be able to add static values or scripts (snippets) running on a well guarded context. These script would be stored, and executed later. I wanted to enable the whole power of the IDE when writing the script, but would like to guard my scripts from code injections and help the user to use only the context values the script requires.
The trick I used to achieve this is to enable adding script in kotlin, but before I run the whole DSL script and create the business objects, I convert the script into a string. (This string will be executed later in a guarded, wrapped context by JSR233 engine.) This conversation forced me to tokenize the whole script before execution and search/replace some of the tokens. (The whole tokenizer and converter is rather long and boring, so I won't insert here.)
First approach
What my goal was to be able to write any of this:
myobject {
value = static { 42 } // A static solution
value = static { 6 * 7 } // Even this is possible
value = dynamic{ calc(x, y) } // A pure cotlin solution with IDE support
value = dynamic("""calc(x * x)""") // This is the form I convert the above script to
}
where calc, x and y are defined in the context class:
class SpecialScriptContext : ScriptContextBase() {
val hello = "Hello"
val x = 29
val y = 13
fun calc(x: Int, y: Int) = x + y
fun greet(name: String) = println("$hello $name!")
}
So let's see the solution! First I need a DynamicValue class to hold one of the values:
class DynamicValue<T, C : ScriptContextBase, D: ScriptContextDescriptor<C>>
private constructor(val directValue: T?, val script: String?) {
constructor(value: T?) : this(value, null)
constructor(script: String) : this(null, script)
}
This structure will ensure that exactly one of the options (static, script) will be set. (Don't bother with the C and D type parameters, they are for context-based script support.)
Then I made top level DSL functions to support syntax:
#PlsDsl
fun <T, C : ScriptContextBase, D : ScriptContextDescriptor<C>> static(block: () -> T): DynamicValue<T, C, D>
= DynamicValue<T, C, D>(value = block.invoke())
#PlsDsl
fun <T, C : ScriptContextBase, D : ScriptContextDescriptor<C>> dynamic(s: String): DynamicValue<T, C, D>
= DynamicValue<T, C, D>(script = s)
#PlsDsl
fun <T, C : ScriptContextBase, D : ScriptContextDescriptor<C>> dynamic(block: C.() -> T): DynamicValue<T, C, D> {
throw IllegalStateException("Can't use this format")
}
An explanation to the third form. As I wrote before, I don't want to execute the block of the function. When the script is executed, this form is converted to the string form, so normally this function would never appear in the script when executed. The exception is a sanity warning, which would never be thrown.
Finally added the field to my business object builder:
#PlsDsl
class MyObjectBuilder {
var value: DynamicValue<Int, SpecialScriptContext, SpecialScriptContextDescriptor>? = null
}
Second approach
The previous solution worked but had some flaws: the expression was not associated with the variable it set, neither with the entity the value was set in. With my second approach I solved this problem and removed the need of equal sign and most of the unnecessary curly brackets.
What helped: extension functions, infix functions and sealed classes.
First, I split the two value types into separated classes defined a common ancestor:
sealed class Value<T, C : ScriptContextBase> {
abstract val scriptExecutor: ScriptExecutor
abstract val descriptor: ScriptContextDescriptor<C>
abstract val code: String
abstract fun get(context: C): T?
}
class StaticValue<T, C : ScriptContextBase>(override val code: String,
override val scriptExecutor: ScriptExecutor,
override val descriptor: ScriptContextDescriptor<C>,
val value: T? = null
) : Value<T, C>() {
override fun get(context: C) = value
constructor(oldValue: Value<T, C>, value: T?) : this(oldValue.code, oldValue.scriptExecutor, oldValue.descriptor, value)
}
class DynamicValue<T, C : ScriptContextBase>(override val code: String,
script: String,
override val scriptExecutor: ScriptExecutor,
override val descriptor: ScriptContextDescriptor<C>)
: Value<T, C>() {
constructor(oldValue: Value<T, C>, script: String) : this(oldValue.code, script, oldValue.scriptExecutor, oldValue.descriptor)
private val scriptCache = scriptExecutor.register(descriptor)
val source = script?.replace("\\\"\\\"\\\"", "\"\"\"")
private val compiledScript = scriptCache.register(generateUniqueId(code), source)
override fun get(context: C): T? = compiledScript.execute<T?>(context)
}
Note, that I made the primary constructor internal and created a kind of copy and alter constructor. Then I defined the new functions as extension of the common ancestor and marked them infix:
infix fun <T, C : ScriptContextBase> Value<T, C>.static(value: T?): Value<T, C> = StaticValue(this, value)
infix fun <T, C : ScriptContextBase> Value<T, C>.expr(script: String): Value<T, C> = DynamicValue(this, script)
infix fun <T, C : ScriptContextBase> Value<T, C>.dynamic(block: C.() -> T): Value<T, C> {
throw IllegalStateException("Can't use this format")
}
Using the secondary copy-and-alter constructor allows to inherit the context sensitive values. Finally I initialize the value inside the DSL builder:
#PlsDsl
class MyDslBuilder {
var value: Value<Int, SpecialScriptContext> = StaticValue("pl.value", scriptExecutor, SpecialScriptContextDescriptor)
var value2: Value<Int, SpecialScriptContext> = StaticValue("pl.value2", scriptExecutor, SpecialScriptContextDescriptor)
}
Everything is in place and now I can use it in my script:
myobject {
value static 42
value2 expr "6 * 7"
value2 dynamic { calc(x, y) }
}

How to convert String to Int in Kotlin?

I am working on a console application in Kotlin where I accept multiple arguments in main() function
fun main(args: Array<String>) {
// validation & String to Integer conversion
}
I want to check whether the String is a valid integer and convert the same or else I have to throw some exception.
How can I resolve this?
You could call toInt() on your String instances:
fun main(args: Array<String>) {
for (str in args) {
try {
val parsedInt = str.toInt()
println("The parsed int is $parsedInt")
} catch (nfe: NumberFormatException) {
// not a valid int
}
}
}
Or toIntOrNull() as an alternative:
for (str in args) {
val parsedInt = str.toIntOrNull()
if (parsedInt != null) {
println("The parsed int is $parsedInt")
} else {
// not a valid int
}
}
If you don't care about the invalid values, then you could combine toIntOrNull() with the safe call operator and a scope function, for example:
for (str in args) {
str.toIntOrNull()?.let {
println("The parsed int is $it")
}
}
Actually, there are several ways:
Given:
// aString is the string that we want to convert to number
// defaultValue is the backup value (integer) we'll have in case of conversion failed
var aString: String = "aString"
var defaultValue : Int = defaultValue
Then we have:
Operation
Successful operation
Unsuccessful Operation
aString.toInt()
Numeric value
NumberFormatException
aString.toIntOrNull()
Numeric value
null
aString.toIntOrNull() ?: defaultValue
Numeric value
defaultValue
If aString is a valid integer, then we will get is numeric value, else, based on the function used, see a result in column Unsuccessful Operation.
val i = "42".toIntOrNull()
Keep in mind that the result is nullable as the name suggests.
As suggested above, use toIntOrNull().
Parses the string as an [Int] number and returns the result
or null if the string is not a valid representation of a number.
val a = "11".toIntOrNull() // 11
val b = "-11".toIntOrNull() // -11
val c = "11.7".toIntOrNull() // null
val d = "11.0".toIntOrNull() // null
val e = "abc".toIntOrNull() // null
val f = null?.toIntOrNull() // null
I use this util function:
fun safeInt(text: String, fallback: Int): Int {
return text.toIntOrNull() ?: fallback
}
In Kotlin:
Simply do that
val abc = try {stringNumber.toInt()}catch (e:Exception){0}
In catch block you can set default value for any case string is not converted to "Int".
string_name.toString().toInt()
converts string_name to String and then the resulting String is converted to int.
i would go with something like this.
import java.util.*
fun String?.asOptionalInt() = Optional.ofNullable(this).map { it.toIntOrNull() }
fun main(args: Array<String>) {
val intArgs = args.map {
it.asOptionalInt().orElseThrow {
IllegalArgumentException("cannot parse to int $it")
}
}
println(intArgs)
}
this is quite a nice way to do this, without introducing unsafe nullable values.
add (?) before fun toInt()
val number_int = str?.toInt()
You can Direct Change by using readLine()!!.toInt()
Example:
fun main(){
print("Enter the radius = ")
var r1 = readLine()!!.toInt()
var area = (3.14*r1*r1)
println("Area is $area")
}
fun getIntValueFromString(value : String): Int {
var returnValue = ""
value.forEach {
val item = it.toString().toIntOrNull()
if(item is Int){
returnValue += item.toString()
}
}
return returnValue.toInt()
}

Filter a substring in kotlin

In kotlin I'd like to filter a string and return a substring of only valid characters. Say we have valid characters,
valid = listOf('A', 'B', 'C')
How can I define a fcn in kotlin in the most succinct way to filter a string and only retain valid characters? For example,
'ABCDEBCA' --> 'ABCBCA'
'AEDC' --> 'AC'
Having trouble finding a canonical way to do this without resorting to using an array of string.
import kotlin.text.filter
class Test(){
val VALID = listOf("A", "B", "C")
fun filterString(expression: String): String{
expression.filter(x --> !VALID.contains(x)) #Doesn't work
}
}
The filter docs doesn't show any examples specifically for spring manipulation.
val VALID = setOf('A', 'B', 'C') // lookup in a set is O(1), whereas it's O(n) in a list. The set must contain Chars, not Strings
val expression = "ABCDEFEDCBA"
val filtered = expression.filter { VALID.contains(it) }
println(filtered)
// ABCCBA
Or
val VALID = setOf('A', 'B', 'C')
fun filterString(expression: String) = expression.filter { it in VALID }
fun main(args: Array<String>) {
val expression = "ABCDEFEDCBA"
val filtered = filterString(expression)
println(filtered)
// ABCCBA
}
In case you have a long set of chars you could join them in a String and convert it to a Set:
val VALID = "ABC".toSet()
fun filterString(expression: String) = expression.filter { it in VALID }
fun main(args: Array<String>) {
val expression = "ABCDEFEDCBA"
val filtered = filterString(expression)
println(filtered)
// ABCCBA
}