Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using platform specific standard formatters #1

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,24 @@ This is a Kotlin Multiplatform project targeting Android, iOS, Desktop.
you need this entry point for your iOS app. This is also where you should add SwiftUI code for your project.


Learn more about [Kotlin Multiplatform](https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html)…
Learn more about [Kotlin Multiplatform](https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html)…

## Recommendation

Why you should use standard formatters, because every locale uses different style for date/time, like spacing, 12/24 format, etc...

This result is from same code with different locales (US, DE, FR, CS):

US: uses 12h format, `MM dd, yyyy`, comma and space as date separator.
![US](./imgs/image_us.png)

DE: uses 24h format, `dd.MM.yyyy`, dot as date separator.
![DE](./imgs/image_de.png)

FR: uses 24h format, `dd MMM yyyy`, space separator for date.
![FR](./imgs/image_fr.png)

CS: uses 24h format, `dd. MM. yyyy`, dot and space as date separator.
![CS](./imgs/image_cs.png)

Please respect regional settings and use standard formatters if you return output for users :).
4 changes: 4 additions & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,16 @@ android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11

isCoreLibraryDesugaringEnabled = true
}
buildFeatures {
compose = true
}
dependencies {
debugImplementation(compose.uiTooling)

coreLibraryDesugaring(libs.desugar.jdk.libs)
}
}

Expand Down
30 changes: 30 additions & 0 deletions composeApp/src/androidMain/kotlin/DateTimeAdapter.android.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import kotlinx.datetime.*
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle

/**
* You should use standard date and time formatting patterns for the platform you are targeting.
* The reason is that every Locale uses different patterns for formatting dates and times.
* Standard FormatStyle.[SHORT|MEDIUM|LONG] solve this problem for you.
* For example 12/24-hour format, dividers, order of day/month/year in output, etc...
*
* Try to change Locale of your system from US/FR/DE and see how the output changes.
*
* We can create our domain FormatStyle of course and propagate expected format from common
* module and not use only MEDIUM format here...
*/
actual fun getDateTimeAdapter(): DateTimeAdapter = object : DateTimeAdapter {
override fun formatLocalDateTime(dateTime: LocalDateTime): String {
return dateTime.toJavaLocalDateTime().format(
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.MEDIUM)
)
}

override fun formatLocalTime(time: LocalTime): String = time.toJavaLocalTime().format(
DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM)
)

override fun formatLocalDate(date: LocalDate): String = date.toJavaLocalDate().format(
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
)
}
31 changes: 3 additions & 28 deletions composeApp/src/commonMain/kotlin/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,10 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
import kotlinx.datetime.Clock
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.UtcOffset
import kotlinx.datetime.format
import kotlinx.datetime.format.char
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
import org.jetbrains.compose.ui.tooling.preview.Preview
import kotlin.time.Duration.Companion.hours

data class City(
val name: String,
Expand All @@ -58,7 +52,7 @@ fun App() {
)
}
LaunchedEffect(true) {
while(true) {
while (true) {
cityTimes = cities.map {
val now = Clock.System.now()
it to now.toLocalDateTime(it.timeZone)
Expand Down Expand Up @@ -87,31 +81,12 @@ fun App() {
horizontalAlignment = Alignment.End
) {
Text(
text = dateTime
.format(
LocalDateTime.Format {
hour()
char(':')
minute()
char(':')
second()
}
),
text = getDateTimeAdapter().formatLocalTime(dateTime.time),
fontSize = 30.sp,
fontWeight = FontWeight.Light
)
Text(
text = dateTime
.format(
LocalDateTime.Format {
dayOfMonth()
char('/')
monthNumber()
char('/')
year()
}
),
fontSize = 16.sp,
text = getDateTimeAdapter().formatLocalDate(dateTime.date),
fontWeight = FontWeight.Light,
textAlign = TextAlign.End
)
Expand Down
11 changes: 11 additions & 0 deletions composeApp/src/commonMain/kotlin/DateTimeAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.LocalTime

interface DateTimeAdapter {
fun formatLocalDateTime(dateTime: LocalDateTime): String
fun formatLocalTime(time: LocalTime): String
fun formatLocalDate(date: LocalDate): String
}

expect fun getDateTimeAdapter(): DateTimeAdapter
17 changes: 17 additions & 0 deletions composeApp/src/desktopMain/kotlin/DateTimeAdapter.desktop.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import kotlinx.datetime.*
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle

actual fun getDateTimeAdapter(): DateTimeAdapter = object : DateTimeAdapter {
override fun formatLocalDateTime(dateTime: LocalDateTime): String = dateTime.toJavaLocalDateTime().format(
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.MEDIUM)
)

override fun formatLocalTime(time: LocalTime): String = time.toJavaLocalTime().format(
DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM)
)

override fun formatLocalDate(date: LocalDate): String = date.toJavaLocalDate().format(
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
)
}
20 changes: 20 additions & 0 deletions composeApp/src/iosMain/kotlin/DateTimeAdapter.ios.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.LocalTime

/**
* Use iOS DateFormatter: https://stackoverflow.com/a/41769435/4024146"
*/
actual fun getDateTimeAdapter(): DateTimeAdapter = object : DateTimeAdapter {
override fun formatLocalDateTime(dateTime: LocalDateTime): String {
TODO("Not yet implemented")
}

override fun formatLocalTime(time: LocalTime): String {
TODO("Not yet implemented")
}

override fun formatLocalDate(date: LocalDate): String {
TODO("Not yet implemented")
}
}
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ androidx-espresso-core = "3.6.1"
androidx-material = "1.12.0"
androidx-test-junit = "1.2.1"
compose-plugin = "1.6.11"
desugar_jdk_libs = "2.0.4"
junit = "4.13.2"
kotlin = "2.0.0"
datetime = "0.6.0"
Expand All @@ -28,6 +29,9 @@ androidx-constraintlayout = { group = "androidx.constraintlayout", name = "const
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime"}

# Desugar for support Java Time API on older Android versions
desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" }

[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
Expand Down
Binary file added imgs/image_cs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added imgs/image_de.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added imgs/image_fr.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added imgs/image_us.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.