Формат времени в UTC

This commit is contained in:
emelianov
2026-06-25 16:21:17 +03:00
parent d0ec0ef754
commit fe54c095ab
164 changed files with 1847 additions and 634 deletions
@@ -22,7 +22,7 @@ class TLEStepper(str1: String, str2: String, earthType: EarthType) : AbstractSte
private val ascendingNodeCache = mutableMapOf<Int, Double>()
init {
baseEpoch = extractUTCMillis(satellite) / 1000.0 + 10800
baseEpoch = extractUTCMillis(satellite) / 1000.0
nextAscendingNodeAfterEpoch = findNextAscendingNodeAfterEpoch()
ascendingNodeCache[0] = nextAscendingNodeAfterEpoch
}
@@ -26,9 +26,9 @@ class AstronomerJ2000(var earthType: EarthType) {
var d = tDtTm / 86400.0
var dat = truncate(d)
var tt = d - dat
if (tt < 0.125) {
dat--
}
// if (tt < 0.125) {
// dat--
// }
var t = (dat - baseDate) / (36525.0)
var t2 = t * t
@@ -69,11 +69,11 @@ class AstronomerJ2000(var earthType: EarthType) {
psi += r
if (tt < 0.125) {
tt += 0.875
} else {
tt -= 0.125
}
// if (tt < 0.125) {
// tt += 0.875
// } else {
// tt -= 0.125
// }
r = 84381.448e0 - 46.84024e0 * t - 0.00059e0 * t2 + 0.001813e0 * t3
r = r * PI / 180.0 / 3600.0
@@ -172,7 +172,7 @@ class AstronomerJ2000(var earthType: EarthType) {
fun moonCoordinates(tm: Double): Vector3D {
var d: Double = tm / 86400.0
d = d - 0.125
// d = d - 0.125
val t: Double = (d - baseDate) / 36525.0 // Юлианский период ot 12h 1.1.2000
val t2 = t * t
val t3 = t2 * t
@@ -241,8 +241,8 @@ class AstronomerJ2000(var earthType: EarthType) {
}
fun sunCoordinates(tm: Double): Vector3D {
var d = tm / 86400.0
var dd = d - 0.125
var dd = tm / 86400.0
// var dd = d - 0.125
var t = (dd - baseDate) / 36525.0
var t2 = t * t
var t3 = t2 * t
+1 -1
View File
@@ -22,7 +22,7 @@ fun main() {
val tt2 = LocalDateTime.of(2028, 12, 11, 1, 24, 24, 257000000)
val tt2 = LocalDateTime.of(2028, 12, 10, 22, 24, 24, 257000000)
val op = OrbitalPoint(
fromDateTime(tt2),
@@ -2,11 +2,12 @@ package space.nstart.pcp.pcp_types_lib.dto.ballistics
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.DecimalMin
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
@Schema(description = "Запрос на расчет параметров выхода на заданное время")
class ExactTimePositionRequestDTO(
val time : LocalDateTime = LocalDateTime.now(),
val time : LocalDateTime = UtcDateTimes.now(),
val timeStop : LocalDateTime? = null,
@field:DecimalMin(value = "1",
message = "Шаг не может быть меньше 1 мс")
@@ -2,6 +2,7 @@ package space.nstart.pcp.pcp_types_lib.dto.ballistics
import com.fasterxml.jackson.annotation.JsonProperty
import io.swagger.v3.oas.annotations.media.Schema
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
@Schema(description = "Модель данных параметров трассы полета и полосы обзора")
@@ -12,7 +13,7 @@ class FlightLineDTO(
required = true,
nullable = false
)
val time : LocalDateTime = LocalDateTime.now(),
val time : LocalDateTime = UtcDateTimes.now(),
@JsonProperty
@field:Schema(
description = "Виток"
@@ -2,6 +2,7 @@ package space.nstart.pcp.pcp_types_lib.dto.ballistics
import com.fasterxml.jackson.annotation.JsonProperty
import jakarta.validation.constraints.NotEmpty
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
class IcRvaRequestDTO(
@@ -13,8 +14,8 @@ class IcRvaRequestDTO(
@field:NotEmpty(message = "Массив станций приема")
val station: StationDTO = StationDTO(),
val timeStart : LocalDateTime = LocalDateTime.now(),
val timeStart : LocalDateTime = UtcDateTimes.now(),
val timeStop : LocalDateTime = LocalDateTime.now()
val timeStop : LocalDateTime = UtcDateTimes.now()
) {
}
@@ -1,10 +1,11 @@
package space.nstart.pcp.pcp_types_lib.dto.ballistics
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
class IntervalDTO(
val begin : LocalDateTime = LocalDateTime.now(),
val end : LocalDateTime = LocalDateTime.now(),
val begin : LocalDateTime = UtcDateTimes.now(),
val end : LocalDateTime = UtcDateTimes.now(),
val durationSec : Double = 0.0
) {
}
@@ -1,11 +1,12 @@
package space.nstart.pcp.pcp_types_lib.dto.ballistics
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
class ObjViewRequestDTO(
val satellites : List<Long> = listOf(),
val timeStart : LocalDateTime = LocalDateTime.now(),
val timeStop : LocalDateTime = LocalDateTime.now(),
val timeStart : LocalDateTime = UtcDateTimes.now(),
val timeStop : LocalDateTime = UtcDateTimes.now(),
val obj : ObjDTO = ObjDTO(),
val sunAngleMin : Double? = null,
val rollMin : Double? = null,
@@ -1,9 +1,10 @@
package space.nstart.pcp.pcp_types_lib.dto.ballistics
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
class OrbPointDTO(
val time : LocalDateTime = LocalDateTime.now(),
val time : LocalDateTime = UtcDateTimes.now(),
val revolution : Long = 0,
val vx : Double = 0.0,
val vy : Double = 0.0,
@@ -1,11 +1,12 @@
package space.nstart.pcp.pcp_types_lib.dto.ballistics
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
class PointViewParamDTO(
val noradId : Long = 0,
val objectId : String = "",
val time : LocalDateTime = LocalDateTime.now(),
val time : LocalDateTime = UtcDateTimes.now(),
val revolution : Long = 0,
val gamma : Double = 0.0,
val sunAngle : Double = 0.0,
@@ -1,10 +1,11 @@
package space.nstart.pcp.pcp_types_lib.dto.ballistics
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
class SatelliteOrbitAvailabilityDTO(
val satelliteId: Long = 0,
val timeStart: LocalDateTime = LocalDateTime.now(),
val timeStop: LocalDateTime = LocalDateTime.now()
val timeStart: LocalDateTime = UtcDateTimes.now(),
val timeStop: LocalDateTime = UtcDateTimes.now()
) {
}
@@ -1,5 +1,6 @@
package space.nstart.pcp.pcp_types_lib.dto.ballistics
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
@@ -8,10 +9,10 @@ class SquareViewParamDTO(
val noradId : Long = 0,
val objectId : String = "",
val revolutionBegin : Long = 0,
val timeBegin : LocalDateTime = LocalDateTime.now(),
val timeBegin : LocalDateTime = UtcDateTimes.now(),
val revSignBegin : RevolutionSign = RevolutionSign.ASC,
val revolutionEnd: Long = 0,
val timeEnd : LocalDateTime = LocalDateTime.now(),
val timeEnd : LocalDateTime = UtcDateTimes.now(),
val revSignEnd : RevolutionSign = RevolutionSign.ASC,
val gammaMin : Double = 0.0,
val gammaMax : Double = 0.0
@@ -1,12 +1,13 @@
package space.nstart.pcp.pcp_types_lib.dto.ballistics
import io.swagger.v3.oas.annotations.media.Schema
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
@Schema(description = "Модель данных целеуказаний")
class TargetPositionDTO(
val time : LocalDateTime = LocalDateTime.now(),
val time : LocalDateTime = UtcDateTimes.now(),
val elevation : Double = 0.0,
val azimuth : Double = 0.0,
val range : Double = 0.0
@@ -1,8 +1,9 @@
package space.nstart.pcp.pcp_types_lib.dto.ballistics
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
class TlePointRequestDTO(
var tle: TLEDTO = TLEDTO(),
var time: LocalDateTime = LocalDateTime.now()
var time: LocalDateTime = UtcDateTimes.now()
)
@@ -2,6 +2,7 @@ package space.nstart.pcp.pcp_types_lib.dto.ballistics
import com.fasterxml.jackson.annotation.JsonProperty
import jakarta.validation.constraints.NotEmpty
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
class TleRvaRequestDTO(
@@ -11,9 +12,9 @@ class TleRvaRequestDTO(
@field:NotEmpty(message = "Массив станций приема")
val station: StationDTO = StationDTO(),
val timeStart : LocalDateTime = LocalDateTime.now(),
val timeStart : LocalDateTime = UtcDateTimes.now(),
val timeStop : LocalDateTime = LocalDateTime.now()
val timeStop : LocalDateTime = UtcDateTimes.now()
) {
}
@@ -1,11 +1,12 @@
package space.nstart.pcp.pcp_types_lib.dto.coverage
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
class CoverageCandidateDebugDTO(
val satelliteId: Long = 0,
val timeStart: LocalDateTime = LocalDateTime.now(),
val timeEnd: LocalDateTime = LocalDateTime.now(),
val timeStart: LocalDateTime = UtcDateTimes.now(),
val timeEnd: LocalDateTime = UtcDateTimes.now(),
val rollDegrees: Double = 0.0,
val accepted: Boolean = false,
val reason: String = "",
@@ -1,12 +1,13 @@
package space.nstart.pcp.pcp_types_lib.dto.coverage
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
class CoverageSchemeCalculateRequestDTO(
val polygonWkt: String = "",
val satelliteIds: List<Long> = listOf(),
val timeStart: LocalDateTime = LocalDateTime.now(),
val timeEnd: LocalDateTime = LocalDateTime.now(),
val timeStart: LocalDateTime = UtcDateTimes.now(),
val timeEnd: LocalDateTime = UtcDateTimes.now(),
val rollStepDegrees: Double? = null,
val minimumTechnologyOverlap: Double? = null,
val strictContinuousCoverage: Boolean = true,
@@ -1,10 +1,11 @@
package space.nstart.pcp.pcp_types_lib.dto.requests
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
class EarthCellCalculationRequestDTO(
val timeStart : LocalDateTime = LocalDateTime.now(),
val timeStop : LocalDateTime = LocalDateTime.now(),
val timeStart : LocalDateTime = UtcDateTimes.now(),
val timeStop : LocalDateTime = UtcDateTimes.now(),
val cell : EarthCellWithRequestsDTO = EarthCellWithRequestsDTO()
) {
}
@@ -1,12 +1,13 @@
package space.nstart.pcp.pcp_types_lib.dto.requests.slots
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
import java.util.UUID
data class BookedSlotsStatusChangedEvent(
val bookedSlotIds: List<Long> = emptyList(),
val status: BookedSlotStatus = BookedSlotStatus.BOOKED,
val occurredAt: LocalDateTime = LocalDateTime.now(),
val occurredAt: LocalDateTime = UtcDateTimes.now(),
val sourceService: String = "",
val missionId: UUID? = null,
val modeId: Long? = null,
@@ -1,6 +1,7 @@
package space.nstart.pcp.pcp_types_lib.dto.satellite.mission
import io.swagger.v3.oas.annotations.media.Schema
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
/**
@@ -23,10 +24,10 @@ data class DropRouteSaveRequestDTO(
val revolution: Long = 0,
@field:Schema(description = "Начало зоны радиовидимости (локальное время без зоны)")
val zoneStart: LocalDateTime = LocalDateTime.now(),
val zoneStart: LocalDateTime = UtcDateTimes.now(),
@field:Schema(description = "Конец зоны радиовидимости (локальное время без зоны)")
val zoneStop: LocalDateTime = LocalDateTime.now(),
val zoneStop: LocalDateTime = UtcDateTimes.now(),
@field:Schema(description = "Идентификаторы режимов съёмки (survey_modes), которые сбрасываются в этой зоне")
val surveyModeIds: List<Long> = emptyList(),
@@ -3,6 +3,7 @@ package space.nstart.pcp.pcp_types_lib.dto.satellite.mission
import com.fasterxml.jackson.annotation.JsonProperty
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.DecimalMin
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
/**
@@ -22,7 +23,7 @@ data class SurveyRouteBuildRequestDTO(
val satelliteId: Long = 0,
@field:Schema(description = "Момент видимости (локальное время без зоны)")
val time: LocalDateTime = LocalDateTime.now(),
val time: LocalDateTime = UtcDateTimes.now(),
@field:DecimalMin(value = "1", message = "Длительность съёмки должна быть не меньше 1 секунды")
@field:Schema(description = "Длительность съёмки, секунды")
@@ -1,11 +1,12 @@
package space.nstart.pcp.pcp_types_lib.dto.satellite.modes
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
data class SurveyModeInfoDTO(
val revolution : Long = 0,
val time : LocalDateTime = LocalDateTime.now(),
val timStop : LocalDateTime = LocalDateTime.now(),
val time : LocalDateTime = UtcDateTimes.now(),
val timStop : LocalDateTime = UtcDateTimes.now(),
val duration : Double = 0.0,
val roll : Double = 0.0,
val contourWKT : String = "",
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonFormat
import com.fasterxml.jackson.annotation.JsonAlias
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import space.nstart.pcp.pcp_types_lib.utils.json.LocalDateTimeTimeZoneSerializer
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
import java.util.*
@@ -22,7 +23,7 @@ class KafkaMessage<out T> (
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS[xxx]")
@JsonSerialize(using = LocalDateTimeTimeZoneSerializer::class)
val time: LocalDateTime = LocalDateTime.now()
val time: LocalDateTime = UtcDateTimes.now()
val dataContentType = "application/json"
val specVersion = "1.0"
@@ -0,0 +1,60 @@
package space.nstart.pcp.pcp_types_lib.utils
import java.time.Clock
import java.time.Instant
import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeParseException
/**
* Shared UTC helpers for services that still keep LocalDateTime in domain and DB models.
*
* LocalDateTime values are treated as UTC-naive: the stored object has no zone, but its
* numeric fields represent a UTC instant. Public API may receive either explicit UTC/offset
* ISO strings or legacy naive ISO strings; explicit offsets are normalized to UTC.
*/
object UtcDateTimes {
val UTC: ZoneOffset = ZoneOffset.UTC
fun now(clock: Clock = Clock.systemUTC()): LocalDateTime =
LocalDateTime.ofInstant(clock.instant(), UTC)
fun parse(value: String): LocalDateTime {
val normalized = normalize(value)
parseOffset(normalized)?.let { return it }
parseInstant(normalized)?.let { return it }
parseLocal(normalized)?.let { return it }
throw DateTimeParseException(
"Expected ISO-8601 UTC/offset date-time or legacy UTC-naive date-time",
value,
0
)
}
fun format(value: LocalDateTime): String = value.toInstant(UTC).toString()
fun parseOrNull(value: String): LocalDateTime? = runCatching { parse(value) }.getOrNull()
private fun normalize(value: String): String {
val trimmed = value.trim().replace(Regex(" (\\d{2}:\\d{2})$"), "+\$1")
return if (trimmed.length == 16) "$trimmed:00" else trimmed
}
private fun parseOffset(value: String): LocalDateTime? = runCatching {
OffsetDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME)
.withOffsetSameInstant(UTC)
.toLocalDateTime()
}.getOrNull()
private fun parseInstant(value: String): LocalDateTime? = runCatching {
LocalDateTime.ofInstant(Instant.parse(value), UTC)
}.getOrNull()
private fun parseLocal(value: String): LocalDateTime? = runCatching {
LocalDateTime.parse(value, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
}.getOrNull()
}
@@ -3,20 +3,16 @@ package space.nstart.pcp.pcp_types_lib.utils.json
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
class LocalDateTimeTimeZoneSerializer: JsonSerializer<LocalDateTime?>() {
companion object {
val fmt: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSxxx")
}
override fun serialize(p0: LocalDateTime?, p1: JsonGenerator, p2: SerializerProvider) {
//todo: проверить корректность зоны по нормальным правилам
// p1.writeString(p0?.atZone(ZoneId.of("Europe/Moscow"))?.format(fmt))
p1.writeString(p0?.atZone(ZoneId.systemDefault())?.format(fmt))
if (p0 == null) {
p1.writeNull()
} else {
p1.writeString(UtcDateTimes.format(p0))
}
}
}
@@ -0,0 +1,29 @@
package space.nstart.pcp.pcp_types_lib.utils
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.time.LocalDateTime
class UtcDateTimesTest {
@Test
fun `normalizes offset date-time to utc naive LocalDateTime`() {
val actual = UtcDateTimes.parse("2026-06-17T09:28:27.334+03:00")
assertEquals(LocalDateTime.of(2026, 6, 17, 6, 28, 27, 334_000_000), actual)
}
@Test
fun `treats legacy naive date-time as utc naive LocalDateTime`() {
val actual = UtcDateTimes.parse("2026-06-17T06:28:27.334")
assertEquals(LocalDateTime.of(2026, 6, 17, 6, 28, 27, 334_000_000), actual)
}
@Test
fun `formats utc naive LocalDateTime with Z suffix`() {
val actual = UtcDateTimes.format(LocalDateTime.of(2026, 6, 17, 6, 28, 27, 334_000_000))
assertEquals("2026-06-17T06:28:27.334Z", actual)
}
}