Skip to content

Commit d077a3a

Browse files
committed
Use kotlinx.datetime for time handling
1 parent f94c932 commit d077a3a

File tree

13 files changed

+187
-247
lines changed

13 files changed

+187
-247
lines changed

backend/src/main/kotlin/org/jetbrains/kotlinconf/backend/Api.kt

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
package org.jetbrains.kotlinconf.backend
22

3-
import io.ktor.http.*
4-
import io.ktor.server.application.*
5-
import io.ktor.server.auth.*
6-
import io.ktor.server.request.*
7-
import io.ktor.server.response.*
8-
import io.ktor.server.routing.*
9-
import io.ktor.util.date.*
10-
import org.jetbrains.kotlinconf.*
3+
import io.ktor.http.HttpStatusCode
4+
import io.ktor.server.application.ApplicationCall
5+
import io.ktor.server.auth.principal
6+
import io.ktor.server.request.receive
7+
import io.ktor.server.response.respond
8+
import io.ktor.server.routing.Route
9+
import io.ktor.server.routing.get
10+
import io.ktor.server.routing.post
11+
import io.ktor.server.routing.route
12+
import io.ktor.util.date.GMTDate
13+
import kotlinx.datetime.toInstant
14+
import org.jetbrains.kotlinconf.EVENT_TIME_ZONE
15+
import org.jetbrains.kotlinconf.FeedbackInfo
16+
import org.jetbrains.kotlinconf.VoteInfo
1117
import org.jetbrains.kotlinconf.Votes
12-
import java.time.*
18+
import java.time.Clock
19+
import java.time.LocalDateTime
1320

1421
internal fun Route.api(
1522
store: Store,
@@ -64,8 +71,8 @@ private fun Route.apiVote(
6471

6572
val nowTime = now()
6673

67-
val startVotesAt = session.startsAt
68-
val votingPeriodStarted = nowTime >= startVotesAt.timestamp
74+
val startVotesAt = session.startsAt.toInstant(EVENT_TIME_ZONE)
75+
val votingPeriodStarted = nowTime >= startVotesAt.toEpochMilliseconds()
6976

7077
if (!votingPeriodStarted) {
7178
return@post call.respond(comeBackLater)

backend/src/main/kotlin/org/jetbrains/kotlinconf/backend/SessionizeData.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package org.jetbrains.kotlinconf.backend
22

3+
import kotlinx.datetime.LocalDateTime
34
import kotlinx.serialization.SerialName
45
import kotlinx.serialization.Serializable
5-
import org.jetbrains.kotlinconf.GMTDateSerializable
66
import org.jetbrains.kotlinconf.SessionId
77
import org.jetbrains.kotlinconf.SpeakerId
88

@@ -24,8 +24,8 @@ data class SessionData(
2424
val speakers: List<SpeakerId>,
2525
@SerialName("description")
2626
var descriptionText: String? = "",
27-
val startsAt: GMTDateSerializable?,
28-
val endsAt: GMTDateSerializable?,
27+
val startsAt: LocalDateTime?,
28+
val endsAt: LocalDateTime?,
2929
val title: String,
3030
val roomId: Int?,
3131
val questionAnswers: List<QuestionAnswerData> = emptyList(),

shared/src/commonMain/kotlin/org/jetbrains/kotlinconf/APIClient.kt

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,11 @@ import io.ktor.http.isSuccess
2323
import io.ktor.http.path
2424
import io.ktor.http.takeFrom
2525
import io.ktor.serialization.kotlinx.json.json
26-
import io.ktor.util.date.GMTDate
27-
import io.ktor.util.date.Month
2826
import io.ktor.utils.io.core.Closeable
27+
import kotlinx.datetime.Instant
28+
import kotlinx.datetime.LocalDateTime
2929
import org.jetbrains.kotlinconf.utils.appLogger
3030

31-
val HTTP_CLIENT = HttpClient()
32-
3331
/**
3432
* Adapter to handle backend API and manage auth information.
3533
*/
@@ -38,7 +36,7 @@ class APIClient(
3836
) : Closeable {
3937
var userId: String? = null
4038

41-
private val client = HTTP_CLIENT.config {
39+
private val client = HttpClient {
4240
install(ContentNegotiation) {
4341
json()
4442
}
@@ -135,12 +133,9 @@ class APIClient(
135133
}.body<Votes>().votes
136134
}
137135

138-
/**
139-
* Get server time.
140-
*/
141-
suspend fun getServerTime(): GMTDate = client.get {
136+
suspend fun getServerTime(): Instant = client.get {
142137
apiUrl("time")
143-
}.bodyAsText().let { response -> GMTDate(response.toLong()) }
138+
}.bodyAsText().let { response -> Instant.fromEpochMilliseconds(response.toLong()) }
144139

145140
// TODO real api call https://github.com/JetBrains/kotlinconf-app/issues/268
146141
suspend fun getNews(): List<NewsItem> = EXAMPLE_NEWS_ITEMS
@@ -171,56 +166,35 @@ private val EXAMPLE_NEWS_ITEMS = listOf(
171166
id = "0",
172167
title = "Kotlin 1.9 Released",
173168
content = "**Exciting news for Kotlin developers!** The latest version of Kotlin brings significant improvements and new features to enhance your development experience.\n\nSome highlights include:\n- *K2 compiler* improvements for faster compilation\n- Enhanced type inference system\n- New stdlib functions\n\nCheck out the detailed release notes at [kotlinlang.org](https://kotlinlang.org) and start exploring these amazing features today! The Kotlin team has been working hard to make this release even more **powerful** and *developer-friendly*.",
174-
date = GMTDate(
175-
year = 2024,
176-
month = Month.APRIL,
177-
dayOfMonth = 23,
178-
hours = 10,
179-
minutes = 24,
180-
seconds = 2
181-
),
169+
date = LocalDateTime.parse("2024-04-23T10:24:02"),
182170
photoUrl = "https://picsum.photos/1800/900"
183171
),
184172
NewsItem(
185173
id = "1",
186174
title = "KotlinConf 2024 Announced",
187175
content = "Get ready for the most anticipated Kotlin event of the year! **KotlinConf 2024** brings together developers from around the world for an unforgettable experience.\n\n*What to expect:*\n- Inspiring keynotes from Kotlin leaders\n- In-depth technical sessions\n- Hands-on workshops\n- Networking opportunities\n\nDon't miss the chance to meet fellow Kotlin enthusiasts and learn from industry experts. Early bird tickets are now available at [kotlinconf.com/2024](https://kotlinconf.com/2024).\n\n**Pro tip:** Check out the *conference app* to plan your schedule and connect with other attendees!",
188-
date = GMTDate(year = 2024, month = Month.MAY, dayOfMonth = 22, hours = 10, minutes = 24, seconds = 2),
176+
date = LocalDateTime.parse("2024-05-22T10:24:02"),
189177
photoUrl = null
190178
),
191179
NewsItem(
192180
id = "2",
193181
title = "Jetpack Compose Updates",
194182
content = "The world of **Jetpack Compose** continues to evolve with exciting new features for both Android and Desktop development!\n\n*Latest improvements include:*\n- Enhanced performance optimizations\n- New material design components\n- Improved animation APIs\n- Better desktop window management\n\nRead the comprehensive guide on the [Android Developers Blog](https://android-developers.googleblog.com) and explore the [Compose Multiplatform documentation](https://www.jetbrains.com/compose-multiplatform/).\n\n**Did you know?** You can now easily share up to *90% of your UI code* between Android and Desktop applications using Compose Multiplatform!",
195-
date = GMTDate(
196-
year = 2024,
197-
month = Month.JANUARY,
198-
dayOfMonth = 22,
199-
hours = 10,
200-
minutes = 24,
201-
seconds = 2
202-
),
183+
date = LocalDateTime.parse("2024-01-22T10:24:02"),
203184
photoUrl = null
204185
),
205186
NewsItem(
206187
id = "3",
207188
title = "New Kotlin Multiplatform Features",
208189
content = "**Kotlin Multiplatform** technology reaches new heights with groundbreaking features and improvements!\n\n*Key highlights of the latest release:*\n- Simplified project setup and configuration\n- Enhanced iOS integration with new Kotlin/Native features\n- Improved dependency management\n- Extended WebAssembly support\n\nStart building your next cross-platform project with [KMP](https://kotlinlang.org/docs/multiplatform.html) today!\n\n**Success Story:** *Philips* recently shared how they achieved a **75% code sharing rate** across platforms using Kotlin Multiplatform. Read their detailed case study on the [Kotlin Blog](https://blog.jetbrains.com/kotlin/).\n\nExplore the [official documentation](https://kotlinlang.org/docs/multiplatform-get-started.html) to learn more about these exciting developments!",
209-
date = GMTDate(year = 2024, month = Month.MAY, dayOfMonth = 23, hours = 10, minutes = 24, seconds = 2),
190+
date = LocalDateTime.parse("2024-05-23T10:24:02"),
210191
photoUrl = "https://picsum.photos/1800/900"
211192
),
212193
NewsItem(
213194
id = "4",
214195
title = "Kotlin Community Highlights",
215196
content = "The **Kotlin community** continues to innovate and inspire! Let's celebrate some remarkable community contributions.\n\n*Featured Projects:*\n- **Ktor 2.0**: A powerful framework for building asynchronous servers and clients\n- *Kotlin Native Bridge*: Seamless integration between Kotlin and native platforms\n- **KMP-NativeCoroutines**: Simplified concurrency for multiplatform projects\n\nJoin the community on [Kotlin Slack](https://kotlinlang.slack.com) with over *100,000 members* and share your own projects!\n\n**Want to contribute?** Check out the [Kotlin Contributing Guidelines](https://github.com/JetBrains/kotlin) and help shape the future of Kotlin. The community has already contributed more than *500 patches* to the latest release!\n\nExplore more community projects on [Kotlin Weekly](https://kotlinweekly.net) and get inspired for your next project.",
216-
date = GMTDate(
217-
year = 2024,
218-
month = Month.APRIL,
219-
dayOfMonth = 20,
220-
hours = 10,
221-
minutes = 24,
222-
seconds = 2
223-
),
197+
date = LocalDateTime.parse("2024-04-20T10:24:02"),
224198
photoUrl = "https://picsum.photos/1800/900"
225199
)
226200
)

shared/src/commonMain/kotlin/org/jetbrains/kotlinconf/ConferenceService.kt

Lines changed: 31 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.jetbrains.kotlinconf
22

3-
import io.ktor.util.date.GMTDate
43
import kotlinx.coroutines.CoroutineScope
54
import kotlinx.coroutines.Dispatchers
65
import kotlinx.coroutines.SupervisorJob
@@ -14,28 +13,11 @@ import kotlinx.coroutines.flow.flowOn
1413
import kotlinx.coroutines.flow.map
1514
import kotlinx.coroutines.flow.stateIn
1615
import kotlinx.coroutines.launch
16+
import kotlinx.datetime.LocalDateTime
17+
import kotlinx.datetime.toInstant
1718
import org.jetbrains.kotlinconf.storage.ApplicationStorage
18-
import org.jetbrains.kotlinconf.utils.time
19-
20-
val UNKNOWN_SESSION_CARD: SessionCardView = SessionCardView(
21-
id = SessionId("unknown"),
22-
title = "unknown",
23-
speakerLine = "unknown",
24-
locationLine = "unknown",
25-
startsAt = GMTDate.START,
26-
endsAt = GMTDate.START,
27-
speakerIds = emptyList(),
28-
isFavorite = false,
29-
description = "unknown",
30-
vote = null,
31-
tags = emptyList(),
32-
startsInMinutes = null,
33-
state = SessionState.Upcoming,
34-
)
35-
36-
val UNKNOWN_SPEAKER: Speaker = Speaker(
37-
SpeakerId("unknown"), "unknown", "unknown", "unknown", ""
38-
)
19+
import org.jetbrains.kotlinconf.utils.DateTimeFormatting
20+
import kotlin.time.Duration.Companion.minutes
3921

4022
class ConferenceService(
4123
private val client: APIClient,
@@ -162,18 +144,14 @@ class ConferenceService(
162144
suspend fun sendFeedback(sessionId: SessionId, feedbackValue: String): Boolean =
163145
client.sendFeedback(sessionId, feedbackValue)
164146

165-
fun speakerById(id: SpeakerId): Speaker = speakers.value[id] ?: UNKNOWN_SPEAKER
147+
fun speakerById(id: SpeakerId): Speaker? = speakers.value[id]
166148

167-
fun sessionById(id: SessionId): SessionCardView =
168-
sessionCards.value.find { it.id == id } ?: UNKNOWN_SESSION_CARD
169-
170-
fun sessionByIdFlow(id: SessionId): Flow<SessionCardView> =
171-
sessionCards
172-
.map { sessions -> sessions.find { it.id == id } ?: UNKNOWN_SESSION_CARD }
149+
fun sessionByIdFlow(id: SessionId): Flow<SessionCardView?> =
150+
sessionCards.map { sessions -> sessions.find { it.id == id } }
173151

174152
fun speakersBySessionId(id: SessionId): Flow<List<Speaker>> =
175153
sessionByIdFlow(id).map { session ->
176-
session.speakerIds.map { speakerId -> speakerById(speakerId) }
154+
session?.speakerIds?.mapNotNull { speakerId -> speakerById(speakerId) } ?: emptyList()
177155
}
178156

179157
fun sessionsForSpeaker(id: SpeakerId): List<SessionCardView> =
@@ -204,34 +182,26 @@ class ConferenceService(
204182
val notificationsAllowed = storage.getNotificationsAllowed().first()
205183
if (!notificationsAllowed) return@launch
206184

207-
val startTimestamp = session.startsAt.timestamp
208-
val reminderTimestamp = startTimestamp - 5 * 60 * 1000
209-
val nowTimestamp = timeProvider.now().timestamp
210-
val delay = reminderTimestamp - nowTimestamp
211-
val voteTimeStamp = session.endsAt.timestamp
212-
213-
when {
214-
delay >= 0 -> {
215-
notificationManager.schedule(delay, session.title, "Starts in 5 minutes.")
216-
}
185+
val start = session.startsAt.toInstant(EVENT_TIME_ZONE)
186+
val end = session.endsAt.toInstant(EVENT_TIME_ZONE)
187+
val now = timeProvider.now().toInstant(EVENT_TIME_ZONE)
217188

218-
nowTimestamp in reminderTimestamp..<startTimestamp -> {
219-
notificationManager.schedule(0, session.title, "The session is about to start.")
220-
}
189+
val reminderTime = start - 5.minutes
221190

222-
nowTimestamp in startTimestamp..<voteTimeStamp -> {
223-
notificationManager.schedule(0, session.title, "Hurry up! The session has already started!")
224-
}
191+
// Notifications for session start
192+
val startsLater = now < reminderTime
193+
val startsSoon = now in reminderTime..<start
194+
val isLive = now in start..<end
195+
when {
196+
startsLater -> notificationManager.schedule((reminderTime - now).inWholeMilliseconds, session.title, "Starts in 5 minutes.")
197+
startsSoon -> notificationManager.schedule(0, session.title, "The session is about to start.")
198+
isLive -> notificationManager.schedule(0, session.title, "Hurry up! The session has already started!")
225199
}
226200

227-
if (nowTimestamp > voteTimeStamp) return@launch
228-
229-
val voteDelay = voteTimeStamp - nowTimestamp
230-
notificationManager.schedule(
231-
voteDelay,
232-
"${session.title} finished",
233-
"How was the talk?"
234-
)
201+
// Notifications for session end
202+
if (end > now) {
203+
notificationManager.schedule((end - now).inWholeMilliseconds, "${session.title} finished", "How was the talk?")
204+
}
235205
}
236206
}
237207

@@ -247,7 +217,7 @@ class ConferenceService(
247217

248218
private fun mapNewsItemToDisplayItem(
249219
item: NewsItem,
250-
now: GMTDate,
220+
now: LocalDateTime,
251221
): NewsDisplayItem {
252222
return NewsDisplayItem(
253223
id = item.id,
@@ -258,13 +228,12 @@ class ConferenceService(
258228
)
259229
}
260230

261-
private fun GMTDate.toNewsDisplayTime(now: GMTDate): String {
262-
return if (year == now.year && dayOfYear == now.dayOfYear) {
263-
return time()
264-
} else if (year == now.year) {
265-
"${month.value} $dayOfMonth"
266-
} else {
267-
"${month.value} $dayOfMonth, $year"
231+
private fun LocalDateTime.toNewsDisplayTime(now: LocalDateTime): String {
232+
val isToday = year == now.year && dayOfYear == now.dayOfYear
233+
return when {
234+
isToday -> DateTimeFormatting.time(this)
235+
year == now.year -> DateTimeFormatting.date(this)
236+
else -> DateTimeFormatting.dateWithYear(this)
268237
}
269238
}
270239

shared/src/commonMain/kotlin/org/jetbrains/kotlinconf/Model.kt

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
package org.jetbrains.kotlinconf
22

3-
import io.ktor.util.date.*
4-
import kotlinx.serialization.*
5-
import org.jetbrains.kotlinconf.utils.*
3+
import kotlinx.datetime.LocalDateTime
4+
import kotlinx.serialization.Serializable
65
import kotlin.jvm.JvmInline
76

8-
typealias GMTDateSerializable = @Serializable(GMTDateSerializer::class) GMTDate
9-
107
@Serializable
118
@JvmInline
129
value class SpeakerId(val id: String) {
@@ -46,12 +43,10 @@ class Session(
4643
val description: String,
4744
val speakerIds: List<SpeakerId>,
4845
val location: String,
49-
val startsAt: GMTDateSerializable,
50-
val endsAt: GMTDateSerializable,
46+
val startsAt: LocalDateTime,
47+
val endsAt: LocalDateTime,
5148
val tags: List<String>? = null
52-
) {
53-
val timeLine get() = startsAt.time() + " - " + endsAt.time()
54-
}
49+
)
5550

5651
@Serializable
5752
class VoteInfo(
@@ -92,7 +87,7 @@ enum class Theme {
9287
class NewsItem(
9388
val id: String,
9489
val photoUrl: String?,
95-
val date: GMTDateSerializable,
90+
val date: LocalDateTime,
9691
val title: String,
9792
val content: String,
9893
)

0 commit comments

Comments
 (0)