Skip to content

Instantly share code, notes, and snippets.

@ivan200
Created April 22, 2021 10:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ivan200/9cb4bcf3cb2b02e8d58db39166c34329 to your computer and use it in GitHub Desktop.
Save ivan200/9cb4bcf3cb2b02e8d58db39166c34329 to your computer and use it in GitHub Desktop.
Managing correct time in android app
object AppLocalTime : AppLocalTimeBase() {
override fun saveServerDateTimeMls(millis: Long) = Prefs::serverDateTimeMls.set(millis)
override fun savePhoneDateTimeMls(millis: Long) = Prefs::phoneDateTimeMls.set(millis)
override fun savePhoneBootMillis(millis: Long) = Prefs::phonePhoneBootMls.set(millis)
override fun loadServerDateTimeMls(): Long = Prefs.serverDateTimeMls
override fun loadPhoneDateTimeMls(): Long = Prefs.phoneDateTimeMls
override fun loadPhoneBootMillis(): Long = Prefs.phonePhoneBootMls
override fun getServerDateFormat(): String = "yyyy-MM-dd HH:mm:ss ZZZZ" // sample 2017-04-24 15:14:39 +0000
override fun getServerDateString(ondate: (String?) -> Unit, onError: (Throwable?) -> Unit) {
//ClientServer.Requests.getServerTime()
}
}
import android.content.Context
import android.os.Build
import android.os.SystemClock
import android.provider.Settings
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.logging.Level
import java.util.logging.Logger
import kotlin.math.abs
inline val <T : Any> T.TAG: String get() = this::class.java.simpleName
inline val <T : Any> T.logger: Logger get() = Logger.getLogger(this.TAG)
abstract class AppLocalTimeBase {
private var lastServerTimeMillis: Long = 0
private var lastPhoneTimeMillis: Long = 0
private var lastPhoneBootMillis: Long = 0
private var needToUpdateTime = true
private var alreadyUpdating = false
/**
* Если получаем данные первый раз, пытаемся восстановить их из телефона
* Если при сравнении времени, которое было с последней загрузки, и времени в телефоне разница больше 5 минут
* то нам потребуется срочно обновить время
* Если не получилось восстановить данные о последней загрузке, то просто используем текущее время
* Высчитываем время сервера при помощи таймеров телефона
*/
val currentDateTimeMls: Long
get() {
if (lastServerTimeMillis <= 0 || lastPhoneTimeMillis <= 0 || lastPhoneBootMillis <= 0) {
restoreTimesFromPrefs()
}
val phoneBootMillis = SystemClock.elapsedRealtime()
val phoneTimeMillis = System.currentTimeMillis()
val phoneBootDiff = phoneBootMillis - lastPhoneBootMillis
val phoneTimeDiff = phoneTimeMillis - lastPhoneTimeMillis
val diffMillis = abs(phoneBootDiff - phoneTimeDiff)
val diffMinutes = TimeUnit.MILLISECONDS.toMinutes(diffMillis)
if (diffMinutes > 5) {
needToUpdateTime = true
updateTime()
logger.log(Level.INFO, "time needToUpdateTime")
} else {
needToUpdateTime = false
}
if (lastPhoneBootMillis <= 0) {
return phoneTimeMillis
}
val countedServerTimeMillis: Long
if (phoneBootMillis > lastPhoneBootMillis) {
countedServerTimeMillis = lastServerTimeMillis + phoneBootDiff
logger.log(Level.FINEST, "time fromBoot: $countedServerTimeMillis")
} else {
countedServerTimeMillis = lastServerTimeMillis + phoneTimeDiff
logger.log(Level.FINEST, "time fromPhone: $countedServerTimeMillis")
}
return countedServerTimeMillis
}
val currentDate: Date
get() {
return Date(currentDateTimeMls)
}
val isTimeCorrect: Boolean get() = !needToUpdateTime
val currentCalendar: Calendar
get() {
val calendar = Calendar.getInstance()
calendar.time = currentDate
return calendar
}
val currentDayOfYear: Int
get() {
return currentCalendar.get(Calendar.DAY_OF_YEAR)
}
val currentDateString: String
get() {
return dateToString(currentDate, getServerDateFormat())
}
//При любом получении данных с сервера, записываем данные о времени в настройки телефона
fun saveOurServerDate(serverTime: String) {
if(serverTime.isEmpty()){
return
}
val serverDateTime = dateFromString(serverTime, getServerDateFormat())
if (serverDateTime == null) {
logger.log(Level.WARNING, "Ошибка обновления времени: $serverTime")
return
}
logger.log(Level.FINEST, "time saveTime")
this.lastServerTimeMillis = serverDateTime.time
this.lastPhoneTimeMillis = System.currentTimeMillis()
this.lastPhoneBootMillis = SystemClock.elapsedRealtime()
if (lastPhoneBootMillis > 0 && lastPhoneTimeMillis > 0 && lastPhoneBootMillis > 0) {
saveServerDateTimeMls(lastServerTimeMillis)
savePhoneDateTimeMls(lastPhoneTimeMillis)
savePhoneBootMillis(lastPhoneBootMillis)
needToUpdateTime = false
}
}
abstract fun saveServerDateTimeMls(millis: Long)
abstract fun savePhoneDateTimeMls(millis: Long)
abstract fun savePhoneBootMillis(millis: Long)
abstract fun loadServerDateTimeMls(): Long
abstract fun loadPhoneDateTimeMls(): Long
abstract fun loadPhoneBootMillis(): Long
abstract fun getServerDateFormat(): String
private fun updateTime() {
if (needToUpdateTime && !alreadyUpdating) {
alreadyUpdating = true
getServerDateString(ondate = {
alreadyUpdating = false
if (it.isNullOrEmpty()) {
logger.log(Level.WARNING, "Ошибка обновления времени")
} else {
saveOurServerDate(it)
}
}, onError = {
alreadyUpdating = false
logger.log(Level.WARNING, "Ошибка обновления времени, ${it?.message}")
})
}
}
abstract fun getServerDateString(ondate: (String?)->Unit, onError: (Throwable?) -> Unit)
//Восстановление данных о времени из настроек
private fun restoreTimesFromPrefs() {
lastServerTimeMillis = loadServerDateTimeMls()
lastPhoneTimeMillis = loadPhoneDateTimeMls()
lastPhoneBootMillis = loadPhoneBootMillis()
}
companion object {
fun dateFromString(str: String?, pattern: String, timeZone: String? = null): Date? {
if(str == null) return null
logger.log(Level.FINEST, "dateTimeFromString: $str, zone:$timeZone")
val sdf = SimpleDateFormat(pattern, Locale.getDefault())
if(!timeZone.isNullOrEmpty()) {
sdf.timeZone = TimeZone.getTimeZone(timeZone)
}
var d: Date? = null
try {
d = sdf.parse(str)
} catch (throwable: Throwable){
logger.log(Level.WARNING, throwable.getStackTraceText())
}
return d
}
fun millisToString(millis: Long?, pattern: String): String? {
if(millis == null || millis <= 0) return null
val sdf = SimpleDateFormat(pattern, Locale.ENGLISH)
// sdf.timeZone = instance.currentCalendar.timeZone
return sdf.format(millis)
}
private fun dateToString(date: Date, pattern: String): String {
val sdf = SimpleDateFormat(pattern, Locale.ENGLISH)
// sdf.timeZone = instance.currentCalendar.timeZone
return sdf.format(date)
}
@Suppress("DEPRECATION")
fun isTimeAutomatic(c: Context): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Settings.Global.getInt(c.contentResolver, Settings.Global.AUTO_TIME, 0) == 1
} else {
Settings.System.getInt(c.contentResolver, Settings.System.AUTO_TIME, 0) == 1
}
}
@Suppress("DEPRECATION")
fun isTimeZoneAutomatic(c: Context): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Settings.Global.getInt(c.contentResolver, Settings.Global.AUTO_TIME_ZONE, 0) == 1
} else {
Settings.System.getInt(c.contentResolver, Settings.System.AUTO_TIME_ZONE, 0) == 1
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment