Convert User Time To Server Time In Kotlin
A simple approach to convert user date and time to server date and time, in Kotlin.
Timezone handling can be a big nuisance when designing systems. How do you keep everything consistent and in sync, but also have it make sense to the user? I will outline a very quick approach on how I handle timezone problems by converting user time to server time in Kotlin.
The Frontend
Your frontend can be anything, but I want to show you a quick and dirty example of how I manage date and time in basic JavaScript.
{
date: document.getElementById('dateInput').value,
hour: document.getElementById('hourInput').value,
minute: document.getElementById('minuteInput').value,
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
}
Here is a basic JS object that has several date and time properties. Obviously, you shouldn't do this directly, always do an early validation of your inputs, and you always have the option to use the datetime HTML object instead of splitting it up as I did, but I like having more control in my projects.
This object would now be sent to our backend server through your favorite means such as ajax etc. The most important thing to note is that we are sending the user's time zone in the data.
The Backend
Okay, in our backend server we have received the json data and converted it to something nice. Maybe this data object:
data class UserTimeInput(
val date: LocalDate,
val hour: Int,
val minute: Int,
val rawTimeZone: String
)
Simple enough. We could do some fancier in-place conversion of the timezone by managing our serializers but I am going to separate that out and start with rawTimeZone as the string we passed in.
There is one thing I very strongly recommend you do. Make all your server times consistent by setting that as a property in your server setup. In my ktor app, this is incredibly simple. This is what it looks like:
fun main(args: Array<String>) {
TimeZone.setDefault(TimeZone.getTimeZone("UTC")) //<=== the important part
startApplication(4567, true, ::baseModule)
}
Whenever the server is starting up, I force the default timezone to be UTC. This saves me a lot of headaches throughout the app when I need to refer to defaults.
Utilities
Finally, lets just create a few simple utility calls and then we are good to go!
fun String.toZoneId(): ZoneId {
return ZoneId.of(this)
}
This will take our raw timezone string and convert it to something usable, a ZoneId object.
fun LocalDate.toServerDateTime(hour: Int, minute: Int, timeZone: ZoneId): LocalDateTime {
val usersDateTime = this.atTime(hour, minute)
return usersDateTime.toServerDateTime(timeZone)
}
fun LocalDateTime.toServerDateTime(timeZone: ZoneId): LocalDateTime {
return this.atZone(timeZone)
.withZoneSameInstant(ZoneId.systemDefault())
.toLocalDateTime()
}
These are fairly straightforward utilities. The first one is just a handy wrapper to take split apart time, wrap it up for the other utility function. The second function is where all the logic is. We take our users datetime and apply our timezone to it. We use withZoneSameInstant to apply our servers timezone. Finally, we convert it to our nicely usable localdatetime.
An Example
Heres a quick example of how I would use it in the backend.
fun call(input: UserTimeInput) {
//validate inputs
val usersTimeZone = input.rawTimeZone.toZoneId()
val serverDateTime = input.date.toServerDateTime(input.hour, input.minute, usersTimeZone)
//I know have the server timezone, can save my data in a consistent way!
}