Kotlin: How to iterate all dates within a Joda Interval? - kotlin

I'd like to iterate all dates within a given Joda interval:
val interval = Interval(DateTime.now().minusDays(42), DateTime.now())
How to do that in Kotlin?

Heavily inspired by your current solution:
fun Interval.toDateTimes() = generateSequence(start) { it.plusDays(1) }
.takeWhile(::contains)
Usage:
interval.toDateTimes()
.forEach { println(it) }
If you need the LocalDate you could still do the following instead:
interval.toDateTimes()
.map(DateTime::toLocalDate)
.forEach { println(it) }
or as an extension function to Interval again:
fun Interval.toLocalDates() = toDateTimes().map(DateTime::toLocalDate)
If you want the end date to be inclusive instead, use takeWhile { it <= end } instead.

The following extension function gives a Sequence of LocalDate objects from the given Interval, which can be used to iterate those dates.
fun Interval.toLocalDates(): Sequence<LocalDate> = generateSequence(start) { d ->
d.plusDays(1).takeIf { it < end }
}.map(DateTime::toLocalDate)
Usage:
val interval = Interval(DateTime.now().minusDays(42), DateTime.now())
interval.toLocalDates().forEach {
println(it)
}
In this solution, the last day, DateTime.now() is not included in the Sequence since that's how Interval is implemented as well:
"A time interval represents a period of time between two instants. Intervals are inclusive of the start instant and exclusive of the end."
If, for any reason, you want to make it include the last day, just change the takeIf condition to it <= end.

I guess if you need it more than once, it would be better to overload rangeTo operator to allow this syntax
for (i in LocalDate.now() .. LocalDate.now().plusWeeks(1)) {
System.out.print(i) // 2018-08-30 2018-08-31 2018-09-01
}
Here is the code for operator extension:
operator fun LocalDate.rangeTo(other: LocalDate): LocalDateRange {
return LocalDateRange(this, other)
}
And necessary classes:
class LocalDateRange(override val start: LocalDate, override val endInclusive: LocalDate)
: ClosedRange<LocalDate>, Iterable<LocalDate> {
override fun iterator(): Iterator<LocalDate> {
return DateIterator(start, endInclusive)
}
}
class DateIterator(start: LocalDate, private val endInclusive: LocalDate)
: Iterator<LocalDate> {
private var current = start
override fun hasNext(): Boolean {
return current <= endInclusive
}
override fun next(): LocalDate {
current = current.plusDays(1)
return current
}
}

LocalDate is preferred nowadays, so we can simply iterate with day as number:
for (day in minDate.toEpochDay()..maxDate.toEpochDay()) {
// ...
}
or:
(minDate.toEpochDay()..maxDate.toEpochDay()).forEach {
// ...
}
Iterate with day as date:
generateSequence(minDate) { it.plusDays(1) }.takeWhile { it < maxDate }.forEach {
// it ...
}
or:
var day = minDate;
while (day < maxDate) {
day = day.plusDays(1);
// ...
}

Related

How to parse time stamp and time zone offset simultaneously with Moshi?

A JSON-API-response contains the following properties:
created_at_timestamp: 1565979486,
timezone: "+01:00",
I am using Moshi and ThreeTenBp to parse the time stamps and prepared the following custom adapters:
class ZonedDateTimeAdapter {
#FromJson
fun fromJson(jsonValue: Long?) = jsonValue?.let {
try {
ZonedDateTime.ofInstant(Instant.ofEpochSecond(jsonValue), ZoneOffset.UTC) // <---
} catch (e: DateTimeParseException) {
println(e.message)
null
}
}
}
As you can see the zone offset is hardcoded here.
class ZonedDateTimeJsonAdapter : JsonAdapter<ZonedDateTime>() {
private val delegate = ZonedDateTimeAdapter()
override fun fromJson(reader: JsonReader): ZonedDateTime? {
val jsonValue = reader.nextLong()
return delegate.fromJson(jsonValue)
}
}
...
class ZoneOffsetAdapter {
#FromJson
fun fromJson(jsonValue: String?) = jsonValue?.let {
try {
ZoneOffset.of(jsonValue)
} catch (e: DateTimeException) {
println(e.message)
null
}
}
}
...
class ZoneOffsetJsonAdapter : JsonAdapter<ZoneOffset>() {
private val delegate = ZoneOffsetAdapter()
override fun fromJson(reader: JsonReader): ZoneOffset? {
val jsonValue = reader.nextString()
return delegate.fromJson(jsonValue)
}
}
The adapters are registered with Moshi as follows:
Moshi.Builder()
.add(ZoneOffset::class.java, ZoneOffsetJsonAdapter())
.add(ZonedDateTime::class.java, ZonedDateTimeJsonAdapter())
.build()
Parsing the individual fields (created_at_timestamp, timezone) works fine. I want however get rid of the hardcoded zone offset. How can I configure Moshi to fall back on the timezone property when parsing the created_at_timestamp property.
Related
Advanced JSON parsing techniques using Moshi and Kotlin
The work-in-progress branch of the related project
For the created_at_timestamp field you should use a type that doesn't have a timezone. This is usually Instant. It identifies a moment in time independent of which timezone it is being interpreted in.
Then in your enclosing type you can define a getter method to combine the instant and zone into one value. The ZonedDateTime.ofInstant method can do this.

Find closest time range from current date

I have some list of objects. Each of them contains specific "from" and "to" time range specified.
So for example:
import org.joda.time.DateTime
data class MyObject(
val from: String?,
val to: String?
)
The asUtcDateTime() is just my extension method that converts the given String do DateTime
How can I find the nearest object which:
is not in today time range
will be closest from today (future or past)?
What I've tried so far is just to get the nearest MyObject from the past and future like so:
val now = DateTime.now()
val nearestPastSchedule = allSchedules
.sortedBy { it.to.asUtcDateTime() }
.filter { it.to.asUtcDateTime() != null }
.lastOrNull { it.to.asUtcDateTime()!!.millis < now.withTimeAtStartOfDay().millis }
val nearestFutureSchedule = allSchedules
.sortedBy { it.from.asUtcDateTime() }
.filter { it.from.asUtcDateTime() != null }
.lastOrNull { it.from.asUtcDateTime()!!.millis > now.withTimeAtStartOfDay().millis }
Don't know what would be good solution in terms of comparing them (considered that there are nullable) and also have the actual MyObject returned for each of them
Instead of sorting, you can find the specified element yourself. I do so by finding the absolute minimum difference between now and the time specified in the object.
For simplicity reasons, I adjusted the data class to use ZonedDateTime (assuming Java >=8 to be available):
data class MyObject(
val from: ZonedDateTime?,
val to: ZonedDateTime?
)
With that, you can filter and find the minimum absolute value between now and the corresponding time:
val nearestPastSchedule =
allSchedules.filter { it.to != null }
.minBy { abs(it.to!!.toInstant().toEpochMilli() - now) }
val nearestFutureSchedule =
allSchedules.filter { it.from != null }
.minBy { abs(it.from!!.toInstant().toEpochMilli() - now) }

Kotlin way in conditional flows

What would be the best kotlin way to have the following logic?
if (it.records.isNotEmpty()) {
if (it.records[0].fields.isNotEmpty()) {
if (it.records[0].fields["lastModifiedDate"] != null) {
RECORD_DATA_LAST_MODIFIED_DATE_FORMAT.parse(
it.records[0].fields["lastModifiedDate"])
} else {
Date(0)
}
} else {
Date(0)
}
} else {
Date(0)
}
Since you didn't provide all the code necessary to run your code I decided to create 2 classes and a function
data class Musician(
val records: List<Record>
)
data class Record(
val fields: Map<String, String>
)
fun test(mus: Musician): Date { }
Only by using the elvis operator and some common syntax you could get something like this:
fun test(mus: Musician): Date {
val sdf = SimpleDateFormat("dd/MM/yyyy")
return if(mus.records.isNotEmpty() &&
mus.records[0].fields.isNotEmpty())
sdf.parse(mus.records[0].fields["lastModifiedDate"]) ?: Date(0)
else
Date(0)
}
And if you want to use even more Kotlin fun stuff you could create somethig like:
fun test(mus: Musician): Date {
val sdf = SimpleDateFormat("dd/MM/yyyy")
return mus.records.firstOrNull()?.fields?.get("lasModifiedDate")?.let {
sdf.parse(it)
} ?: Date(0)
}
This is probably not the best way to handle this situation, but these are some options of what you can do with Kotlin
Your question is missing some detail, but probably something like this:
val date = it.records[0]?.fields["lastModifiedDate"]?.let { RECORD_DATA_LAST_MODIFIED_DATE.parse(it)} ?: Date(0)

Transform a list, filtering out the items that cause an exception

How to convert this array of String:
"2018-05-08T23:22:49Z"
"n/a"
"2018-05-07T16:37:00Z"
to an array of Date using Higher-Order Functions such as map, flatMap or reduce?
I do know that it's possible to do that using forEach, but I'm interested to involve Kotlin Higher-Order Functions:
val stringArray
= mutableListOf("2018-05-08T23:22:49Z", "n/a", "2018-05-07T16:37:00Z")
val dateArray = mutableListOf<Date>()
stringArray.forEach {
try {
val date = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
.parse(it)
dateArray.add(date)
} catch (e: ParseException) {
//* Just prevents app from crash */
}
}
Using mapNotNull
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
val dates = listOf("2018-05-08T23:22:49Z", "n/a", "2018-05-07T16:37:00Z")
.mapNotNull {
try {
format.parse(it)
} catch (e: ParseException) {
null
}
}
println(dates)
This avoids creating a list for each item in the list, it maps the bad dates to null, and mapNotNull removes the nulls from the list.
Using an extension function
You could also extract the tryOrRemove to an extension function, making the code look like this:
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
fun <T, U: Any> Iterable<T>.tryOrRemove(block:(T)->U): List<U> {
return mapNotNull {
try {
block(it)
} catch (ex: Throwable) {
null
}
}
}
val dates = listOf("2018-05-08T23:22:49Z", "n/a", "2018-05-07T16:37:00Z")
.tryOrRemove(format::parse)
println(dates)
Using filter
I have written it based on the only bad dates being n/a, which simplifies it.
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
val dates = listOf("2018-05-08T23:22:49Z", "n/a", "2018-05-07T16:37:00Z")
.filter { it != "n/a" }
.map(format::parse)
println(dates)
You're looking for a transformation that can output zero or one element per input element. This is flatMap. The result of a flatmapping function must be an Iterable, so:
val dateArray = stringArray.flatMap {
try {
listOf(SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).parse(it))
} catch (e: ParseException) {
emptyList<Date>()
}
}
Adding the following based on #pwolaq's input:
It's highly recommended to extract the SimpleDateFormat instance because it has heavyweight initialization. Further, a solution with mapNotNull is cleaner than flatMap, I wasn't aware of it. This becomes especially convenient if you add a function that I feel is missing from the Kotlin standard library:
inline fun <T> runOrNull(block: () -> T) = try {
block()
} catch (t: Throwable) {
null
}
With this in your toolbox you can say:
val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
val dateArray: List<Date> = stringArray.mapNotNull {
runOrNull { formatter.parse(it) }
}

How can I change this to use "for loop" instead of `forEach`

I'm struggling to change it to use a for loop and still do the same thing.
The program is supposed to read a file with some flights and this specific part of the program needs to read the file using two different days that the user inputs then it needs to show how many passengers there are per flight and each day.
And how it's done now works but I'm trying to change it to use a for loop as I said before but doesn't work because I don't know how to do the same thing as map does but only in the fun interval.
fun interval(reservas: List<Reservas>, dayInferior: Int, daySuperior: Int) {
val map = mapReservas(reservas)
for(day in dayInferior..daySuperior) {
map.forEach {
val reservasNum = it.key.first
val reservasDay = it.key.second
val reservasCount = it.value.count()
if (reservasDay == day) {
println("$reservasNum has $reservasCount passengers on day $day")
}
}
}
println()
println("Press Enter")
readLine()
}
fun mapReservas(reservas: List<Reservas>): Map<Pair<String, Int>, List<Reservas>> {
val map = mutableMapOf<Pair<String, Int>, MutableList<Reservas>>()
for (reserva in reservas) {
val key = reserva.numFlight to reserva.day
val list = map[key] ?: mutableListOf()
list.add(reserva)
map[key] = list
}
return map
}
All your code can be replaced only with one function.
fun interval(reservas: List<Reservas>, dayInferior: Int, daySuperior: Int) {
reservas.groupBy { reserva -> reserva.day to reserva.numFlight }
.filter { (key, _) -> key.first in dayInferior..daySuperior }
.forEach { (key, reservas) ->
val (reservasNum, reservasDay) = key
val reservasCount = reservas.count()
println("$reservasNum has $reservasCount passengers on day $reservasDay")
}
println()
println("Press Enter")
readLine()
}
Explaining:
As I undestand, at first you trying to group all your Reservas by day and numFlight. It can be done via one function groupBy where you pass pair of day and numFlight.
Filter all Reservas by day. It can be done by checking if day belongs to range dayInferior..daySuperior (operator in).
Print all reservas by using forEach.
Other things
Destructing declarations
val reservasNum = it.key.first
val reservasDay = it.key.second
same as
val (reservasNum, reservasDa) = it.key
Omitting one unused parameter in lamda:
.filter { (key, _) -> ... }
If you iterate with a for loop over the Map each element is a Pair. If you write (pair, list) you destructure each Pair which itself consists of a Pair and a List.
fun interval(reservas: List<Reservas>, dayInferior: Int, daySuperior: Int) {
val map = mapReservas(reservas)
for(day in dayInferior..daySuperior) {
for((pair, list) in map) {
val reservasNum = pair.first
val reservasDay = pair.second
val reservasCount = list.count()
// ...
}
}
// ...
}
Maybe this makes it more clear:
for(outerPair in map){
val (innerPair, list) = outerPair
val reservasNum = innerPair.first
val reservasDay = innerPair.second
val reservasCount = list.count()
// ...
}
I left this function (mapReservas) untouched intentionally, because maybe you are using it somewhere else. But you can improve it right away by using Type aliases (since Kotlin 1.1).
typealias FlightNum = String
typealias Day = Int
fun mapReservas(reservas: List<Reservas>):
Map<Pair<FlightNum, Day>, List<Reservas>> {
// ...
}
As you can see the code becomes much more readable if you use the destructure syntax and Type aliases.