Skip to content

Commit

Permalink
Handle DOS dates in Kotlin/Multiplatform (#1438)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
squarejesse authored Feb 21, 2024
1 parent c437fa6 commit 3bcb813
Show file tree
Hide file tree
Showing 8 changed files with 381 additions and 13 deletions.
2 changes: 2 additions & 0 deletions okio/src/commonTest/kotlin/okio/OkioTesting.kt
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,5 @@ expect fun assertRelativeToFails(
b: Path,
sameAsNio: Boolean = true,
): IllegalArgumentException

expect fun <T> withUtc(block: () -> T): T
17 changes: 17 additions & 0 deletions okio/src/jvmMain/kotlin/okio/internal/-ZlibJvm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,23 @@
*/
package okio.internal

import java.util.Calendar
import java.util.GregorianCalendar

internal actual val DEFAULT_COMPRESSION = java.util.zip.Deflater.DEFAULT_COMPRESSION

internal actual typealias CRC32 = java.util.zip.CRC32

internal actual fun datePartsToEpochMillis(
year: Int,
month: Int,
day: Int,
hour: Int,
minute: Int,
second: Int,
): Long {
val calendar = GregorianCalendar()
calendar.set(Calendar.MILLISECOND, 0)
calendar.set(year, month - 1, day, hour, minute, second)
return calendar.time.time
}
21 changes: 8 additions & 13 deletions okio/src/jvmMain/kotlin/okio/internal/ZipFiles.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
*/
package okio.internal

import java.util.Calendar
import java.util.GregorianCalendar
import okio.BufferedSource
import okio.FileMetadata
import okio.FileSystem
Expand Down Expand Up @@ -435,17 +433,14 @@ private fun dosDateTimeToEpochMillis(date: Int, time: Int): Long? {
return null
}

// Note that this inherits the local time zone.
val cal = GregorianCalendar()
cal.set(Calendar.MILLISECOND, 0)
val year = 1980 + (date shr 9 and 0x7f)
val month = date shr 5 and 0xf
val day = date and 0x1f
val hour = time shr 11 and 0x1f
val minute = time shr 5 and 0x3f
val second = time and 0x1f shl 1
cal.set(year, month - 1, day, hour, minute, second)
return cal.time.time
return datePartsToEpochMillis(
year = 1980 + (date shr 9 and 0x7f),
month = date shr 5 and 0xf,
day = date and 0x1f,
hour = time shr 11 and 0x1f,
minute = time shr 5 and 0x3f,
second = time and 0x1f shl 1,
)
}

private class EocdRecord(
Expand Down
11 changes: 11 additions & 0 deletions okio/src/jvmTest/kotlin/okio/JvmTesting.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package okio

import java.util.TimeZone
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import okio.Path.Companion.toOkioPath
Expand Down Expand Up @@ -52,3 +53,13 @@ actual fun assertRelativeToFails(
// Return okio.
return assertFailsWith { b.relativeTo(a) }
}

actual fun <T> withUtc(block: () -> T): T {
val original = TimeZone.getDefault()
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
try {
return block()
} finally {
TimeZone.setDefault(original)
}
}
59 changes: 59 additions & 0 deletions okio/src/nativeMain/kotlin/okio/internal/-ZlibNative.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,62 @@
package okio.internal

internal actual val DEFAULT_COMPRESSION: Int = platform.zlib.Z_DEFAULT_COMPRESSION

/**
* Roll our own date math because Kotlin doesn't include a built-in date math API, and the
* kotlinx.datetime library doesn't offer a stable at this time.
*
* Also, we don't necessarily want to take on that dependency for Okio.
*
* This implementation assumes UTC.
*
* This code is broken for years before 1970. It doesn't implement subtraction for leap years.
*
* This code is broken for out-of-range values. For example, it doesn't correctly implement leap
* year offsets when the month is -24 or when the day is -365.
*/
internal actual fun datePartsToEpochMillis(
year: Int,
month: Int,
day: Int,
hour: Int,
minute: Int,
second: Int,
): Long {
// Make sure month is in 1..12, adding or subtracting years as necessary.
val rawMonth = month
val month = (month - 1).mod(12) + 1
val year = year + (rawMonth - month) / 12

// Start with the cumulative number of days elapsed preceding the current year.
var dayCount = (year - 1970) * 365L

// Adjust by leap years. Years that divide 4 are leap years, unless they divide 100 but not 400.
val leapYear = if (month > 2) year else year - 1
dayCount += (leapYear - 1968) / 4 - (leapYear - 1900) / 100 + (leapYear - 1600) / 400

// Add the cumulative number of days elapsed preceding the current month.
dayCount += when (month) {
1 -> 0
2 -> 31
3 -> 59
4 -> 90
5 -> 120
6 -> 151
7 -> 181
8 -> 212
9 -> 243
10 -> 273
11 -> 304
else -> 334
}

// Add the cumulative number of days that precede the current day.
dayCount += (day - 1)

// Add hours + minutes + seconds for the current day.
val hourCount = dayCount * 24 + hour
val minuteCount = hourCount * 60 + minute
val secondCount = minuteCount * 60 + second
return secondCount * 1_000L
}
4 changes: 4 additions & 0 deletions okio/src/nonJvmTest/kotlin/okio/NonJvmTesting.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ actual fun assertRelativeToFails(
): IllegalArgumentException {
return assertFailsWith { b.relativeTo(a) }
}

actual fun <T> withUtc(block: () -> T): T {
return block()
}
19 changes: 19 additions & 0 deletions okio/src/zlibMain/kotlin/okio/internal/-Zlib.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,22 @@
package okio.internal

internal expect val DEFAULT_COMPRESSION: Int

/**
* Note that this inherits the local time zone.
*
* @param year such as 1970 or 2024
* @param month a value in the range 1 (January) through 12 (December).
* @param day a value in the range 1 through 31.
* @param hour a value in the range 0 through 23.
* @param minute a value in the range 0 through 59.
* @param second a value in the range 0 through 59.
*/
internal expect fun datePartsToEpochMillis(
year: Int,
month: Int,
day: Int,
hour: Int,
minute: Int,
second: Int,
): Long
Loading

0 comments on commit 3bcb813

Please sign in to comment.