You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用LocalDateTime生成服药时间时年度周期出现多余时间单元的解决方法

Android服药时间生成器:年度疗程场景下多余时间单元问题

我开发了一款Android应用,用户可通过三个下拉菜单选择服药开始时间、间隔时长和疗程周期,应用会基于这些参数生成后续服药时间列表。小时、天、周、月等短周期场景下功能正常,但使用年度疗程时会生成多余的时间单元。

当前实现代码

import library

sealed class DropDownType {
    data object MedicineTime : DropDownType()
    data object MedicineIntervals : DropDownType()
    data object TreatmentPeriod : DropDownType()
}

@RequiresApi(Build.VERSION_CODES.O)
fun getDropDownData(type: DropDownType): Pair<String, List<Pair<String, Int>>> {
    return when (type) {
        is DropDownType.MedicineTime -> {
            val title = "Set intake time"
            val formatter = DateTimeFormatter.ofPattern("h:mm a")

            // Generate times for each hour of the day
            val times = (0 until 24).map {
                val time = LocalDateTime.of(2024, 1, 1, it, 0).format(formatter)
                time to it
            }

            // If you want to exclude the exact time, use just the 'times' list
            title to times
        }

        is DropDownType.MedicineIntervals -> {
            val title = "Select Interval"

            // You can dynamically map ChronoUnit intervals instead of hardcoding
            val intervals = listOf(
                "1 hour" to 1,
                "6 hours" to 6,
                "8 hours" to 8,
                "16 hours" to 16,
                "1 day" to 24,        // 24 hours = 1 day
                "2 days" to 48,
                "3 days" to 72,
                "4 days" to 96,
                "5 days" to 120,
                "6 days" to 144,
                "1 week" to 168,      // 1 week = 7 days = 168 hours
                "2 weeks" to 336,
                "1 month" to 720,     // Approximate 1 month (30 days)
                "2 months" to 1440,
                "3 months" to 2160,
                "6 months" to 4320,
            )
            title to intervals
        }

        is DropDownType.TreatmentPeriod -> {
            val title = "Select Treatment Period"

            // Same logic for treatment periods
            val treatmentPeriods = listOf(
                "1 day" to 24,
                "2 days" to 48,
                "1 week" to 168,
                "2 weeks" to 336,
                "3 weeks" to 504,
                "4 weeks" to 672,
                "1 month" to 720,
                "2 months" to 1440,
                "3 months" to 2160,
                "6 months" to 4320,
                "1 year" to 8760
            )
            title to treatmentPeriods
        }

        else -> "" to emptyList()
    }
}


import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import org.differenttouch.myapp.ui.theme.MyAppTheme
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit

class MainActivity : ComponentActivity() {
    @RequiresApi(Build.VERSION_CODES.O)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyAppTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = Color.White
                ) {
                    CalculateHoursScreen()
                }
            }
        }
    }
}

@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun CalculateHoursScreen() {
    // Hold the selected options for intervals, time, and treatment period
    var selectedInterval by remember { mutableStateOf<Pair<String, Any>?>(null) }
    var selectedTime by remember { mutableStateOf<Pair<String, Any>?>(null) }
    var selectedTreatmentPeriod by remember { mutableStateOf<Pair<String, Any>?>(null) }

    var hoursList by remember { mutableStateOf<List<LocalDateTime>>(emptyList()) }

    Column(modifier = Modifier.padding(16.dp)) {

        // Dropdown for Medicine Time
        val timeOptions = getDropDownData(DropDownType.MedicineTime)

        CustomDropDown(
            label = timeOptions.first,
            options = timeOptions.second,
            selectedOption = selectedTime
        ) { selectedTime = it }

        Spacer(modifier = Modifier.height(16.dp))

        // Dropdown for Medicine Intervals
        val intervalOptions = getDropDownData(DropDownType.MedicineIntervals)

        CustomDropDown(
            label = intervalOptions.first,
            options = intervalOptions.second,
            selectedOption = selectedInterval
        ) { selectedInterval = it }

        Spacer(modifier = Modifier.height(16.dp))

        // Dropdown for Treatment Period
        val treatmentPeriodOptions = getDropDownData(DropDownType.TreatmentPeriod)

        CustomDropDown(
            label = treatmentPeriodOptions.first,
            options = treatmentPeriodOptions.second,
            selectedOption = selectedTreatmentPeriod
        ) { selectedTreatmentPeriod = it }

        Spacer(modifier = Modifier.height(16.dp))

        // Button to trigger calculation
        Button(
            onClick = {
                // Use the selected intake time instead of LocalDateTime.now()
                val selectedHour = selectedTime?.second as? Int ?: LocalDateTime.now().hour
                val startTime = LocalDateTime.now().withHour(selectedHour).withMinute(0)  // Set start time to selected hour with 0 minutes

                val intervalInHours = selectedInterval?.second as? Int ?: 1

                // Define end time based on treatment period dropdown (can be plusDays(), plusWeeks(), etc.)
                val endTime = calculateEndTime(startTime, selectedTreatmentPeriod?.second as? Int)

                // Calculate intake times
                hoursList = calculateTimesWithLargerUnits(startTime, endTime, intervalInHours, determineTimeUnit(selectedInterval?.first))
            },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("Calculate Hours")
        }

        Spacer(modifier = Modifier.height(16.dp))

        // Display the calculated hours
        DisplayHoursList(hoursList)
    }
}

@RequiresApi(Build.VERSION_CODES.O)
fun calculateEndTime(startTime: LocalDateTime, treatmentPeriodInHours: Int?): LocalDateTime {
    return when {
        treatmentPeriodInHours == null -> startTime.plusDays(1)  // Default: plus 1 day
        treatmentPeriodInHours <= 24 -> startTime.plusHours(treatmentPeriodInHours.toLong())  // Add hours if <= 24 hours
        treatmentPeriodInHours <= 168 -> startTime.plusDays((treatmentPeriodInHours / 24).toLong())  // Add days if <= 1 week
        treatmentPeriodInHours <= 720 -> startTime.plusWeeks((treatmentPeriodInHours / 168).toLong())  // Add weeks if <= 1 month
        else -> startTime.plusMonths((treatmentPeriodInHours / 730).toLong())  // Add months for longer durations (730 hours ~ 30.42 days)
    }
}

@RequiresApi(Build.VERSION_CODES.O)
fun determineTimeUnit(intervalLabel: String?): ChronoUnit {
    return when (intervalLabel) {
        "Week", "Weeks" -> ChronoUnit.WEEKS
        "Month", "Months" -> ChronoUnit.MONTHS
        "Year", "Years" -> ChronoUnit.YEARS
        else -> ChronoUnit.HOURS
    }
}

@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun DisplayHoursList(hoursList: List<LocalDateTime>) {
    // Formatter for date and 12-hour time format with AM/PM
    val formatter = DateTimeFormatter.ofPattern("MMM d, yyyy h:mm a")

    LazyColumn {
        items(hoursList) { time ->
            // Format each time to show date and time in 12-hour format
            val formattedTime = time.format(formatter)
            Text(
                text = formattedTime,
                modifier = Modifier.padding(8.dp)
            )
        }
    }
}

@RequiresApi(Build.VERSION_CODES.O)
fun calculateTimesWithLargerUnits(
    startTime: LocalDateTime,
    endTime: LocalDateTime,
    intervalValue: Int,
    unit: ChronoUnit
): List<LocalDateTime> {
    val steps = mutableListOf<LocalDateTime>()

    // Always include the start time in the steps
    steps.add(startTime)

    // If unit is YEARS, calculate only the second time point
    if (unit == ChronoUnit.YEARS) {
        val secondTime = startTime.plusYears(intervalValue.toLong())
        if (secondTime.isAfter(endTime)) {
            steps.add(secondTime)
        }
    } else {
        // For other units, calculate time points as before
        var time = startTime
        while (true) {
            val nextTime = when (unit) {
                ChronoUnit.DAYS -> time.plusDays(intervalValue.toLong())
                ChronoUnit.WEEKS -> time.plusWeeks(intervalValue.toLong())
                ChronoUnit.MONTHS -> time.plusMonths(intervalValue.toLong())
                ChronoUnit.YEARS -> time.plusYears(intervalValue.toLong())
                else -> time.plusHours(intervalValue.toLong())
            }

            if (nextTime.isAfter(endTime) || nextTime.isEqual(endTime)) {
                break
            }

            steps.add(nextTime)
            time = nextTime
        }
    }

    return steps
}

测试与期望结果

实际异常情况

设置开始时间为12:00 AM、间隔6个月、疗程1年时,应用生成了3个服药时间:

Sep 22, 2024 12:00 am
Mar 21, 2025 12:00 am
Sep 17, 2025 12:00 am
但实际仅需要前2个时间。

正确示例用例

月份周期场景

  • 开始时间:Sep 24, 2024 12:00 AM
  • 间隔:1个月
  • 疗程:3个月
  • 期望输出:

    Sep 24, 2024 12:00 AM
    Oct 24, 2024 12:00 AM
    Nov 24, 2024 12:00 AM

年度周期场景

  • 开始时间:Sep 24, 2024 12:00 AM
  • 间隔:6个月
  • 疗程:1年
  • 期望输出:

    Sep 24, 2024 12:00 AM
    Mar 24, 2025 12:00 AM

内容的提问来源于stack exchange,提问作者Himel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 22:10:53