Формат времени в UTC
This commit is contained in:
+55
@@ -0,0 +1,55 @@
|
||||
package space.nstart.pcp.slots_service.configuration
|
||||
|
||||
import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.core.convert.converter.Converter
|
||||
import org.springframework.format.FormatterRegistry
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
||||
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
|
||||
import tools.jackson.core.JsonGenerator
|
||||
import tools.jackson.core.JsonParser
|
||||
import tools.jackson.databind.DeserializationContext
|
||||
import tools.jackson.databind.SerializationContext
|
||||
import tools.jackson.databind.deser.std.StdDeserializer
|
||||
import tools.jackson.databind.module.SimpleModule
|
||||
import tools.jackson.databind.ser.std.StdSerializer
|
||||
import java.time.Clock
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Configuration
|
||||
class UtcTimeConfiguration : WebMvcConfigurer {
|
||||
|
||||
@Bean
|
||||
fun clock(): Clock = Clock.systemUTC()
|
||||
|
||||
@Bean
|
||||
fun pcpUiUtcLocalDateTimeCustomizer(): JsonMapperBuilderCustomizer = JsonMapperBuilderCustomizer { builder ->
|
||||
builder.addModule(
|
||||
SimpleModule("pcp-ui-service-utc-local-date-time")
|
||||
.addSerializer(LocalDateTime::class.java, UtcLocalDateTimeSerializer())
|
||||
.addDeserializer(LocalDateTime::class.java, UtcLocalDateTimeDeserializer())
|
||||
)
|
||||
}
|
||||
|
||||
override fun addFormatters(registry: FormatterRegistry) {
|
||||
registry.addConverter(UtcLocalDateTimeQueryParamConverter())
|
||||
}
|
||||
}
|
||||
|
||||
private class UtcLocalDateTimeQueryParamConverter : Converter<String, LocalDateTime> {
|
||||
override fun convert(source: String): LocalDateTime = UtcDateTimes.parse(source)
|
||||
}
|
||||
|
||||
private class UtcLocalDateTimeSerializer : StdSerializer<LocalDateTime>(LocalDateTime::class.java) {
|
||||
override fun serialize(value: LocalDateTime, gen: JsonGenerator, ctxt: SerializationContext) {
|
||||
gen.writeString(UtcDateTimes.format(value))
|
||||
}
|
||||
}
|
||||
|
||||
private class UtcLocalDateTimeDeserializer : StdDeserializer<LocalDateTime>(LocalDateTime::class.java) {
|
||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): LocalDateTime {
|
||||
val value = p.valueAsString ?: throw IllegalArgumentException("Expected ISO-8601 UTC date-time string")
|
||||
return UtcDateTimes.parse(value)
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
package space.nstart.pcp.slots_service.controller
|
||||
|
||||
import org.springframework.format.annotation.DateTimeFormat
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
@@ -46,8 +45,8 @@ class BookingsController(
|
||||
|
||||
@GetMapping
|
||||
fun bookedSlots(
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) timeStart: LocalDateTime?,
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) timeStop: LocalDateTime?,
|
||||
@RequestParam(required = false) timeStart: LocalDateTime?,
|
||||
@RequestParam(required = false) timeStop: LocalDateTime?,
|
||||
@RequestParam(required = false) requestId: String?,
|
||||
@RequestParam(required = false) satelliteIds: List<Long>?,
|
||||
): List<BookedSlotViewDTO> {
|
||||
|
||||
+5
-2
@@ -27,6 +27,7 @@ import space.nstart.pcp.slots_service.service.BallisticsService
|
||||
import space.nstart.pcp.slots_service.service.SatelliteCatalogService
|
||||
import space.nstart.pcp.slots_service.service.SatellitePdcmService
|
||||
import space.nstart.pcp.slots_service.service.SlotService
|
||||
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.DateTimeParseException
|
||||
@@ -103,14 +104,16 @@ class CatalogRestController(
|
||||
|
||||
private fun parseSendInitialConditionsTime(time: String?): LocalDateTime {
|
||||
val normalizedTime = time?.trim()?.takeIf { it.isNotEmpty() }
|
||||
?: return LocalDateTime.now()
|
||||
?: return UtcDateTimes.now()
|
||||
|
||||
UtcDateTimes.parseOrNull(normalizedTime)?.let { return it }
|
||||
|
||||
return try {
|
||||
LocalDateTime.parse(normalizedTime, sendInitialConditionsTimeFormatter)
|
||||
} catch (exception: DateTimeParseException) {
|
||||
throw ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Параметр time должен быть в формате dd.mm.yyyy hh:MM:ss.zzz",
|
||||
"Параметр time должен быть ISO-8601 UTC/offset или legacy dd.mm.yyyy hh:MM:ss.zzz",
|
||||
exception
|
||||
)
|
||||
}
|
||||
|
||||
+3
-2
@@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.RestController
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.AngularMotionCalculationRequestDTO
|
||||
import space.nstart.pcp.slots_service.dto.dynamicplan.DynamicPlanCalculationRequestDTO
|
||||
import space.nstart.pcp.slots_service.service.DynamicPlanService
|
||||
import java.time.LocalDateTime
|
||||
import java.util.UUID
|
||||
|
||||
@Controller
|
||||
@@ -83,14 +84,14 @@ class AngularMotionRestController(
|
||||
) {
|
||||
@GetMapping("/satellites")
|
||||
@ResponseBody
|
||||
fun satellites(@RequestParam(required = false) time: String?) =
|
||||
fun satellites(@RequestParam(required = false) time: LocalDateTime?) =
|
||||
dynamicPlanService.getAngularMotionSatellites(time)
|
||||
|
||||
@GetMapping("/satellites/{satelliteId}/flight-line")
|
||||
@ResponseBody
|
||||
fun flightLine(
|
||||
@PathVariable satelliteId: Long,
|
||||
@RequestParam time: String
|
||||
@RequestParam time: LocalDateTime
|
||||
) = dynamicPlanService.getAngularMotionFlightLine(satelliteId, time)
|
||||
|
||||
@PostMapping("/calculate")
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ class MapController {
|
||||
fun showMap(model: Model): Mono<String> {
|
||||
model.addAttribute("requests", earthService.reqs().collectList().block())
|
||||
model.addAttribute("satellites", complexMissionService.satellites().collectList().block())
|
||||
model.addAttribute("stations", stationService.stations().collectList().block())
|
||||
model.addAttribute("stations", stationService.allStationsOrEmpty())
|
||||
return Mono.just("map")
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -15,9 +15,9 @@ import space.nstart.pcp.slots_service.dto.tlecomparison.TlePositionDTO
|
||||
import space.nstart.pcp.slots_service.dto.tlecomparison.TlePositionDeltaDTO
|
||||
import space.nstart.pcp.slots_service.dto.tlecomparison.TleTextRequestDTO
|
||||
import space.nstart.pcp.slots_service.service.BallisticsService
|
||||
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
|
||||
import java.time.Duration
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.sqrt
|
||||
@@ -144,14 +144,13 @@ class TleComparisonController(
|
||||
)
|
||||
|
||||
private fun timeRow(code: String, name: String, first: LocalDateTime?, second: LocalDateTime?): TleCompareRowDTO {
|
||||
val formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME
|
||||
val delta = if (first != null && second != null) {
|
||||
val seconds = Duration.between(first, second).seconds
|
||||
formatSecondsDelta(seconds)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
return TleCompareRowDTO(code, name, first?.format(formatter), second?.format(formatter), delta)
|
||||
return TleCompareRowDTO(code, name, first?.let(UtcDateTimes::format), second?.let(UtcDateTimes::format), delta)
|
||||
}
|
||||
|
||||
private fun numberRow(code: String, name: String, first: Double?, second: Double?): TleCompareRowDTO =
|
||||
|
||||
+3
-7
@@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.media.Schema
|
||||
import lombok.AllArgsConstructor
|
||||
import lombok.Data
|
||||
import lombok.NoArgsConstructor
|
||||
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
|
||||
import java.time.LocalDateTime
|
||||
|
||||
|
||||
@@ -23,13 +24,8 @@ class CZMLClock(
|
||||
var step : String = "SYSTEM_CLOCK_MULTIPLIER"
|
||||
) {
|
||||
constructor(tn : LocalDateTime, tk : LocalDateTime, cur : LocalDateTime) : this(
|
||||
interval ="${
|
||||
tn.toString()
|
||||
}/${
|
||||
tk
|
||||
.toString()
|
||||
}",
|
||||
currentTime = cur.toString()
|
||||
interval = "${UtcDateTimes.format(tn)}/${UtcDateTimes.format(tk)}",
|
||||
currentTime = UtcDateTimes.format(cur)
|
||||
)
|
||||
|
||||
}
|
||||
+13
-28
@@ -7,6 +7,7 @@ import lombok.AllArgsConstructor
|
||||
import lombok.Data
|
||||
import lombok.NoArgsConstructor
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.AscNodeDTO
|
||||
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
@@ -28,6 +29,10 @@ class CZMLPath(
|
||||
|
||||
private fun fromDateTime(t: LocalDateTime) = t.toEpochSecond(ZoneOffset.UTC) + t.nano / 1e9
|
||||
|
||||
private fun utcIso(t: Double): String = UtcDateTimes.format(
|
||||
LocalDateTime.ofEpochSecond(t.toLong(), ((t % 1) * 1e9).toInt(), ZoneOffset.UTC)
|
||||
)
|
||||
|
||||
constructor(
|
||||
vuz: Iterable<AscNodeDTO>, epoch: LocalDateTime, epochStop: LocalDateTime, width: Int, resolution: Int,
|
||||
material: CZMLMaterial
|
||||
@@ -44,26 +49,16 @@ class CZMLPath(
|
||||
if (dt > 1) {
|
||||
lt.add(
|
||||
CZMLInterval(
|
||||
"${
|
||||
LocalDateTime.ofEpochSecond((t).toLong(), (t % 1 * 1e9).toInt(), ZoneOffset.UTC)
|
||||
}/${
|
||||
LocalDateTime.ofEpochSecond(fromDateTime(v.time).toLong(), (fromDateTime(v.time) % 1 * 1e9).toInt(), ZoneOffset.UTC)
|
||||
.toString()
|
||||
}",
|
||||
LocalDateTime.ofEpochSecond((t).toLong(), (t % 1 * 1e9).toInt(), ZoneOffset.UTC).toString(),
|
||||
"${utcIso(t)}/${utcIso(fromDateTime(v.time))}",
|
||||
utcIso(t),
|
||||
listOf(0.0, dt, dt, 0.0)
|
||||
)
|
||||
)
|
||||
|
||||
tt.add(
|
||||
CZMLInterval(
|
||||
"${
|
||||
LocalDateTime.ofEpochSecond(t.toLong(), ((t) % 1 * 1e9).toInt(), ZoneOffset.UTC)
|
||||
}/${
|
||||
LocalDateTime.ofEpochSecond((fromDateTime(v.time)).toLong(), (fromDateTime(v.time) % 1 * 1e9).toInt(), ZoneOffset.UTC)
|
||||
.toString()
|
||||
}",
|
||||
LocalDateTime.ofEpochSecond((t).toLong(), (t % 1 * 1e9).toInt(), ZoneOffset.UTC).toString(),
|
||||
"${utcIso(t)}/${utcIso(fromDateTime(v.time))}",
|
||||
utcIso(t),
|
||||
listOf(0.0, 0.0, dt, dt)
|
||||
)
|
||||
)
|
||||
@@ -76,26 +71,16 @@ class CZMLPath(
|
||||
if (dt > 1) {
|
||||
lt.add(
|
||||
CZMLInterval(
|
||||
"${
|
||||
LocalDateTime.ofEpochSecond((t).toLong(), (t % 1 * 1e9).toInt(), ZoneOffset.UTC)
|
||||
}/${
|
||||
LocalDateTime.ofEpochSecond((tk).toLong(), (tk % 1 * 1e9).toInt(), ZoneOffset.UTC)
|
||||
.toString()
|
||||
}",
|
||||
LocalDateTime.ofEpochSecond((t).toLong(), (t % 1 * 1e9).toInt(), ZoneOffset.UTC).toString(),
|
||||
"${utcIso(t)}/${utcIso(tk)}",
|
||||
utcIso(t),
|
||||
listOf(0.0, dt, dt, 0.0)
|
||||
)
|
||||
)
|
||||
|
||||
tt.add(
|
||||
CZMLInterval(
|
||||
"${
|
||||
LocalDateTime.ofEpochSecond((t).toLong(), (t % 1 * 1e9).toInt(), ZoneOffset.UTC)
|
||||
}/${
|
||||
LocalDateTime.ofEpochSecond((tk).toLong(), (tk % 1 * 1e9).toInt(), ZoneOffset.UTC)
|
||||
.toString()
|
||||
}",
|
||||
LocalDateTime.ofEpochSecond((t).toLong(), (t % 1 * 1e9).toInt(), ZoneOffset.UTC).toString(),
|
||||
"${utcIso(t)}/${utcIso(tk)}",
|
||||
utcIso(t),
|
||||
listOf(0.0, 0.0, dt, dt)
|
||||
)
|
||||
)
|
||||
|
||||
+3
-2
@@ -16,6 +16,7 @@ import space.nstart.pcp.pcp_types_lib.dto.ballistics.TlePointRequestDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.TleRevolutionsRequestDTO
|
||||
import space.nstart.pcp.slots_service.dto.tlecomparison.TleParsedDTO
|
||||
import space.nstart.pcp.slots_service.configuration.CustomErrorException
|
||||
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
|
||||
import java.net.URI
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@@ -133,8 +134,8 @@ class BallisticsService (webClientBuilderProvider: ObjectProvider<WebClient.Buil
|
||||
timeStop: LocalDateTime?
|
||||
): URI {
|
||||
val builder = UriComponentsBuilder.fromUriString("$url$path")
|
||||
timeStart?.let { builder.queryParam("time_start", it) }
|
||||
timeStop?.let { builder.queryParam("time_stop", it) }
|
||||
timeStart?.let { builder.queryParam("time_start", UtcDateTimes.format(it)) }
|
||||
timeStop?.let { builder.queryParam("time_stop", UtcDateTimes.format(it)) }
|
||||
return builder.build().toUri()
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -15,6 +15,7 @@ import space.nstart.pcp.pcp_types_lib.utils.wkt.WKTParser
|
||||
import space.nstart.pcp.slots_service.configuration.CustomErrorException
|
||||
import space.nstart.pcp.slots_service.czml.*
|
||||
import java.time.Duration
|
||||
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import kotlin.math.PI
|
||||
@@ -426,7 +427,7 @@ private val docReqCovName ="req_cov_"
|
||||
timeStop: LocalDateTime? = null
|
||||
): Iterable<CZMLEntity> {
|
||||
|
||||
val tstart = timeStart ?: LocalDateTime.now()
|
||||
val tstart = timeStart ?: UtcDateTimes.now()
|
||||
val tstop = timeStop ?: tstart.plusDays(1)
|
||||
var vuz = ballisticsService.ascNodes(id, tstart, tstop).collectList().block()
|
||||
vuz = vuz?.filter { it.time >= tstart && it.time <= tstop }?.sortedBy { it.time }
|
||||
@@ -489,7 +490,7 @@ private val docReqCovName ="req_cov_"
|
||||
cartographicDegrees = flr,
|
||||
interpolationAlgorithm = "LAGRANGE",
|
||||
interpolationDegree = 1.0,
|
||||
epoch = tstart.toString()
|
||||
epoch = UtcDateTimes.format(tstart)
|
||||
),
|
||||
path = path.apply { show = showSw }
|
||||
|
||||
@@ -500,7 +501,7 @@ private val docReqCovName ="req_cov_"
|
||||
cartographicDegrees = fll,
|
||||
interpolationAlgorithm = "LAGRANGE",
|
||||
interpolationDegree = 1.0,
|
||||
epoch = tstart.toString()
|
||||
epoch = UtcDateTimes.format(tstart)
|
||||
),
|
||||
path = path.apply { show = showSw }
|
||||
),
|
||||
@@ -521,7 +522,7 @@ private val docReqCovName ="req_cov_"
|
||||
cartographicDegrees = sw,
|
||||
interpolationAlgorithm = "LAGRANGE",
|
||||
interpolationDegree = 1.0,
|
||||
epoch = tstart.toString()
|
||||
epoch = UtcDateTimes.format(tstart)
|
||||
),
|
||||
path = path.apply { show = showFl }
|
||||
),
|
||||
@@ -550,7 +551,7 @@ private val docReqCovName ="req_cov_"
|
||||
cartesian = orb,
|
||||
interpolationAlgorithm = "LAGRANGE",
|
||||
interpolationDegree = 1.0,
|
||||
epoch = tstart.toString()
|
||||
epoch = UtcDateTimes.format(tstart)
|
||||
),
|
||||
path = path.apply { show = showOrbit }
|
||||
)
|
||||
|
||||
+3
-2
@@ -19,6 +19,7 @@ import space.nstart.pcp.slots_service.czml.CZMLMaterial
|
||||
import space.nstart.pcp.slots_service.czml.CZMLPolygon
|
||||
import space.nstart.pcp.slots_service.czml.CZMLPosition
|
||||
import tools.jackson.databind.ObjectMapper
|
||||
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Service
|
||||
@@ -185,8 +186,8 @@ class CoverageSchemeModeRowDTO(
|
||||
val entityId: String = "",
|
||||
val sequence: Int = 0,
|
||||
val satelliteId: Long = 0,
|
||||
val tn: LocalDateTime = LocalDateTime.now(),
|
||||
val tk: LocalDateTime = LocalDateTime.now(),
|
||||
val tn: LocalDateTime = UtcDateTimes.now(),
|
||||
val tk: LocalDateTime = UtcDateTimes.now(),
|
||||
val roll: Double? = null,
|
||||
val revolutionSign: RevolutionSign? = null,
|
||||
val contour: String = "",
|
||||
|
||||
+6
-5
@@ -8,11 +8,13 @@ import reactor.core.publisher.Mono
|
||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.AngularMotionCalculationRequestDTO
|
||||
import space.nstart.pcp.slots_service.configuration.CustomErrorException
|
||||
import space.nstart.pcp.slots_service.dto.dynamicplan.DynamicPlanCalculationRequestDTO
|
||||
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
|
||||
import tools.jackson.databind.JsonNode
|
||||
import tools.jackson.databind.ObjectMapper
|
||||
import java.net.URI
|
||||
import java.net.URLEncoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.time.LocalDateTime
|
||||
import java.util.UUID
|
||||
|
||||
@Service
|
||||
@@ -112,10 +114,9 @@ class DynamicPlanService(webClientBuilderProvider: ObjectProvider<WebClient.Buil
|
||||
.block() ?: ObjectMapper().createObjectNode()
|
||||
|
||||
|
||||
fun getAngularMotionSatellites(time: String?): JsonNode {
|
||||
fun getAngularMotionSatellites(time: LocalDateTime?): JsonNode {
|
||||
val timeParam = time
|
||||
?.takeIf { value -> value.isNotBlank() }
|
||||
?.let { value -> "?time=${URLEncoder.encode(value, StandardCharsets.UTF_8)}" }
|
||||
?.let { value -> "?time=${URLEncoder.encode(UtcDateTimes.format(value), StandardCharsets.UTF_8)}" }
|
||||
?: ""
|
||||
|
||||
return webClientBuilder.build()
|
||||
@@ -131,10 +132,10 @@ class DynamicPlanService(webClientBuilderProvider: ObjectProvider<WebClient.Buil
|
||||
.block() ?: ObjectMapper().createArrayNode()
|
||||
}
|
||||
|
||||
fun getAngularMotionFlightLine(satelliteId: Long, time: String): JsonNode =
|
||||
fun getAngularMotionFlightLine(satelliteId: Long, time: LocalDateTime): JsonNode =
|
||||
webClientBuilder.build()
|
||||
.get()
|
||||
.uri(URI.create("$ballisticsServiceUrl/api/angular-motion/satellites/$satelliteId/flight-line?time=${URLEncoder.encode(time, StandardCharsets.UTF_8)}"))
|
||||
.uri(URI.create("$ballisticsServiceUrl/api/angular-motion/satellites/$satelliteId/flight-line?time=${URLEncoder.encode(UtcDateTimes.format(time), StandardCharsets.UTF_8)}"))
|
||||
.retrieve()
|
||||
.onStatus({ status -> status.isError }) { response ->
|
||||
response.bodyToMono(String::class.java)
|
||||
|
||||
+2
-1
@@ -11,6 +11,7 @@ import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.MissionResponseDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.MissionSurveyCalculationResponseDTO
|
||||
import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.ModeResponseDTO
|
||||
import space.nstart.pcp.slots_service.dto.currentplans.CurrentPlansMissionPageDTO
|
||||
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
|
||||
import java.util.UUID
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@@ -25,7 +26,7 @@ class MissionService(webClientBuilderProvider: ObjectProvider<WebClient.Builder>
|
||||
fun plan(sat : Long, tn : LocalDateTime, tk : LocalDateTime) =
|
||||
webClientBuilder.build()
|
||||
.get()
|
||||
.uri("$url/api/missions/modes/satellite?satellite_id=$sat&timeStart=$tn&timeStop=$tk")
|
||||
.uri("$url/api/missions/modes/satellite?satellite_id=$sat&timeStart=${UtcDateTimes.format(tn)}&timeStop=${UtcDateTimes.format(tk)}")
|
||||
.retrieve()
|
||||
.bodyToFlux<ModeResponseDTO>()
|
||||
|
||||
|
||||
+3
-2
@@ -6,6 +6,7 @@ import space.nstart.pcp.slots_service.configuration.CustomErrorException
|
||||
import space.nstart.pcp.slots_service.dto.pdcm.SatellitePdcmAvailabilityDTO
|
||||
import space.nstart.pcp.slots_service.dto.pdcm.SatellitePdcmDetailsDTO
|
||||
import space.nstart.pcp.slots_service.dto.pdcm.SatellitePdcmSummaryDTO
|
||||
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Service
|
||||
@@ -14,7 +15,7 @@ class SatellitePdcmService(
|
||||
private val ballisticsService: BallisticsService
|
||||
) {
|
||||
|
||||
fun satelliteSummaries(): List<SatellitePdcmSummaryDTO> = satelliteSummaries(LocalDateTime.now())
|
||||
fun satelliteSummaries(): List<SatellitePdcmSummaryDTO> = satelliteSummaries(UtcDateTimes.now())
|
||||
|
||||
fun satelliteSummaries(referenceTime: LocalDateTime): List<SatellitePdcmSummaryDTO> {
|
||||
val satellites = satelliteCatalogService.allSatellites()
|
||||
@@ -39,7 +40,7 @@ class SatellitePdcmService(
|
||||
}
|
||||
|
||||
fun satelliteDetails(satelliteId: Long): SatellitePdcmDetailsDTO =
|
||||
satelliteDetails(satelliteId, LocalDateTime.now())
|
||||
satelliteDetails(satelliteId, UtcDateTimes.now())
|
||||
|
||||
fun satelliteDetails(satelliteId: Long, referenceTime: LocalDateTime): SatellitePdcmDetailsDTO {
|
||||
val satellites = satelliteCatalogService.allSatellites()
|
||||
|
||||
+8
-7
@@ -23,6 +23,7 @@ import space.nstart.pcp.pcp_types_lib.dto.satellite.SatelliteSlotProfileDTO
|
||||
import tools.jackson.databind.ObjectMapper
|
||||
import space.nstart.pcp.slots_service.configuration.CustomErrorException
|
||||
import space.nstart.pcp.pcp_types_lib.dto.BookedSlotDetailsDTO
|
||||
import space.nstart.pcp.pcp_types_lib.utils.UtcDateTimes
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Service
|
||||
@@ -47,8 +48,8 @@ class SlotService(webClientBuilderProvider: ObjectProvider<WebClient.Builder>) {
|
||||
UriComponentsBuilder
|
||||
.fromUriString("$url/api/slots/request-cover")
|
||||
.queryParam("requestId", id)
|
||||
.queryParam("timeStart", tn)
|
||||
.queryParam("timeStop", tk)
|
||||
.queryParam("timeStart", UtcDateTimes.format(tn))
|
||||
.queryParam("timeStop", UtcDateTimes.format(tk))
|
||||
.queryParamIfPresent("revSign", java.util.Optional.ofNullable(sign))
|
||||
.queryParam("cov", cov)
|
||||
.queryParamIfPresent("sun", java.util.Optional.ofNullable(sun))
|
||||
@@ -70,7 +71,7 @@ class SlotService(webClientBuilderProvider: ObjectProvider<WebClient.Builder>) {
|
||||
fun plan(id : Long, tn : LocalDateTime, tk : LocalDateTime) =
|
||||
webClientBuilder.build()
|
||||
.get()
|
||||
.uri("$url/api/slots/mission/satellite?satellite_id=$id&timeStart=$tn&timeStop=$tk")
|
||||
.uri("$url/api/slots/mission/satellite?satellite_id=$id&timeStart=${UtcDateTimes.format(tn)}&timeStop=${UtcDateTimes.format(tk)}")
|
||||
.retrieve()
|
||||
.onStatus({ status -> status.isError }) { response ->
|
||||
response.bodyToMono(String::class.java)
|
||||
@@ -82,7 +83,7 @@ class SlotService(webClientBuilderProvider: ObjectProvider<WebClient.Builder>) {
|
||||
fun allSlotsByInterval(id: Long, tn: LocalDateTime, tk: LocalDateTime): List<SlotDTO> =
|
||||
webClientBuilder.build()
|
||||
.get()
|
||||
.uri("$url/api/slots/interval?satelliteId=$id&timeStart=$tn&timeStop=$tk")
|
||||
.uri("$url/api/slots/interval?satelliteId=$id&timeStart=${UtcDateTimes.format(tn)}&timeStop=${UtcDateTimes.format(tk)}")
|
||||
.retrieve()
|
||||
.onStatus({ status -> status.isError }) { response ->
|
||||
response.bodyToMono(String::class.java)
|
||||
@@ -193,8 +194,8 @@ class SlotService(webClientBuilderProvider: ObjectProvider<WebClient.Builder>) {
|
||||
.uri(
|
||||
UriComponentsBuilder
|
||||
.fromUriString("$url/api/slots/booking/search")
|
||||
.queryParamIfPresent("timeStart", java.util.Optional.ofNullable(timeStart))
|
||||
.queryParamIfPresent("timeStop", java.util.Optional.ofNullable(timeStop))
|
||||
.queryParamIfPresent("timeStart", java.util.Optional.ofNullable(timeStart?.let { UtcDateTimes.format(it) }))
|
||||
.queryParamIfPresent("timeStop", java.util.Optional.ofNullable(timeStop?.let { UtcDateTimes.format(it) }))
|
||||
.queryParamIfPresent("requestId", java.util.Optional.ofNullable(requestId?.trim()?.takeIf { it.isNotEmpty() }))
|
||||
.apply {
|
||||
satelliteIds.orEmpty().distinct().forEach { queryParam("satelliteIds", it) }
|
||||
@@ -331,7 +332,7 @@ class SlotService(webClientBuilderProvider: ObjectProvider<WebClient.Builder>) {
|
||||
.uri(
|
||||
UriComponentsBuilder
|
||||
.fromUriString("$url/api/satellite/{satelliteId}/send-ic")
|
||||
.queryParam("time", time)
|
||||
.queryParam("time", UtcDateTimes.format(time))
|
||||
.buildAndExpand(satelliteId)
|
||||
.toUriString()
|
||||
)
|
||||
|
||||
+9
@@ -2,6 +2,7 @@ package space.nstart.pcp.slots_service.service
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.web.reactive.function.client.WebClient
|
||||
import reactor.core.publisher.Mono
|
||||
@@ -11,6 +12,7 @@ import tools.jackson.databind.ObjectMapper
|
||||
|
||||
@Service
|
||||
class StationService(webClientBuilderProvider: ObjectProvider<WebClient.Builder>) {
|
||||
private val log = LoggerFactory.getLogger(StationService::class.java)
|
||||
private val webClientBuilder: WebClient.Builder = webClientBuilderProvider.ifAvailable ?: WebClient.builder()
|
||||
|
||||
@Value("\${settings.stations-service:stations-service}")
|
||||
@@ -36,6 +38,13 @@ class StationService(webClientBuilderProvider: ObjectProvider<WebClient.Builder>
|
||||
fun allStations(): List<StationDTO> =
|
||||
stations().collectList().block() ?: emptyList()
|
||||
|
||||
fun allStationsOrEmpty(): List<StationDTO> =
|
||||
runCatching { allStations() }
|
||||
.onFailure { error ->
|
||||
log.warn("Stations service is unavailable. Map page will be opened without stations.", error)
|
||||
}
|
||||
.getOrDefault(emptyList())
|
||||
|
||||
fun saveStation(request: StationDTO): StationDTO =
|
||||
webClientBuilder.build()
|
||||
.post()
|
||||
|
||||
+12
-14
@@ -53,26 +53,24 @@
|
||||
function nowLocalInput() {
|
||||
const d = new Date();
|
||||
d.setSeconds(0, 0);
|
||||
const offsetMs = d.getTimezoneOffset() * 60000;
|
||||
return new Date(d.getTime() - offsetMs).toISOString().slice(0, 16);
|
||||
return PcpUtcTime.toLocalInputValue(d);
|
||||
}
|
||||
|
||||
function localDateTimeValue() {
|
||||
const value = el('angular-motion-time').value;
|
||||
return value && value.length === 16 ? `${value}:00` : value;
|
||||
return PcpUtcTime.toUtcIsoFromInput(el('angular-motion-time').value);
|
||||
}
|
||||
|
||||
function toDateTimeInputValue(value) {
|
||||
return text(value).slice(0, 16);
|
||||
return PcpUtcTime.toLocalInputValue(value);
|
||||
}
|
||||
|
||||
function normalizeDateTimeSeconds(value) {
|
||||
const textValue = text(value);
|
||||
return textValue && textValue.length === 16 ? `${textValue}:00` : textValue;
|
||||
function dateTimeMillis(value) {
|
||||
const date = PcpUtcTime.parseApiDate(value);
|
||||
return date ? date.getTime() : NaN;
|
||||
}
|
||||
|
||||
function formatDateTime(value) {
|
||||
return text(value, '-').replace('T', ' ');
|
||||
return PcpUtcTime.formatDateTime(value);
|
||||
}
|
||||
|
||||
function formatNumber(value, digits = 6) {
|
||||
@@ -119,11 +117,11 @@
|
||||
const sat = state.selectedSatellite;
|
||||
if (!sat?.availabilityStart || !sat?.availabilityStop) return;
|
||||
|
||||
const current = normalizeDateTimeSeconds(localDateTimeValue());
|
||||
const start = normalizeDateTimeSeconds(sat.availabilityStart);
|
||||
const stop = normalizeDateTimeSeconds(sat.availabilityStop);
|
||||
if (!current || current < start || current > stop) {
|
||||
el('angular-motion-time').value = toDateTimeInputValue(start);
|
||||
const current = dateTimeMillis(localDateTimeValue());
|
||||
const start = dateTimeMillis(sat.availabilityStart);
|
||||
const stop = dateTimeMillis(sat.availabilityStop);
|
||||
if (!Number.isFinite(current) || current < start || current > stop) {
|
||||
el('angular-motion-time').value = toDateTimeInputValue(sat.availabilityStart);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
const timeStop = el('bookings-time-stop')?.value;
|
||||
const requestId = el('bookings-request')?.value;
|
||||
|
||||
if (timeStart) params.set('timeStart', toIsoLocal(timeStart));
|
||||
if (timeStop) params.set('timeStop', toIsoLocal(timeStop));
|
||||
if (timeStart) params.set('timeStart', PcpUtcTime.toUtcIsoFromInput(timeStart));
|
||||
if (timeStop) params.set('timeStop', PcpUtcTime.toUtcIsoFromInput(timeStop));
|
||||
if (requestId) params.set('requestId', requestId);
|
||||
state.selectedSatelliteIds.forEach((id) => params.append('satelliteIds', id));
|
||||
|
||||
@@ -240,13 +240,7 @@
|
||||
}
|
||||
|
||||
function formatDateTime(value) {
|
||||
if (!value) return '—';
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return escapeHtml(value);
|
||||
return date.toLocaleString('ru-RU', {
|
||||
year: '2-digit', month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit', second: '2-digit'
|
||||
});
|
||||
return value ? PcpUtcTime.formatDateTime(value) : '—';
|
||||
}
|
||||
|
||||
function formatNumber(value) {
|
||||
@@ -257,12 +251,7 @@
|
||||
}
|
||||
|
||||
function toDateTimeLocal(date) {
|
||||
const pad = (value) => String(value).padStart(2, '0');
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||
}
|
||||
|
||||
function toIsoLocal(value) {
|
||||
return value && value.length === 16 ? `${value}:00` : value;
|
||||
return PcpUtcTime.toLocalInputValue(date);
|
||||
}
|
||||
|
||||
function showAlert(message, type = 'info') {
|
||||
|
||||
@@ -50,14 +50,10 @@
|
||||
}
|
||||
|
||||
function formatDateTimeInput(value) {
|
||||
if (!value) return '';
|
||||
const match = String(value).match(
|
||||
/^(\d{4})-(\d{2})-(\d{2})[T\s](\d{2}):(\d{2})(?::(\d{2})(?:[.,](\d{1,9}))?)?/
|
||||
);
|
||||
if (!match) return value;
|
||||
|
||||
const [, year, month, day, hour, minute, second = '00', millis = '000'] = match;
|
||||
return `${day}.${month}.${year} ${hour}:${minute}:${second}.${millis.slice(0, 3).padEnd(3, '0')}`;
|
||||
const date = PcpUtcTime.parseApiDate(value);
|
||||
if (!date) return value || '';
|
||||
const pad = (number, length = 2) => String(number).padStart(length, '0');
|
||||
return `${pad(date.getDate())}.${pad(date.getMonth() + 1)}.${date.getFullYear()} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.${pad(date.getMilliseconds(), 3)}`;
|
||||
}
|
||||
|
||||
function parseDateTimeInput(value, fieldName, required = false) {
|
||||
@@ -73,14 +69,14 @@
|
||||
if (displayMatch) {
|
||||
const [, day, month, year, hour, minute, second, millis] = displayMatch;
|
||||
validateDateTimeParts(day, month, year, hour, minute, second, fieldName);
|
||||
return `${year}-${month}-${day}T${hour}:${minute}:${second}.${millis.padEnd(3, '0')}`;
|
||||
return PcpUtcTime.toUtcIsoFromRussianDateTime(`${day}.${month}.${year} ${hour}:${minute}:${second}.${millis.padEnd(3, '0')}`);
|
||||
}
|
||||
|
||||
const isoMatch = text.match(/^(\d{4})-(\d{2})-(\d{2})[T\s](\d{2}):(\d{2})(?::(\d{2})(?:[.,](\d{1,3}))?)?$/);
|
||||
if (isoMatch) {
|
||||
const [, year, month, day, hour, minute, second = '00', millis = '000'] = isoMatch;
|
||||
validateDateTimeParts(day, month, year, hour, minute, second, fieldName);
|
||||
return `${year}-${month}-${day}T${hour}:${minute}:${second}.${millis.padEnd(3, '0')}`;
|
||||
return PcpUtcTime.toUtcIsoFromRussianDateTime(`${day}.${month}.${year} ${hour}:${minute}:${second}.${millis.padEnd(3, '0')}`);
|
||||
}
|
||||
|
||||
throw new Error(`${fieldName}: укажите время в формате dd.mm.yyyy hh:mm:ss.zzz`);
|
||||
|
||||
@@ -19,45 +19,54 @@
|
||||
}
|
||||
|
||||
|
||||
/** получение строки декретного московского времени
|
||||
*/
|
||||
var TDMT = function GetDMTime(e,t)
|
||||
function localDateFromJulianDate(julianDate)
|
||||
{
|
||||
var d = new Cesium.JulianDate();
|
||||
Cesium.JulianDate.addHours(e,3,d)
|
||||
var gregorian = new Cesium.JulianDate.toGregorianDate(d);
|
||||
//var s = new String();
|
||||
if (Math.abs(t._clockViewModel.multiplier)<1)
|
||||
s = STRTOFORMAT(gregorian.hour,2) + ':' + STRTOFORMAT(gregorian.minute,2) + ':' + STRTOFORMAT(gregorian.second,2)+'.'+STRTOFORMAT(Math.round(gregorian.millisecond))
|
||||
else
|
||||
s = STRTOFORMAT(gregorian.hour,2) + ':' + STRTOFORMAT(gregorian.minute,2) + ':' + STRTOFORMAT(gregorian.second,2)+' ДМВ';
|
||||
|
||||
return s;
|
||||
return Cesium.JulianDate.toDate(julianDate);
|
||||
}
|
||||
|
||||
/** получение строки декретной московской даты
|
||||
*/
|
||||
var TDMD = function GetDMDate(e)
|
||||
function localTimeZoneLabel()
|
||||
{
|
||||
var d = new Cesium.JulianDate();
|
||||
Cesium.JulianDate.addHours(e,3,d)
|
||||
var gregorian = new Cesium.JulianDate.toGregorianDate(d);
|
||||
|
||||
s = STRTOFORMAT(gregorian.day,2)+ ' ' + MNTH[gregorian.month-1] + ' ' + STRTOFORMAT(gregorian.year,4);
|
||||
|
||||
return s;
|
||||
try {
|
||||
var parts = new Intl.DateTimeFormat('ru-RU', {timeZoneName: 'short'}).formatToParts(new Date());
|
||||
var part = parts.find(function (item) { return item.type === 'timeZoneName'; });
|
||||
return part && part.value ? part.value : 'лок.';
|
||||
} catch (error) {
|
||||
return 'лок.';
|
||||
}
|
||||
}
|
||||
|
||||
/** получение строки декретного московского даты и времени
|
||||
/** получение строки времени в часовом поясе браузера
|
||||
*/
|
||||
var TDMDT = function GetDMDdateTime(e)
|
||||
var TDMT = function GetLocalTime(e,t)
|
||||
{
|
||||
var date = localDateFromJulianDate(e);
|
||||
if (Math.abs(t._clockViewModel.multiplier)<1)
|
||||
s = STRTOFORMAT(date.getHours(),2) + ':' + STRTOFORMAT(date.getMinutes(),2) + ':' + STRTOFORMAT(date.getSeconds(),2)+'.'+STRTOFORMAT(date.getMilliseconds(),3)
|
||||
else
|
||||
s = STRTOFORMAT(date.getHours(),2) + ':' + STRTOFORMAT(date.getMinutes(),2) + ':' + STRTOFORMAT(date.getSeconds(),2)+' '+localTimeZoneLabel();
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/** получение строки даты в часовом поясе браузера
|
||||
*/
|
||||
var TDMD = function GetLocalDate(e)
|
||||
{
|
||||
var date = localDateFromJulianDate(e);
|
||||
|
||||
s = STRTOFORMAT(date.getDate(),2)+ ' ' + MNTH[date.getMonth()] + ' ' + STRTOFORMAT(date.getFullYear(),4);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/** получение строки даты и времени в часовом поясе браузера
|
||||
*/
|
||||
var TDMDT = function GetLocalDateTime(e)
|
||||
{
|
||||
s = "";
|
||||
var d = new Cesium.JulianDate();
|
||||
Cesium.JulianDate.addHours(e,3,d);
|
||||
var gregorian = new Cesium.JulianDate.toGregorianDate(d);
|
||||
s = gregorian.day+" "+ MNTH[gregorian.month-1]+" "+ gregorian.year+" "+STRTOFORMAT(gregorian.hour,2)+":"+STRTOFORMAT(gregorian.minute,2)+":"+STRTOFORMAT(gregorian.second,2)+" ДМВ";
|
||||
return s;
|
||||
var date = localDateFromJulianDate(e);
|
||||
s = date.getDate()+" "+ MNTH[date.getMonth()]+" "+date.getFullYear()+" "+STRTOFORMAT(date.getHours(),2)+":"+STRTOFORMAT(date.getMinutes(),2)+":"+STRTOFORMAT(date.getSeconds(),2)+" "+localTimeZoneLabel();
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -34,12 +34,7 @@
|
||||
}
|
||||
|
||||
function formatDateTime(value) {
|
||||
if (!value) return '-';
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return value;
|
||||
}
|
||||
return date.toLocaleString('ru-RU');
|
||||
return value ? PcpUtcTime.formatDateTime(value) : '-';
|
||||
}
|
||||
|
||||
function formatDuration(value) {
|
||||
@@ -70,23 +65,12 @@
|
||||
|
||||
function isoDateTime(value) {
|
||||
if (!value) return '';
|
||||
const date = new Date(value);
|
||||
return Number.isNaN(date.getTime()) ? String(value) : date.toISOString();
|
||||
const date = PcpUtcTime.parseApiDate(value);
|
||||
return date ? date.toISOString() : String(value);
|
||||
}
|
||||
|
||||
function formatDateTimeLocal(value) {
|
||||
if (!value) return '';
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return String(value).slice(0, 16);
|
||||
}
|
||||
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}T${hours}:${minutes}`;
|
||||
return PcpUtcTime.toLocalInputValue(value);
|
||||
}
|
||||
|
||||
async function requestJson(url, options) {
|
||||
@@ -464,8 +448,8 @@
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
intervalStart: intervalStart,
|
||||
intervalEnd: intervalEnd,
|
||||
intervalStart: PcpUtcTime.toUtcIsoFromInput(intervalStart),
|
||||
intervalEnd: PcpUtcTime.toUtcIsoFromInput(intervalEnd),
|
||||
satelliteId: null,
|
||||
satelliteIds: satelliteIds,
|
||||
sun: sunValue ? Number(sunValue) : null,
|
||||
@@ -565,9 +549,7 @@
|
||||
}
|
||||
|
||||
function parseDate(value) {
|
||||
if (!value) return null;
|
||||
const date = new Date(value);
|
||||
return Number.isNaN(date.getTime()) ? null : date;
|
||||
return PcpUtcTime.parseApiDate(value);
|
||||
}
|
||||
|
||||
function intervalDurationDays(run) {
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
.angular-motion-page {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.angular-motion-card {
|
||||
min-height: 0;
|
||||
border: 1px solid #dbe4f0;
|
||||
box-shadow: 0 0.25rem 0.75rem rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
.angular-motion-layout {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.angular-motion-layout > [class*="col-"] {
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.angular-motion-section-title {
|
||||
font-weight: 600;
|
||||
color: #1f2a44;
|
||||
@@ -18,8 +33,7 @@
|
||||
}
|
||||
|
||||
.angular-motion-form-body {
|
||||
max-height: calc(100vh - 11rem);
|
||||
overflow: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.angular-motion-map {
|
||||
@@ -123,8 +137,16 @@
|
||||
}
|
||||
|
||||
@media (max-width: 1199.98px) {
|
||||
.angular-motion-page {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.angular-motion-layout > [class*="col-"] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.angular-motion-form-body {
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.angular-motion-table-wrap {
|
||||
|
||||
@@ -475,8 +475,8 @@
|
||||
const body = {
|
||||
satelliteId: Number(state.selectedSatelliteId),
|
||||
station: trimToNull(el('current-plans-create-station')?.value),
|
||||
missionStart,
|
||||
missionStop,
|
||||
missionStart: PcpUtcTime.toUtcIsoFromInput(missionStart),
|
||||
missionStop: PcpUtcTime.toUtcIsoFromInput(missionStop),
|
||||
status: trimToNull(el('current-plans-create-status')?.value) || 'NEW'
|
||||
};
|
||||
|
||||
@@ -741,23 +741,7 @@
|
||||
}
|
||||
|
||||
function formatDateTime(value) {
|
||||
if (!value) return '—';
|
||||
const match = String(value).match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?/);
|
||||
if (match) {
|
||||
const [, year, month, day, hour, minute, second] = match;
|
||||
return `${day}.${month}.${year.slice(2)}, ${hour}:${minute}:${second || '00'}`;
|
||||
}
|
||||
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return escapeHtml(value);
|
||||
return date.toLocaleString('ru-RU', {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
return value ? PcpUtcTime.formatDateTime(value) : '—';
|
||||
}
|
||||
|
||||
function formatNumber(value) {
|
||||
@@ -782,8 +766,7 @@
|
||||
}
|
||||
|
||||
function toDateTimeLocalValue(date) {
|
||||
const pad = (number) => String(number).padStart(2, '0');
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||
return PcpUtcTime.toLocalInputValue(date);
|
||||
}
|
||||
|
||||
function trimToNull(value) {
|
||||
|
||||
@@ -124,7 +124,11 @@
|
||||
}
|
||||
|
||||
function formatValue(value) {
|
||||
return value === null || value === undefined || value === '' ? '-' : String(value).replace('T', ' ');
|
||||
return value === null || value === undefined || value === '' ? '-' : String(value);
|
||||
}
|
||||
|
||||
function formatDateValue(value) {
|
||||
return value === null || value === undefined || value === '' ? '-' : PcpUtcTime.formatDateTime(value);
|
||||
}
|
||||
|
||||
function createRoutesDataSource(run, routes, intervals) {
|
||||
@@ -178,8 +182,8 @@
|
||||
`Run ID: ${formatValue(run.runId)}`,
|
||||
`Request ID: ${formatValue(route.requestId || run.requestId)}`,
|
||||
`Satellite: ${formatValue(route.satelliteId)}`,
|
||||
`Start: ${formatValue(route.startTime)}`,
|
||||
`End: ${formatValue(route.endTime)}`,
|
||||
`Start: ${formatDateValue(route.startTime)}`,
|
||||
`End: ${formatDateValue(route.endTime)}`,
|
||||
`Duration: ${formatValue(route.duration)}`,
|
||||
`Revolution: ${formatValue(route.revolution)}`,
|
||||
`Roll: ${formatValue(route.roll)}`
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
}
|
||||
|
||||
function formatDateTime(value) {
|
||||
return text(value, '-').replace('T', ' ');
|
||||
return value ? PcpUtcTime.formatDateTime(value) : '-';
|
||||
}
|
||||
|
||||
function startOfDay(value) {
|
||||
|
||||
@@ -51,14 +51,11 @@
|
||||
}
|
||||
|
||||
function formatDateTimeLabel(value) {
|
||||
return value ? value.replace('T', ' ') : '-';
|
||||
return value ? PcpUtcTime.formatDateTime(value) : '-';
|
||||
}
|
||||
|
||||
function formatInputDateTime(value) {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
return value.length >= 19 ? value.slice(0, 19) : value;
|
||||
return PcpUtcTime.toLocalInputValue(value, true);
|
||||
}
|
||||
|
||||
function formatAngle(value) {
|
||||
@@ -273,7 +270,7 @@
|
||||
state.selectedGroupId = groupId;
|
||||
renderGroupList();
|
||||
|
||||
const query = requestedTime ? `?time=${encodeURIComponent(requestedTime)}` : '';
|
||||
const query = requestedTime ? `?time=${encodeURIComponent(PcpUtcTime.toUtcIsoFromInput(requestedTime))}` : '';
|
||||
const details = await requestJson(`${apiBase}/${groupId}${query}`);
|
||||
renderDetails(details);
|
||||
}
|
||||
|
||||
@@ -971,13 +971,13 @@ function renderSlotsTable() {
|
||||
return (aValue - bValue) * direction;
|
||||
|
||||
case 'tn':
|
||||
aValue = new Date(a.tn).getTime();
|
||||
bValue = new Date(b.tn).getTime();
|
||||
aValue = PcpUtcTime.parseApiDate(a.tn)?.getTime() ?? 0;
|
||||
bValue = PcpUtcTime.parseApiDate(b.tn)?.getTime() ?? 0;
|
||||
return (aValue - bValue) * direction;
|
||||
|
||||
case 'tk':
|
||||
aValue = new Date(a.tk).getTime();
|
||||
bValue = new Date(b.tk).getTime();
|
||||
aValue = PcpUtcTime.parseApiDate(a.tk)?.getTime() ?? 0;
|
||||
bValue = PcpUtcTime.parseApiDate(b.tk)?.getTime() ?? 0;
|
||||
return (aValue - bValue) * direction;
|
||||
|
||||
case 'duration':
|
||||
@@ -1078,29 +1078,23 @@ function createSlotRowHTML(slot, index) {
|
||||
// Форматирование даты и времени
|
||||
function formatDateTime(dateTimeString) {
|
||||
if (!dateTimeString) return '-';
|
||||
|
||||
try {
|
||||
const date = new Date(dateTimeString);
|
||||
return date.toLocaleString('ru-RU', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Ошибка форматирования даты:', e);
|
||||
return dateTimeString;
|
||||
}
|
||||
return PcpUtcTime.formatDateTime(dateTimeString, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
// Расчет длительности в минутах
|
||||
function calculateDuration(tn, tk) {
|
||||
if (!tn || !tk) return 0;
|
||||
|
||||
const startDate = new Date(tn);
|
||||
const endDate = new Date(tk);
|
||||
const startDate = PcpUtcTime.parseApiDate(tn);
|
||||
const endDate = PcpUtcTime.parseApiDate(tk);
|
||||
if (!startDate || !endDate) return 0;
|
||||
|
||||
return (endDate - startDate) / (1000 * 60); // в минутах
|
||||
}
|
||||
@@ -1206,12 +1200,14 @@ function updateSlotsSummary() {
|
||||
).size;
|
||||
|
||||
// Период покрытия
|
||||
const startTimes = currentSlotsData.map(slot => new Date(slot.tn));
|
||||
const endTimes = currentSlotsData.map(slot => new Date(slot.tk));
|
||||
const earliest = new Date(Math.min(...startTimes));
|
||||
const latest = new Date(Math.max(...endTimes));
|
||||
const startTimes = currentSlotsData.map(slot => PcpUtcTime.parseApiDate(slot.tn)).filter(Boolean);
|
||||
const endTimes = currentSlotsData.map(slot => PcpUtcTime.parseApiDate(slot.tk)).filter(Boolean);
|
||||
const earliest = startTimes.length ? new Date(Math.min(...startTimes)) : null;
|
||||
const latest = endTimes.length ? new Date(Math.max(...endTimes)) : null;
|
||||
|
||||
const period = `${earliest.toLocaleDateString('ru-RU')} - ${latest.toLocaleDateString('ru-RU')}`;
|
||||
const period = earliest && latest
|
||||
? `${earliest.toLocaleDateString('ru-RU')} - ${latest.toLocaleDateString('ru-RU')}`
|
||||
: '-';
|
||||
|
||||
// Обновляем элементы
|
||||
document.getElementById('totalSlotsCount').textContent = totalSlots;
|
||||
|
||||
@@ -22,12 +22,7 @@
|
||||
}
|
||||
|
||||
function formatDateTime(value) {
|
||||
if (!value) return '';
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return value;
|
||||
}
|
||||
return date.toLocaleString('ru-RU');
|
||||
return value ? PcpUtcTime.formatDateTime(value) : '';
|
||||
}
|
||||
|
||||
function formatNumber(value, digits = 3) {
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
}
|
||||
|
||||
function formatDateTimeLabel(value) {
|
||||
return value ? value.replace('T', ' ') : '-';
|
||||
return value ? PcpUtcTime.formatDateTime(value) : '-';
|
||||
}
|
||||
|
||||
function pad(value, length = 2) {
|
||||
@@ -69,18 +69,11 @@
|
||||
}
|
||||
|
||||
function formatDateTimeInput(value) {
|
||||
if (!value) {
|
||||
return '';
|
||||
const date = PcpUtcTime.parseApiDate(value);
|
||||
if (!date) {
|
||||
return value || '';
|
||||
}
|
||||
const match = String(value).match(
|
||||
/^(\d{4})-(\d{2})-(\d{2})[T\s](\d{2}):(\d{2})(?::(\d{2})(?:[.,](\d{1,9}))?)?/
|
||||
);
|
||||
if (!match) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const [, year, month, day, hour, minute, second = '00', millis = '000'] = match;
|
||||
return `${day}.${month}.${year} ${hour}:${minute}:${second}.${millis.slice(0, 3).padEnd(3, '0')}`;
|
||||
return `${pad(date.getDate())}.${pad(date.getMonth() + 1)}.${date.getFullYear()} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.${pad(date.getMilliseconds(), 3)}`;
|
||||
}
|
||||
|
||||
function parseSendInitialConditionsTime() {
|
||||
@@ -121,7 +114,8 @@
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
text: value
|
||||
text: value,
|
||||
iso: PcpUtcTime.toUtcIsoFromRussianDateTime(value)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -154,7 +148,7 @@
|
||||
throw new Error(`${fieldName}: некорректная дата или время`);
|
||||
}
|
||||
|
||||
return `${year}-${month}-${day}T${hour}:${minute}:${second}.${millisecond}`;
|
||||
return PcpUtcTime.toUtcIsoFromRussianDateTime(text);
|
||||
}
|
||||
|
||||
function parseRequiredNumber(inputId, fieldName) {
|
||||
@@ -436,7 +430,7 @@
|
||||
renderSendInitialConditionsState(state.details, true);
|
||||
showAlert('');
|
||||
try {
|
||||
await requestJson(`${apiBase}/${state.selectedSatelliteId}/send-ic?time=${encodeURIComponent(time.text)}`, {method: 'POST'});
|
||||
await requestJson(`${apiBase}/${state.selectedSatelliteId}/send-ic?time=${encodeURIComponent(time.iso)}`, {method: 'POST'});
|
||||
showAlert('Начальные условия спутника переданы в slot-service', 'success');
|
||||
} catch (error) {
|
||||
showAlert(error.message || 'Не удалось передать начальные условия в slot-service');
|
||||
|
||||
@@ -480,9 +480,7 @@
|
||||
}
|
||||
|
||||
function parsePlanDate(value) {
|
||||
if (!value) return null;
|
||||
const date = new Date(value);
|
||||
return Number.isNaN(date.getTime()) ? null : date;
|
||||
return PcpUtcTime.parseApiDate(value);
|
||||
}
|
||||
|
||||
function startOfToday() {
|
||||
@@ -491,8 +489,7 @@
|
||||
}
|
||||
|
||||
function toDatetimeLocalValue(date) {
|
||||
const pad = (value) => String(value).padStart(2, '0');
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||
return PcpUtcTime.toLocalInputValue(date);
|
||||
}
|
||||
|
||||
function timelineTicks(from, to) {
|
||||
@@ -632,9 +629,7 @@
|
||||
|
||||
function formatDateTime(value) {
|
||||
if (!value) return '—';
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return escapeHtml(value);
|
||||
return date.toLocaleString('ru-RU', {
|
||||
const formatted = PcpUtcTime.formatDateTime(value, {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
@@ -642,6 +637,7 @@
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
return formatted === '—' ? escapeHtml(value) : formatted;
|
||||
}
|
||||
|
||||
function formatAxisTick(value) {
|
||||
|
||||
@@ -235,8 +235,7 @@
|
||||
}
|
||||
|
||||
function formatDateTime(value) {
|
||||
if (!value) return '—';
|
||||
return String(value).replace('T', ' ');
|
||||
return value ? PcpUtcTime.formatDateTime(value) : '—';
|
||||
}
|
||||
|
||||
function formatNumber(value, fractionDigits) {
|
||||
|
||||
@@ -64,7 +64,9 @@
|
||||
return;
|
||||
}
|
||||
const minTime = maxEpoch();
|
||||
if (minTime && time < minTime) {
|
||||
const calculationDate = parseCalculationInput();
|
||||
const minDate = PcpUtcTime.parseApiDate(minTime);
|
||||
if (calculationDate && minDate && calculationDate.getTime() < minDate.getTime()) {
|
||||
showAlert(`Время расчета должно быть не раньше максимальной эпохи TLE: ${formatDateTime(minTime)}.`, 'warning');
|
||||
return;
|
||||
}
|
||||
@@ -145,8 +147,9 @@
|
||||
const input = el('tle-comparison-time');
|
||||
const minTime = maxEpoch();
|
||||
if (!input || !minTime || !input.value) return;
|
||||
const time = getCalculationTime();
|
||||
if (time && time < minTime) {
|
||||
const time = parseCalculationInput();
|
||||
const minDate = PcpUtcTime.parseApiDate(minTime);
|
||||
if (time && minDate && time.getTime() < minDate.getTime()) {
|
||||
showAlert(`Время расчета должно быть не раньше максимальной эпохи TLE: ${formatDateTime(minTime)}.`, 'warning');
|
||||
}
|
||||
}
|
||||
@@ -186,13 +189,20 @@
|
||||
}
|
||||
|
||||
function maxEpoch() {
|
||||
if (!state.first?.time || !state.second?.time) return null;
|
||||
return state.first.time >= state.second.time ? state.first.time : state.second.time;
|
||||
const first = PcpUtcTime.parseApiDate(state.first?.time);
|
||||
const second = PcpUtcTime.parseApiDate(state.second?.time);
|
||||
if (!first || !second) return null;
|
||||
return first.getTime() >= second.getTime() ? state.first.time : state.second.time;
|
||||
}
|
||||
|
||||
function parseCalculationInput() {
|
||||
const value = el('tle-comparison-time')?.value;
|
||||
return value ? new Date(value) : null;
|
||||
}
|
||||
|
||||
function getCalculationTime() {
|
||||
const value = el('tle-comparison-time')?.value;
|
||||
return value ? value : null;
|
||||
return value ? PcpUtcTime.toUtcIsoFromInput(value) : null;
|
||||
}
|
||||
|
||||
function getTleText(side) {
|
||||
@@ -225,13 +235,11 @@
|
||||
}
|
||||
|
||||
function toDateTimeLocalValue(value) {
|
||||
if (!value) return '';
|
||||
return String(value).slice(0, 19);
|
||||
return PcpUtcTime.toLocalInputValue(value, true);
|
||||
}
|
||||
|
||||
function formatDateTime(value) {
|
||||
if (!value) return '';
|
||||
return String(value).replace('T', ' ');
|
||||
return value ? PcpUtcTime.formatDateTime(value) : '';
|
||||
}
|
||||
|
||||
function formatNumber(value) {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
(function () {
|
||||
function parseApiDate(value) {
|
||||
if (!value) return null;
|
||||
const text = String(value).trim();
|
||||
if (!text) return null;
|
||||
const isoLike = /^\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}/.test(text);
|
||||
const hasZone = /(?:Z|[+-]\d{2}:?\d{2})$/.test(text);
|
||||
const normalized = isoLike && !hasZone ? `${text.replace(' ', 'T')}Z` : text;
|
||||
const date = new Date(normalized);
|
||||
return Number.isNaN(date.getTime()) ? null : date;
|
||||
}
|
||||
|
||||
function pad(value, length = 2) {
|
||||
return String(value).padStart(length, '0');
|
||||
}
|
||||
|
||||
function toLocalInputValue(value, includeSeconds = false) {
|
||||
const date = value instanceof Date ? value : parseApiDate(value);
|
||||
if (!date) return '';
|
||||
const base = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||
return includeSeconds ? `${base}:${pad(date.getSeconds())}` : base;
|
||||
}
|
||||
|
||||
function toUtcIsoFromInput(value) {
|
||||
if (!value) return '';
|
||||
const date = new Date(value);
|
||||
return Number.isNaN(date.getTime()) ? '' : date.toISOString();
|
||||
}
|
||||
|
||||
function toUtcIsoFromRussianDateTime(value) {
|
||||
const match = String(value || '').trim().match(/^(\d{2})\.(\d{2})\.(\d{4}) (\d{2}):(\d{2}):(\d{2})\.(\d{1,3})$/);
|
||||
if (!match) return '';
|
||||
const [, day, month, year, hour, minute, second, millisecond] = match;
|
||||
const date = new Date(
|
||||
Number(year),
|
||||
Number(month) - 1,
|
||||
Number(day),
|
||||
Number(hour),
|
||||
Number(minute),
|
||||
Number(second),
|
||||
Number(millisecond.padEnd(3, '0'))
|
||||
);
|
||||
const valid = date.getFullYear() === Number(year)
|
||||
&& date.getMonth() === Number(month) - 1
|
||||
&& date.getDate() === Number(day)
|
||||
&& date.getHours() === Number(hour)
|
||||
&& date.getMinutes() === Number(minute)
|
||||
&& date.getSeconds() === Number(second);
|
||||
return valid ? date.toISOString() : '';
|
||||
}
|
||||
|
||||
function formatDateTime(value, options = {}) {
|
||||
const date = value instanceof Date ? value : parseApiDate(value);
|
||||
if (!date) return options.fallback || '-';
|
||||
return date.toLocaleString(options.locale || 'ru-RU', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: options.seconds === false ? undefined : '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
window.PcpUtcTime = {
|
||||
parseApiDate,
|
||||
toLocalInputValue,
|
||||
toUtcIsoFromInput,
|
||||
toUtcIsoFromRussianDateTime,
|
||||
formatDateTime
|
||||
};
|
||||
})();
|
||||
@@ -1,10 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:th="http://www.thymeleaf.org">
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:th="http://www.thymeleaf.org">
|
||||
<head th:fragment="head">
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet"
|
||||
href="/webjars/bootstrap/5.3.0/css/bootstrap.min.css"/>
|
||||
<script src="/utc_time.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
@@ -1268,23 +1268,22 @@
|
||||
}
|
||||
|
||||
|
||||
// Функция для преобразования datetime-local в ISO
|
||||
// Преобразует локальное datetime-local браузера в UTC ISO для API
|
||||
function toISOString(datetimeLocal) {
|
||||
if (!datetimeLocal) {
|
||||
return null; // или можно вернуть текущее время по умолчанию
|
||||
return null;
|
||||
}
|
||||
|
||||
// datetime-local возвращает строку в формате "YYYY-MM-DDTHH:mm"
|
||||
// Создаем Date объект из этой строки
|
||||
let date = new Date(datetimeLocal);
|
||||
date.setHours(date.getHours() + 3);
|
||||
const date = new Date(datetimeLocal);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
throw new Error('Некорректный формат даты');
|
||||
}
|
||||
|
||||
// Возвращаем в ISO формате
|
||||
return date.toISOString().split('Z')[0];
|
||||
return date.toISOString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function covToReq(show, req_id){
|
||||
|
||||
// Сбрасываем чекбокс видимости всех слотов
|
||||
|
||||
Reference in New Issue
Block a user