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
@@ -5,6 +5,7 @@ import jakarta.persistence.Entity
import jakarta.persistence.Id
import jakarta.persistence.Table
import java.time.LocalDateTime
import java.time.ZoneOffset
@Entity
@Table(name = "satellite_ic")
@@ -13,7 +14,7 @@ class SatelliteIcEntity(
@Column(name = "satellite_id", nullable = false)
val satelliteId: Long = 0,
@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)
@@ -9,6 +9,7 @@ import space.nstart.pcp.pcp_types_lib.dto.requests.slots.SlotDTO
import space.nstart.pcp.pcp_types_lib.utils.wkt.WKTParser
import space.nstart.pcp.slots_service.model.Mar
import java.time.LocalDateTime
import java.time.ZoneOffset
@Entity
@@ -26,9 +27,9 @@ class SlotEntity(
@Column(nullable = false)
val coveringType : Int = 0,
@Column(nullable = false)
var tn : LocalDateTime = LocalDateTime.now(),
var tn : LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
@Column(nullable = false)
var tk : LocalDateTime = LocalDateTime.now(),
var tk : LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
val roll : Double = 0.0,
@Column(name = "contour_wkt", columnDefinition = "TEXT", nullable = false)
val contour : String = "",
@@ -3,11 +3,12 @@ package space.nstart.pcp.slots_service.model
import org.locationtech.jts.geom.Geometry
import space.nstart.pcp.pcp_types_lib.dto.ballistics.RevolutionSign
import java.time.LocalDateTime
import java.time.ZoneOffset
class Mar (
val sat : Long = 0,
val tn : LocalDateTime = LocalDateTime.now(),
val tk : LocalDateTime = LocalDateTime.now(),
val tn : LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
val tk : LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
val roll : Double = 0.0,
val contour : String = "",
var poly : Geometry? = null,
@@ -33,8 +33,11 @@ class SlotRepositoryImpl(
cycle = rs.getLong("cycle"),
satelliteId = rs.getLong("satellite_id"),
coveringType = rs.getInt("covering_type"),
tn = rs.getTimestamp("tn").toLocalDateTime(),
tk = rs.getTimestamp("tk").toLocalDateTime(),
// getObject(LocalDateTime) читает `timestamp without time zone` без участия
// зоны JVM. getTimestamp().toLocalDateTime() зоно-нейтрален лишь для фикс-офсета;
// на зонах с DST граничные значения сдвигались бы. Времена в системе — UTC.
tn = rs.getObject("tn", java.time.LocalDateTime::class.java),
tk = rs.getObject("tk", java.time.LocalDateTime::class.java),
roll = rs.getDouble("roll"),
contour = rs.getString("contour_wkt"),
revolution = rs.getLong("revolution"),