Skip to content
Merged
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
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {
}

detekt {
toolVersion = "1.23.7"
toolVersion = "1.23.8"
config.setFrom(files("$rootDir/config/detekt/detekt.yml"))
parallel = true
buildUponDefaultConfig = true
Expand Down
3 changes: 2 additions & 1 deletion config/detekt/detekt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ naming:
MatchingDeclarationName:
active: true
mustBeFirst: true
multiplatformTargets: ['ios', 'android', 'js', 'jvm', 'native', 'iosArm64', 'iosX64', 'macosX64', 'mingwX64', 'linuxX64' , 'commonJvm', 'commonJs']
MemberNameEqualsClassName:
active: true
ignoreOverridden: true
Expand Down Expand Up @@ -782,4 +783,4 @@ style:
WildcardImport:
active: true
excludeImports:
- 'java.util.*'
- 'java.util.*'
6 changes: 4 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
[versions]
agp = "8.5.2"
kotlin = "2.1.0"
android-minSdk = "24"
android-minSdk = "21"
android-compileSdk = "34"
kotlinxDatetime = "0.6.2"

[libraries]
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }

[plugins]
androidLibrary = { id = "com.android.library", version.ref = "agp" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
vanniktech-mavenPublish = { id = "com.vanniktech.maven.publish", version = "0.30.0" }
detekt = { id ="io.gitlab.arturbosch.detekt", version ="1.23.7" }
detekt = { id ="io.gitlab.arturbosch.detekt", version ="1.23.8" }
10 changes: 10 additions & 0 deletions kotlin-js-store/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"

"@js-joda/core@3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273"
integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg==

"@leichtgewicht/ip-codec@^2.0.1":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1"
Expand Down Expand Up @@ -1703,6 +1708,11 @@ log4js@^6.4.1:
rfdc "^1.3.0"
streamroller "^3.1.5"

luxon@3.4.3:
version "3.4.3"
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.3.tgz#8ddf0358a9492267ffec6a13675fbaab5551315d"
integrity sha512-tFWBiv3h7z+T/tDaoxA8rqTxy1CHV6gHS//QdaH4pulbq/JuBSGgQspQQqcgnwdAx6pNI7cmvz5Sv/addzHmUg==

math-intrinsics@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
Expand Down
18 changes: 18 additions & 0 deletions library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,14 @@ kotlin {
binaries.executable()
}


@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser()
binaries.executable()
compilerOptions {
freeCompilerArgs.add("-Xwasm-attach-js-exception")
}
}

@OptIn(ExperimentalKotlinGradlePluginApi::class)
Expand All @@ -60,16 +64,30 @@ kotlin {
@Suppress("UnusedPrivateMember")
val commonMain by getting {
dependencies {
implementation(libs.kotlinx.datetime)
// put your multiplatform dependencies here
}
}

@Suppress("UnusedPrivateMember")
val commonJsMain by getting {
dependencies {
implementation(npm("luxon", "3.4.3"))
}
}

@Suppress("UnusedPrivateMember")
val commonTest by getting {
dependencies {
implementation(libs.kotlin.test)
}
}

val wasmJsMain by getting {
dependencies{
implementation(npm("luxon", "3.4.3"))
}
}
}

targets.configureEach {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.bngdev.formatk.date

import com.bngdev.formatk.utils.normalizeWhitespaces
import kotlinx.datetime.Instant
import kotlinx.datetime.toKotlinInstant
import kotlinx.datetime.toNSDate
import platform.Foundation.NSDateFormatter

class AppleDateFormatter(
private val dateFormatter: NSDateFormatter,
) : DateFormatter {

override fun format(instant: Instant): String {
val date = instant.toNSDate()
return dateFormatter.stringFromDate(date).normalizeWhitespaces()
}

override fun parse(value: String): Instant? {
val date = dateFormatter.dateFromString(value)
return date?.toKotlinInstant()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.bngdev.formatk.date

import com.bngdev.formatk.LocaleInfo
import com.bngdev.formatk.number.toNSLocale
import kotlinx.datetime.toNSTimeZone
import platform.Foundation.NSDateFormatter
import platform.Foundation.NSDateFormatterFullStyle
import platform.Foundation.NSDateFormatterLongStyle
import platform.Foundation.NSDateFormatterMediumStyle
import platform.Foundation.NSDateFormatterNoStyle
import platform.Foundation.NSDateFormatterShortStyle
import platform.Foundation.NSDateFormatterStyle
import platform.Foundation.NSTimeZone
import platform.Foundation.localTimeZone
import platform.Foundation.timeZoneWithName

class AppleDateFormatterFactory(private val localeInfo: LocaleInfo) : DateFormatterFactory {

override fun getFormatter(formatOptions: DateFormatterSettings): DateFormatter {
val dateFormatter = NSDateFormatter()

dateFormatter.locale = localeInfo.toNSLocale()

if (formatOptions.pattern != null) {
dateFormatter.dateFormat = formatOptions.pattern
} else {
dateFormatter.dateStyle = toNSDateFormatterStyle(formatOptions.dateStyle)
dateFormatter.timeStyle = toNSDateFormatterStyle(formatOptions.timeStyle)
}

formatOptions.timeZone?.let { timeZone ->
dateFormatter.timeZone = timeZone.toNSTimeZone()
}
if(formatOptions.pattern == null){
dateFormatter.dateFormat = removeQuotedWordsWithSpaceAndQuotes(dateFormatter.dateFormat)
}

return AppleDateFormatter(dateFormatter)
}

private fun removeQuotedWordsWithSpaceAndQuotes(format: String): String {
// with iso we have often additional text inside format for example: "dd.MM.y 'at' HH:mm:ss"
return Regex("'\\s*[^']+\\s*'" ).replace(format, "") // Replaces with an empty string
}

private fun toNSDateFormatterStyle(style: FormatStyle): NSDateFormatterStyle {
return when (style) {
FormatStyle.SHORT -> NSDateFormatterShortStyle
FormatStyle.MEDIUM -> NSDateFormatterMediumStyle
FormatStyle.LONG -> NSDateFormatterLongStyle
FormatStyle.FULL -> NSDateFormatterFullStyle
FormatStyle.NONE -> NSDateFormatterNoStyle
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.bngdev.formatk.date

import com.bngdev.formatk.LocaleInfo

actual object DateFormatterProvider {
actual fun getInstance(locale: LocaleInfo): DateFormatterFactory {
return AppleDateFormatterFactory(locale)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.bngdev.formatk.date

import com.bngdev.formatk.LocaleInfo

actual object DateFormatterProvider {
actual fun getInstance(locale: LocaleInfo): DateFormatterFactory {
return JsDateFormatFactory(locale.toLanguageTag())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.bngdev.formatk.date

import luxon.DateTimeOptions
import luxon.FromFormatOptions
import luxon.ToLocaleStringOptions

expect fun toLocaleStringTemplate(): ToLocaleStringOptions

expect fun dateTimeOptionsTemplate(): DateTimeOptions

expect fun fromFormatOptionsTemplate(): FromFormatOptions

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.bngdev.formatk.date;

class JsDateFormatFactory(private val locale: String) : DateFormatterFactory {

override fun getFormatter(formatOptions: DateFormatterSettings): DateFormatter {
return JsDateFormatter(locale, formatOptions)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.bngdev.formatk.date

import com.bngdev.formatk.utils.normalizeWhitespaces
import kotlinx.datetime.Instant
import luxon.DateTime
import luxon.DateTimeOptions
import luxon.FromFormatOptions
import luxon.ToLocaleStringOptions

class JsDateFormatter(
private val locale: String,
private val settings: DateFormatterSettings
) : DateFormatter {

override fun format(instant: Instant): String {
val dateTime = DateTime.fromMillis(
instant.toEpochMilliseconds().toDouble(), createDateTimeOptions()
)
return if (settings.pattern != null) {
dateTime.toFormat(settings.pattern).normalizeWhitespaces()
} else {
dateTime.toLocaleString(createLocaleOptions()).normalizeWhitespaces()
}
}

override fun parse(value: String): Instant? {
if(settings.pattern == null){
return null
}

val luxonDateTime = try {
DateTime.fromFormat(value, settings.pattern, createFromFormatOptions())
} catch (e: Throwable) {
return null
}

return try {
luxonDateTime.toMillis().let { Instant.fromEpochMilliseconds(it.toLong()) }
} catch (e: Throwable) {
null
}
}

private fun createFromFormatOptions(): FromFormatOptions {
val fromFormatOptions = fromFormatOptionsTemplate()
fromFormatOptions.locale = locale
if (settings.timeZone != null) {
fromFormatOptions.zone = settings.timeZone.id
}
return fromFormatOptions
}

private fun createDateTimeOptions(): DateTimeOptions {
val dateTimeOptionsTemplate = dateTimeOptionsTemplate()
dateTimeOptionsTemplate.locale = locale
getTimezone(settings)?.let {
dateTimeOptionsTemplate.zone = it
}
return dateTimeOptionsTemplate
}

private fun createLocaleOptions(): ToLocaleStringOptions {
val toLocaleStringOptions = toLocaleStringTemplate()
settings.dateStyle.toLuxon()?.let {
toLocaleStringOptions.dateStyle = it
}
settings.timeStyle.toLuxon()?.let {
toLocaleStringOptions.timeStyle = it
}
toLocaleStringOptions.locale = locale
getTimezone(settings)?.let {
toLocaleStringOptions.timeZone = it
}
return toLocaleStringOptions
}

private fun getTimezone(settings: DateFormatterSettings): String? {
if(settings.timeZone == null) {
return null
}
if(settings.timeZone.id == "Z"){
return "UTC"
}
return settings.timeZone.id
}
}

private fun FormatStyle.toLuxon(): String? {
return when (this) {
FormatStyle.SHORT -> "short"
FormatStyle.MEDIUM -> "medium"
FormatStyle.LONG -> "long"
FormatStyle.FULL -> "full"
FormatStyle.NONE -> null
}
}
35 changes: 35 additions & 0 deletions library/src/commonJsMain/kotlin/luxon/LuxonOptions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

package luxon

expect class DateTime {
companion object {
fun now(): DateTime
fun fromMillis(millis: Double): DateTime
fun fromMillis(millis: Double, options: DateTimeOptions): DateTime
fun fromISO(isoString: String, options: DateTimeOptions): DateTime
fun fromFormat(text: String, format: String, options: FromFormatOptions): DateTime
}

fun toMillis(): Double
fun toLocaleString(options: ToLocaleStringOptions): String
fun toFormat(format: String): String
fun isValid(): Boolean
}


external interface DateTimeOptions {
var zone: String?
var locale: String?
}

external interface ToLocaleStringOptions {
var locale: String?
var timeZone: String?
var dateStyle: String?
var timeStyle: String?
}

external interface FromFormatOptions {
var zone: String?
var locale: String?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.bngdev.formatk

import java.util.Locale

// TODO check version of java if possible take into consideration android (Locale.of)
fun LocaleInfo.toLocale(): Locale = Locale(this.language, this.region)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.bngdev.formatk.date

import com.bngdev.formatk.LocaleInfo

actual object DateFormatterProvider {

private val isJavaTimeSupported: Boolean by lazy {
@Suppress("SwallowedException")
try {
Class.forName("java.time.format.DateTimeFormatter")
true
} catch (e: ClassNotFoundException) {
false
}
}

actual fun getInstance(locale: LocaleInfo): DateFormatterFactory {
return if (isJavaTimeSupported) {
JvmDateFormatterFactory(locale)
} else {
LegacyJvmDateFormatterFactory(locale)
}
}
}
Loading