Skip to content

Commit 44d087a

Browse files
committed
kotlinx.datetime extension
1 parent a2de7fa commit 44d087a

54 files changed

Lines changed: 1569 additions & 240 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build.gradle.kts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
plugins {
2-
kotlin("multiplatform").apply(false)
3-
id("com.android.application").apply(false)
2+
alias(libs.plugins.multiplatform).apply(false)
3+
alias(libs.plugins.compose).apply(false)
4+
alias(libs.plugins.cocoapods).apply(false)
5+
alias(libs.plugins.android.application).apply(false)
6+
alias(libs.plugins.android.library).apply(false)
7+
alias(libs.plugins.swift.klib).apply(false)
48
}

gradle.properties

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
21
#Gradle
3-
org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
2+
org.gradle.jvmargs=-Xmx16g -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx16g" -XX:+UseParallelGC
3+
org.gradle.parallel=true
4+
org.gradle.daemon=true
5+
org.gradle.configureondemand=true
6+
org.gradle.caching=true
47

58
#Kotlin
69
kotlin.code.style=official

gradle/libs.versions.toml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[versions]
2+
3+
lyft-kronos = "0.0.1-alpha11"
4+
kotlin = "1.8.20"
5+
agp = "7.4.2"
6+
compose = "1.4.0-rc03"
7+
androidx-appcompat = "1.6.1"
8+
androidx-activityCompose = "1.7.0"
9+
androidx-test = "1.5.0"
10+
compose-uitooling = "1.4.0"
11+
napier = "2.6.1"
12+
kotlinx-coroutines = "1.6.4"
13+
kotlinx-datetime = "0.4.0"
14+
swift-klib = "0.2.1"
15+
16+
[libraries]
17+
18+
lyft-kronos-android = { module = "com.lyft.kronos:kronos-android", version.ref = "lyft-kronos" }
19+
lyft-kronos-java = { module = "com.lyft.kronos:kronos-java", version.ref = "lyft-kronos" }
20+
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
21+
androidx-activityCompose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
22+
androidx-test = { module = "androidx.test:core", version.ref = "androidx-test" }
23+
compose-uitooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose-uitooling" }
24+
napier = { module = "io.github.aakira:napier", version.ref = "napier" }
25+
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
26+
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
27+
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
28+
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
29+
30+
[plugins]
31+
32+
multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
33+
cocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" }
34+
compose = { id = "org.jetbrains.compose", version.ref = "compose" }
35+
android-application = { id = "com.android.application", version.ref = "agp" }
36+
android-library = { id = "com.android.library", version.ref = "agp" }
37+
swift-klib = { id = "io.github.ttypic.swiftklib", version.ref = "swift-klib" }

iosApp/Podfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
target 'iosApp' do
22
use_frameworks!
3-
platform :ios, '16.2'
3+
platform :ios, '14.1'
44
pod 'sampleApp', :path => '../sampleApp'
55
end

iosApp/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ EXTERNAL SOURCES:
99
:path: "../sampleApp"
1010

1111
SPEC CHECKSUMS:
12-
sampleApp: 6e28c1a5d33f2ff537dfe1cb36fbfea37c62e266
12+
sampleApp: edaddecea9407fd3435157244bdbed90f2dffe7b
1313

14-
PODFILE CHECKSUM: b85c23e81afce349a8da7e6f2d69ba6ae084aa4b
14+
PODFILE CHECKSUM: 31aecb1c889393f7cb5f9aac6f63f896eb049a8e
1515

1616
COCOAPODS: 1.11.3

iosApp/iosApp/iosApp.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import ComposeApp
66
struct iosApp: App {
77

88
init() {
9-
MainKt.debugBuild()
9+
MainKt.appInit()
1010
}
1111

1212
var body: some Scene {

kronos/build.gradle.kts

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
plugins {
2-
kotlin("multiplatform")
3-
kotlin("native.cocoapods")
4-
id("com.android.library")
2+
alias(libs.plugins.multiplatform)
3+
alias(libs.plugins.android.library)
4+
alias(libs.plugins.swift.klib)
55
}
66
group = "com.softartdev.kronos"
77
version = "0.0.1"
@@ -10,40 +10,48 @@ kotlin {
1010
jvmToolchain(11)
1111
jvm()
1212
android()
13-
iosX64()
14-
iosArm64()
15-
iosSimulatorArm64()
1613

17-
cocoapods {
18-
summary = "Some description for the Shared Module"
19-
homepage = "Link to the Shared Module homepage"
20-
version = "1.0"
21-
ios.deploymentTarget = "14.1"
22-
framework {
23-
baseName = "kronos"
14+
listOf(
15+
iosX64(),
16+
iosArm64(),
17+
iosSimulatorArm64(),
18+
).forEach {
19+
it.compilations {
20+
val main by getting {
21+
cinterops {
22+
create("KronosMultiplatform")
23+
}
24+
}
2425
}
25-
pod("Kronos", "~> 4.2")
2626
}
27-
2827
sourceSets {
29-
val commonMain by getting
28+
val commonMain by getting {
29+
dependencies {
30+
api(libs.kotlinx.datetime)
31+
}
32+
}
3033
val commonTest by getting {
3134
dependencies {
3235
implementation(kotlin("test"))
36+
implementation(libs.kotlinx.coroutines.test)
3337
}
3438
}
3539
val jvmMain by getting {
3640
dependencies {
37-
api("com.lyft.kronos:kronos-java:0.0.1-alpha11")
41+
api(libs.lyft.kronos.java)
3842
}
3943
}
4044
val jvmTest by getting
4145
val androidMain by getting {
4246
dependencies {
43-
api("com.lyft.kronos:kronos-android:0.0.1-alpha11")
47+
api(libs.lyft.kronos.android)
48+
}
49+
}
50+
val androidUnitTest by getting {
51+
dependencies {
52+
implementation(libs.androidx.test)
4453
}
4554
}
46-
val androidUnitTest by getting
4755
val iosX64Main by getting
4856
val iosArm64Main by getting
4957
val iosSimulatorArm64Main by getting
@@ -70,10 +78,16 @@ android {
7078
compileSdk = 33
7179
defaultConfig {
7280
minSdk = 21
73-
targetSdk = 33
7481
}
7582
compileOptions {
7683
sourceCompatibility = JavaVersion.VERSION_11
7784
targetCompatibility = JavaVersion.VERSION_11
7885
}
86+
}
87+
88+
swiftklib {
89+
create("KronosMultiplatform") {
90+
path = file("native/Kronos")
91+
packageName("com.softartdev.kronos")
92+
}
7993
}

kronos/kronos.podspec

Lines changed: 0 additions & 39 deletions
This file was deleted.

kronos/native/Kronos/Clock.swift

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import Foundation
2+
3+
/// Struct that has time + related metadata
4+
public typealias AnnotatedTime = (
5+
6+
/// Time that is being annotated
7+
date: Date,
8+
9+
/// Amount of time that has passed since the last NTP sync; in other words, the NTP response age.
10+
timeSinceLastNtpSync: TimeInterval
11+
)
12+
13+
/// High level implementation for clock synchronization using NTP. All returned dates use the most accurate
14+
/// synchronization and it's not affected by clock changes. The NTP synchronization implementation has sub-
15+
/// second accuracy but given that Darwin doesn't support microseconds on bootTime, dates don't have sub-
16+
/// second accuracy.
17+
///
18+
/// Example usage:
19+
///
20+
/// ```swift
21+
/// Clock.sync { date, offset in
22+
/// print(date)
23+
/// }
24+
/// // (... later on ...)
25+
/// print(Clock.now)
26+
/// ```
27+
public struct Clock {
28+
private static var stableTime: TimeFreeze? {
29+
didSet {
30+
self.storage.stableTime = self.stableTime
31+
}
32+
}
33+
34+
/// Determines where the most current stable time is stored. Use TimeStoragePolicy.appGroup to share
35+
/// between your app and an extension.
36+
public static var storage = TimeStorage(storagePolicy: .standard)
37+
38+
/// The most accurate timestamp that we have so far (nil if no synchronization was done yet)
39+
public static var timestamp: TimeInterval? {
40+
return self.stableTime?.adjustedTimestamp
41+
}
42+
43+
/// The most accurate date that we have so far (nil if no synchronization was done yet)
44+
public static var now: Date? {
45+
return self.annotatedNow?.date
46+
}
47+
48+
/// Same as `now` except with analytic metadata about the time
49+
public static var annotatedNow: AnnotatedTime? {
50+
guard let stableTime = self.stableTime else {
51+
return nil
52+
}
53+
54+
return AnnotatedTime(date: Date(timeIntervalSince1970: stableTime.adjustedTimestamp),
55+
timeSinceLastNtpSync: stableTime.timeSinceLastNtpSync)
56+
}
57+
58+
/// Syncs the clock using NTP. Note that the full synchronization could take a few seconds. The given
59+
/// closure will be called with the first valid NTP response which accuracy should be good enough for the
60+
/// initial clock adjustment but it might not be the most accurate representation. After calling the
61+
/// closure this method will continue syncing with multiple servers and multiple passes.
62+
///
63+
/// - parameter pool: NTP pool that will be resolved into multiple NTP servers that will be used for
64+
/// the synchronization.
65+
/// - parameter samples: The number of samples to be acquired from each server (default 4).
66+
/// - parameter completion: A closure that will be called after _all_ the NTP calls are finished.
67+
/// - parameter first: A closure that will be called after the first valid date is calculated.
68+
public static func sync(from pool: String = "time.apple.com", samples: Int = 4,
69+
first: ((Date, TimeInterval) -> Void)? = nil,
70+
completion: ((Date?, TimeInterval?) -> Void)? = nil)
71+
{
72+
self.loadFromDefaults()
73+
74+
NTPClient().query(pool: pool, numberOfSamples: samples) { offset, done, total in
75+
if let offset = offset {
76+
self.stableTime = TimeFreeze(offset: offset)
77+
78+
if done == 1, let now = self.now {
79+
first?(now, offset)
80+
}
81+
}
82+
83+
if done == total {
84+
completion?(self.now, offset)
85+
}
86+
}
87+
}
88+
89+
/// Resets all state of the monotonic clock. Note that you won't be able to access `now` until you `sync`
90+
/// again.
91+
public static func reset() {
92+
self.stableTime = nil
93+
}
94+
95+
private static func loadFromDefaults() {
96+
guard let previousStableTime = self.storage.stableTime else {
97+
self.stableTime = nil
98+
return
99+
}
100+
self.stableTime = previousStableTime
101+
}
102+
}

0 commit comments

Comments
 (0)