pcp: единообразие времени (UTC) по группе сервисов

Фронт (pcp-tgu-ops-ui):
- тест-страж utcTime.guard.test.ts: запрещает Date.parse / new Date(string) /
  toLocale* без timeZone вне utils/utcTime; тестовые фикстуры переведены на parseUtc.

Бэкенд:
- UTC-now: LocalDateTime.now() -> LocalDateTime.now(ZoneOffset.UTC) по всем сервисам
  (entity-дефолты, воркеры, use case'ы) и в тестовых фикстурах.
- Конвертации к UTC (баги на не-UTC JVM, каждая с регресс-тестом в не-UTC поясе):
  - tgu: VisibilityPayloadParser, SyncSpacecraftFromNsiUseCase (Instant -> UTC LocalDateTime);
  - mission-planing: MissionPlaningService (OffsetDateTime -> UTC через toUtcLocalDateTime);
  - dynamic-plan: RequestServiceClient (OffsetDateTime -> UTC);
  - slots: SlotRepositoryImpl (getObject(LocalDateTime) вместо getTimestamp().toLocalDateTime()).
- Контрактные сериализационные тесты формата на проводе (naive без зоны / Z):
  PlanResponse (tgu), RequestResponseDto (request, эталон), TguPlanning DTO (ui).
- KDoc-инвариант «=UTC» на UI-facing DTO (tgu PlanResponse, ui-service DTO вкладки ТГУ).

Документация: docs/standards/DATETIME_UTC.md (соглашение, таблица сервисов, под-задачи на миграцию к Z),
docs/standards/CHECKLIST.md (пункт про UTC на API).
This commit is contained in:
Дмитрий Соловьев
2026-06-02 21:51:38 +03:00
parent ac2220d063
commit 260583b134
84 changed files with 760 additions and 166 deletions
@@ -13,6 +13,7 @@ import jakarta.persistence.Table
import space.nstart.pcp.pcp_types_lib.dto.ballistics.AscNodeDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.KeplersDTO
import java.time.LocalDateTime
import java.time.ZoneOffset
import kotlin.math.PI
@@ -26,7 +27,7 @@ class AscNodeEntity(
@Column(nullable = false)
val satelliteId : Long = 0,
@Column(name = "t", nullable = false)
val time : LocalDateTime = LocalDateTime.now(),
val time : LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
@Column(nullable = false)
val revolution : Long = 0,
val longitude : Double = 0.0,
@@ -10,6 +10,7 @@ import jakarta.persistence.Table
import space.nstart.pcp.pcp_types_lib.dto.ballistics.RevolutionSign
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SquareViewParamDTO
import java.time.LocalDateTime
import java.time.ZoneOffset
import kotlin.math.PI
@Entity
@@ -26,13 +27,13 @@ class EarthCellViewsEntity(
@Column(name = "rev_begin", nullable = false)
val revolutionBegin : Long = 0,
@Column(nullable = false)
val timeBegin : LocalDateTime = LocalDateTime.now(),
val timeBegin : LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
@Column(nullable = false)
val revSignBegin : Int = 0,
@Column(name = "rev_end", nullable = false)
val revolutionEnd: Long = 0,
@Column(nullable = false)
val timeEnd : LocalDateTime = LocalDateTime.now(),
val timeEnd : LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
@Column(nullable = false)
val revSignEnd : Int = 0,
@Column(name = "roll_min")
@@ -9,6 +9,7 @@ import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.Table
import java.time.LocalDateTime
import java.time.ZoneOffset
@Entity
@Table(name = "earth_coverage")
@@ -22,9 +23,9 @@ class EarthCoverageEntity(
@Column(nullable = false)
val cellNumber : Long = 0,
@Column(nullable = false)
val tStart : LocalDateTime = LocalDateTime.now(),
val tStart : LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
@Column(nullable = false)
val tStop : LocalDateTime = LocalDateTime.now(),
val tStop : LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
) {
constructor(fl : FleghtLineSector, sector : Int, sat : Long) : this(
null,
@@ -6,6 +6,7 @@ import jakarta.persistence.*
import space.nstart.pcp.pcp_types_lib.dto.ballistics.FlightLineDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.RevolutionSign
import java.time.LocalDateTime
import java.time.ZoneOffset
import kotlin.math.PI
@@ -19,7 +20,7 @@ class FlightLineEntity(
@Column(nullable = false)
val satelliteId : Long = 0,
@Column(name = "t", nullable = false)
val time : LocalDateTime = LocalDateTime.now(),
val time : LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
@Column(nullable = false)
val revolution : Long = 0,
@Column(nullable = false)
@@ -7,6 +7,7 @@ import ballistics.utils.toDateTime
import jakarta.persistence.*
import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO
import java.time.LocalDateTime
import java.time.ZoneOffset
@Entity
@Table(name = "pdcm")
@@ -18,7 +19,7 @@ class PDCMEntity (
@Column(nullable = false)
val satelliteId : Long = 0,
@Column(name = "t", nullable = false)
val time : LocalDateTime = LocalDateTime.now(),
val time : LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
@Column(nullable = false)
val revolution : Long = 0,
val x: Double = 0.0,
@@ -13,6 +13,7 @@ import space.nstart.pcp.pcp_types_lib.dto.ballistics.MovementModel
import space.nstart.pcp.pcp_types_lib.dto.ballistics.OrbPointDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.SatelliteICDTO
import java.time.LocalDateTime
import java.time.ZoneOffset
@Entity
@Table(name = "satellite_initial_conditions")
@@ -27,7 +28,7 @@ class SatelliteInitialConditionEntity(
@Column(name = "movement_model", nullable = false)
val movementModel: MovementModel = MovementModel.FOTO,
@Column(name = "orb_time", nullable = false)
val orbTime: LocalDateTime = LocalDateTime.now(),
val orbTime: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
@Column(name = "orb_revolution", nullable = false)
val orbRevolution: Long = 0,
@Column(nullable = false)
@@ -47,7 +48,7 @@ class SatelliteInitialConditionEntity(
@Column(nullable = false)
val f81: Double = 147.8,
@Column(name = "received_at", nullable = false)
val receivedAt: LocalDateTime = LocalDateTime.now(),
val receivedAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
@Column(name = "calculated_at")
var calculatedAt: LocalDateTime? = null,
@Column(name = "last_calculated", nullable = false)
@@ -68,7 +69,7 @@ class SatelliteInitialConditionEntity(
f81 = request.ic.f81
)
fun markLastCalculated(calculatedAt: LocalDateTime = LocalDateTime.now()) {
fun markLastCalculated(calculatedAt: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)) {
this.calculatedAt = calculatedAt
this.lastCalculated = true
}
@@ -63,6 +63,7 @@ import space.nstart.pcp.pcp_types_lib.dto.ballistics.TLEDTO
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TargetPositionDTO
import tools.jackson.databind.ObjectMapper
import java.time.LocalDateTime
import java.time.ZoneOffset
import kotlin.collections.forEach
import kotlin.math.PI
import kotlin.math.abs
@@ -761,8 +762,8 @@ class SatelliteService {
fun getOrbPoints(id : Long, tn : LocalDateTime?, tk : LocalDateTime?) =
pdcmRepository.findBySatelliteIdAndTimeBetween(
id,
tn?: LocalDateTime.now(),
tk?: LocalDateTime.now().plusDays(1))
tn?: LocalDateTime.now(ZoneOffset.UTC),
tk?: LocalDateTime.now(ZoneOffset.UTC).plusDays(1))
.map { it.toDTO()
}
@@ -775,8 +776,8 @@ class SatelliteService {
fun getFL(id : Long, tn : LocalDateTime?, tk : LocalDateTime?, step : Double?) =
flightLineRepository.findBySatelliteIdAndTimeBetween(id,
tn?: LocalDateTime.now(),
tk?: LocalDateTime.now().plusDays(1)
tn?: LocalDateTime.now(ZoneOffset.UTC),
tk?: LocalDateTime.now(ZoneOffset.UTC).plusDays(1)
)
.map { it.toDTO() }
@@ -897,8 +898,8 @@ class SatelliteService {
return ascNodeRepository.findBySatelliteIdAndTimeBetween(
id,
tn ?: LocalDateTime.now(),
tk ?: LocalDateTime.now().plusDays(1)
tn ?: LocalDateTime.now(ZoneOffset.UTC),
tk ?: LocalDateTime.now(ZoneOffset.UTC).plusDays(1)
)
.map {
val keps = calculateKeplerParams(