Skip to content

Instantly share code, notes, and snippets.

@andrewrlee
Created January 11, 2021 17:18
Show Gist options
  • Save andrewrlee/681f62f9cb27261e899bc79649d842f9 to your computer and use it in GitHub Desktop.
Save andrewrlee/681f62f9cb27261e899bc79649d842f9 to your computer and use it in GitHub Desktop.
Representing appointments as a timeline
import java.time.LocalTime
fun isAvailable(request: Request) {
val timeLine = createTimeLine(LocalTime.of(8, 0), LocalTime.of(18, 0), 5)
val bookings = getTodaysBookings() // listOf(RoomBooking(Room.ONE, TimeRange("2007-12-03T10:10:00", "2007-12-03T10:40:00")))
bookings.filter { notPartOf(request.bookingToAmmend) }. forEach { timeLine.add(it) }
return timeLine.isAvailable(request)
}
import java.time.LocalTime
fun main() {
val timeLine = createTimeLine(LocalTime.of(4, 0), LocalTime.of(20, 0), 5)
timeLine.add(RoomBooking(Room.ONE, TimeRange("2007-12-03T10:10:00", "2007-12-03T10:40:00")))
timeLine.add(RoomBooking(Room.ONE, TimeRange("2007-12-03T10:50:00", "2007-12-03T11:10:00")))
timeLine.add(RoomBooking(Room.TWO, TimeRange("2007-12-03T10:20:00", "2007-12-03T10:50:00")))
timeLine.add(RoomBooking(Room.THREE, TimeRange("2007-12-03T09:50:00", "2007-12-03T10:05:00")))
println(timeLine.isAvailable(RoomBooking(Room.ONE, TimeRange("2007-12-03T09:10:00", "2007-12-03T09:40:00"))))
println(timeLine.isAvailable(RoomBooking(Room.ONE, TimeRange("2007-12-03T10:10:00", "2007-12-03T10:40:00"))))
println(timeLine.isAvailable(RoomBooking(Room.ONE, TimeRange("2007-12-03T10:40:00", "2007-12-03T10:45:00"))))
println(timeLine.isAvailable(RoomBooking(Room.ONE, TimeRange("2007-12-03T10:40:00", "2007-12-03T10:50:00"))))
println(timeLine.isAvailable(RoomBooking(Room.ONE, TimeRange("2007-12-03T10:50:00", "2007-12-03T10:55:00"))))
println(timeLine.getBookedRoomsBetween(TimeRange("2007-12-03T09:30:00", "2007-12-03T10:50:00")))
val roomRequest = RoomRequest(
pre = RoomBooking(Room.ONE, TimeRange("2007-12-03T10:00:00", "2007-12-03T10:20:00")),
main = RoomBooking(Room.ONE, TimeRange("2007-12-03T10:20:00", "2007-12-03T10:40:00")),
post = RoomBooking(Room.ONE, TimeRange("2007-12-03T10:40:00", "2007-12-03T11:00:00"))
)
println(timeLine.isAvailable(roomRequest))
println(timeLine.findAvailableRooms(roomRequest.toRequest(), Room.values().toSet()))
println(timeLine.availableToday(roomRequest.toRequest(), Room.values().toSet()))
}
import java.time.Duration
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.util.TreeMap
fun LocalTime.minutesOfDay(): Long = Duration.between(LocalTime.MIDNIGHT, this).toMinutes()
fun LocalDateTime.minutesOfDay(): Long = this.toLocalTime().minutesOfDay()
typealias TimeLine = TreeMap<Long, MutableList<Room>>
enum class Room { ONE, TWO, THREE, FOUR }
data class TimeRange(val startTime: LocalDateTime, val endTime: LocalDateTime) {
constructor(startTime: String, endTime: String) : this(LocalDateTime.parse(startTime), LocalDateTime.parse(endTime))
val start: Long get() = startTime.minutesOfDay()
val end: Long get() = endTime.minutesOfDay()
fun adjust(duration: Duration) = TimeRange(startTime.minus(duration), endTime.minus(duration))
}
data class RoomBooking(val room: Room, val timeRange: TimeRange) {
val start: Long get() = timeRange.start
val end: Long get() = timeRange.end
}
data class RoomRequest(val pre: RoomBooking?, val main: RoomBooking, val post: RoomBooking?) {
fun toRequest() = Request(main = main.timeRange, pre = pre?.timeRange, post = post?.timeRange)
}
data class AvailableRooms(val pre: List<Room>?, val main: Room, val post: List<Room>?)
data class Request(val main: TimeRange, val pre: TimeRange?, val post: TimeRange?) {
fun move(startTime: LocalDateTime): Request {
val earliestDate = pre?.startTime ?: main.startTime
val adjustment = Duration.between(startTime, earliestDate)
return Request(
pre = pre?.adjust(adjustment),
main = main.adjust(adjustment),
post = post?.adjust(adjustment))
}
}
fun createTimeLine(startOfDay: LocalTime, endOfDay: LocalTime, interval: Long): TimeLine {
val indices = (startOfDay.minutesOfDay()..endOfDay.minutesOfDay() step interval)
return indices.map { Pair(it, mutableListOf<Room>()) }.toMap(TreeMap())
}
fun TimeLine.add(booking: RoomBooking) = (booking.start until booking.end).forEach { slot ->
this.computeIfPresent(slot) { _, list -> list.also { it.add(booking.room) } }
}
fun TimeLine.getBookedRoomsBetween(timeRange: TimeRange) = this.subMap(timeRange.start, timeRange.end).flatMap { it.value }.toSortedSet()
fun TimeLine.isAvailable(booking: RoomBooking) = booking.room !in this.getBookedRoomsBetween(booking.timeRange)
fun TimeLine.isAvailable(request: RoomRequest): Boolean {
val mainAvailable = isAvailable(request.main)
val preAvailable = request.pre?.let { isAvailable(it) } ?: true
val postAvailable = request.post?.let { isAvailable(it) } ?: true
return mainAvailable && preAvailable && postAvailable
}
fun TimeLine.findAvailableRooms(request: Request, rooms: Collection<Room>): AvailableRooms? {
val availableForPre = request.pre?.let { rooms - getBookedRoomsBetween(it) }
val availableForMain = rooms - getBookedRoomsBetween(request.main)
val availableForPost = request.post?.let { rooms - getBookedRoomsBetween(it) }
return availableForMain.map { room ->
val leftOverForPre = availableForPre?.let { it - room }
val leftOverForPost = availableForPost?.let { it - room }
AvailableRooms(leftOverForPre, room, leftOverForPost)
}.find { (leftOverForPre, _, leftOverForPost) ->
(leftOverForPre == null || leftOverForPre.isNotEmpty()) && (leftOverForPost == null || leftOverForPost.isNotEmpty())
}
}
fun TimeLine.availableToday(request: Request, rooms: Collection<Room>): Boolean {
val availableSlots = this.keys
.map {
val newStartTime = LocalDateTime.of(LocalDate.now(), LocalTime.ofSecondOfDay(it * 60)) // default to localdate.now for convenience? move to just work on times?
val availableRoomsAtTime = findAvailableRooms(request.move(newStartTime), rooms)
Pair(it, availableRoomsAtTime)
}
.filter { it.second != null }
println(availableSlots)
// TODO need to ensure bookings that finish after closed time aren't counted
return availableSlots.isNotEmpty()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment