A call to action! When designing the format strin...
# kotlinx-datetime
d
A call to action! When designing the format string API for non-localized date/time formats, we decided on two possible forms: • A format compatible with most common usages of Java's
DateTimeFormatter
and Swift's `DateFormatter`:
yyyy-MM-dd'T'HH:mm:ssxxx
. The localization functionality will be removed, and some common warts (for example,
yyyy
vs
YYYY
vs
uuuu
) will be fixed. • A brand new format that splits the format string into date, time, and UTC offset sections. The format above would look like
ld<yyyy-mm-dd>'T'lt<hh:mm:ss>uo<+hh:mm>
, where
<
and
>
separate sections, and
ld
,
lt
, and
uo
are the section names, where
ld
means `l`ocal `d`ate,
lt
means `l`ocal `t`ime, and
uo
means `U`TC `o`ffset. This helps keep the letters in the format intuitive and not worry about
MM
vs
mm
. ◦ Also, a conversion function from the
DateTimeFormatter
format will be provided to simplify migration. Both approaches have their benefits. Those familiar with Java's
DateTimeFormatter
syntax may not want to migrate to and learn some new format, while everyone else may be against remembering what
xxx
means and what the difference is between
HH
and
hh
. Please tell us which benefits are more important to you! 1️⃣ I strongly prefer to see formats like
yyyy-MM-dd'T'HH:mm:ssxxx
(with the problematic parts of the API fixed) in my codebase. 2️⃣ I somewhat prefer formats like
yyyy-MM-dd'T'HH:mm:ssxxx
. 3️⃣ I somewhat prefer that the strings for date formats be rethought from the ground up. 4️⃣ I strongly want the strings for date formats to be rethought from the ground up (and if I encounter a format like
yyyy-MM-dd
, I'll use the conversion function). 5️⃣ What format strings? I just want a concise builder API for the formats. We want as many votes as possible! Please also ask your friends and colleagues. If they don't want to register in the Kotlin Slack, it's okay if you tell us their vote in the comments 🧵
5️⃣ 153
2️⃣ 13
4️⃣ 24
3️⃣ 4
1️⃣ 58
Share additional votes and ask questions here!
k
The localization functionality will be removed,
I assume this is the case because the general lack of localization capabilities in Kotlin without the JVM? Or are there other reasons this decision was made.
And just to be certain, you’re referring to the
JavaFormatStyle
which respects localization, right? Or are you referring to other warts.
d
There are other reasons, yes. Basically, it's incorrect to support localization via format strings.
MMMM dd, yyyy
would change the localization of the
MMMM
field, but in some locales, this whole field order is just incorrect. The modern locale-aware APIs deal with sets of fields, like "month name, and also a day, and also a year" and let the runtime decide on the correct form for the given locale. Details: https://github.com/Kotlin/kotlinx-datetime/discussions/253
k
Neat! Thanks.
I’m glad kotlinx-dt isn’t avoiding the localized dt problem altogether. You have my vote 🙂
d
JavaFormatStyle
You mean Java's
FormatStyle
? Actually, we may support something like this at a later point, but the question, for now, is purely about the format strings: whether to keep them Java-like (options 1️⃣ and 2️⃣) or introduce new ones (options 3️⃣ and 4️⃣).
k
yes, I do mean
FormatStyle
. 🤦 The place I use it in my codebase I had an import alias and forgot its true name.
After reading that discussion I changed my vote. The builder API in swift looks really nice.
d
Even though the java/swift format strings aren't always intuitive, it is still a relatively well known format. I would prefer that over a new format that isn't standardized either, even if it may be a bit more intuitive. That being said, a builder API is by far the most Kotlin approach imo. 🙂
k
Why not ISO8601 format? It's a standard...there's Java support...backends support it. You can use ISO8601DateFormatter to parse it in Swift too
d
ISO 8601 is already supported via
parse
and
toString
functions, this is about parsing and formatting in custom formats. If a string like
2023-02-03 23:15
arrives from somewhere, ISO 8601 won't be of help!
c
Out of curiosity, what would the builder look like?
m
I would typically want something that would support behaviors like https://developer.android.com/reference/android/text/format/DateFormat#getBestDateTimePattern(java.util.Locale,%20java.lang.String) Where the order of things are corrected based on the local being used. A builder might be nice also, but these format strings are well known.
d
@CLOVIS We didn't discuss it thoroughly, but it could look something like Swift's new API:
localTime.format { hour(2).char(':').minute(2).char(':').second(2) }
or, using separate lines and naming all parameters,
Copy code
localTime.format {
  hour(minDigits = 2)
  char(':')
  minute(minDigits = 2)
  char(':')
  second(minDigits = 2)
}
c
That looks better for code reusability to me
d
@mkrussel, this is out of scope for now, as Kotlin doesn't have locale support, but when the locale support arrives, something
getBestDateTimePattern
will certainly be needed. There's a separate discussion about it: https://github.com/Kotlin/kotlinx-datetime/discussions/253
k
but when the locale support arrives,
👀
m
The string version is nice for being able to extract the string into a configuration, where the builder is easier and less likely to introduce bugs.
k
You could fairly easily use your own string format to dynamically format datetimes with a builder API, though
m
yes
d
The formats in general can be serializable, so if the problem is just storing them as strings in configuration (and not storing the configuration in any specific format), the builder API is not disqualified.
c
Depending on your manpower, I think the ideal solution would be to develop the builder as the recommended solution, and offer a convenience function that converts the Java-style pattern into a builder instance.
d
Also, a conversion function from the
DateTimeFormatter
format will be provided to simplify migration.
So, no doubt Java's format will be available in some form, the question is, is it the central way to build formats that is supposed to stay in the codebase, or is it just some tiny extension function in the
kotlinx.datetime.format.migration
package for those who actually need it.
c
IMO a Kotlin DSL is better in every way: autocomplete, automatic validation, no risks of typos, sub-parts of it can be reused between multiple formats.
t
I like the builder plus wrapper approach. My worry with solution 1 is that people will expect certain behavior because the formats strings look close enough to a standard and then they will be surprised it did not do what they expected (the uncanny valley of api design. 🙂 )
k
no need to look up what random sequences of characters means, no conflict with existing stackoverflow posts with the java time format…
d
There's a way in which a DSL loses: code golfing.
yyyy-MM-dd'T'HH:mm:ssxxx
(and its equivalent
ld<yyyy-mm-dd>'T'lt<hh:mm:ss>uo<+hh:mm>
) both take a single line of code, whereas with a DSL, you'd have trouble packing it into even two lines. And during our research, we've seen people go to great lengths to keep using string-based API: for example, using
[.S][.SS][.SSS][.SSSS][.SSSSS][.SSSSSS][.SSSSSSS][.SSSSSSSS][.SSSSSSSSS]
to parse a fractional second of arbitrary length instead of using a builder with
appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
. So, clearly, there's a demand for conciseness, which is why we thought that format strings would be a welcome addition!
no need to look up what random sequences of characters means, no conflict with existing stackoverflow posts with the java time format…
Also solved by introducing a more intuitive format!
k
using
[.S][.SS][.SSS][.SSSS][.SSSSS][.SSSSSS][.SSSSSSS][.SSSSSSSS][.SSSSSSSSS]
to parse a fractional second of arbitrary length
Do you have any idea whether this is because people don’t know about the “better” approach versus doing the stingy one?
d
I know that at least some of them did this intentionally, yes. They said so.
k
Would it make sense to expose a string format parsing thing on top of a builder and annotate it with
@DelicateDateTimeApi
or something like that?
My opinion is that code golf is silly in this scenario, but if people want to do it then they should know it’s delicate and potentially error prone
d
Maybe. We'll need to see the outcome of the poll first. If, in the end, there will be something like 1000 "1"s and fewer than 100 of everything else combined, it'll be obvious that the Java format strings are very desired, and hiding them behind
@DelicateDateTimeApi
isn't viable. There's no point in trying to decide anything now.
p
Maybe a naive question: Within my career I almost never had the need for a non localized date formatting. What's the use case for this? Is it only for serialization?
d
@Paul Woitaschek It's anything where you need machine-machine interaction. • Outputting logs. • Interacting with third-party APIs that output or accept date/time data in a specific format. • Parsing anything. For example, some arbitrary dates you see in a web page when doing web scraping.
p
Thanks. Then a big fat 5️⃣ ^^
I usually don’t trust string templates at all. Whenever I see them being used I usually ask for a ton of tests so I can build trust in the implementation.
m
Seems good an idea to implement a few formats (
FormatStyle
) and give programmer an ability to choose between them: •
Iso8601
— Java format •
Kotlinx
— new format by kotlinx-datetime • ... (some other formats in the future) • <default format style> It will apply to multiplatform programming, because devs from different platforms could prefer different format styles. It will also allow oldschool devs to use JavaFormat It will also allow new wave devs to use precise format.
d
Not really. Imagine this situation: there are five people working on a project, and each one has their own preference for format strings. One would like to use C-style
strftime
strings, one wants Go-style strings, someone else loves Java's
DateTimeFormatter
, etc. Initially, each one works on their component separately, and bam! The codebase has five different string format styles. When someone needs to look into a component written by another person, now they need to learn a new format for format strings. As a result, literally everyone is irritated. Now generalize this to Stack Overflow answers, blog posts, etc, where different people use would different format strings. This kind of community fragmentation is significantly detrimental. Of course, we need to provide some migration functions for compatibility, but there still needs to be at most one idiomatic way to write datetime formats as strings. Possibly none at all.
m
True. At one moment we can find out that our project use different formats. Nevertheless I suppose that it is a problem of a projects' standards, not the problem of the library. In programming we have many different ways to solve one problem (e.g.
for
vs
while
). It doesn't mean we need to force exactly one way to do things. I think it's a problem of library positioning. If kotlinx-datetime wants to move toward giving some de-facto standards (like kotlinx-coroutines does), so it is reasonable to choose one format style and force exact opinion. But in date/time domain I suppose everything was already invented. So for me it seems more clever way is to structure different ways of formatting into one library and give dev possibility to choose.
f
golang uses
RFC 3339
and java
ISO 8601
so to me as long as the new format is compatible with both, I am ok with. I don't want to have to build custom parser everywhere
c
ISO 8601 is already the default in KotlinX.Datetime. This discussion is not about changing it, it's about the way you create formats that are not already built-in.
d
In programming we have many different ways to solve one problem (e.g.
for
vs
while
).
... and as a result, everyone has to learn about both types of loops. Now imagine a programming language that introduced another ten types of loops to accommodate all preferences. That's what we would be doing if we introduced many formats as equals, except format strings are trickier than loops.
m
Both
for
and
while
are usable. Different cases require different approaches. For kotlinx-datetime different format styles could fit developers from different platforms. As a developer that is not familiar with Java world (and with ISO standards) I could expect from multiplatform date/time library for-one-thing that one of popular formats I am familiar with is supported. Personally I would prefer one format as maximum like now (for configs) and also a DSL that would allow to format dates and parse them (if I want to do things in source code).
e
Having both DSL and a string format is an option. They, indeed, might better fit different use-cases. Having out-of-the-box support for different string formats is definitely not an option that we are willing to consider.
c
Would having a DSL + conversion methods for each platform (e.g. on the JVM, some kind of wrapper to be able to use existing instances of
DateTimeFormatter
) be an option? This way, there is a clear default—the DSL is the only multiplatform option—while still being compatible with whatever each platform's developers expect. Of course, the risk is libraries that start as Java-only become harder to migrate to multiplatform later on. I have no idea how complicated converting between existing formatting utilities is, but I'd be willing to assume it's not easier than reimplementing their spec.
d
Would having a DSL + conversion methods for each platform (e.g. on the
JVM, some kind of wrapper to be able to use existing instances of
DateTimeFormatter
) be an option?
Yes.
Of course, the risk is libraries that start as Java-only become harder to migrate to multiplatform later on.
Someone who uses
java.time.DateTimeFormatter
in their code probably knows that this code is not going to run on other platforms, so they are aware of the compromise.
c
I obviously can't speak for everyone else in this thread, but for me, that sounds like the ideal compromise between familiarity, readability and idiomatic usage.
m
Would having a DSL + conversion methods for each platform (e.g. on the JVM, some kind of wrapper to be able to use existing instances of
DateTimeFormatter
) be an option?
Ideally (a converter from) platform specific formats (like from`DateTimeFormatter`) should be available on all other platforms as well - for example if one is converting their kotlin code to multiplatform, or maybe reads the formats from config or some external sources etc.
c
@mcpiroman that's not possible, DateTimeFormatter doesn't exist on all platforms, so you can't create a function that takes it as argument.
m
But it it were to be converters from those formats, at least those could be multiplatform.
j
As much as possible we should guide developers towards RFC 3339 and ISO-8601, which are totally fantastic and suitable for all machine-readable dates. In the rare case where interop with somebody else’s non-ISO-8601 API is necessary, I’d prefer it use syntax that tracks Java’s DateTimeFormatter APIs as closely as possible so I can convert between the two without effort. For example, if I see a StackOverflow answer for how to configure DateTimeFormatter to interop with some API, that format string should work with Kotlin. A nice DSL or builder is cute, but it is more work to convert between that and other platforms.
1
j
This is probably a dumb question, but isn't ISO8601 a specification of the meaning of the symbols like H, Y, m etc? Rather (or in addition to) the whole string like "2010-06-01T221944.475Z". As in a format of just "HH:mm" would still fall under the ISO8601 specification? In that case shouldn't that be used instead of a new custom format? (Though a builder syntax would be far better imo)
or actually maybe I'm thinking of this existing standard https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
r
My question may be stupid but what's the usage of offset dates? I either have a time zone, or everything is UTC (or more exactly, I either display an absolute point in time for humans, or store it). I don't get offset dates I'm asking because I see mention of UTC offset in the proposed format itself, but I don't see anything about time zones
e
I think a DSL + string format is the way to go (same for localized whenever that is available). I think using the java format is best considering the ecosystem, and it's not really as bad as it's perceived to be (weird bugs aside)
d
For example, if I see a StackOverflow answer for how to configure DateTimeFormatter to interop with some API, that format string should work with Kotlin
It will, don't worry. The question is, will it be the main format, or will it work through a conversion function.
ISO 8601
It defines a specific format, not a way to create custom formats, and in this discussion, we're interested in exactly the custom formats. If you can use ISO 8601, then
parse
and
format
functions in
kotlinx-datetime
already work as you expect.
My question may be stupid but what's the usage of offset dates?
Some use cases are mentioned in https://github.com/Kotlin/kotlinx-datetime/issues/90 : some API endpoints use datetimes with an offset to encode both an
Instant
and a
LocalDateTime
. For example, a timetable: we're interested in both what the local time is when a plane arrives (so we know whether the shops are closed) and the actual instant when it happens (so we know whether we'll be in time for some event).
f
I think a java style would be nice as a v1 and then we can add other styles if needed?
c
My opinion would be to prefer a DSL for an idiomatic MPP API. It’s clearly separate from java-style String formats, easier to verify correctness at a glance, and is more discoverable because of strong type-safety and IDE support. Using String formats is necessary for Java compatibility, but I think it would probably be fine to simply delegate to the underlying Java APIs for those cases. Other targets probably don’t need such strong compatibility guarantees, as they’re likely not using Java-style format strings anyway (either using kotlinx.datetime without format strings, or using native datetime APIs with a different format). There does not currently exist a KMP common datetime String format, and for the reasons stated above of fragmentation, it’s probably best to avoid attempting to create one.
f
I feel the opposite, DSL doesn't provide compile time safety and is more difficult to read than only one string. Dsl is probably one of the worst features of Kotlin as it gives you a false feeling of safety
r
@Dmitry Khalanskiy [JB] a quick scan of that issue tells me the use for offsets is compatibility with old stuff and antipatterns. For me, it's at most something that you should be able to work with if you already have badly designed data, but I don't get why it's in the poll for "what should be the main format". Offsets need to die and I worry seeing them being this important here. Offsets are easier to use than zones and ALWAYS a bad idea, and most/bad developers will prefer an incorrect easy solution over a correct more complicated one. As you can see in the last post of that very issue, the "new JS Date" thing will not support offsets, while still providing some way to handle niche cases.
c
My concern with a String format that isn’t exactly the Java-style, is that there will be a lot of folks passing Java-style strings to the format function expecting them to work. It’s inevitable that some people won’t read the documentation, or they will miss a spot when migrating from Java, and will end up getting very subtle runtime errors because they’re using a Java-style format in a place that should be using the Kotlin-style format. Even with IDE Inspections, it will only be able to validate hard-coded format strings and be unable to warn when the format is defined in a configuration file or coming from an API. To @florent’s point, a DSL might not give any true guarantees, but neither does a String format. At least with a DSL, you have some kind of compile-time checks to know whether you should be working with Kotlin-style or Java-style date formats.
d
Other targets probably don’t need such strong compatibility guarantees, as they’re likely not using Java-style format strings anyway
But they do. For example, it's the standard datetime parsing/formatting mechanism in Objective-C/Swift: https://developer.apple.com/documentation/foundation/nsdateformatter
DSL doesn't provide compile time safety
Are we talking about the same thing?
localTime.format { hour(); char(':'); minute() }
is an example of using a DSL, and there's certainly the type safety of not being able to use
day()
when formatting times. Not sure what you mean.
Offsets need to die and I worry seeing them being this important here.
I don't understand your point at all, sorry. The fact is, offsets are already widely used in the API endpoints. Regardless of whether offsets need to die (and you're probably thinking about calculations with offsets, which is a separate problem from data exchange), people need to parse and format dates using them today, it's non-negotiable. For example, in the list of commonly used Java format strings we collected (https://github.com/dkhalanskyjb/journal/blob/main/Datetime/FormattingWiki/patterns.csv), the 32nd most popular format uses an offset. As does the 41st. So, offsets really are quite important, if you have to interact with anything at all that already uses offsets today, and there are a lot of such places: https://grep.app/search?q=yyyy-MM-dd%27T%27HH%3Amm%3AssZ
but I don't get why it's in the poll for "what should be the main format".
The reason is, something like
yyyy-MM-dd
is such a simple thing that it's hard to get that wrong, no matter which string format is used. The moment the format becomes important is when it stops being so trivial and you can accidentally get it wrong.
there will be a lot of folks passing Java-style strings to the format function expecting them to work
We tried our best to avoid this exact problem. The fixes we're talking about are things like interpreting
YYYY-MM-dd
as
yyyy-MM-dd
, since, clearly, no one could have actually meant the first one.
d
Personally I love the new format with the extra metadata. I also love the builder. I voted for 5 because I had to pick one and builders are super nice. I care less about lines of code then I do ease of use and maintainability.
Having both DSL and a string format is an option. They, indeed, might better fit different use-cases.
— Roman Elizarov
Is really what I want, both. Only 1 way of doing things sounds like a nice ideal. But I am not convinced reality works like that. Adaptability is generally that which endures the best.
p
If Java's format strings are going to be supported (in case some of my teammates wouldn't like to use the new API builder), I would like to use the new API.. but I am still confused about the usecases for localized datetime. Also, how would you enable 24-hour to 12-hour (and vis-à-vis) migration?
d
OR give us a library that uses ML to parse any kind of date you can imagine into an ADT of Instant or Date or Time or DateTime? … … Look, a man can dream right?
c
give us a library that uses ML to slightly incorrectly parse any kind of date you can imagine into an ADT of Instant or Date or Time or DateTime
There, fixed it for you
j
Wild idea: pick an arbitrary date & time where all fields are distinct: perhaps 2019-10-31 000102.345678. Then to make a formatter, just show the code how this magic date is formatted: val dateFormatter1 = DateFormatter.patternMatch(“October 31 ’19”) val dateFormatter2 = DateFormatter.patternMatch(“00:01 OCT. 31”) val dateFormatter3 = DateFormatter.patternMatch(“12:01 am Thursday”) I believe these are all unambiguous. Treat tokens like
OCT
as if it were SimpleDateFormat’s
MMM
, and
00
as if it were SimpleDateFormat’s
HH
.
j
Oh neat!
I must have seen that before this idea’s too weird to be original
m
“Brand new” = FAIL. Please emphasize brain share and minimize new things which nobody has ever heard of, ESPECIALLY for dates which are incredibly common and do not warrant language-specific solutions.
c
It's exactly because the only current option are language-specific formats that are similar but different enough to be footguns, that I think we need something entirely different.
p
My preference is the format string approach, since it can be easier externalised (like defined in a property file) than a DSL based solution.
s
I think that the builder is more likely to be timeless than format strings. Maybe they will be used for serialization, and you can theoretically write them manually, but the same is true of SVGs. As a teacher who uses Kotlin for programming beginners, I don't forsee students learning the obtuse syntax of format strings. It is a mini-language like Regex, there is no IDE help, it is not intuitive what the difference between
YYYY
and
yyyy
is, etc. As a dev, every time I come across a format string that I have to write or modify, I have to dig through the abstraction layers and helper functions from
String.format
until I get to the javadoc with the format string spec to remind myself what something means or how to do something. Having a builder would circumvent that.
The concept of a date string format is fundamentally a different paradigm than the rest of the Kotlin language and requires its own study and memorization. And, it does not seem to be a step in the direction of abstraction and declarative programming. I don’t see why a core Kotlin functionality should be relegated to exclusive use by a mini-language. Regex is its own language, and Kotlin provides facilities for using that language (so I understand why there is no argument for building a Regex builder and instead forcing them to learn Regex), but why should date formatting be its own language, and the only way to use date formatting be through this language? Playing devil’s advocate, you can argue that it is similar to Regex, for which there is support in other languages so the skill is transferrable, and therefore here too the skill is transferable. Whereas if all they learn is the builder library, they may either have to learn the format string syntax when they move to a language without an alternative to format strings and we will be back to square one, or learn how that language’s alternative works. I reply, but why is this something we want to prolong? Swift is already moving towards a cleaner, more intuitive, more discoverable builder interface. Just like the Compose wave starting with React and SwiftUI (which are ultimately abstractions above pixel and memory address manipulation), I see a trend towards higher abstractions coming, and I think a format string is an artifact of computing’s ancient days which is holding back the new generation from learning programming (many of whom will not be CS majors, just practitioners, so won’t have time to learn all of the ins and outs that are not core Kotlin syntax) and is a step down in abstraction. Creating a solution to replace format strings with e.g. a builder which is closer to plain english prose is similar to the progression from Machine Code to Kotlin. Machine Code is comprised of terse “codes” that aren’t self-explanatory to english-speaking humans. Kotlin often reads close to plain english prose:
if(trafficLight.isGreen) car.drive() else car.stop()
. Assembly was an abstraction above Machine Code, but it wasn’t much better (they sure thought it was at the time). Rethinking format strings would be going from MC to Assembly - why not jump straight to Kotlin (literally)?
@Dmitry Khalanskiy [JB] Is there a timeline for when this will be ready? Has a consensus been reached about the path forward?
1
193 Views