Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import com.sopt.clody.data.remote.datasource.RemoteConfigDataSource
import com.sopt.clody.domain.appupdate.AppUpdateChecker
import com.sopt.clody.domain.model.AppUpdateState
import com.sopt.clody.domain.util.VersionComparator
import java.time.LocalDateTime
import java.time.format.TextStyle
import java.util.Locale
import javax.inject.Inject

class AppUpdateCheckerImpl @Inject constructor(
Expand All @@ -30,4 +33,30 @@ class AppUpdateCheckerImpl @Inject constructor(
}
}
}

override suspend fun isUnderInspection(): Boolean {
val start = remoteConfigDataSource.getInspectionStart() ?: return false
val end = remoteConfigDataSource.getInspectionEnd() ?: return false
val now = LocalDateTime.now()
return now.isAfter(start) && now.isBefore(end)
}
Comment on lines +37 to +42
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Consider edge cases in time comparison logic.

The current implementation uses isAfter(start) && isBefore(end), which excludes the exact start and end times. For maintenance windows, you might want to include the boundaries.

Additionally, consider timezone implications when using LocalDateTime.now() if the server and client could be in different timezones.

-        return now.isAfter(start) && now.isBefore(end)
+        return (now.isEqual(start) || now.isAfter(start)) && (now.isEqual(end) || now.isBefore(end))

Or more concisely:

-        return now.isAfter(start) && now.isBefore(end)
+        return !now.isBefore(start) && !now.isAfter(end)

Would you like me to search for more information about timezone best practices for maintenance windows?


🌐 Web query:

Best practices for handling timezones in maintenance windows for mobile apps

💡 Result:

When handling timezones in maintenance windows for mobile apps, it's essential to consider user experience, accuracy, and clarity. Here are some best practices:

  1. Use UTC Internally:

    • Store and manage all maintenance windows in Coordinated Universal Time (UTC) to avoid confusion and simplify calculations.
  2. Convert to Local Time:

    • Convert UTC times to the user's local timezone before displaying maintenance windows.
    • Use the device’s current timezone setting to perform conversions.
  3. Clear Communication:

    • Clearly specify the timezone when displaying maintenance windows if not showing local time.
    • Use relative times where applicable (e.g., "Maintenance starts in 2 hours").
  4. Handle Daylight Saving Time (DST):

    • Use reliable timezone libraries that account for DST changes (e.g., moment-timezone in JavaScript).
    • Test maintenance windows around DST changes carefully.
  5. Send Reminders:

    • Provide notifications or reminders about upcoming maintenance in the local timezone.
    • Allow users to set reminders if possible.
  6. Maintain Consistency:

    • Ensure the displayed timezone matches across all parts of the app (notifications, in-app messages, emails).
  7. Consider User Location Changes:

    • Detect if the user changes their timezone and update the displayed maintenance window times dynamically.
  8. Provide Flexibility:

    • Offer users the option to view maintenance times in UTC or another preferred timezone.
  9. Test Extensively:

    • Test the handling of timezones and maintenance windows on devices set to different timezones, including edge cases like the International Date Line.

Would you like suggestions on specific libraries or tools to implement these best practices in your mobile app?


Ensure inclusive time checks and normalize to UTC

Please update the inspection‐window logic in app/src/main/java/com/sopt/clody/data/remote/appupdate/AppUpdateCheckerImpl.kt to:

  • Include the exact start and end times (avoid excluding boundaries).
  • Use UTC internally (e.g. Instant or ZonedDateTime with ZoneOffset.UTC) to eliminate timezone drift.
  • Convert to the device’s local timezone only when displaying to the user.

Locations to update:

  • AppUpdateCheckerImpl.kt, isUnderInspection() method.

Suggested diff:

-    override suspend fun isUnderInspection(): Boolean {
-        val start = remoteConfigDataSource.getInspectionStart() ?: return false
-        val end = remoteConfigDataSource.getInspectionEnd() ?: return false
-        val now = LocalDateTime.now()
-        return now.isAfter(start) && now.isBefore(end)
-    }
+    override suspend fun isUnderInspection(): Boolean {
+        // Treat all times as UTC Instants
+        val start = remoteConfigDataSource.getInspectionStartUtc() ?: return false
+        val end = remoteConfigDataSource.getInspectionEndUtc() ?: return false
+        val now = Instant.now()
+        // inclusive: now ≥ start && now ≤ end
+        return !now.isBefore(start) && !now.isAfter(end)
+    }

If your RemoteConfig returns LocalDateTime, convert it:

val startUtc = start.atZone(ZoneId.systemDefault())
                     .withZoneSameInstant(ZoneOffset.UTC)
                     .toInstant()

And when showing maintenance times in‐app, convert back:

val userLocal = Instant.parse(remoteConfigTime)
                .atZone(ZoneId.systemDefault())

This approach:

  • Guarantees inclusive windows.
  • Centralizes all comparisons in UTC.
  • Avoids DST and device‐timezone inconsistencies.
🤖 Prompt for AI Agents
In
app/src/main/java/com/sopt/clody/data/remote/appupdate/AppUpdateCheckerImpl.kt
around lines 37 to 42, update the isUnderInspection() method to perform
inclusive time checks by using isEqual or isAfter for the start time and isEqual
or isBefore for the end time. Convert the start and end LocalDateTime values to
UTC Instants before comparison by first assigning the system default zone then
converting to ZoneOffset.UTC and to Instant. Use these UTC Instants for the time
range check against the current time in UTC. This ensures all comparisons are
timezone-agnostic and inclusive of boundary times.


override fun getInspectionTimeText(): String? {
val start = remoteConfigDataSource.getInspectionStart()
val end = remoteConfigDataSource.getInspectionEnd()
if (start == null || end == null) return null

val startText = formatDateTimeWithDayOfWeek(start)
val endText = formatDateTimeWithDayOfWeek(end)
return "$startText ~ $endText"
}

private fun formatDateTimeWithDayOfWeek(dateTime: LocalDateTime): String {
val dayOfWeek = dateTime.dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.KOREAN)
val month = dateTime.monthValue
val day = dateTime.dayOfMonth
val hour = dateTime.hour.toString().padStart(2, '0')

return "$month/$day($dayOfWeek) ${hour}시"
}
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[p2]
AppUpdateChecker에서 이 역할 가져가는 이유가 있을까요? 네이밍에 비해 많은 역할을 하는 것 같아 책임분리 측면에서 AppInspectionChecker로 분리하는 방법은 어떨지 건의해봅니다.

Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ import com.sopt.clody.domain.model.AppUpdateState

interface AppUpdateChecker {
suspend fun getAppUpdateState(currentVersion: String): AppUpdateState
suspend fun isUnderInspection(): Boolean
fun getInspectionTimeText(): String?
}