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

Permit missing /etc/localtime #426

Merged
merged 1 commit into from
Aug 19, 2024
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
5 changes: 4 additions & 1 deletion core/common/src/TimeZone.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,17 @@ public expect open class TimeZone {
*
* If the current system time zone changes, this function can reflect this change on the next invocation.
*
* On Linux, this function queries the `/etc/localtime` symbolic link. If the link is missing, [UTC] is used.
* If the link points to an invalid location, [IllegalTimeZoneException] is thrown.
*
* @sample kotlinx.datetime.test.samples.TimeZoneSamples.currentSystemDefault
*/
public fun currentSystemDefault(): TimeZone

/**
* Returns the time zone with the fixed UTC+0 offset.
*
* The [id] of this time zone is `"UTC"`.
* The [id] of this time zone is `"Z"`.
*
* @sample kotlinx.datetime.test.samples.TimeZoneSamples.utc
*/
Expand Down
8 changes: 6 additions & 2 deletions core/linux/src/internal/TimeZoneNative.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@

package kotlinx.datetime.internal

import kotlinx.datetime.IllegalTimeZoneException

internal actual val systemTzdb: TimeZoneDatabase get() = tzdb.getOrThrow()

private val tzdb = runCatching { TzdbOnFilesystem() }

internal actual fun currentSystemDefaultZone(): Pair<String, TimeZoneRules?> {
val zoneId = pathToSystemDefault()?.second?.toString()
?: throw IllegalStateException("Failed to get the system timezone")
// according to https://www.man7.org/linux/man-pages/man5/localtime.5.html, when there is no symlink, UTC is used
val zonePath = currentSystemTimeZonePath ?: return "Z" to null
val zoneId = zonePath.splitTimeZonePath()?.second?.toString()
?: throw IllegalTimeZoneException("Could not determine the timezone ID that `$zonePath` corresponds to")
return zoneId to null
}
19 changes: 12 additions & 7 deletions core/tzdbOnFilesystem/src/internal/TzdbOnFilesystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,21 @@ internal fun tzdbPaths(defaultTzdbPath: Path?) = sequence {
defaultTzdbPath?.let { yield(it) }
// taken from https://github.com/tzinfo/tzinfo/blob/9953fc092424d55deaea2dcdf6279943f3495724/lib/tzinfo/data_sources/zoneinfo_data_source.rb#L70
yieldAll(listOf("/usr/share/zoneinfo", "/usr/share/lib/zoneinfo", "/etc/zoneinfo").map { Path.fromString(it) })
pathToSystemDefault()?.first?.let { yield(it) }
currentSystemTimeZonePath?.splitTimeZonePath()?.first?.let { yield(it) }
}

internal val currentSystemTimeZonePath get() = chaseSymlinks("/etc/localtime")

/**
* Given a path like `/usr/share/zoneinfo/Europe/Berlin`, produces `/usr/share/zoneinfo to Europe/Berlin`.
* Returns null if the function can't recognize the boundary between the time zone and the tzdb.
*/
// taken from https://github.com/HowardHinnant/date/blob/ab37c362e35267d6dee02cb47760f9e9c669d3be/src/tz.cpp#L3951-L3952
internal fun pathToSystemDefault(): Pair<Path, Path>? {
val info = chaseSymlinks("/etc/localtime") ?: return null
val i = info.components.indexOf("zoneinfo")
if (!info.isAbsolute || i == -1 || i == info.components.size - 1) return null
internal fun Path.splitTimeZonePath(): Pair<Path, Path>? {
val i = components.indexOf("zoneinfo")
if (!isAbsolute || i == -1 || i == components.size - 1) return null
return Pair(
Path(true, info.components.subList(0, i + 1)),
Path(false, info.components.subList(i + 1, info.components.size))
Path(true, components.subList(0, i + 1)),
Path(false, components.subList(i + 1, components.size))
)
}