@@ -14,6 +14,10 @@ import kotlinx.coroutines.launch
1414import kotlinx.coroutines.flow.MutableStateFlow
1515import kotlinx.coroutines.flow.StateFlow
1616import kotlinx.coroutines.flow.asStateFlow
17+ import java.text.SimpleDateFormat
18+ import java.util.Date
19+ import java.util.Locale
20+ import java.util.TimeZone
1721
1822/* *
1923 * Singleton bridge between Kotlin UI and Go core.
@@ -78,13 +82,14 @@ object VpnManager {
7882 }
7983
8084 fun appendLog (line : String ) {
85+ val normalizedLine = normalizeLogTimestampToLocal(line)
8186 val current = _logs .value.toMutableList()
82- current.add(line )
87+ current.add(normalizedLine )
8388 if (current.size > MAX_LOG_LINES ) {
8489 current.removeAt(0 )
8590 }
8691 _logs .value = current
87- parseScanLine(line )
92+ parseScanLine(normalizedLine )
8893 }
8994
9095 fun clearLogs () {
@@ -212,4 +217,56 @@ object VpnManager {
212217 _scanStatus .value = _scanStatus .value.copy(scanning = false )
213218 }
214219 }
220+
221+ private fun normalizeLogTimestampToLocal (line : String ): String {
222+ val candidates = listOf (
223+ // Example: 2026-04-05T10:20:30.123Z
224+ Triple (
225+ Regex (" ^(\\ d{4}-\\ d{2}-\\ d{2}T\\ d{2}:\\ d{2}:\\ d{2}\\ .\\ d{3}Z)(.*)$" ),
226+ " yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" ,
227+ " yyyy-MM-dd HH:mm:ss.SSS"
228+ ),
229+ // Example: 2026-04-05T10:20:30Z
230+ Triple (
231+ Regex (" ^(\\ d{4}-\\ d{2}-\\ d{2}T\\ d{2}:\\ d{2}:\\ d{2}Z)(.*)$" ),
232+ " yyyy-MM-dd'T'HH:mm:ss'Z'" ,
233+ " yyyy-MM-dd HH:mm:ss"
234+ ),
235+ // Example: 2026-04-05 10:20:30 UTC
236+ Triple (
237+ Regex (" ^(\\ d{4}-\\ d{2}-\\ d{2} \\ d{2}:\\ d{2}:\\ d{2})\\ s+UTC(.*)$" ),
238+ " yyyy-MM-dd HH:mm:ss" ,
239+ " yyyy-MM-dd HH:mm:ss"
240+ )
241+ )
242+
243+ for ((regex, inputFormat, outputFormat) in candidates) {
244+ val match = regex.find(line) ? : continue
245+ val utcStamp = match.groupValues[1 ]
246+ val suffix = match.groupValues[2 ]
247+ val localStamp = convertUtcToLocal(utcStamp, inputFormat, outputFormat) ? : continue
248+ return " $localStamp$suffix "
249+ }
250+ return line
251+ }
252+
253+ private fun convertUtcToLocal (
254+ utcValue : String ,
255+ inputPattern : String ,
256+ outputPattern : String
257+ ): String? {
258+ return try {
259+ val input = SimpleDateFormat (inputPattern, Locale .US ).apply {
260+ timeZone = TimeZone .getTimeZone(" UTC" )
261+ isLenient = false
262+ }
263+ val parsed: Date = input.parse(utcValue) ? : return null
264+ val output = SimpleDateFormat (outputPattern, Locale .US ).apply {
265+ timeZone = TimeZone .getDefault()
266+ }
267+ output.format(parsed)
268+ } catch (_: Exception ) {
269+ null
270+ }
271+ }
215272}
0 commit comments