@@ -25,16 +25,25 @@ import com.lagradost.nicehttp.RequestBodyTypes
2525import okhttp3.Interceptor
2626import okhttp3.MediaType.Companion.toMediaTypeOrNull
2727import okhttp3.RequestBody.Companion.toRequestBody
28+ import kotlinx.datetime.LocalDate
29+ import kotlinx.datetime.LocalTime
30+ import kotlinx.datetime.TimeZone
31+ import kotlinx.datetime.atStartOfDayIn
32+ import kotlinx.datetime.format.DateTimeComponents
33+ import kotlinx.datetime.format.FormatStringsInDatetimeFormats
34+ import kotlinx.datetime.format.byUnicodePattern
35+ import kotlinx.datetime.format.char
36+ import kotlinx.datetime.format.parse
37+ import kotlinx.datetime.toInstant
2838import java.net.URI
29- import java.text.SimpleDateFormat
30- import java.util.Date
3139import java.util.EnumSet
32- import java.util.Locale
3340import kotlinx.serialization.json.Json
3441import kotlin.io.encoding.Base64
3542import kotlin.io.encoding.ExperimentalEncodingApi
3643import kotlin.math.absoluteValue
3744import kotlin.math.roundToInt
45+ import kotlin.time.Clock
46+ import kotlin.time.Instant
3847
3948/* *
4049 * API available only on prerelease builds.
@@ -88,10 +97,10 @@ val mapper = JsonMapper.builder().addModule(kotlinModule())
8897 .configure(DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES , false ).build()!!
8998
9099object APIHolder {
91- val unixTime: Long
92- get() = System .currentTimeMillis() / 1000L
93100 val unixTimeMS: Long
94- get() = System .currentTimeMillis()
101+ get() = Clock .System .now().toEpochMilliseconds()
102+ val unixTime: Long
103+ get() = unixTimeMS / 1000L
95104
96105 val allProviders = atomicListOf<MainAPI >()
97106
@@ -2512,15 +2521,45 @@ constructor(
25122521 get() = score?.toInt(100 )
25132522}
25142523
2524+ @OptIn(FormatStringsInDatetimeFormats ::class )
25152525fun Episode.addDate (date : String? , format : String = "yyyy-MM -dd") {
2516- try {
2517- this .date = SimpleDateFormat (format, Locale .getDefault()).parse(date ? : return )?.time
2518- } catch (e: Exception ) {
2519- logError(e)
2520- }
2526+ if (date == null ) return
2527+ this .date = runCatching {
2528+ // First try standard ISO 8601 (e.g. "2026-01-01T12:30:00.000Z", "2026-05-17T14:35+02:00")
2529+ runCatching { Instant .parse(date).toEpochMilliseconds() }
2530+ .getOrElse {
2531+ val fmt = DateTimeComponents .Format { byUnicodePattern(format) }
2532+ val components = DateTimeComponents .parse(date, fmt)
2533+ /* *
2534+ * Try multiple conversions in order of precision for non-ISO-8601 formats,
2535+ * since the date string may or may not include time and/or timezone offset:
2536+ * 1. If the custom format produced a UTC offset (e.g. "2026-05-17 14:35+02:00"), use it directly
2537+ * 2. If it has time but no offset (e.g. "2026-05-17 14:35"), fall back to device timezone
2538+ * 3. If it's date-only (e.g. "2026-05-17"), use start of day in device timezone
2539+ */
2540+ runCatching { components.toInstantUsingOffset().toEpochMilliseconds() }
2541+ .recoverCatching { components.toLocalDateTime().toInstant(TimeZone .currentSystemDefault()).toEpochMilliseconds() }
2542+ .getOrElse { components.toLocalDate().atStartOfDayIn(TimeZone .currentSystemDefault()).toEpochMilliseconds() }
2543+ }
2544+ }.onFailure { logError(it) }.getOrNull()
25212545}
25222546
2523- fun Episode.addDate (date : Date ? ) {
2547+ @Prerelease
2548+ fun Episode.addDate (date : LocalDate ? ) {
2549+ this .date = date?.atStartOfDayIn(TimeZone .currentSystemDefault())?.toEpochMilliseconds()
2550+ }
2551+
2552+ @Prerelease
2553+ fun Episode.addDate (date : Instant ? ) {
2554+ this .date = date?.toEpochMilliseconds()
2555+ }
2556+
2557+ // Deprecate after next stable
2558+ /* @Deprecated(
2559+ message = "Use addDate with LocalDate, Instant, or String instead.",
2560+ level = DeprecationLevel.WARNING,
2561+ ) */
2562+ fun Episode.addDate (date : java.util.Date ? ) {
25242563 this .date = date?.time
25252564}
25262565
@@ -2657,6 +2696,27 @@ fun fetchUrls(text: String?): List<String> {
26572696 return linkRegex.findAll(text).map { it.value.trim().removeSurrounding(" \" " ) }.toList()
26582697}
26592698
2699+ @Prerelease
2700+ fun isUpcoming (dateString : String? ): Boolean {
2701+ return runCatching {
2702+ val fmt = DateTimeComponents .Format {
2703+ year(); char(' -' ); monthNumber(); char(' -' ); day()
2704+ }
2705+ val components = DateTimeComponents .parse(dateString ? : return false , fmt)
2706+ /* *
2707+ * Try multiple conversions in order of precision, since the date string format
2708+ * may or may not include time and/or timezone offset information:
2709+ * 1. If the string has a UTC offset (e.g. "2026-05-17T14:35+02:00"), use it directly
2710+ * 2. If it has time but no offset (e.g. "2026-05-17T14:35"), fall back to device timezone
2711+ * 3. If it's date-only (e.g. "2026-05-17"), use start of day in device timezone
2712+ */
2713+ val instant = runCatching { components.toInstantUsingOffset() }
2714+ .recoverCatching { components.toLocalDateTime().toInstant(TimeZone .currentSystemDefault()) }
2715+ .getOrElse { components.toLocalDate().atStartOfDayIn(TimeZone .currentSystemDefault()) }
2716+ Clock .System .now() < instant
2717+ }.onFailure { logError(it) }.getOrElse { false }
2718+ }
2719+
26602720@Deprecated(
26612721 " toRatingInt() is deprecated. Use new score API instead." ,
26622722 level = DeprecationLevel .ERROR
0 commit comments