使用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
相关产品推荐
相关产品推荐

