Pith - wedge_android
wedge_android/app/src/main/java/com/vgmlr/wedge/WedgeCalendar.kt [7.3 kb]
Modified: 22:37:12 74 026 (01 Jun 026)
22 Days Ago
package com.vgmlr.wedge

import java.text.SimpleDateFormat
import java.util.*

object WedgeCalendar {
    private data class ChildLine(val start: Int, val end: Int, val content: String)
    private data class HeaderInfo(val start: Int, val otcDay: Int)

    private fun parseTimeToMinutes(timeStr: String): Int? {
        val sanitized = if (!timeStr.contains(":") && timeStr.length == 4) {
            "${timeStr.substring(0, 2)}:${timeStr.substring(2)}"
        } else if (!timeStr.contains(":") && timeStr.length == 3) {
            "0${timeStr.substring(0, 1)}:${timeStr.substring(1)}"
        } else {
            timeStr
        }
        val parts = sanitized.split(":")
        if (parts.size != 2) return null
        val h = parts[0].toIntOrNull() ?: return null
        val m = parts[1].toIntOrNull() ?: return null
        return h * 60 + m
    }

    fun insertCalendarEntry(
        currentText: String,
        timestamp: Long,
        timeStr: String,
        entryText: String
    ): String {
        val otcDay = WedgeDate.getOtcParts(timestamp).first
        val dayOfWeek = SimpleDateFormat("EEE", Locale.US).format(Date(timestamp))
        val calendar = Calendar.getInstance().apply { timeInMillis = timestamp }
        val dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH)
        val month = SimpleDateFormat("MMM", Locale.US).format(Date(timestamp))

        val targetHeader = "$otcDay ${dayOfWeek.lowercase(Locale.US)} $dayOfMonth ${month.lowercase(Locale.US)}*"
        val formattedTime = if (!timeStr.contains(":") && timeStr.length == 4) {
            "${timeStr.substring(0, 2)}:${timeStr.substring(2)}"
        } else if (!timeStr.contains(":") && timeStr.length == 3) {
            "0${timeStr.substring(0, 1)}:${timeStr.substring(1)}"
        } else {
            timeStr
        }
        val newTimeEntry = "$formattedTime  $entryText"

        val headerRegex = Regex("""(?i)$otcDay\s+$dayOfWeek\s+0?$dayOfMonth\s+$month""")
        val headerMatch = headerRegex.find(currentText)

        if (headerMatch != null) {
            val headerEnd = headerMatch.range.last + 1
            var lineEnd = headerEnd
            while (lineEnd < currentText.length && currentText[lineEnd] != '\n') {
                lineEnd++
            }
            val headerLineEndIndex = lineEnd

            val childLines = mutableListOf<ChildLine>()
            var scanIndex = headerLineEndIndex
            if (scanIndex < currentText.length && currentText[scanIndex] == '\n') {
                scanIndex++
            }

            val generalHeaderRegex = Regex("""^(\d+)\s+([a-zA-Z]{3})\s+(\d{1,2})\s+([a-zA-Z]{3})""", RegexOption.IGNORE_CASE)

            while (scanIndex < currentText.length) {
                var nextLineEnd = scanIndex
                while (nextLineEnd < currentText.length && currentText[nextLineEnd] != '\n') {
                    nextLineEnd++
                }
                val lineContent = currentText.substring(scanIndex, nextLineEnd)

                if (lineContent.trim().isEmpty() || generalHeaderRegex.containsMatchIn(lineContent)) {
                    break
                }

                childLines.add(ChildLine(start = scanIndex, end = nextLineEnd, content = lineContent))
                scanIndex = nextLineEnd
                if (scanIndex < currentText.length && currentText[scanIndex] == '\n') {
                    scanIndex++
                }
            }

            val newTimeVal = parseTimeToMinutes(timeStr) ?: 0
            var matchingLine: ChildLine? = null
            var insertBeforeLine: ChildLine? = null

            for (line in childLines) {
                val match = WedgeRegex.TIME_ENTRY.find(line.content)
                if (match != null) {
                    val h = match.groupValues[1].toInt()
                    val m = match.groupValues[2].toInt()
                    val lineTimeVal = h * 60 + m
                    if (lineTimeVal == newTimeVal) {
                        matchingLine = line
                        break
                    } else if (lineTimeVal > newTimeVal && insertBeforeLine == null) {
                        insertBeforeLine = line
                    }
                }
            }
            
            if (matchingLine != null) {
                return currentText.substring(0, matchingLine.end) + "\n       $entryText" + currentText.substring(matchingLine.end)
            }

            return if (insertBeforeLine != null) {
                currentText.substring(0, insertBeforeLine.start) + "$newTimeEntry\n" + currentText.substring(insertBeforeLine.start)
            } else if (childLines.isNotEmpty()) {
                val lastLine = childLines.last()
                currentText.substring(0, lastLine.end) + "\n$newTimeEntry" + currentText.substring(lastLine.end)
            } else {
                currentText.substring(0, headerLineEndIndex) + "\n$newTimeEntry" + currentText.substring(headerLineEndIndex)
            }
        }

        val generalHeaderRegex = Regex("""(?m)^(\d+)\s+([a-zA-Z]{3})\s+(\d{1,2})\s+([a-zA-Z]{3})""")
        val matches = generalHeaderRegex.findAll(currentText).toList()        

        if (matches.isEmpty()) {
            val headerBlock = "$targetHeader\n$newTimeEntry\n\n"
            return headerBlock + currentText
        }

        val headers = matches.map { m ->
            val startIdx = m.range.first
            val otcDayVal = m.groupValues[1].toIntOrNull() ?: 0
            HeaderInfo(start = startIdx, otcDay = otcDayVal)
        }

        var isDescending = false
        if (headers.size >= 2) {
            isDescending = headers.first().otcDay >= headers.last().otcDay
        }

        val targetOtcDay = otcDay.toInt()

        if (isDescending) {
            val insertBeforeHeader = headers.find { it.otcDay < targetOtcDay }
            if (insertBeforeHeader != null) {
                return currentText.substring(0, insertBeforeHeader.start) + "$targetHeader\n$newTimeEntry\n\n" + currentText.substring(insertBeforeHeader.start)
            }
        } else {
            val insertBeforeHeader = headers.find { it.otcDay > targetOtcDay }
            if (insertBeforeHeader != null) {
                return currentText.substring(0, insertBeforeHeader.start) + "$targetHeader\n$newTimeEntry\n\n" + currentText.substring(insertBeforeHeader.start)
            }
        }

        val lastHeader = headers.last()
        var scanIdx = lastHeader.start
        while (scanIdx < currentText.length && currentText[scanIdx] != '\n') scanIdx++
        if (scanIdx < currentText.length && currentText[scanIdx] == '\n') scanIdx++
        while (scanIdx < currentText.length) {
            var lineEnd = scanIdx
            while (lineEnd < currentText.length && currentText[lineEnd] != '\n') lineEnd++
            val lineContent = currentText.substring(scanIdx, lineEnd)
            if (lineContent.trim().isEmpty() || generalHeaderRegex.containsMatchIn(lineContent)) break
            scanIdx = lineEnd
            if (scanIdx < currentText.length && currentText[scanIdx] == '\n') scanIdx++
        }
        val prefix = currentText.substring(0, scanIdx)
        val suffix = currentText.substring(scanIdx)
        val spacing = if (prefix.endsWith("\n\n")) "" else if (prefix.endsWith("\n")) "\n" else "\n\n"
        return prefix + spacing + "$targetHeader\n$newTimeEntry\n\n" + suffix.trimStart()
    }
}
Updates
OTC Applet - Linux 93.026.1
Wedge - Linux 90.026.1
Wedge - Android 90.026.1
Shim - Android 86.026.1
Kerf - Android 86.026.4
Dev
TVShow (227) 'CSA'
TVShow (228) 'APT'
TVProgram (83) 'BXT'
Miter Update(s)
Peen (Messaging)

Menu
Calendar
Project Tin (024/029)
Miter
RSS Feed