You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailscale-android/android/src/main/java/com/tailscale/ipn/ui/util/TimeUtil.kt

125 lines
4.5 KiB
Kotlin

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package com.tailscale.ipn.ui.util
import android.util.Log
import com.tailscale.ipn.R
import java.time.Duration
import java.time.Instant
import java.time.format.DateTimeFormatter
import java.util.Date
object TimeUtil {
val TAG = "TimeUtil"
fun keyExpiryFromGoTime(goTime: String?): ComposableStringFormatter {
val time = goTime ?: return ComposableStringFormatter(R.string.empty)
val expTime = epochMillisFromGoTime(time)
val now = Instant.now().toEpochMilli()
var diff = (expTime - now) / 1000
// Rather than use plurals here, we'll just use the singular form for everything and
// double the minimum. "in 70 minutes" instead of "in 1 hour". 121 minutes becomes
// 2 hours, as does 179 minutes... Close enough for what this is used for.
// Key is already expired (x minutes ago)
if (diff < 0) {
diff = -diff
return when (diff) {
in 0..60 -> ComposableStringFormatter(R.string.under_a_minute)
in 61..7200 ->
ComposableStringFormatter(R.string.ago_x_minutes, diff / 60) // 1 minute to 1 hour
in 7201..172800 ->
ComposableStringFormatter(R.string.ago_x_hours, diff / 3600) // 2 hours to 24 hours
in 172801..5184000 ->
ComposableStringFormatter(R.string.ago_x_days, diff / 86400) // 2 Days to 60 days
in 5184001..124416000 ->
ComposableStringFormatter(R.string.ago_x_months, diff / 2592000) // ~2 months to 2 years
else ->
ComposableStringFormatter(
R.string.ago_x_years,
diff.toDouble() / 31536000.0) // 2 years to n years (in decimal)
}
}
// Key is not expired (in x minutes)
return when (diff) {
in 0..60 -> ComposableStringFormatter(R.string.under_a_minute)
in 61..7200 ->
ComposableStringFormatter(R.string.in_x_minutes, diff / 60) // 1 minute to 1 hour
in 7201..172800 ->
ComposableStringFormatter(R.string.in_x_hours, diff / 3600) // 2 hours to 24 hours
in 172801..5184000 ->
ComposableStringFormatter(R.string.in_x_days, diff / 86400) // 2 Days to 60 days
in 5184001..124416000 ->
ComposableStringFormatter(R.string.in_x_months, diff / 2592000) // ~2 months to 2 years
else ->
ComposableStringFormatter(
R.string.in_x_years, diff.toDouble() / 31536000.0) // 2 years to n years (in decimal)
}
}
fun epochMillisFromGoTime(goTime: String): Long {
val ta = DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(goTime)
val i = Instant.from(ta)
return i.toEpochMilli()
}
fun dateFromGoString(goTime: String): Date {
val ta = DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(goTime)
val i = Instant.from(ta)
return Date.from(i)
}
// Returns true if the given Go time string is in the past, or will occur within the given
// duration from now.
fun isWithinExpiryNotificationWindow(window: Duration, goTime: String): Boolean {
val expTime = epochMillisFromGoTime(goTime)
val now = Instant.now().toEpochMilli()
return (expTime - now) / 1000 < window.seconds
}
// Parses a Go duration string (e.g. "2h3.2m4s") and returns a Java Duration object.
// Returns null if the input string is not a valid Go duration or contains
// units other than y,w,d,h,m,s (ms and us are explicitly not supported).
fun duration(goDuration: String): Duration? {
if (goDuration.contains("ms") || goDuration.contains("us")) {
return null
}
var duration = 0.0
var valStr = ""
for (c in goDuration) {
// Scan digits and decimal points
if (c.isDigit() || c == '.') {
valStr += c
} else {
try {
val durationFragment = valStr.toDouble()
duration +=
when (c) {
'y' -> durationFragment * 31536000.0 // 365 days
'w' -> durationFragment * 604800.0
'd' -> durationFragment * 86400.0
'h' -> durationFragment * 3600.0
'm' -> durationFragment * 60.0
's' -> durationFragment
else -> {
Log.e(TAG, "Invalid duration string: $goDuration")
return null
}
}
} catch (e: NumberFormatException) {
Log.e(TAG, "Invalid duration string: $goDuration")
return null
}
valStr = ""
}
}
return Duration.ofSeconds(duration.toLong())
}
}