@@ -8,8 +8,6 @@ package kotlinx.datetime.internal
88import kotlinx.datetime.*
99import kotlinx.datetime.UtcOffset
1010import kotlinx.datetime.internal.JSJoda.ZoneId
11- import kotlin.math.roundToInt
12- import kotlin.math.roundToLong
1311
1412private val tzdb: Result <TimeZoneDatabase ?> = runCatching {
1513 /* *
@@ -25,49 +23,56 @@ private val tzdb: Result<TimeZoneDatabase?> = runCatching {
2523 in ' A' .. ' X' -> char - ' A' + 36
2624 else -> throw IllegalArgumentException (" Invalid character: $char " )
2725 }
28- fun unpackBase60 (string : String ): Double {
26+ /* * converts a base60 number of minutes to a whole number of seconds */
27+ fun base60MinutesInSeconds (string : String ): Long {
2928 var i = 0
3029 var parts = string.split(' .' )
31- val whole = parts[0 ]
32- var multiplier = 1.0
33- var out = 0.0
34- var sign = 1
3530
3631 // handle negative numbers
37- if (string.startsWith(' -' )) {
32+ val sign = if (string.startsWith(' -' )) {
3833 i = 1
39- sign = - 1
34+ - 1
35+ } else {
36+ 1
4037 }
4138
42- // handle digits before the decimal
39+ var wholeMinutes: Long = 0
40+ val whole = parts[0 ]
41+ // handle digits before the decimal (whole minutes)
4342 for (ix in i.. whole.lastIndex) {
44- out = 60 * out + charCodeToInt(whole[ix])
43+ wholeMinutes = 60 * wholeMinutes + charCodeToInt(whole[ix])
4544 }
4645
47- // handle digits after the decimal
48- parts.getOrNull(1 )?.let { fractional ->
49- for (c in fractional) {
50- multiplier = multiplier / 60.0
51- out + = charCodeToInt(c) * multiplier
46+ // handle digits after the decimal (seconds and less)
47+ val seconds = parts.getOrNull(1 )?.let { fractional ->
48+ when (fractional.length) {
49+ 1 -> charCodeToInt(fractional[0 ]) // single digit, representing seconds
50+ 0 -> 0 // actually no fractional part
51+ else -> {
52+ charCodeToInt(fractional[0 ]) + charCodeToInt(fractional[1 ]).let {
53+ if (it >= 30 ) 1 else 0 // rounding the seconds digit
54+ }
55+ }
5256 }
53- }
57+ } ? : 0
5458
55- return out * sign
59+ return (wholeMinutes * SECONDS_PER_MINUTE + seconds) * sign
5660 }
5761
5862 val zones = mutableMapOf<String , TimeZoneRules >()
5963 val (zonesPacked, linksPacked) = readTzdb() ? : return @runCatching null
6064 for (zone in zonesPacked) {
6165 val components = zone.split(' |' )
62- val offsets = components[2 ].split(' ' ).map { unpackBase60(it) }
63- val indices = components[3 ].map { charCodeToInt(it) }
64- val lengthsOfPeriodsWithOffsets = components[4 ].split(' ' ).map {
65- (unpackBase60(it) * SECONDS_PER_MINUTE * MILLIS_PER_ONE ).roundToLong() / // minutes to milliseconds
66- MILLIS_PER_ONE // but we only need seconds
66+ val offsets = components[2 ].split(' ' ).map {
67+ UtcOffset (null , null , - base60MinutesInSeconds(it).toInt())
6768 }
69+ val indices = components[3 ].map { charCodeToInt(it) }
70+ val lengthsOfPeriodsWithOffsets = components[4 ].split(' ' ).map(::base60MinutesInSeconds)
6871 zones[components[0 ]] = TimeZoneRules (
69- transitionEpochSeconds = lengthsOfPeriodsWithOffsets.runningReduce(Long ::plus).take<Long >(indices.size - 1 ),
70- offsets = indices.map { UtcOffset (null , null , - (offsets[it] * 60 ).roundToInt()) },
72+ transitionEpochSeconds = lengthsOfPeriodsWithOffsets.runningReduce(Long ::plus).let {
73+ if (it.size == indices.size - 1 ) it else it.take<Long >(indices.size - 1 )
74+ },
75+ offsets = indices.map { offsets[it] },
7176 recurringZoneRules = null
7277 )
7378 }
0 commit comments