按现行 docs/flight-state.md(111 行版,§1-§7)全面对齐 domain 包及其消费方, 删除整套指向已退役长版文档(§5.x-§10)的引用与死代码: - 语义:FlightStateEngine.snapshotState 由"完整快照整体替换"改为 §3.1 合并语义 ——出现 Set/Replace、缺失保留、标量空串显式清空;DELETED 不被日计划恢复(§3.3)。 - 校验:validateMessage 移除从未接线的报文覆盖范围(scope)参数,只保留 §4 步骤 2 的声明数量/航班标识/运营日推导校验;ScheduleBody 删除 scopeStart/scopeEnd。 - 删除死代码:domain/Decision.kt、FlightModel 的 SnapshotPatch/UpsertOutcome、 ProcState.isTerminal/archivable、SnapshotFlag.SEQN_REGRESSION、 ScheduleRecord.seqn(及 wire FlightRecordXml.SEQN);codec 移除未消费 FFID。 - 删除未接线且引用已退役列(fday/last_message_id)的 SqlDialect 方言脚手架, oracle11g README 改为按 V1 现列重建的口径。 - 注释/测试:domain、processing、infra 仓储与 jobs/delivery、配置类及对应测试的 KDoc 章节引用全部对齐现行 flight-state.md/design.md;FlightStateEngineTest 重写为合并语义(62/62 通过)。 V1__flight_state_baseline.sql 保留原样(内容注释仍带旧章节号,改动会破坏 已应用迁移的 Flyway checksum,待重建基线或 V2 净迁移时收敛)。
51 lines
2.2 KiB
Kotlin
51 lines
2.2 KiB
Kotlin
package com.gzzn.omms.msgexchange.domain
|
||
|
||
import java.time.LocalDate
|
||
import java.time.LocalDateTime
|
||
import java.time.ZoneId
|
||
|
||
/**
|
||
* 运营日计算(docs/flight-state.md §2.1):由计划运行时间字段 SODT(ddMMMyyHHmm)
|
||
* 与机场时区按切日边界推导;OPERATION_DAY 不是消息接收日或落库日,一经确定不可变。
|
||
* 切日边界由 msgx.operation-day.cutoff-hour 配置(默认 0 = 自然日零点,业务口径待确认)。
|
||
*/
|
||
class OperationDayCalculator(
|
||
zone: ZoneId,
|
||
cutoffHour: Int,
|
||
) {
|
||
private val zone: ZoneId = zone
|
||
/** 切日边界:SODT 本地时刻早于该小时的归属前一运营日(0–23,越界收敛到边界值)。 */
|
||
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 解析跨实现不一致,
|
||
* 显式展开保证 AODB 侧年份窗口唯一口径;基准年待真实报文验收确认)。
|
||
*/
|
||
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()
|
||
}
|
||
}
|
||
}
|