Skip to content

Commit

Permalink
Extract date time formatter patterns to common implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
msasikanth committed Oct 2, 2024
1 parent 2b78a2d commit 33bc255
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,46 +22,29 @@ import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoField
import java.time.temporal.UnsupportedTemporalTypeException
import java.util.Locale
import kotlinx.datetime.Clock
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toJavaLocalDateTime
import kotlinx.datetime.toJavaZoneId
import kotlinx.datetime.toLocalDateTime

private val dateFormatters =
listOf(
DateTimeFormatter.ofPattern("E, d MMM yyyy HH:mm:ss O"),
DateTimeFormatter.ofPattern("E, d MMM yyyy HH:mm:ss Z"),
DateTimeFormatter.ofPattern("E, d MMM yyyy HH:mm:ss z"),
DateTimeFormatter.ofPattern("E, d MMM yyyy HH:mm Z"),
DateTimeFormatter.ofPattern("E, d MMM yy HH:mm:ss Z"),
DateTimeFormatter.ofPattern("E, dd MMM yyyy"),
DateTimeFormatter.ofPattern("d MMM yyyy HH:mm:ss z"),
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssz"),
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"),
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"),
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z"),
DateTimeFormatter.ofPattern("yyyy-MM-dd"),
DateTimeFormatter.ofPattern("MM-dd HH:mm:ss"),
DateTimeFormatter.ofPattern("E, d MMM yyyy HH:mm:ss zzzz"),
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"),
)

@Throws(DateTimeFormatException::class)
actual fun String?.dateStringToEpochMillis(clock: Clock): Long? {
if (this.isNullOrBlank()) return null

val currentDate =
clock.now().toLocalDateTime(TimeZone.currentSystemDefault()).toJavaLocalDateTime()

for (dateFormatter in dateFormatters) {
for (pattern in dateFormatterPatterns) {
val dateTimeFormatter = DateTimeFormatter.ofPattern(pattern, Locale.US)

try {
val parsedValue = parseToInstant(dateFormatter, this)
val parsedValue = parseToInstant(dateTimeFormatter, this)
return parsedValue.toEpochMilli()
} catch (e: Exception) {
try {
val parsedValue = fallbackParseToInstant(currentDate, dateFormatter, this)
val parsedValue = fallbackParseToInstant(currentDate, dateTimeFormatter, this)
return parsedValue.toEpochMilli()
} catch (e: Exception) {
// no-op
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,29 @@ package dev.sasikanth.rss.reader.util

import kotlinx.datetime.Clock

internal val dateFormatterPatterns =
setOf(
// Keep the two character year before parsing the four
// character year of similar pattern. Not sure why,
// but unlike JVM, iOS is not keep it strict?
"E, d MMM yy HH:mm:ss Z",
"E, d MMM yyyy HH:mm:ss O",
"E, d MMM yyyy HH:mm:ss Z",
"E, d MMM yyyy HH:mm:ss z",
"E, d MMM yyyy HH:mm Z",
"E, dd MMM yyyy",
"d MMM yyyy HH:mm:ss z",
"yyyy-MM-dd'T'HH:mm:ssz",
"yyyy-MM-dd'T'HH:mm:ssZ",
"yyyy-MM-dd'T'HH:mm:ss",
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd HH:mm:ss z",
"yyyy-MM-dd",
"MM-dd HH:mm:ss",
"E, d MMM yyyy HH:mm:ss zzzz",
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
)

expect fun String?.dateStringToEpochMillis(clock: Clock = Clock.System): Long?

data class DateTimeFormatException(val exception: Exception) : Exception()
Original file line number Diff line number Diff line change
Expand Up @@ -33,49 +33,26 @@ import platform.Foundation.NSDateFormatter
import platform.Foundation.NSLocale
import platform.Foundation.timeIntervalSince1970

private fun createDateFormatter(
pattern: String,
timeZone: TimeZone? = null,
): NSDateFormatter {
return NSDateFormatter().apply {
dateFormat = pattern
locale = NSLocale("en_US_POSIX")

timeZone?.let { this.timeZone = it.toNSTimeZone() }
}
}

private val dateFormatters =
listOf(
// Keep the two character year before parsing the four
// character year of similar pattern. Not sure why,
// but unlike JVM, iOS is not keep it strict?
createDateFormatter("E, d MMM yy HH:mm:ss Z"),
createDateFormatter("E, d MMM yyyy HH:mm:ss O"),
createDateFormatter("E, d MMM yyyy HH:mm:ss Z"),
createDateFormatter("E, d MMM yyyy HH:mm:ss z"),
createDateFormatter("E, d MMM yyyy HH:mm Z"),
createDateFormatter("E, dd MMM yyyy", TimeZone.UTC),
createDateFormatter("d MMM yyyy HH:mm:ss z"),
createDateFormatter("MM-dd HH:mm:ss", TimeZone.UTC),
createDateFormatter("yyyy-MM-dd'T'HH:mm:ssz"),
createDateFormatter("yyyy-MM-dd'T'HH:mm:ssZ"),
createDateFormatter("yyyy-MM-dd'T'HH:mm:ss", TimeZone.UTC),
createDateFormatter("yyyy-MM-dd HH:mm:ss", TimeZone.UTC),
createDateFormatter("yyyy-MM-dd HH:mm:ss z"),
createDateFormatter("yyyy-MM-dd", TimeZone.UTC),
createDateFormatter("E, d MMM yyyy HH:mm:ss zzzz"),
createDateFormatter("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"),
)

@Throws(DateTimeFormatException::class)
actual fun String?.dateStringToEpochMillis(clock: Clock): Long? {
if (this.isNullOrBlank()) return null

try {
val date =
dateFormatters.firstNotNullOfOrNull { dateFormatter ->
dateFormatter.dateFromString(this.trim())
dateFormatterPatterns.firstNotNullOfOrNull { pattern ->
val timeZone =
if (hasTimeZonePattern(pattern)) {
null
} else {
TimeZone.UTC
}
val dateTimeFormatter =
createDateFormatter(
pattern = pattern,
timeZone = timeZone,
)

dateTimeFormatter.dateFromString(this.trim())
}

if (date != null) {
Expand Down Expand Up @@ -114,3 +91,20 @@ actual fun String?.dateStringToEpochMillis(clock: Clock): Long? {

return null
}

private fun hasTimeZonePattern(pattern: String) =
pattern.contains("Z", ignoreCase = true) ||
pattern.contains("O", ignoreCase = true) ||
pattern.contains("X", ignoreCase = true)

private fun createDateFormatter(
pattern: String,
timeZone: TimeZone? = null,
): NSDateFormatter {
return NSDateFormatter().apply {
dateFormat = pattern
locale = NSLocale("en_US_POSIX")

timeZone?.let { this.timeZone = it.toNSTimeZone() }
}
}

0 comments on commit 33bc255

Please sign in to comment.