Trying to parse with Kotlin DateTime the following...
# getting-started
l
Trying to parse with Kotlin DateTime the following string:
Copy code
2024-06-04T15:36:13Z
using the following pattern:
Copy code
val PATTERN = "yyyy-MM-dd'T'HH:mm:ssX"

    @OptIn(FormatStringsInDatetimeFormats::class)
    val dateTimeFormat = LocalDateTime.Format {
        byUnicodePattern(PATTERN)
    }

    override fun deserialize(decoder: Decoder): LocalDateTime {
        return LocalDateTime.parse(decoder.decodeString(), dateTimeFormat)
    }
Getting the following error:
Copy code
java.lang.IllegalArgumentException: A UTC-offset-based directive X was used in a format builder that doesn't support UTC offset components
Is it because
LocalDateTimeFormat
doesn't support time zones?
r
Exactly right, if you have a TimeZone you should be using Instant https://kotlinlang.org/api/kotlinx-datetime/kotlinx-datetime/kotlinx.datetime/-local-date-time/
l
How can I parse this string to an Instant?
r
Code should be exactly the same, just replace LocalDateTime with Instant
l
There is no
Instant.Format
All the parsing examples are with
LocalDateFormat
or
LocalDate
. Apparently timezon has to be first separated from the string and parsed separately. https://github.com/Kotlin/kotlinx-datetime?tab=readme-ov-file#working-with-other-string-formats
s
In your case, the string you have is in the default ISO format, so you don't need a custom formatter. In the general case, you'd use
DateTimeComponents.Format
.
l
Oh, thanks! So I'm using
val dateTimeFormat = DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET
now
s
That's reasonable, if you want to be explicit. But you can also just use
Instant.parse(yourString)
, because ISO format is the default.
l
But then how do I pass the time zone information to the
Instant
if I want to have a
LocalDateTime
?
r
l
It doesn't work
Copy code
object DateSerializer : KSerializer<LocalDateTime> {

    private val dateTimeFormat = DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET

    override val descriptor = PrimitiveSerialDescriptor("kotlinx.datetime.LocalDateTime", PrimitiveKind.STRING)

    override fun deserialize(decoder: Decoder): LocalDateTime {
        return dateTimeFormat.parse(decoder.decodeString()).toLocalDateTime()
    }

    override fun serialize(encoder: Encoder, value: LocalDateTime) {
    }
}
When I parse it like this, the LocalDateTime is not converted to the timezone of my computer
I parse the string
2024-06-04T15:36:13Z
and it gets parsed to
2024-06-04T15:36:13
although I'm in UTC+2
s
You need to use
Instant.parse
instead of
dateTimeFormat.parse
What you're getting at the moment is a
DateTimeComponents
instance, which isn't really intended to be used to store values; just for parsing
l
How can I get a localized datetime (in my local timezone) from this string?
Doesn't
DateTimeComponents.toLocalDateTime()
convert them to a
LocalDateTime
?
I'm returning
LocalDateTime
from
deserialize()
s
You could use
TimeZone.currentSystemDefault()
to get your computer's current time zone
l
When I print the parsed
DateTimeComponents
timeZoneId
it's
null
So it look like the timezone information is ignored in the string
BTW you cannot even pass TimeZone to
DateTimeComponents.toLocalDateTime()
It doesn't take any arguments
s
Yes, that's why I recommended to use
Instant.parse
to get an
Instant
instead of a
DateTimeComponents
.
l
Instant.parse()
loses the timezone information from parsed string
s
It sounds like what you're looking for is something like Java's
ZonedDateTime
, which doesn't exist in Kotlin unfortunately.
l
Yes, that's what I was looking for
BTW
DateTimeComponents
seems to parse timezone information, but it's somehow lost
Copy code
public val ISO_DATE_TIME_OFFSET: DateTimeFormat<DateTimeComponents> = Format {
            date(ISO_DATE)
            alternativeParsing({
                char('t')
            }) {
                char('T')
            }
            hour()
            char(':')
            minute()
            char(':')
            second()
            optional {
                char('.')
                secondFraction(1, 9)
            }
            alternativeParsing({
                offsetHours()
            }) {
                offset(UtcOffset.Formats.ISO)
            }
        }
I don't know why the
timeZoneId
is then
null
after parsing
s
Are you sure you need the timezone? The
Instant
isn't "losing" the timezone information, it's just decoding it into an actual moment in time.
r
Instant doesn't have any concept of time zone (or of dates or calendars), but if you parse the same string with a different zone you will get a different Instant
☝️ 1
l
Hmm, my thinking was kind of
toLocalDateTime()
would take implicitly the timezone information from the string and put it into my local time zone.
Ok, so the Instant is by default in UTC?
s
Ok, so the Instant is by default in UTC?
No, an Instant is not in any time zone at all
l
Sorry, I confused
DateTimeComponents
with
Instant
Why is there a
DateTimeComponents.toLocalDateTime()
method when it neither takes into account a timezone from the
DateTimeComponents
nor takes a timezone as parameter?
Or it just assumes the parsed timezone is the same as the timezone of the parsing computer?
s
DateTimeComponents
isn't for conversion; just for parsing and formatting. So its
toLocalDateTime()
function doesn't look at any of its fields besides the date and time of day.
l
Hmm ok
That's a bit confusing API for me
BTW how do I serialize an Instant to an ISO format?
s
.toString()
😉
l
Perfect
Thank you so much for your help!
s
No worries! By the way I figured out why
DateTimeComponents
had a null time zone ID after parsing the ISO string. It actually puts that information in the
offsetHours
field, since the ISO format contains a numeric offset rather than the ID of a specific timezone.
385 Views