2026-09-09 17:53:08 +08:00
|
|
|
|
package com.gzzn.omms.msgexchange.domain
|
|
|
|
|
|
|
|
|
|
|
|
import java.time.LocalDate
|
|
|
|
|
|
import java.time.LocalDateTime
|
|
|
|
|
|
import java.time.ZoneId
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-09-09 22:17:04 +08:00
|
|
|
|
* 运营日计算(docs/flight-state.md §2.1):由计划运行时间字段 SODT(ddMMMyyHHmm)
|
|
|
|
|
|
* 与机场时区按切日边界推导;OPERATION_DAY 不是消息接收日或落库日,一经确定不可变。
|
|
|
|
|
|
* 切日边界由 msgx.operation-day.cutoff-hour 配置(默认 0 = 自然日零点,业务口径待确认)。
|
2026-09-09 17:53:08 +08:00
|
|
|
|
*/
|
|
|
|
|
|
class OperationDayCalculator(
|
|
|
|
|
|
zone: ZoneId,
|
|
|
|
|
|
cutoffHour: Int,
|
|
|
|
|
|
) {
|
|
|
|
|
|
private val zone: ZoneId = zone
|
2026-09-09 22:17:04 +08:00
|
|
|
|
/** 切日边界:SODT 本地时刻早于该小时的归属前一运营日(0–23,越界收敛到边界值)。 */
|
2026-09-09 17:53:08 +08:00
|
|
|
|
private val cutoffHour: Int = cutoffHour.coerceIn(0, 23)
|
|
|
|
|
|
|
|
|
|
|
|
/** SODT → 运营日;输入 null/空/非法返回 null(不抛异常,调用方按"运营日不可计算"处理)。 */
|
|
|
|
|
|
fun compute(sodt: String?): LocalDate? {
|
|
|
|
|
|
if (sodt.isNullOrBlank()) return null
|
|
|
|
|
|
val local = parseSodt(sodt.trim()) ?: return null
|
|
|
|
|
|
val day = local.atZone(zone).toLocalDate()
|
|
|
|
|
|
return if (local.hour < cutoffHour) day.minusDays(1) else day
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
|
/**
|
|
|
|
|
|
* SIS SODT 线格式:ddMMMyyHHmm(如 15DEC031723),月份英文三字母、大小写不敏感。
|
|
|
|
|
|
* 两位年显式按 2000 基准展开(java.time 的 yy reduced-value 解析跨实现不一致,
|
2026-09-09 22:17:04 +08:00
|
|
|
|
* 显式展开保证 AODB 侧年份窗口唯一口径;基准年待真实报文验收确认)。
|
2026-09-09 17:53:08 +08:00
|
|
|
|
*/
|
|
|
|
|
|
private val SODT_REGEX = Regex("(\\d{1,2})([A-Za-z]{3})(\\d{2})(\\d{2})(\\d{2})")
|
|
|
|
|
|
|
|
|
|
|
|
private val MONTHS = mapOf(
|
|
|
|
|
|
"JAN" to 1, "FEB" to 2, "MAR" to 3, "APR" to 4, "MAY" to 5, "JUN" to 6,
|
|
|
|
|
|
"JUL" to 7, "AUG" to 8, "SEP" to 9, "OCT" to 10, "NOV" to 11, "DEC" to 12,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
internal fun parseSodt(raw: String): LocalDateTime? {
|
|
|
|
|
|
val m = SODT_REGEX.matchEntire(raw.uppercase()) ?: return null
|
|
|
|
|
|
val (d, mon, yy, hh, mm) = m.destructured
|
|
|
|
|
|
val month = MONTHS[mon] ?: return null
|
|
|
|
|
|
return runCatching {
|
|
|
|
|
|
LocalDateTime.of(2000 + yy.toInt(), month, d.toInt(), hh.toInt(), mm.toInt())
|
|
|
|
|
|
}.getOrNull()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|