Skip to content

Commit

Permalink
Permit missing /etc/localtime (#426)
Browse files Browse the repository at this point in the history
We received a report that in some container images,
/etc/localtime is left unspecified.
According to the Linux documentation
(https://www.man7.org/linux/man-pages/man5/localtime.5.html), this
means that the UTC time zone must be used.

We still throw an exception if we see a time zone we don't
recognize.
  • Loading branch information
dkhalanskyjb authored Aug 19, 2024
1 parent 1cb9cdb commit a86e7f8
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 10 deletions.
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))
)
}

0 comments on commit a86e7f8

Please sign in to comment.