Skip to content

Commit 3bcb813

Browse files
authored
Handle DOS dates in Kotlin/Multiplatform (#1438)
* Handle DOS dates in Kotlin/Multiplatform This might be foolish. The total amount of new code is relatively small (45 lines of code), but that code does something we don't otherwise want to be doing in Okio - date math. But this unblocks implementing ZipFileSystem in Kotlin/Native. * Don't loop in addDay
1 parent c437fa6 commit 3bcb813

File tree

8 files changed

+381
-13
lines changed

8 files changed

+381
-13
lines changed

okio/src/commonTest/kotlin/okio/OkioTesting.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,5 @@ expect fun assertRelativeToFails(
102102
b: Path,
103103
sameAsNio: Boolean = true,
104104
): IllegalArgumentException
105+
106+
expect fun <T> withUtc(block: () -> T): T

okio/src/jvmMain/kotlin/okio/internal/-ZlibJvm.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,23 @@
1616
*/
1717
package okio.internal
1818

19+
import java.util.Calendar
20+
import java.util.GregorianCalendar
21+
1922
internal actual val DEFAULT_COMPRESSION = java.util.zip.Deflater.DEFAULT_COMPRESSION
2023

2124
internal actual typealias CRC32 = java.util.zip.CRC32
25+
26+
internal actual fun datePartsToEpochMillis(
27+
year: Int,
28+
month: Int,
29+
day: Int,
30+
hour: Int,
31+
minute: Int,
32+
second: Int,
33+
): Long {
34+
val calendar = GregorianCalendar()
35+
calendar.set(Calendar.MILLISECOND, 0)
36+
calendar.set(year, month - 1, day, hour, minute, second)
37+
return calendar.time.time
38+
}

okio/src/jvmMain/kotlin/okio/internal/ZipFiles.kt

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
*/
1717
package okio.internal
1818

19-
import java.util.Calendar
20-
import java.util.GregorianCalendar
2119
import okio.BufferedSource
2220
import okio.FileMetadata
2321
import okio.FileSystem
@@ -435,17 +433,14 @@ private fun dosDateTimeToEpochMillis(date: Int, time: Int): Long? {
435433
return null
436434
}
437435

438-
// Note that this inherits the local time zone.
439-
val cal = GregorianCalendar()
440-
cal.set(Calendar.MILLISECOND, 0)
441-
val year = 1980 + (date shr 9 and 0x7f)
442-
val month = date shr 5 and 0xf
443-
val day = date and 0x1f
444-
val hour = time shr 11 and 0x1f
445-
val minute = time shr 5 and 0x3f
446-
val second = time and 0x1f shl 1
447-
cal.set(year, month - 1, day, hour, minute, second)
448-
return cal.time.time
436+
return datePartsToEpochMillis(
437+
year = 1980 + (date shr 9 and 0x7f),
438+
month = date shr 5 and 0xf,
439+
day = date and 0x1f,
440+
hour = time shr 11 and 0x1f,
441+
minute = time shr 5 and 0x3f,
442+
second = time and 0x1f shl 1,
443+
)
449444
}
450445

451446
private class EocdRecord(

okio/src/jvmTest/kotlin/okio/JvmTesting.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package okio
1717

18+
import java.util.TimeZone
1819
import kotlin.test.assertEquals
1920
import kotlin.test.assertFailsWith
2021
import okio.Path.Companion.toOkioPath
@@ -52,3 +53,13 @@ actual fun assertRelativeToFails(
5253
// Return okio.
5354
return assertFailsWith { b.relativeTo(a) }
5455
}
56+
57+
actual fun <T> withUtc(block: () -> T): T {
58+
val original = TimeZone.getDefault()
59+
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
60+
try {
61+
return block()
62+
} finally {
63+
TimeZone.setDefault(original)
64+
}
65+
}

okio/src/nativeMain/kotlin/okio/internal/-ZlibNative.kt

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,62 @@
1717
package okio.internal
1818

1919
internal actual val DEFAULT_COMPRESSION: Int = platform.zlib.Z_DEFAULT_COMPRESSION
20+
21+
/**
22+
* Roll our own date math because Kotlin doesn't include a built-in date math API, and the
23+
* kotlinx.datetime library doesn't offer a stable at this time.
24+
*
25+
* Also, we don't necessarily want to take on that dependency for Okio.
26+
*
27+
* This implementation assumes UTC.
28+
*
29+
* This code is broken for years before 1970. It doesn't implement subtraction for leap years.
30+
*
31+
* This code is broken for out-of-range values. For example, it doesn't correctly implement leap
32+
* year offsets when the month is -24 or when the day is -365.
33+
*/
34+
internal actual fun datePartsToEpochMillis(
35+
year: Int,
36+
month: Int,
37+
day: Int,
38+
hour: Int,
39+
minute: Int,
40+
second: Int,
41+
): Long {
42+
// Make sure month is in 1..12, adding or subtracting years as necessary.
43+
val rawMonth = month
44+
val month = (month - 1).mod(12) + 1
45+
val year = year + (rawMonth - month) / 12
46+
47+
// Start with the cumulative number of days elapsed preceding the current year.
48+
var dayCount = (year - 1970) * 365L
49+
50+
// Adjust by leap years. Years that divide 4 are leap years, unless they divide 100 but not 400.
51+
val leapYear = if (month > 2) year else year - 1
52+
dayCount += (leapYear - 1968) / 4 - (leapYear - 1900) / 100 + (leapYear - 1600) / 400
53+
54+
// Add the cumulative number of days elapsed preceding the current month.
55+
dayCount += when (month) {
56+
1 -> 0
57+
2 -> 31
58+
3 -> 59
59+
4 -> 90
60+
5 -> 120
61+
6 -> 151
62+
7 -> 181
63+
8 -> 212
64+
9 -> 243
65+
10 -> 273
66+
11 -> 304
67+
else -> 334
68+
}
69+
70+
// Add the cumulative number of days that precede the current day.
71+
dayCount += (day - 1)
72+
73+
// Add hours + minutes + seconds for the current day.
74+
val hourCount = dayCount * 24 + hour
75+
val minuteCount = hourCount * 60 + minute
76+
val secondCount = minuteCount * 60 + second
77+
return secondCount * 1_000L
78+
}

okio/src/nonJvmTest/kotlin/okio/NonJvmTesting.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ actual fun assertRelativeToFails(
3636
): IllegalArgumentException {
3737
return assertFailsWith { b.relativeTo(a) }
3838
}
39+
40+
actual fun <T> withUtc(block: () -> T): T {
41+
return block()
42+
}

okio/src/zlibMain/kotlin/okio/internal/-Zlib.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,22 @@
1717
package okio.internal
1818

1919
internal expect val DEFAULT_COMPRESSION: Int
20+
21+
/**
22+
* Note that this inherits the local time zone.
23+
*
24+
* @param year such as 1970 or 2024
25+
* @param month a value in the range 1 (January) through 12 (December).
26+
* @param day a value in the range 1 through 31.
27+
* @param hour a value in the range 0 through 23.
28+
* @param minute a value in the range 0 through 59.
29+
* @param second a value in the range 0 through 59.
30+
*/
31+
internal expect fun datePartsToEpochMillis(
32+
year: Int,
33+
month: Int,
34+
day: Int,
35+
hour: Int,
36+
minute: Int,
37+
second: Int,
38+
): Long

0 commit comments

Comments
 (0)