Skip to content

Commit 6b39f4a

Browse files
authored
Fix kotlinx-datetime-zoneinfo not being published (#480)
Also, update tzdb to 2025a, and to make that work, introduce a workaround for non-compliant generation of POSIX strings by the timezone database compiler.
1 parent 3720474 commit 6b39f4a

File tree

320 files changed

+49
-5
lines changed

Some content is hidden

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

320 files changed

+49
-5
lines changed

README.md

Lines changed: 1 addition & 1 deletion

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ infra {
99
}
1010
publishing {
1111
include(":kotlinx-datetime")
12+
include(":kotlinx-datetime-zoneinfo")
1213
libraryRepoUrl = "https://github.com/Kotlin/kotlinx-datetime"
1314
sonatype {
1415
libraryStagingRepoDescription = project.name

core/commonKotlin/src/internal/Tzfile.kt

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package kotlinx.datetime.internal
77

88
import kotlinx.datetime.*
9+
import kotlinx.datetime.format.optional
910

1011
internal class TzFileData(
1112
val leapSecondRules: List<LeapSecondRule>,
@@ -204,8 +205,34 @@ private fun BinaryDataReader.readPosixTzString(): PosixTzString? {
204205
fun readName(): String? {
205206
if (c == '\n') return null
206207
val name = StringBuilder()
208+
/* This check is a workaround for a bug in our tzdb processor used in kotlinx-datetime-zoneinfo.
209+
In 2024b+, the tzdb includes the `%z` directive instead of the timezone abbreviations in cases where the
210+
abbreviation can be inferred from the offset
211+
(https://lists.iana.org/hyperkitty/list/[email protected]/thread/IZ7AO6WRE3W3TWBL5IR6PMQUL433BQIE/):
212+
instead of writing "abbreviation = -03, offset = -3", they now write "abbreviation = %z, offset = -3".
213+
The first-party tzdb compiler zic knows how to support this:
214+
https://github.com/eggert/tz/blob/271a5784a59e454b659d85948b5e65c17c11516a/zic.8#L590-L602
215+
The compiler we're using (`tubular_time_tzdb`) doesn't seem to, though, and generates invalid POSIX strings.
216+
This is a quick and dirty workaround. A proper solution would be to have correct data in `-zoneinfo`, but
217+
it doesn't matter if we publish broken tzdb info now, as we are not planning on supporting consuming old tzdb
218+
versions from new library versions, so the workaround can be removed as soon as the `-zoneinfo` artifact is
219+
fixed. */
220+
if (c == '%') {
221+
c = readAsciiChar()
222+
check(c == 'z') { "Invalid directive %$c in the timezone name abbreviation" }
223+
c = readAsciiChar()
224+
return GENERATE_NAME
225+
}
207226
if (c == '<') {
208227
c = readAsciiChar()
228+
if (c == '%') {
229+
c = readAsciiChar()
230+
check(c == 'z') { "Invalid directive %$c in the timezone name abbreviation" }
231+
c = readAsciiChar()
232+
check(c == '>') { "<%z> expected, got %$c" }
233+
c = readAsciiChar()
234+
return GENERATE_NAME
235+
}
209236
while (c != '>') {
210237
check(c.isLetterOrDigit() || c == '-' || c == '+') { "Invalid char '$c' in the std name in POSIX TZ string" }
211238
name.append(c)
@@ -218,7 +245,7 @@ private fun BinaryDataReader.readPosixTzString(): PosixTzString? {
218245
c = readAsciiChar()
219246
}
220247
}
221-
check(name.isNotEmpty()) { "Empty std name in POSIX TZ string" }
248+
check(name.isNotEmpty()) { "Empty std name in POSIX TZ string: got $c" }
222249
return name.toString()
223250
}
224251

@@ -341,13 +368,29 @@ private fun BinaryDataReader.readPosixTzString(): PosixTzString? {
341368

342369
val std = readName() ?: return null
343370
val stdOffset = readOffset() ?: throw IllegalArgumentException("Could not parse the std offset in POSIX TZ string")
344-
val dst = readName() ?: return PosixTzString(std to stdOffset, null, null)
371+
val stdName = if (std === GENERATE_NAME) ISO_OFFSET_BASIC_NO_Z.format(stdOffset) else std
372+
val dst = readName() ?: return PosixTzString(stdName to stdOffset, null, null)
345373
val dstOffset = readOffset() ?: UtcOffset(seconds = stdOffset.totalSeconds + 3600)
374+
val dstName = if (dst === GENERATE_NAME) ISO_OFFSET_BASIC_NO_Z.format(dstOffset) else dst
346375
val startDate = readDate() ?: return PosixTzString(std to stdOffset, dst to dstOffset, null)
347376
val startTime = readTime() ?: MonthDayTime.TransitionLocaltime(2, 0, 0)
348377
val endDate = readDate() ?: throw IllegalArgumentException("Could not parse the end date in POSIX TZ string")
349378
val endTime = readTime() ?: MonthDayTime.TransitionLocaltime(2, 0, 0)
350379
val start = MonthDayTime(startDate, startTime, MonthDayTime.OffsetResolver.WallClockOffset)
351380
val end = MonthDayTime(endDate, endTime, MonthDayTime.OffsetResolver.WallClockOffset)
352-
return PosixTzString(std to stdOffset, dst to dstOffset, start to end)
381+
return PosixTzString(stdName to stdOffset, dstName to dstOffset, start to end)
382+
}
383+
384+
private const val GENERATE_NAME = "%z"
385+
386+
private val ISO_OFFSET_BASIC_NO_Z by lazy {
387+
UtcOffset.Format {
388+
offsetHours()
389+
optional {
390+
offsetMinutesOfHour()
391+
optional {
392+
offsetSecondsOfMinute()
393+
}
394+
}
395+
}
353396
}

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ group=org.jetbrains.kotlinx
55
version=0.6.1
66
versionSuffix=SNAPSHOT
77

8-
tzdbVersion=2024a
8+
tzdbVersion=2025a
99

1010
defaultKotlinVersion=1.9.21
1111
dokkaVersion=1.9.20
-9 Bytes
Binary file not shown.

timezones/full/tzdb/Africa/Asmara

-9 Bytes
Binary file not shown.

timezones/full/tzdb/Africa/Asmera

-9 Bytes
Binary file not shown.

timezones/full/tzdb/Africa/Bangui

-3 Bytes
Binary file not shown.

timezones/full/tzdb/Africa/Bissau

-1 Bytes
Binary file not shown.

timezones/full/tzdb/Africa/Blantyre

0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)