pcp: время — сквозной naive LocalDateTime как локальное настенное время (без UTC)
Отказ от остаточной UTC-семантики: naive LocalDateTime трактуется как локальное время единой зоны всех сервисов. Backend (границы внешних систем): - VisibilityPayloadParser, SyncSpacecraftFromNsiUseCase: ZoneOffset.UTC → ZoneId.systemDefault() для проекции внешних Instant/ISO-с-Z в naive. - тесты границ перевёрнуты на systemDefault (под Europe/Moscow ждут +3). - чистка UTC-формулировок в DTO-комментариях, мёртвых ссылок на docs/standards/DATETIME_UTC.md. Frontend (pcp-tgu-ops-ui): - utils/utcTime.ts → utils/localTime.ts: parseLocal/formatLocal*/toLocalIso/ toLocalInputValue с локальной семантикой (без дописывания Z и timeZone:UTC). - замена getUTC*/Date.UTC/toISOString-сериализации на локальные аналоги. - убраны пользовательские подписи «UTC» в редакторе/таймлайне. - удалён utcTime.guard.test.ts (форсил обратный UTC-инвариант); тест-фикстуры сделаны зоно-независимыми. tsc + vitest зелёные под TZ=UTC и TZ=Europe/Moscow. Включает также прочие незакоммиченные правки рабочего дерева.
This commit is contained in:
+2
-2
@@ -12,9 +12,9 @@ object DraftMode {
|
||||
data class CreateDraftRequest(
|
||||
/** [DraftMode.REPLACE] — черновик в том же слоте; [DraftMode.FORK_FORWARD] — черновик на следующей ЗРВ. */
|
||||
val mode: String,
|
||||
/** Начало окна целевой ЗРВ (UTC naive). Обязательно для fork-forward. */
|
||||
/** Начало окна целевой ЗРВ (naive, локальное время). Обязательно для fork-forward. */
|
||||
val windowStart: LocalDateTime? = null,
|
||||
/** Конец окна целевой ЗРВ (UTC naive). Обязательно для fork-forward. */
|
||||
/** Конец окна целевой ЗРВ (naive, локальное время). Обязательно для fork-forward. */
|
||||
val windowStop: LocalDateTime? = null,
|
||||
/** КПП целевой ЗРВ. Обязательно для fork-forward. */
|
||||
val windowKppId: String? = null
|
||||
|
||||
+2
-2
@@ -33,9 +33,9 @@ data class CreateChainPlanRequest(
|
||||
val spacecraftId: String,
|
||||
/** Прошлый план-предыстория или null для цепочки без истории. */
|
||||
val basePlanId: UUID? = null,
|
||||
/** Начало окна (UTC naive) — старт выбранной стартовой ЗРВ. */
|
||||
/** Начало окна (naive, локальное время) — старт выбранной стартовой ЗРВ. */
|
||||
val windowStart: LocalDateTime,
|
||||
/** Конец окна (UTC naive) — конец выбранной конечной ЗРВ. */
|
||||
/** Конец окна (naive, локальное время) — конец выбранной конечной ЗРВ. */
|
||||
val windowStop: LocalDateTime,
|
||||
/** КПП стартовой ЗРВ. */
|
||||
val windowKppId: String
|
||||
|
||||
+2
-3
@@ -7,7 +7,6 @@ import jakarta.persistence.Enumerated
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.Table
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
@@ -40,10 +39,10 @@ class AcceptedPlanSlotsUpdateJobEntity(
|
||||
var lastError: String? = null,
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
var createdAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
|
||||
var createdAt: LocalDateTime = LocalDateTime.now(),
|
||||
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
var updatedAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
var updatedAt: LocalDateTime = LocalDateTime.now()
|
||||
) {
|
||||
/** Создаёт пустой экземпляр сущности, необходимый для JPA. */
|
||||
constructor() : this(planId = UUID.randomUUID())
|
||||
|
||||
+2
-3
@@ -7,7 +7,6 @@ import jakarta.persistence.Enumerated
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.Table
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
@@ -46,10 +45,10 @@ class PlanIssueAttemptEntity(
|
||||
var finishedAt: LocalDateTime? = null,
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
var createdAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
|
||||
var createdAt: LocalDateTime = LocalDateTime.now(),
|
||||
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
var updatedAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
var updatedAt: LocalDateTime = LocalDateTime.now()
|
||||
) {
|
||||
/** Создаёт пустой экземпляр сущности, необходимый для JPA. */
|
||||
constructor() : this(attemptId = UUID.randomUUID(), planId = UUID.randomUUID(), attemptNo = 0)
|
||||
|
||||
+2
-3
@@ -7,7 +7,6 @@ import jakarta.persistence.Enumerated
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.Table
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
@@ -49,10 +48,10 @@ class PlannedPlanEntity(
|
||||
var acceptedAt: LocalDateTime? = null,
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
var createdAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
|
||||
var createdAt: LocalDateTime = LocalDateTime.now(),
|
||||
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
var updatedAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
var updatedAt: LocalDateTime = LocalDateTime.now()
|
||||
) {
|
||||
/** Создаёт пустой экземпляр сущности, необходимый для JPA. */
|
||||
constructor() : this(
|
||||
|
||||
+1
-2
@@ -5,7 +5,6 @@ import jakarta.persistence.Entity
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.Table
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
@@ -19,7 +18,7 @@ class ProcessedPlanDecisionEventEntity(
|
||||
var eventId: UUID,
|
||||
|
||||
@Column(name = "received_at", nullable = false)
|
||||
var receivedAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
var receivedAt: LocalDateTime = LocalDateTime.now()
|
||||
) {
|
||||
/** Создаёт пустой экземпляр сущности, необходимый для JPA. */
|
||||
constructor() : this(eventId = UUID.randomUUID())
|
||||
|
||||
+1
-2
@@ -6,7 +6,6 @@ import jakarta.persistence.Entity
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.Table
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.UUID
|
||||
|
||||
@Entity
|
||||
@@ -43,7 +42,7 @@ class SpacecraftPlanningStateEntity(
|
||||
var pausedAfterReject: Boolean = false,
|
||||
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
var updatedAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
var updatedAt: LocalDateTime = LocalDateTime.now()
|
||||
) {
|
||||
/** Создаёт пустой экземпляр сущности, необходимый для JPA. */
|
||||
constructor() : this(spacecraftId = "")
|
||||
|
||||
+2
-3
@@ -5,7 +5,6 @@ import jakarta.persistence.Entity
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.Table
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
/**
|
||||
* Persisted snapshot КА из НСИ, используемый как вход для refresh jobs.
|
||||
@@ -45,10 +44,10 @@ class SpacecraftSnapshotEntity(
|
||||
var active: Boolean = true,
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
var createdAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
|
||||
var createdAt: LocalDateTime = LocalDateTime.now(),
|
||||
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
var updatedAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
var updatedAt: LocalDateTime = LocalDateTime.now()
|
||||
) {
|
||||
/** Создаёт пустой экземпляр сущности, необходимый для JPA. */
|
||||
constructor() : this(spacecraftId = "", noradId = 0)
|
||||
|
||||
+2
-3
@@ -5,7 +5,6 @@ import jakarta.persistence.Entity
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.Table
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
/**
|
||||
* Полный raw snapshot окон видимости КА из баллистики.
|
||||
@@ -41,10 +40,10 @@ class SpacecraftVisibilitySnapshotEntity(
|
||||
var windowsCount: Int? = null,
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
var createdAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
|
||||
var createdAt: LocalDateTime = LocalDateTime.now(),
|
||||
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
var updatedAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
var updatedAt: LocalDateTime = LocalDateTime.now()
|
||||
) {
|
||||
/** Создаёт пустой экземпляр сущности, необходимый для JPA. */
|
||||
constructor() : this(spacecraftId = "", noradId = 0, payloadJson = "", payloadHash = "", fetchedAt = LocalDateTime.MIN)
|
||||
|
||||
+2
-3
@@ -7,7 +7,6 @@ import jakarta.persistence.Enumerated
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.Table
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
/**
|
||||
* Job на перечитывание окон видимости одного КА у баллистики.
|
||||
@@ -42,10 +41,10 @@ class VisibilityRefreshJobEntity(
|
||||
var rerunRequested: Boolean = false,
|
||||
|
||||
@Column(name = "requested_at", nullable = false)
|
||||
var requestedAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
|
||||
var requestedAt: LocalDateTime = LocalDateTime.now(),
|
||||
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
var updatedAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
var updatedAt: LocalDateTime = LocalDateTime.now()
|
||||
) {
|
||||
/** Создаёт пустой экземпляр сущности, необходимый для JPA. */
|
||||
constructor() : this(spacecraftId = "")
|
||||
|
||||
+1
-2
@@ -12,7 +12,6 @@ import org.springframework.web.reactive.function.client.WebClient
|
||||
import org.springframework.web.reactive.function.client.WebClientException
|
||||
import org.springframework.web.reactive.function.client.WebClientResponseException
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
/**
|
||||
@@ -37,7 +36,7 @@ class ExternalPointsClient(
|
||||
* которые разрешены для работы TGU.
|
||||
*/
|
||||
fun fetchAllSpacecraftPoints(): List<SpacecraftPoints> {
|
||||
val timeStart = LocalDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.MINUTES)
|
||||
val timeStart = LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES)
|
||||
val timeStop = timeStart.plusDays(planningProperties.externalPointsLookaheadDays)
|
||||
|
||||
return platformService.loadAllowedPlatforms()
|
||||
|
||||
+6
-5
@@ -8,7 +8,7 @@ import space.nstart.pcp_tgu_service.domain.SpacecraftPoints
|
||||
import space.nstart.pcp_tgu_service.dto.ExternalRvaItemDto
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeParseException
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
@@ -43,10 +43,11 @@ class VisibilityPayloadParser(
|
||||
}
|
||||
|
||||
/**
|
||||
* Разбирает UTC/ISO строку времени баллистики в naive `LocalDateTime` (по соглашению системы — UTC).
|
||||
* Разбирает ISO-строку времени баллистики в naive `LocalDateTime`.
|
||||
*
|
||||
* Момент из `Instant.parse` берём в UTC (а не в поясе JVM): naive-поля `planned_plan.start_time/end_time`
|
||||
* по контракту хранят UTC, поэтому `atZone(systemDefault)` на не-UTC машине сдвигал бы время на офсет.
|
||||
* Если время приходит с зоной (`...Z`/офсет), момент проецируем в локальный пояс JVM
|
||||
* (`ZoneId.systemDefault()`): naive-время в системе — настенные часы локальной зоны, в которой
|
||||
* работают все сервисы, без UTC-семантики. Naive-строку без зоны берём как есть.
|
||||
*
|
||||
* Обрезаем до микросекунд — это эффективная точность системы: `planned_plan.start_time/end_time`
|
||||
* хранятся как Postgres `timestamp(6)`, при записи наносекундный хвост баллистики всё равно теряется.
|
||||
@@ -55,7 +56,7 @@ class VisibilityPayloadParser(
|
||||
*/
|
||||
private fun toLocalDateTime(value: String): LocalDateTime =
|
||||
try {
|
||||
LocalDateTime.ofInstant(Instant.parse(value), ZoneOffset.UTC)
|
||||
LocalDateTime.ofInstant(Instant.parse(value), ZoneId.systemDefault())
|
||||
} catch (_: DateTimeParseException) {
|
||||
LocalDateTime.parse(value)
|
||||
}.truncatedTo(ChronoUnit.MICROS)
|
||||
|
||||
+1
-2
@@ -4,7 +4,6 @@ import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import space.nstart.pcp_tgu_service.repository.AcceptedPlanSlotsUpdateJobRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
@@ -17,6 +16,6 @@ class AcceptedPlanSlotsUpdateJobService(
|
||||
/** Idempotent enqueue: один planId получает максимум одну job. */
|
||||
@Transactional
|
||||
fun enqueue(planId: UUID) {
|
||||
acceptedPlanSlotsUpdateJobRepository.insertPendingIfAbsent(planId, LocalDateTime.now(ZoneOffset.UTC))
|
||||
acceptedPlanSlotsUpdateJobRepository.insertPendingIfAbsent(planId, LocalDateTime.now())
|
||||
}
|
||||
}
|
||||
|
||||
+3
-4
@@ -10,7 +10,6 @@ import space.nstart.pcp_tgu_service.entity.AcceptedPlanSlotsUpdateJobStatus
|
||||
import space.nstart.pcp_tgu_service.repository.AcceptedPlanSlotsUpdateJobRepository
|
||||
import java.net.InetAddress
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
@@ -36,7 +35,7 @@ class AcceptedPlanSlotsUpdateWorker(
|
||||
/** Claim видим для тестов; в production защищён FOR UPDATE SKIP LOCKED. */
|
||||
fun claimJobs(limit: Int): List<AcceptedPlanSlotsUpdateJobEntity> =
|
||||
transactionTemplate.execute {
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val now = LocalDateTime.now()
|
||||
val jobs = acceptedPlanSlotsUpdateJobRepository.findClaimableForUpdate(now, limit)
|
||||
jobs.forEach { job ->
|
||||
job.status = AcceptedPlanSlotsUpdateJobStatus.RUNNING
|
||||
@@ -70,7 +69,7 @@ class AcceptedPlanSlotsUpdateWorker(
|
||||
|
||||
private fun markDone(planId: UUID, processInstanceKey: Long) {
|
||||
transactionTemplate.executeWithoutResult {
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val now = LocalDateTime.now()
|
||||
val job = acceptedPlanSlotsUpdateJobRepository.findById(planId).orElse(null) ?: return@executeWithoutResult
|
||||
job.status = AcceptedPlanSlotsUpdateJobStatus.DONE
|
||||
job.processInstanceKey = processInstanceKey
|
||||
@@ -84,7 +83,7 @@ class AcceptedPlanSlotsUpdateWorker(
|
||||
|
||||
private fun markAmbiguous(planId: UUID, processInstanceKey: Long?, ex: Exception) {
|
||||
transactionTemplate.executeWithoutResult {
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val now = LocalDateTime.now()
|
||||
val job = acceptedPlanSlotsUpdateJobRepository.findById(planId).orElse(null) ?: return@executeWithoutResult
|
||||
job.status = AcceptedPlanSlotsUpdateJobStatus.AMBIGUOUS
|
||||
job.processInstanceKey = processInstanceKey ?: job.processInstanceKey
|
||||
|
||||
+1
-2
@@ -17,7 +17,6 @@ import space.nstart.pcp_tgu_service.integration.api.MissionPlaningClient
|
||||
import space.nstart.pcp_tgu_service.repository.PlannedPlanRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftPlanningStateJpaRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
@@ -47,7 +46,7 @@ class CreateChainPlanUseCase(
|
||||
private val log = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
fun createChainPlan(req: CreateChainPlanRequest): CreateChainPlanResponse {
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val now = LocalDateTime.now()
|
||||
|
||||
if (!req.windowStop.isAfter(req.windowStart)) {
|
||||
throw ResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY, "windowStop должен быть позже windowStart")
|
||||
|
||||
+1
-2
@@ -15,7 +15,6 @@ import space.nstart.pcp_tgu_service.integration.api.MissionPlaningClient
|
||||
import space.nstart.pcp_tgu_service.repository.PlannedPlanRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftPlanningStateJpaRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
@@ -46,7 +45,7 @@ class CreateDraftUseCase(
|
||||
val base = plannedPlanRepository.findById(basePlanId)
|
||||
.orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Plan $basePlanId not found") }
|
||||
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val now = LocalDateTime.now()
|
||||
|
||||
// Идемпотентность: один открытый черновик на базовый план. Повторный клик «создать черновик»
|
||||
// не плодит вторую миссию-клон и вторую DRAFT-строку.
|
||||
|
||||
+5
-6
@@ -19,7 +19,6 @@ import space.nstart.pcp_tgu_service.repository.PlannedPlanRepository
|
||||
import space.nstart.pcp_tgu_service.repository.ProcessedPlanDecisionEventRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftPlanningStateJpaRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
/**
|
||||
* Обрабатывает decision и закладочные feedback-события идемпотентно по eventId.
|
||||
@@ -119,7 +118,7 @@ class HandlePlanDecisionUseCase(
|
||||
return
|
||||
}
|
||||
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val now = LocalDateTime.now()
|
||||
plan.status = terminalStatus
|
||||
plan.updatedAt = now
|
||||
plannedPlanRepository.save(plan)
|
||||
@@ -131,7 +130,7 @@ class HandlePlanDecisionUseCase(
|
||||
message: PlanDecisionMessage,
|
||||
attempt: PlanIssueAttemptEntity
|
||||
) {
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val now = LocalDateTime.now()
|
||||
attempt.status = when (message.decision) {
|
||||
PlanDecision.ACCEPTED -> PlanIssueAttemptStatus.ACCEPTED
|
||||
PlanDecision.REJECTED -> PlanIssueAttemptStatus.REJECTED
|
||||
@@ -148,7 +147,7 @@ class HandlePlanDecisionUseCase(
|
||||
plan: PlannedPlanEntity,
|
||||
state: SpacecraftPlanningStateEntity
|
||||
) {
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val now = LocalDateTime.now()
|
||||
recordDecisionOnAttempt(message, attempt)
|
||||
|
||||
plan.status = PlannedPlanStatus.AWAITING_LAYIN
|
||||
@@ -175,7 +174,7 @@ class HandlePlanDecisionUseCase(
|
||||
plan: PlannedPlanEntity,
|
||||
state: SpacecraftPlanningStateEntity
|
||||
) {
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val now = LocalDateTime.now()
|
||||
recordDecisionOnAttempt(message, attempt)
|
||||
|
||||
plan.status = PlannedPlanStatus.REJECTED
|
||||
@@ -217,7 +216,7 @@ class HandlePlanDecisionUseCase(
|
||||
}
|
||||
|
||||
private fun claimEvent(eventId: java.util.UUID): Boolean =
|
||||
processedPlanDecisionEventRepository.insertIfAbsent(eventId, LocalDateTime.now(ZoneOffset.UTC)) != 0
|
||||
processedPlanDecisionEventRepository.insertIfAbsent(eventId, LocalDateTime.now()) != 0
|
||||
|
||||
private fun getOrCreateLockedState(spacecraftId: String): SpacecraftPlanningStateEntity {
|
||||
spacecraftPlanningStateJpaRepository.findById(spacecraftId).orElse(null)
|
||||
|
||||
+1
-2
@@ -7,7 +7,6 @@ import space.nstart.pcp_tgu_service.dto.StationParamsChangedMessage
|
||||
import space.nstart.pcp_tgu_service.repository.ProcessedPlanDecisionEventRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftSnapshotRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
/**
|
||||
* На изменение параметров станций перезапрашивает ЗРВ у баллистики по всем активным КА.
|
||||
@@ -26,7 +25,7 @@ class HandleStationParamsChangedUseCase(
|
||||
/** Идемпотентно по eventId; ставит refresh job по всем активным КА. */
|
||||
@Transactional
|
||||
fun handle(message: StationParamsChangedMessage) {
|
||||
val claimed = processedPlanDecisionEventRepository.insertIfAbsent(message.eventId, LocalDateTime.now(ZoneOffset.UTC)) != 0
|
||||
val claimed = processedPlanDecisionEventRepository.insertIfAbsent(message.eventId, LocalDateTime.now()) != 0
|
||||
if (!claimed) {
|
||||
return
|
||||
}
|
||||
|
||||
+5
-6
@@ -19,7 +19,6 @@ import space.nstart.pcp_tgu_service.repository.PlannedPlanRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftPlanningStateJpaRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftSnapshotRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
@@ -84,7 +83,7 @@ class IssueDuePlanWorker(
|
||||
|
||||
/** Готовит выдачу головы проекции для КА под lock'ом состояния (scale-out safe). */
|
||||
private fun prepareDueForSpacecraft(spacecraftId: String): PreparedAttempt? {
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val now = LocalDateTime.now()
|
||||
val state = getOrCreateLockedState(spacecraftId)
|
||||
if (state.activeAttemptId != null && !tryAdvancePastUnconfirmedPlan(state, now)) {
|
||||
return null
|
||||
@@ -163,7 +162,7 @@ class IssueDuePlanWorker(
|
||||
}
|
||||
|
||||
private fun prepareSpecificPlan(planId: UUID): PreparedAttempt? {
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val now = LocalDateTime.now()
|
||||
val plan = plannedPlanRepository.findById(planId).orElse(null) ?: return null
|
||||
val state = getOrCreateLockedState(plan.spacecraftId)
|
||||
assertReissuableInSlot(plan, state, now)
|
||||
@@ -185,7 +184,7 @@ class IssueDuePlanWorker(
|
||||
}
|
||||
|
||||
private fun prepareDraftPromotion(draftPlanId: UUID): PreparedAttempt? {
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val now = LocalDateTime.now()
|
||||
val plan = plannedPlanRepository.findById(draftPlanId).orElse(null) ?: return null
|
||||
if (plan.status != PlannedPlanStatus.DRAFT) {
|
||||
throw ResponseStatusException(
|
||||
@@ -342,7 +341,7 @@ class IssueDuePlanWorker(
|
||||
|
||||
private fun markStarted(response: ProcessStartResponse) {
|
||||
transactionTemplate.executeWithoutResult {
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val now = LocalDateTime.now()
|
||||
val attempt = planIssueAttemptRepository.findById(response.attemptId).orElse(null) ?: return@executeWithoutResult
|
||||
val plan = plannedPlanRepository.findById(response.planId).orElse(null) ?: return@executeWithoutResult
|
||||
|
||||
@@ -366,7 +365,7 @@ class IssueDuePlanWorker(
|
||||
|
||||
private fun markAmbiguous(preparedAttempt: PreparedAttempt) {
|
||||
transactionTemplate.executeWithoutResult {
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val now = LocalDateTime.now()
|
||||
val attempt = planIssueAttemptRepository.findById(preparedAttempt.attemptId).orElse(null) ?: return@executeWithoutResult
|
||||
val plan = plannedPlanRepository.findById(preparedAttempt.planId).orElse(null) ?: return@executeWithoutResult
|
||||
attempt.status = PlanIssueAttemptStatus.AMBIGUOUS
|
||||
|
||||
+1
-2
@@ -11,7 +11,6 @@ import space.nstart.pcp_tgu_service.entity.PlannedPlanStatus
|
||||
import space.nstart.pcp_tgu_service.repository.PlannedPlanRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftSnapshotRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
@@ -81,7 +80,7 @@ class PlanQueryService(
|
||||
.sortedBy { response -> response.startTime }
|
||||
|
||||
private fun historySince(historyDays: Long): LocalDateTime =
|
||||
LocalDateTime.now(ZoneOffset.UTC).minusDays(historyDays.coerceAtLeast(0))
|
||||
LocalDateTime.now().minusDays(historyDays.coerceAtLeast(0))
|
||||
|
||||
private fun PlannedPlanEntity.businessKey(): String =
|
||||
"$spacecraftId|$startTime|$endTime|$kppId"
|
||||
|
||||
+1
-2
@@ -7,7 +7,6 @@ import space.nstart.pcp_tgu_service.integration.api.VisibilityPayloadParser
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftVisibilitySnapshotRepository
|
||||
import java.security.MessageDigest
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
/**
|
||||
* Сохраняет raw visibility payload. Расписание считается on-demand из снапшота
|
||||
@@ -26,7 +25,7 @@ class SpacecraftVisibilitySnapshotService(
|
||||
val payloadHash = sha256(payloadJson)
|
||||
val existing = spacecraftVisibilitySnapshotRepository.findById(spacecraftId).orElse(null)
|
||||
val metadata = runCatching { extractMetadata(spacecraftId, payloadJson) }.getOrNull()
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val now = LocalDateTime.now()
|
||||
|
||||
if (existing == null) {
|
||||
spacecraftVisibilitySnapshotRepository.save(
|
||||
|
||||
+6
-6
@@ -5,7 +5,7 @@ import org.springframework.transaction.support.TransactionTemplate
|
||||
import space.nstart.pcp_tgu_service.domain.PlatformCatalogItem
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftSnapshotRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.ZoneId
|
||||
|
||||
/**
|
||||
* Синхронизирует разрешённые КА из НСИ в БД и ставит refresh jobs.
|
||||
@@ -26,7 +26,7 @@ class SyncSpacecraftFromNsiUseCase(
|
||||
}
|
||||
|
||||
private fun persistAllowedSpacecraft(allowedPlatforms: List<PlatformCatalogItem>) {
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val now = LocalDateTime.now()
|
||||
val allowedSpacecraftIds = allowedPlatforms.mapNotNull { platform -> platform.noradId?.toString() }
|
||||
|
||||
allowedPlatforms.forEach { platform ->
|
||||
@@ -40,10 +40,10 @@ class SyncSpacecraftFromNsiUseCase(
|
||||
name = platform.name,
|
||||
status = platform.status,
|
||||
mission = platform.mission,
|
||||
// validFrom/validTo приходят как Instant; в naive-поля БД пишем UTC-стенку, а не пояс JVM
|
||||
// (по соглашению системы naive LocalDateTime = UTC) — иначе на не-UTC машине время уезжало бы.
|
||||
validFrom = platform.validFrom?.let { LocalDateTime.ofInstant(it, ZoneOffset.UTC) },
|
||||
validTo = platform.validTo?.let { LocalDateTime.ofInstant(it, ZoneOffset.UTC) },
|
||||
// validFrom/validTo приходят как Instant; в naive-поля БД пишем настенные часы локальной
|
||||
// зоны JVM (ZoneId.systemDefault()) — naive-время в системе локальное, без UTC-семантики.
|
||||
validFrom = platform.validFrom?.let { LocalDateTime.ofInstant(it, ZoneId.systemDefault()) },
|
||||
validTo = platform.validTo?.let { LocalDateTime.ofInstant(it, ZoneId.systemDefault()) },
|
||||
now = now
|
||||
)
|
||||
visibilityRefreshJobService.enqueue(spacecraftId, noradId, "NSI_SYNC")
|
||||
|
||||
+1
-2
@@ -11,7 +11,6 @@ import space.nstart.pcp_tgu_service.repository.PlanIssueAttemptRepository
|
||||
import space.nstart.pcp_tgu_service.repository.PlannedPlanRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftPlanningStateJpaRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
@@ -67,7 +66,7 @@ class TestPlanDecisionService(
|
||||
planId = plan.planId,
|
||||
attemptId = attempt.attemptId,
|
||||
decision = decision,
|
||||
decisionTime = LocalDateTime.now(ZoneOffset.UTC),
|
||||
decisionTime = LocalDateTime.now(),
|
||||
reason = reason?.takeIf { it.isNotBlank() } ?: DEFAULT_REASON
|
||||
)
|
||||
handlePlanDecisionUseCase.handle(message)
|
||||
|
||||
+1
-2
@@ -4,7 +4,6 @@ import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import space.nstart.pcp_tgu_service.repository.VisibilityRefreshJobRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
/**
|
||||
* Coalesce-сервис для visibility_refresh_job по spacecraftId.
|
||||
@@ -16,6 +15,6 @@ class VisibilityRefreshJobService(
|
||||
/** Ставит или переоткрывает refresh job без дублей. */
|
||||
@Transactional
|
||||
fun enqueue(spacecraftId: String, noradId: Long?, reason: String) {
|
||||
visibilityRefreshJobRepository.upsertPending(spacecraftId, noradId, reason, LocalDateTime.now(ZoneOffset.UTC))
|
||||
visibilityRefreshJobRepository.upsertPending(spacecraftId, noradId, reason, LocalDateTime.now())
|
||||
}
|
||||
}
|
||||
|
||||
+6
-7
@@ -12,7 +12,6 @@ import space.nstart.pcp_tgu_service.repository.SpacecraftSnapshotRepository
|
||||
import space.nstart.pcp_tgu_service.repository.VisibilityRefreshJobRepository
|
||||
import java.net.InetAddress
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
/**
|
||||
@@ -39,7 +38,7 @@ class VisibilityRefreshWorker(
|
||||
|
||||
private fun claimJobs(limit: Int): List<VisibilityRefreshJobEntity> =
|
||||
transactionTemplate.execute {
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val now = LocalDateTime.now()
|
||||
val jobs = visibilityRefreshJobRepository.findPendingForUpdate(now, limit)
|
||||
jobs.forEach { job ->
|
||||
job.status = JobStatus.RUNNING
|
||||
@@ -62,10 +61,10 @@ class VisibilityRefreshWorker(
|
||||
}
|
||||
|
||||
try {
|
||||
val timeStart = LocalDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.MINUTES)
|
||||
val timeStart = LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES)
|
||||
val timeStop = timeStart.plusDays(planningProperties.externalPointsLookaheadDays)
|
||||
val payload = externalPointsClient.fetchRawVisibilityPayload(noradId, timeStart, timeStop)
|
||||
spacecraftVisibilitySnapshotService.saveSnapshot(spacecraftId, noradId, payload, LocalDateTime.now(ZoneOffset.UTC))
|
||||
spacecraftVisibilitySnapshotService.saveSnapshot(spacecraftId, noradId, payload, LocalDateTime.now())
|
||||
markDone(spacecraftId)
|
||||
} catch (ex: Exception) {
|
||||
log.warn("Visibility refresh job failed. spacecraftId={}, noradId={}", spacecraftId, noradId, ex)
|
||||
@@ -80,7 +79,7 @@ class VisibilityRefreshWorker(
|
||||
job.rerunRequested = false
|
||||
job.lockedBy = null
|
||||
job.lockedUntil = null
|
||||
job.updatedAt = LocalDateTime.now(ZoneOffset.UTC)
|
||||
job.updatedAt = LocalDateTime.now()
|
||||
visibilityRefreshJobRepository.save(job)
|
||||
}
|
||||
}
|
||||
@@ -91,7 +90,7 @@ class VisibilityRefreshWorker(
|
||||
job.status = if (job.attempts >= MAX_ATTEMPTS) JobStatus.FAILED else JobStatus.PENDING
|
||||
job.lockedBy = null
|
||||
job.lockedUntil = null
|
||||
job.updatedAt = LocalDateTime.now(ZoneOffset.UTC)
|
||||
job.updatedAt = LocalDateTime.now()
|
||||
visibilityRefreshJobRepository.save(job)
|
||||
}
|
||||
}
|
||||
@@ -103,7 +102,7 @@ class VisibilityRefreshWorker(
|
||||
job.reason = reason
|
||||
job.lockedBy = null
|
||||
job.lockedUntil = null
|
||||
job.updatedAt = LocalDateTime.now(ZoneOffset.UTC)
|
||||
job.updatedAt = LocalDateTime.now()
|
||||
visibilityRefreshJobRepository.save(job)
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -19,9 +19,9 @@ class VisibilityPayloadParserTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parses UTC instant as UTC LocalDateTime regardless of JVM default zone`() {
|
||||
// planned_plan.start_time/end_time по контракту хранят UTC. Раньше парсер конвертировал момент
|
||||
// через ZoneId.systemDefault(), и на не-UTC JVM (напр. МСК, +3) время уезжало на офсет.
|
||||
fun `projects zoned instant into JVM default zone as naive local LocalDateTime`() {
|
||||
// naive-время в системе — настенные часы локальной зоны JVM (без UTC-семантики). Парсер проецирует
|
||||
// зонированный инстант через ZoneId.systemDefault(), поэтому на МСК (+3) 14:14Z даёт локальные 17:14.
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Moscow"))
|
||||
val payload = """
|
||||
[{
|
||||
@@ -35,9 +35,9 @@ class VisibilityPayloadParserTest {
|
||||
|
||||
val window = parser.parse("20", payload).points.single().observationWindows.single()
|
||||
|
||||
// Значение совпадает с UTC-стенкой инстанта (14:14), а не со сдвигом на пояс JVM (17:14).
|
||||
assertEquals(LocalDateTime.parse("2026-06-02T14:14:08"), window.startTime)
|
||||
assertEquals(LocalDateTime.parse("2026-06-02T14:20:00"), window.endTime)
|
||||
// Инстант 14:14Z спроецирован в локальную зону JVM (МСК, +3) — настенные 17:14.
|
||||
assertEquals(LocalDateTime.parse("2026-06-02T17:14:08"), window.startTime)
|
||||
assertEquals(LocalDateTime.parse("2026-06-02T17:20:00"), window.endTime)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
-2
@@ -24,7 +24,6 @@ import space.nstart.pcp_tgu_service.integration.api.MissionPlaningClient
|
||||
import space.nstart.pcp_tgu_service.repository.PlannedPlanRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftPlanningStateJpaRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
||||
@@ -227,7 +226,7 @@ class CreateChainPlanUseCaseTest {
|
||||
|
||||
// Фиксированная база времени на инстанс теста: time() детерминирован, окно в запросе и в
|
||||
// моке сырых ЗРВ совпадает байт-в-байт (без дрейфа now() между вызовами).
|
||||
private val t0: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
private val t0: LocalDateTime = LocalDateTime.now()
|
||||
private fun time(hour: Int): LocalDateTime = t0.plusHours(hour.toLong())
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
||||
+1
-2
@@ -23,7 +23,6 @@ import space.nstart.pcp_tgu_service.integration.api.MissionPlaningClient
|
||||
import space.nstart.pcp_tgu_service.repository.PlannedPlanRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftPlanningStateJpaRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
||||
@@ -267,7 +266,7 @@ class CreateDraftUseCaseTest {
|
||||
private fun props(freeze: Long): PlanningProperties =
|
||||
PlanningProperties(30, 180, 60, false, 10, 14, 0, 30_000, 5, 30, 300, 30, freeze)
|
||||
|
||||
private fun time(hour: Int): LocalDateTime = LocalDateTime.now(ZoneOffset.UTC).plusHours(hour.toLong())
|
||||
private fun time(hour: Int): LocalDateTime = LocalDateTime.now().plusHours(hour.toLong())
|
||||
|
||||
// Kotlin non-null матчеры: ArgumentMatchers.any() возвращает null и роняет NPE на non-null параметрах.
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
||||
+2
-3
@@ -19,7 +19,6 @@ import space.nstart.pcp_tgu_service.entity.PlannedPlanStatus
|
||||
import space.nstart.pcp_tgu_service.integration.api.MissionPlaningClient
|
||||
import space.nstart.pcp_tgu_service.repository.PlannedPlanRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
||||
@@ -79,8 +78,8 @@ class DeleteDraftUseCaseTest {
|
||||
PlannedPlanEntity(
|
||||
planId = UUID.randomUUID(),
|
||||
spacecraftId = "25544",
|
||||
startTime = LocalDateTime.now(ZoneOffset.UTC).plusHours(3),
|
||||
endTime = LocalDateTime.now(ZoneOffset.UTC).plusHours(4),
|
||||
startTime = LocalDateTime.now().plusHours(3),
|
||||
endTime = LocalDateTime.now().plusHours(4),
|
||||
kppId = "KPP-1",
|
||||
status = status
|
||||
)
|
||||
|
||||
+1
-2
@@ -10,7 +10,6 @@ import space.nstart.pcp_tgu_service.entity.SpacecraftSnapshotEntity
|
||||
import space.nstart.pcp_tgu_service.repository.ProcessedPlanDecisionEventRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftSnapshotRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.UUID
|
||||
|
||||
class HandleStationParamsChangedUseCaseTest {
|
||||
@@ -41,7 +40,7 @@ class HandleStationParamsChangedUseCaseTest {
|
||||
StationParamsChangedMessage(
|
||||
eventId = UUID.randomUUID(),
|
||||
stationId = "ST-7",
|
||||
occurredAt = LocalDateTime.now(ZoneOffset.UTC),
|
||||
occurredAt = LocalDateTime.now(),
|
||||
reason = "OUT_OF_SERVICE"
|
||||
)
|
||||
|
||||
|
||||
+2
-3
@@ -32,7 +32,6 @@ import space.nstart.pcp_tgu_service.repository.PlannedPlanRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftPlanningStateJpaRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftSnapshotRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
||||
@@ -551,9 +550,9 @@ class IssueDuePlanWorkerOnDemandTest {
|
||||
private fun plan(start: LocalDateTime, end: LocalDateTime): CalculatedPlan =
|
||||
CalculatedPlan(spacecraftId = spacecraftId, startTime = start, endTime = end, kppId = "KPP-1")
|
||||
|
||||
// Тот же UTC-эталон, что и у воркера (IssueDuePlanWorker берёт LocalDateTime.now(ZoneOffset.UTC)),
|
||||
// Тот же UTC-эталон, что и у воркера (IssueDuePlanWorker берёт LocalDateTime.now()),
|
||||
// иначе на не-UTC JVM подготовленные относительно "сейчас" окна расходятся с часами воркера на офсет.
|
||||
private fun now(): LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
private fun now(): LocalDateTime = LocalDateTime.now()
|
||||
|
||||
private fun planningProperties(): PlanningProperties =
|
||||
PlanningProperties(30, 180, 60, false, 10, 14, 0, 30_000, 5, 30, 300, 30, 10)
|
||||
|
||||
+5
-6
@@ -31,7 +31,6 @@ import space.nstart.pcp_tgu_service.repository.ProcessedPlanDecisionEventReposit
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftPlanningStateJpaRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftSnapshotRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
||||
@@ -85,7 +84,7 @@ class PlanIssueAndDecisionFlowTest {
|
||||
@Test
|
||||
fun `rejected decision marks plan REJECTED and pauses the chain without arming reissue`() {
|
||||
val spacecraftId = "25544"
|
||||
val plan = plannedPlan(spacecraftId, LocalDateTime.now(ZoneOffset.UTC).plusHours(2), LocalDateTime.now(ZoneOffset.UTC).plusHours(3), PlannedPlanStatus.WAITING_DECISION)
|
||||
val plan = plannedPlan(spacecraftId, LocalDateTime.now().plusHours(2), LocalDateTime.now().plusHours(3), PlannedPlanStatus.WAITING_DECISION)
|
||||
val attempt = attempt(plan.planId, PlanIssueAttemptStatus.STARTED)
|
||||
val state = SpacecraftPlanningStateEntity(spacecraftId = spacecraftId, activePlanId = plan.planId, activeAttemptId = attempt.attemptId)
|
||||
val slotsUpdateJobService = mock(AcceptedPlanSlotsUpdateJobService::class.java)
|
||||
@@ -295,7 +294,7 @@ class PlanIssueAndDecisionFlowTest {
|
||||
endTime = end,
|
||||
kppId = "KPP-1",
|
||||
status = status,
|
||||
issueNotBefore = LocalDateTime.now(ZoneOffset.UTC).minusMinutes(1)
|
||||
issueNotBefore = LocalDateTime.now().minusMinutes(1)
|
||||
)
|
||||
|
||||
private fun attempt(planId: UUID, status: PlanIssueAttemptStatus): PlanIssueAttemptEntity =
|
||||
@@ -318,7 +317,7 @@ class PlanIssueAndDecisionFlowTest {
|
||||
planId = planId,
|
||||
attemptId = attemptId,
|
||||
decision = decision,
|
||||
decisionTime = LocalDateTime.now(ZoneOffset.UTC),
|
||||
decisionTime = LocalDateTime.now(),
|
||||
reason = null
|
||||
)
|
||||
|
||||
@@ -327,11 +326,11 @@ class PlanIssueAndDecisionFlowTest {
|
||||
eventId = UUID.randomUUID(),
|
||||
spacecraftId = spacecraftId,
|
||||
planId = planId,
|
||||
occurredAt = LocalDateTime.now(ZoneOffset.UTC),
|
||||
occurredAt = LocalDateTime.now(),
|
||||
reason = null
|
||||
)
|
||||
|
||||
private fun time(hour: Int): LocalDateTime = LocalDateTime.now(ZoneOffset.UTC).plusHours(hour.toLong())
|
||||
private fun time(hour: Int): LocalDateTime = LocalDateTime.now().plusHours(hour.toLong())
|
||||
|
||||
private fun planningProperties(): PlanningProperties =
|
||||
PlanningProperties(30, 180, 60, false, 3, 14, 0, 30_000, 5, 30, 300, 30, 10)
|
||||
|
||||
+2
-3
@@ -18,7 +18,6 @@ import space.nstart.pcp_tgu_service.repository.PlannedPlanRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftPlanningStateJpaRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftVisibilitySnapshotRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
||||
@@ -192,13 +191,13 @@ class PlanProjectionServiceTest {
|
||||
noradId = 25544L,
|
||||
payloadJson = "[]",
|
||||
payloadHash = "hash",
|
||||
fetchedAt = LocalDateTime.now(ZoneOffset.UTC)
|
||||
fetchedAt = LocalDateTime.now()
|
||||
)
|
||||
|
||||
private fun plan(start: LocalDateTime, end: LocalDateTime): CalculatedPlan =
|
||||
CalculatedPlan(spacecraftId = spacecraftId, startTime = start, endTime = end, kppId = "KPP-1")
|
||||
|
||||
private fun time(hour: Int): LocalDateTime = LocalDateTime.now(ZoneOffset.UTC).plusHours(hour.toLong())
|
||||
private fun time(hour: Int): LocalDateTime = LocalDateTime.now().plusHours(hour.toLong())
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <T> anyNonNull(): T {
|
||||
|
||||
+1
-2
@@ -12,7 +12,6 @@ import space.nstart.pcp_tgu_service.entity.SpacecraftSnapshotEntity
|
||||
import space.nstart.pcp_tgu_service.repository.PlannedPlanRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftSnapshotRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.UUID
|
||||
|
||||
class PlanQueryServiceTest {
|
||||
@@ -124,5 +123,5 @@ class PlanQueryServiceTest {
|
||||
private fun calculated(start: LocalDateTime, end: LocalDateTime): CalculatedPlan =
|
||||
CalculatedPlan(spacecraftId = spacecraftId, startTime = start, endTime = end, kppId = "KPP-1")
|
||||
|
||||
private fun time(hour: Int): LocalDateTime = LocalDateTime.now(ZoneOffset.UTC).plusHours(hour.toLong())
|
||||
private fun time(hour: Int): LocalDateTime = LocalDateTime.now().plusHours(hour.toLong())
|
||||
}
|
||||
|
||||
+6
-5
@@ -27,9 +27,9 @@ class SyncSpacecraftFromNsiUseCaseTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `converts NSI validity instants to UTC LocalDateTime regardless of JVM default zone`() {
|
||||
// validFrom/validTo пишутся в naive-поля БД, которые по контракту хранят UTC. Раньше конвертация
|
||||
// шла через ZoneId.systemDefault(), и на не-UTC JVM (МСК, +3) значение уезжало на офсет.
|
||||
fun `projects NSI validity instants into JVM default zone as naive local LocalDateTime`() {
|
||||
// validFrom/validTo пишутся в naive-поля БД как настенные часы локальной зоны JVM (без UTC-семантики).
|
||||
// Конвертация идёт через ZoneId.systemDefault(), поэтому на МСК (+3) инстант смещается на офсет.
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Moscow"))
|
||||
|
||||
val platformService = mock(PlatformService::class.java)
|
||||
@@ -76,8 +76,9 @@ class SyncSpacecraftFromNsiUseCaseTest {
|
||||
now = anyNonNull()
|
||||
)
|
||||
|
||||
assertEquals(LocalDateTime.parse("2026-06-02T14:14:08"), validFrom.value)
|
||||
assertEquals(LocalDateTime.parse("2026-12-31T23:00:00"), validTo.value)
|
||||
// Инстанты спроецированы в локальную зону JVM (МСК, +3): 14:14Z → 17:14, 23:00Z → 02:00 следующих суток.
|
||||
assertEquals(LocalDateTime.parse("2026-06-02T17:14:08"), validFrom.value)
|
||||
assertEquals(LocalDateTime.parse("2027-01-01T02:00:00"), validTo.value)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
||||
+2
-3
@@ -21,7 +21,6 @@ import space.nstart.pcp_tgu_service.repository.PlanIssueAttemptRepository
|
||||
import space.nstart.pcp_tgu_service.repository.PlannedPlanRepository
|
||||
import space.nstart.pcp_tgu_service.repository.SpacecraftPlanningStateJpaRepository
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
||||
@@ -92,8 +91,8 @@ class TestPlanDecisionServiceTest {
|
||||
PlannedPlanEntity(
|
||||
planId = UUID.randomUUID(),
|
||||
spacecraftId = "25544",
|
||||
startTime = LocalDateTime.now(ZoneOffset.UTC).plusHours(1),
|
||||
endTime = LocalDateTime.now(ZoneOffset.UTC).plusHours(2),
|
||||
startTime = LocalDateTime.now().plusHours(1),
|
||||
endTime = LocalDateTime.now().plusHours(2),
|
||||
kppId = "KPP-1",
|
||||
status = PlannedPlanStatus.WAITING_DECISION
|
||||
)
|
||||
|
||||
+1
-2
@@ -5,12 +5,11 @@ import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import space.nstart.pcp_tgu_service.domain.CalculatedPlan
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
class ZrvWindowMatcherTest {
|
||||
|
||||
private val sc = "25544"
|
||||
private val t0 = LocalDateTime.now(ZoneOffset.UTC).withNano(0)
|
||||
private val t0 = LocalDateTime.now().withNano(0)
|
||||
private fun at(min: Long) = t0.plusMinutes(min)
|
||||
|
||||
private val zrvA = CalculatedPlan(sc, at(0), at(10), "KPP-1")
|
||||
|
||||
Reference in New Issue
Block a user