Формат времени в 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
@@ -20,7 +20,7 @@ import type { MapPolygon, Tgu2DMapScene } from "../tgu-map-2d/model/mapTypes";
import type { Tgu2DMapSelection } from "../tgu-map-2d/model/mapSelectionTypes";
import { Tgu2DMapView } from "../tgu-map-2d/Tgu2DMapView";
import { MapObjectInspector } from "../tgu-map-2d/MapObjectInspector";
import { formatLocalDateTime, parseLocal, toLocalInputValue, toLocalIso } from "../../utils/localTime";
import { formatLocalDateTime, parseLocal, toLocalInputValue, toUtcIso } from "../../utils/localTime";
import { formatVolume } from "../../utils/dataVolume";
type TguComplexPlanTabProps = {
@@ -173,7 +173,7 @@ export function TguComplexPlanTab({ range, platforms }: TguComplexPlanTabProps)
let cancelled = false;
setRequestsLoading(true);
setRequestsError(undefined);
fetchRequestsForMap({ endFrom: toLocalIso(fromMs), beginTo: toLocalIso(toMs) })
fetchRequestsForMap({ endFrom: toUtcIso(fromMs), beginTo: toUtcIso(toMs) })
.then((result) => {
if (!cancelled) setRequestItems(result.items);
})
@@ -18,7 +18,7 @@ import {
import { abandonDraft, createDraft, fetchDraftPlans } from "../../api/tguPlanApi";
import { TguApiError } from "../../model/tguTypes";
import type { TguPlanUi } from "../../model/timelineTypes";
import { parseLocal, toLocalIso } from "../../utils/localTime";
import { parseLocal, toUtcIso } from "../../utils/localTime";
import { Tgu2DMapView } from "../tgu-map-2d/Tgu2DMapView";
import { parseWktPolygon } from "../tgu-map-2d/geometry/wktParser";
import { buildStationMarkers } from "../tgu-map-2d/Tgu2DMapLayers";
@@ -360,8 +360,8 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
useEffect(() => {
const controller = new AbortController();
void fetchRequestsForMap({
endFrom: toLocalIso(window.fromMs),
beginTo: toLocalIso(window.toMs)
endFrom: toUtcIso(window.fromMs),
beginTo: toUtcIso(window.toMs)
}).then((result) => {
if (!controller.signal.aborted) {
setRequestItems(result.items);
@@ -458,7 +458,7 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
void buildSurveyRoute({
satelliteId: numericNorad,
// Окно задано явно (начало/конец) — строим вперёд от начала на выбранную длительность.
time: toLocalIso(routeWindow.startMs),
time: toUtcIso(routeWindow.startMs),
durationSeconds,
roll: routeRoll,
captureAngle: DEFAULT_CAPTURE_ANGLE_DEG,
@@ -617,9 +617,9 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
noradId: noradId ? Number(noradId) : 0,
stationId: Number.isFinite(stationNum) ? stationNum : 0,
revolution: work.revolution ?? 0,
onStart: { time: toLocalIso(work.startMs).slice(0, 19), elevation: 0, azimuth: 0, range: 0 },
onMaximum: { time: toLocalIso(midMs).slice(0, 19), elevation: 0, azimuth: 0, range: 0 },
onStop: { time: toLocalIso(work.endMs).slice(0, 19), elevation: 0, azimuth: 0, range: 0 },
onStart: { time: toUtcIso(work.startMs), elevation: 0, azimuth: 0, range: 0 },
onMaximum: { time: toUtcIso(midMs), elevation: 0, azimuth: 0, range: 0 },
onStop: { time: toUtcIso(work.endMs), elevation: 0, azimuth: 0, range: 0 },
duration: Math.max(1, (work.endMs - work.startMs) / 1000)
};
editDropPrefillRef.current = { zone, surveyIds: work.surveyIds ?? [] };
@@ -744,15 +744,15 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
if (editWork) await deleteEditedModeFromDraft(draftId, draftExistedBefore, editWork, "SURVEY");
savedRoute = await saveSurveyRoute(draftId, {
satelliteId: numericNorad,
time: toLocalIso(routeWindow.startMs),
time: toUtcIso(routeWindow.startMs),
durationSeconds: routeDurationSeconds,
roll: routeRoll,
captureAngle: DEFAULT_CAPTURE_ANGLE_DEG,
revolution: selectedShootPass.revolution ?? 0,
anchor: "FORWARD",
// Окно выбранного плана — чтобы при автосоздании миссии/плана задать корректный интервал.
planStart: toLocalIso(selectedPlan.startMs),
planEnd: toLocalIso(selectedPlan.endMs)
planStart: toUtcIso(selectedPlan.startMs),
planEnd: toUtcIso(selectedPlan.endMs)
});
setSurveyRoute(savedRoute);
// Ждём перечитывания planModes ДО снятия editingWorkId ниже: иначе на миг editingModeId
@@ -853,8 +853,8 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
zoneStop: selectedZone.onStop.time.slice(0, 19),
surveyModeIds: [...selectedDropSurveyIds],
// Окно выбранного плана — для автосоздания миссии/плана с корректным интервалом.
planStart: toLocalIso(selectedPlan.startMs),
planEnd: toLocalIso(selectedPlan.endMs)
planStart: toUtcIso(selectedPlan.startMs),
planEnd: toUtcIso(selectedPlan.endMs)
});
savedDropId = savedDrop.id;
savedDropVolumeMb = savedDrop.volumeMb;
@@ -13,7 +13,7 @@ import { Tgu2DMapSidebar } from "./Tgu2DMapSidebar";
import { Tgu2DMapToolbar } from "./Tgu2DMapToolbar";
import { Tgu2DMapView } from "./Tgu2DMapView";
import { getTgu2DMapIntervalState, TGU_2D_MAP_INTERVAL_WARNING } from "./tgu2DMapInterval";
import { parseLocal, toLocalInputValue, toLocalIso } from "../../utils/localTime";
import { parseLocal, toLocalInputValue, toUtcIso } from "../../utils/localTime";
type Tgu2DMapTabProps = {
range: TimelineRange;
@@ -82,8 +82,8 @@ export function Tgu2DMapTab({
try {
const result = await fetchRequestsForMap(
{
endFrom: toLocalIso(fromMs),
beginTo: toLocalIso(toMs)
endFrom: toUtcIso(fromMs),
beginTo: toUtcIso(toMs)
},
(loaded, total) => {
setRequestsState((current) => ({ ...current, loadedCount: loaded, totalItems: total }));
@@ -22,10 +22,10 @@ describe("futureZrvOptions", () => {
]);
});
it("produces naive (no zone) start/stop strings", () => {
it("produces UTC start/stop strings", () => {
const [option] = futureZrvOptions([zrv("2026-06-03T13:00:00", "2026-06-03T13:10:00")], NOW);
expect(option.start).toBe("2026-06-03T13:00:00");
expect(option.stop).toBe("2026-06-03T13:10:00");
expect(option.start).toBe(new Date(2026, 5, 3, 13, 0, 0).toISOString());
expect(option.stop).toBe(new Date(2026, 5, 3, 13, 10, 0).toISOString());
expect(option.kppId).toBe("KPP-1");
});
});
@@ -33,11 +33,11 @@ describe("futureZrvOptions", () => {
describe("buildChainPlanRequest", () => {
const start: ZrvOption = {
id: "a", startMs: Date.UTC(2026, 5, 3, 13, 0, 0), endMs: Date.UTC(2026, 5, 3, 13, 10, 0),
start: "2026-06-03T13:00:00", stop: "2026-06-03T13:10:00", kppId: "KPP-1"
start: "2026-06-03T13:00:00.000Z", stop: "2026-06-03T13:10:00.000Z", kppId: "KPP-1"
};
const end: ZrvOption = {
id: "b", startMs: Date.UTC(2026, 5, 3, 15, 0, 0), endMs: Date.UTC(2026, 5, 3, 15, 10, 0),
start: "2026-06-03T15:00:00", stop: "2026-06-03T15:10:00", kppId: "KPP-2"
start: "2026-06-03T15:00:00.000Z", stop: "2026-06-03T15:10:00.000Z", kppId: "KPP-2"
};
it("spans from start zone begin to end zone finish and keeps the start kpp", () => {
@@ -45,16 +45,16 @@ describe("buildChainPlanRequest", () => {
expect(req).toEqual({
spacecraftId: "25544",
basePlanId: "base-1",
windowStart: "2026-06-03T13:00:00",
windowStop: "2026-06-03T15:10:00",
windowStart: "2026-06-03T13:00:00.000Z",
windowStop: "2026-06-03T15:10:00.000Z",
windowKppId: "KPP-1"
});
});
it("allows a single-zone window (start == end zone)", () => {
const req = buildChainPlanRequest("25544", null, start, start);
expect(req.windowStart).toBe("2026-06-03T13:00:00");
expect(req.windowStop).toBe("2026-06-03T13:10:00");
expect(req.windowStart).toBe("2026-06-03T13:00:00.000Z");
expect(req.windowStop).toBe("2026-06-03T13:10:00.000Z");
expect(req.basePlanId).toBeNull();
});
@@ -1,9 +1,9 @@
import { parseLocal, toLocalIso } from "../../utils/localTime";
import { parseLocal, toUtcIso } from "../../utils/localTime";
import type { CreateChainPlanRequest, ZrvWindowResponse } from "../../api/tguPlanApi";
/**
* Опция выбора ЗРВ в диалоге «Создать план». Из стартовой зоны берутся начало окна и КПП, из
* конечной конец окна. `start`/`stop` naive (ISO без зоны, локальное), как ждёт `LocalDateTime` бэка;
* конечной конец окна. `start`/`stop` UTC ISO строки для API;
* `startMs`/`endMs` для сортировки/сравнения и подсветки на таймлайне.
*/
export type ZrvOption = {
@@ -11,9 +11,9 @@ export type ZrvOption = {
id: string;
startMs: number;
endMs: number;
/** Naive (локальное) начала зоны. */
/** UTC ISO начала зоны. */
start: string;
/** Naive (локальное) конца зоны. */
/** UTC ISO конца зоны. */
stop: string;
kppId: string;
};
@@ -28,8 +28,8 @@ export function futureZrvOptions(windows: ZrvWindowResponse[], nowMs: number): Z
id: `${zrv.kppId}|${zrv.startTime}|${zrv.endTime}`,
startMs,
endMs,
start: toNaive(startMs),
stop: toNaive(endMs),
start: toApiUtc(startMs),
stop: toApiUtc(endMs),
kppId: zrv.kppId
};
})
@@ -60,6 +60,6 @@ export function buildChainPlanRequest(
};
}
function toNaive(ms: number): string {
return toLocalIso(ms);
function toApiUtc(ms: number): string {
return toUtcIso(ms);
}
@@ -9,7 +9,7 @@ import type { RequestAtPoint } from "./RequestDetailsPanel";
import type { Tgu2DMapSelection } from "../tgu-map-2d/model/mapSelectionTypes";
import type { GeoPoint, MapPolygon } from "../tgu-map-2d/model/mapTypes";
import { generateUuid } from "../../utils/uuid";
import { parseLocal, toLocalInputValue, toLocalIso } from "../../utils/localTime";
import { parseLocal, toLocalInputValue, toUtcIso } from "../../utils/localTime";
import { DateTime24Field } from "../../components/timeFields";
import { RequestCreatePanel, type RequestFormSubmit } from "./RequestCreatePanel";
import { RequestDetailsPanel } from "./RequestDetailsPanel";
@@ -82,7 +82,7 @@ export function TguRequestsTab({ range, invalidRange, platforms }: TguRequestsTa
setRequestsState((current) => ({ ...current, loading: true, error: undefined, loadedCount: 0 }));
try {
const result = await fetchRequestsForMap(
{ endFrom: toLocalIso(fromMs), beginTo: toLocalIso(toMs) },
{ endFrom: toUtcIso(fromMs), beginTo: toUtcIso(toMs) },
(loaded, total) => setRequestsState((current) => ({ ...current, loadedCount: loaded, totalItems: total }))
);
setRequestsState((current) => ({
@@ -204,8 +204,8 @@ export function TguRequestsTab({ range, invalidRange, platforms }: TguRequestsTa
name: form.name,
geometry,
importance: form.importance,
beginDateTime: toLocalIso(parseLocal(form.beginDateTime)),
endDateTime: toLocalIso(parseLocal(form.endDateTime)),
beginDateTime: toUtcIso(parseLocal(form.beginDateTime)),
endDateTime: toUtcIso(parseLocal(form.endDateTime)),
kpp: form.kpp,
highPriorityTransmit: form.highPriorityTransmit,
optics: form.optics,
@@ -23,7 +23,7 @@ import type { Tgu2DMapLayersState } from "../tgu-map-2d/model/mapLayerTypes";
import type { Tgu2DMapSelection } from "../tgu-map-2d/model/mapSelectionTypes";
import { MapObjectInspector } from "../tgu-map-2d/MapObjectInspector";
import { Tgu2DMapView } from "../tgu-map-2d/Tgu2DMapView";
import { formatLocalDateTimeSeconds, parseLocal, toLocalInputValue, toLocalIso } from "../../utils/localTime";
import { formatLocalDateTimeSeconds, parseLocal, toLocalInputValue, toUtcIso } from "../../utils/localTime";
import { SlotDaySelector, type SlotDay } from "./SlotDaySelector";
import { PassStepper } from "./PassStepper";
import { groupPasses } from "./slotPasses";
@@ -469,7 +469,7 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) {
setRequestsLoading(true);
setRequestsError(undefined);
try {
const result = await fetchRequestsForMap({ endFrom: toLocalIso(fromMs), beginTo: toLocalIso(toMs) });
const result = await fetchRequestsForMap({ endFrom: toUtcIso(fromMs), beginTo: toUtcIso(toMs) });
setRequestItems(result.items);
setRequestsTotal(result.totalItems);
setRequestsTruncated(result.truncated);
@@ -730,8 +730,8 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) {
const result = await fetchRequestSlots(
{
requestId: selectedRequestId,
timeStart: toLocalIso(parsedRange.fromMs),
timeStop: toLocalIso(parsedRange.toMs),
timeStart: toUtcIso(parsedRange.fromMs),
timeStop: toUtcIso(parsedRange.toMs),
revSign: revSign || undefined,
// cov=true (режим покрытия) ровно когда задана стратегия — это путь «Подобрать
// покрытие». «Показать все слоты» идёт без стратегии и получает полный набор кандидатов,