Home > Net >  Kotlin conditional formatting string
Kotlin conditional formatting string

Time:04-26

I have three variables :

val months: Long
val days: Long
val hours: Long

and I want to return something like this :

3 months, 2 days and 5 hours

Now this would simply translate to as :

val str = "$months months, $days days and $hours hours"

And if my months had to be 0 and days as 1 and hours as 0 then it will come like '0 months, 1 days and 0 hours'

But what I am looking for is "1 days" instead. How can I get it done?

I can definitely use some sort of conditional StringBuilder to get this done, but is there something better and elegant?

CodePudding user response:

How does this look to you?

fun formatTime(months: Long, days: Long, hours: Long): String =
  listOf(
    months to "months",
    days to "days",
    hours to "hours"
  )
  .filter { (length,_) -> length > 0L }
  // You can add an optional map predicate to make singular and plurals
  .map { (amount, label) -> amount to if(abs(amount)==1L) label.replace("s", "") else label }
  .joinToString(separator=", ") { (length, label) -> "$length $label" }
  .addressLastItem()

fun String.addressLastItem() =
  if(this.count { it == ','} >= 1) 
    // Dirty hack to get it working quickly
    this.reversed().replaceFirst(" ,", " dna ").reversed() 
  else 
    this

You can see it working over here

CodePudding user response:

Another variant without replacing, counting or reversing the list:

fun formatTime(months: Long, days: Long, hours: Long): String {
  val list = listOfNotNull(
    months.formatOrNull("month", "months"),
    days.formatOrNull("day", "days"),
    hours.formatOrNull("hour", "hours"),
  )
  return if (list.isEmpty()) "all values <= 0"
  else
    listOfNotNull(
      list.take(list.lastIndex).joinToString().takeIf(String::isNotEmpty),
      list.lastOrNull()
    ).joinToString(" and ")
}

fun Long.formatOrNull(singular: String, plural: String = "${singular}s") = when {
  this == 1L -> "$this $singular"
  this > 1L -> "$this $plural"
  else -> null
}

It also has a fallback if all values are <= 0... you could also just use an empty string or whatever you prefer.

If you do not like that there are intermediate lists created to concatenate the string, you may also just use something as follows instead in the else path:

list.iterator().run {
  buildString {
    while (hasNext()) {
      val part = next()
      if (length > 0)
        if (hasNext())
          append(", ")
        else
          append(" and ")
      append(part)
    }
  }
}
  • Related