Hi all. I'm trying to understand an issue: I take ...
# kotlinx-datetime
a
Hi all. I'm trying to understand an issue: I take local time and with
.toInstant(TimeZone.UTC)
I'm converting it to UTC. I noticed that the time is kept the same. I was expecting it to change to a time in UTC timezone. This creates a disparity between the user creating data in their timezone and expecting it to show up with the same time (when converted back from UTC into system's timezone) however this is not the case as the time is not adjusted. Am I missing something?
s
How do you take that initial local time?
m
LocalDateTime
has no timezone, so
toInstant
will just create an
Instant
based on that time in the UTC timezone. You probably need to convert the local time into a zoned time in the users timezone and then convert that to UTC. Or pass the user's time zone into the
toInstant
time.
And be aware that not all
LocalDateTimes
exist in all time zones and sometimes they may exist at multiple instants in some timezones.
c
LocalDateTime refers to a general "wall clock" representation, which is what a user would expect to see. But it doesn't refer to a specific moment in time, because my concept of 10am might not be the same moment in time as your 10am, for example. For that reason, converting it to an Instant is not converting from one time zone to another, but instead simply asking the question "what Instant in time corresponds to 10am on Nov 21 in this particular time zone".
In general, it's a best practice to keep the source of truth for your datetimes as Instants (which refers to a moment in time irrespective of any particular time zone) and perform all calculations on Instants. You should only need to drop down to LocalDateTime when you need to display an Instant to a user, which needs to be shown in their local timezone
a
The reason I'm using LocalDateTime is because I'm getting date and time separately and I'm trying to reconstruct a local datetime to then convert to UTC. I'm expecting the datetime to be correctly transitioned to the new timezone.
m
My guess is you want to switch the timezone passed to
toInstant
to be the user's timezone. The local times have no timezone, so there can be no timezone conversion. Instant does have a timezone, so the timezone passed to
toInstant
will indicate how to convert.
a
Indeed that's what I'm doing. Code:
Copy code
val pickedDateTime = LocalDateTime(
                                year,
                                month,
                                day,
                                hour,
                                minute
                            )
Copy code
val utcDateFormattedTimestamp =
                                pickedDateTime.toInstant(TimeZone.UTC)
                            val utcDateTimestamp = utcDateFormattedTimestamp
                                .toEpochMilliseconds()
I'd have expected
utcDateFormattedTimestamp
to be converted to UTC (with time included). Isn't this enough?
m
No,
pickedDataTime
has no timezone, so converting it to an instant with timezone UTC will create an instant whose time is the same string as
pickedDateTime
in the UTC timezone. If you are trying to make the time change, you need to call
toInstant
with the timezone you are trying to convert from.
a
I've to call
toInstant
on what? Please bare with me as I'm trying to get my head around kotlinx datetime
m
You call it on
packedDataTime
like you are, you are just using the wrong timezone. You should be passing in the timezone that
pickedDataTime
is for not UTC.
a
So call
.toInstant(TimeZone.currentSystemDefault())
and then convert to UTC using
toInstant
?
m
Instant's
don't have a timezone. They are just the time since the epoch. So there's no need to call
toInstant
again since you already have an
Instant
after the first call. Look at the sample code in the documentation for the function.
a
I don't see how that's different from what I wrote in my original code. Anyone else want to chime in with regards to https://kotlinlang.slack.com/archives/C01923PC6A0/p1732210868196879?thread_ts=1732200611.471379&cid=C01923PC6A0 ?
m
The difference is the timezone you are passing to
toInstant
.
pickedDateTime.toInstant(TimeZone.UTC)
says convert
pickedDataTime
to an instant assuming the time specified there is in UTC timezone.
o
@Alexandru Gheorghe does the following make sense for you?
Copy code
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime

fun main() {
    // This datetime has no time zone, it cannot specify a point in time without interpretation.
    val zoneLessDateTime = LocalDateTime(year = 2024, monthNumber = 11, dayOfMonth = 21, hour = 18, minute = 41)

    // This is the timezone we choose to interpret our zoneLessDateTime.
    val localTimeZone = TimeZone.currentSystemDefault()

    // Combining the zoneLessDateTime with the time zone makes it an instant, an actual point in time.
    val instant = zoneLessDateTime.toInstant(localTimeZone)

    println("zone-less: $zoneLessDateTime")
    println("instant: $instant")
    println("instant in the local time zone: ${instant.toLocalDateTime(localTimeZone)} $localTimeZone")
}
Which produces the following output in my timezone:
Copy code
zone-less: 2024-11-21T18:41
instant: 2024-11-21T17:41:00Z
instant in the local time zone: 2024-11-21T18:41 Europe/Berlin
The misunderstanding above is:
pickedDateTime.toInstant(TimeZone.UTC)
does not convert between time zones. Since your
pickedDateTime
never had any time zone, you were basically saying "make
pickedDateTime
an instant by interpreting it to be in UTC".
🌟 1
a
Thank you. I researched a bit more as I'm trying to do the opposite of your example. It seems there isn't a way at the moment (correct me if I'm wrong) to create a moment in time that says this time "2024-11-22T05:44" is in timezone A without conversions between UTC and non UTC, to then do something like
timeInTimeZoneA.toInstant(TimeZone.UTC)
and have the time be correctly transitioned to represent datetime in UTC as transformed from timezone A. There's https://github.com/Kotlin/kotlinx-datetime/discussions/237 but even this uses instant
o
You are right that there is no such thing as a
ZonedDateTime
, which would represent your
timeInTimeZoneA
. Currently, the time zone is always a separate entity, which has to be applied when converting to or from a user-readable form (
LocalDateTime
).
👍🏻 1
d
@Alexandru Gheorghe, it may help to read the README file of our library: https://github.com/Kotlin/kotlinx-datetime?tab=readme-ov-file#design-overview
ZonedDateTime
is not provided, as most of its needs are already fulfilled by
Instant
. The discussion you've linked to is about the introduction of a data type for some rare special occasions. If you're getting date and time separately and know which time zone they are in, then this is exactly what @Oliver.O’s example shows: given a
LocalDateTime
(the wall-clock representation that a normal person sees), you receive an
Instant
(the actual moment in time when something happened). If you then want to find out what a person in the UTC time zone would see, you can do this:
Copy code
val localDateTimeInZoneA = LocalDate(2024, 11, 22)
  .atTime(10, 56)
val instant = localDateTimeInZoneA.toInstant(
  TimeZone.of("Europe/Berlin"))
val localDateTimeInUtc = instant.toLocalDateTime(
  TimeZone.UTC)
👍🏻 1