1package soy.iko.opencode.ui.components
3import java.time.Instant
4import java.time.LocalDate
6import java.util.concurrent.TimeUnit
9 * The time unit + count resolved from a delta, before locale-specific formatting. Kept as a
10 * pure value so unit tests can verify the boundary logic without a [android.content.Context]
11 * (string-resource resolution is Android-only and stubs to null under `isReturnDefaultValues`).
13sealed class RelativeTimeUnit {
14 data class Now(val count: Int = 0) : RelativeTimeUnit()
15 data class Minutes(val count: Long) : RelativeTimeUnit()
16 data class Hours(val count: Long) : RelativeTimeUnit()
17 data class Days(val count: Long) : RelativeTimeUnit()
18 data class Weeks(val count: Long) : RelativeTimeUnit()
19 data class Months(val count: Long) : RelativeTimeUnit()
20 data class Years(val count: Long) : RelativeTimeUnit()
24 * Resolves the [RelativeTimeUnit] for a delta without any locale-specific formatting, so the
25 * boundary logic is unit-testable on the JVM without a [android.content.Context].
27 * Months and years are computed via [java.time] calendar arithmetic (not `days / 30` or
28 * `days / 365`), so the displayed "N months ago" / "N years ago" matches the calendar-month
29 * difference a user expects rather than drifting at month/year boundaries of varying length.
31fun relativeTimeUnit(epochMillis: Long?): RelativeTimeUnit? {
32 if (epochMillis == null || epochMillis <= 0) return null
33 val now = System.currentTimeMillis()
34 val diff = now - epochMillis
35 if (diff < 0) return RelativeTimeUnit.Now()
36 val minutes = TimeUnit.MILLISECONDS.toMinutes(diff)
37 if (minutes < 1) return RelativeTimeUnit.Now()
38 if (minutes < 60) return RelativeTimeUnit.Minutes(minutes)
39 val hours = TimeUnit.MILLISECONDS.toHours(diff)
40 if (hours < 24) return RelativeTimeUnit.Hours(hours)
41 val days = TimeUnit.MILLISECONDS.toDays(diff)
42 if (days < 7) return RelativeTimeUnit.Days(days)
44 if (weeks < 5) return RelativeTimeUnit.Weeks(weeks)
45 // Beyond ~5 weeks, switch to calendar months/years so the label tracks the user's
46 // mental model of "months ago" rather than a 30-day approximation that drifts at
47 // month boundaries (28/29/30/31 days). java.time is available on minSdk 26+.
48 val zone = ZoneId.systemDefault()
49 val past = Instant.ofEpochMilli(epochMillis).atZone(zone).toLocalDate()
50 val today = Instant.ofEpochMilli(now).atZone(zone).toLocalDate()
51 val monthsBetween = java.time.Period.between(past, today).toTotalMonths()
52 if (monthsBetween < 12) return RelativeTimeUnit.Months(monthsBetween)
53 val years = monthsBetween / 12
54 return RelativeTimeUnit.Years(years)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list