Солнечное и лунное время

Работа с астрономическим временем — одна из сложных задач при разработке календарей, систем навигации, приложений для астрономии, погодных сервисов и геолокационных платформ. Библиотека Luxon не содержит встроенных средств вычисления солнечного и лунного времени, однако предоставляет мощный API для работы с часовыми поясами, интервалами времени, локалями и высокоточной обработкой дат. Благодаря этому Luxon удобно использовать совместно со специализированными астрономическими библиотеками.

Особенности солнечного времени

Солнечное время определяется положением Солнца относительно конкретной точки на поверхности Земли. В прикладной разработке чаще всего используются:

  • время восхода;
  • время заката;
  • солнечный полдень;
  • гражданские сумерки;
  • навигационные сумерки;
  • астрономические сумерки.

В отличие от стандартного гражданского времени, солнечное зависит от:

  • географической широты;
  • географической долготы;
  • даты;
  • часового пояса;
  • сезонного смещения;
  • перехода на летнее время.

Luxon особенно полезен именно на этапе обработки этих данных.


Установка Luxon

npm install luxon

Подключение:

import { DateTime } from "luxon";

Представление времени в Luxon

Главный объект библиотеки — DateTime.

Создание даты:

const dt = DateTime.now();

console.log(dt.toISO());

Дата с часовым поясом:

const dt = DateTime.now().setZone("Asia/Almaty");

console.log(dt.toString());

Luxon использует стандарт IANA Time Zone Database, поэтому корректно обрабатывает:

  • DST;
  • смещения UTC;
  • исторические изменения времени;
  • локальные особенности региона.

Солнечное время через библиотеку SunCalc

Для вычисления солнечного времени Luxon обычно объединяют с библиотекой SunCalc.

Установка:

npm install suncalc

Подключение:

import { DateTime } from "luxon";
import SunCalc from "suncalc";

Получение времени восхода и заката

Пример вычисления:

const latitude = 49.8028;
const longitude = 73.0877;

const date = new Date();

const times = SunCalc.getTimes(date, latitude, longitude);

console.log(times.sunrise);
console.log(times.sunset);

SunCalc возвращает обычные объекты Date. Для дальнейшей обработки удобно преобразовать их в DateTime.


Конвертация в Luxon DateTime

const sunrise = DateTime.fromJSDate(times.sunrise)
    .setZone("Asia/Almaty");

const sunset = DateTime.fromJSDate(times.sunset)
    .setZone("Asia/Almaty");

console.log(sunrise.toFormat("HH:mm"));
console.log(sunset.toFormat("HH:mm"));

Работа с часовыми поясами

Астрономические вычисления всегда чувствительны к часовому поясу.

Пример:

const sunriseUtc = DateTime.fromJSDate(times.sunrise, {
    zone: "utc"
});

const localSunrise = sunriseUtc.setZone("Asia/Almaty");

console.log(localSunrise.toString());

Luxon автоматически:

  • учитывает UTC offset;
  • применяет DST;
  • корректно преобразует дату между зонами.

Форматирование солнечного времени

Luxon предоставляет гибкое форматирование.

Краткий формат

console.log(sunrise.toFormat("HH:mm"));

Полный формат

console.log(
    sunrise.toFormat("dd LLL yyyy HH:mm:ss")
);

Локализованный вывод

console.log(
    sunrise.setLocale("ru").toLocaleString(DateTime.DATETIME_FULL)
);

Солнечный полдень

SunCalc возвращает время максимального положения Солнца.

const solarNoon = DateTime
    .fromJSDate(times.solarNoon)
    .setZone("Asia/Almaty");

console.log(solarNoon.toFormat("HH:mm:ss"));

Гражданские сумерки

Гражданские сумерки используются:

  • в авиации;
  • в фотографии;
  • в системах освещения;
  • в погодных сервисах.
const dawn = DateTime
    .fromJSDate(times.dawn)
    .setZone("Asia/Almaty");

const dusk = DateTime
    .fromJSDate(times.dusk)
    .setZone("Asia/Almaty");

console.log(dawn.toFormat("HH:mm"));
console.log(dusk.toFormat("HH:mm"));

Навигационные и астрономические сумерки

const nauticalDawn = DateTime
    .fromJSDate(times.nauticalDawn)
    .setZone("Asia/Almaty");

const astronomicalDawn = DateTime
    .fromJSDate(times.nightEnd)
    .setZone("Asia/Almaty");

console.log(nauticalDawn.toISO());
console.log(astronomicalDawn.toISO());

Продолжительность светового дня

Luxon умеет вычислять интервалы времени.

const daylight = sunset.diff(sunrise, [
    "hours",
    "minutes"
]);

console.log(daylight.toObject());

Результат:

{
  hours: 14,
  minutes: 37
}

Интервалы солнечного времени

Для работы с периодами используется Interval.

import { Interval } from "luxon";

const daylightInterval = Interval.fromDateTimes(
    sunrise,
    sunset
);

console.log(daylightInterval.length("hours"));

Проверка попадания времени в световой день

const current = DateTime.now().setZone("Asia/Almaty");

const isDaylight = daylightInterval.contains(current);

console.log(isDaylight);

Лунное время

SunCalc поддерживает:

  • восход Луны;
  • заход Луны;
  • фазу Луны;
  • положение Луны;
  • освещённость Луны.

Получение времени восхода Луны

const moonTimes = SunCalc.getMoonTimes(
    new Date(),
    latitude,
    longitude
);

console.log(moonTimes.rise);
console.log(moonTimes.set);

Преобразование времени Луны в Luxon

const moonrise = DateTime
    .fromJSDate(moonTimes.rise)
    .setZone("Asia/Almaty");

const moonset = DateTime
    .fromJSDate(moonTimes.set)
    .setZone("Asia/Almaty");

console.log(moonrise.toFormat("HH:mm"));
console.log(moonset.toFormat("HH:mm"));

Проверка отсутствия восхода или захода

Иногда Луна может не появляться над горизонтом.

if (!moonTimes.rise) {
    console.log("Восход Луны отсутствует");
}

Такие ситуации часто встречаются:

  • в северных широтах;
  • возле полюсов;
  • зимой;
  • в периоды аномального положения Луны.

Фаза Луны

const illumination = SunCalc.getMoonIllumination(
    new Date()
);

console.log(illumination.phase);

Фаза возвращается в диапазоне:

0 → новолуние
0.25 → первая четверть
0.5 → полнолуние
0.75 → последняя четверть

Определение текстовой фазы

function getMoonPhase(phase) {
    if (phase < 0.03 || phase > 0.97) {
        return "Новолуние";
    }

    if (phase < 0.22) {
        return "Растущий серп";
    }

    if (phase < 0.28) {
        return "Первая четверть";
    }

    if (phase < 0.47) {
        return "Растущая Луна";
    }

    if (phase < 0.53) {
        return "Полнолуние";
    }

    if (phase < 0.72) {
        return "Убывающая Луна";
    }

    if (phase < 0.78) {
        return "Последняя четверть";
    }

    return "Убывающий серп";
}

Лунная освещённость

console.log(illumination.fraction);

Значение:

0 → Луна невидима
1 → полная освещённость

Положение Луны

const moonPosition = SunCalc.getMoonPosition(
    new Date(),
    latitude,
    longitude
);

console.log(moonPosition.altitude);
console.log(moonPosition.azimuth);

Параметры:

Свойство Описание
altitude высота над горизонтом
azimuth азимут
distance расстояние до Луны
parallacticAngle параллактический угол

Работа с UTC

В астрономии рекомендуется хранить время в UTC.

const utcNow = DateTime.utc();

console.log(utcNow.toISO());

Локальное отображение выполняется отдельно:

const local = utcNow.setZone("Asia/Almaty");

ISO-формат для астрономии

Стандарт ISO 8601 используется практически во всех астрономических API.

console.log(DateTime.utc().toISO());

Пример:

2026-05-24T18:42:11.000Z

Unix Timestamp

Астрономические движки часто используют Unix-время.

const timestamp = DateTime.now().toSeconds();

console.log(timestamp);

Обратное преобразование:

const dt = DateTime.fromSeconds(timestamp);

Работа с юлианской датой

Luxon не поддерживает юлианскую дату напрямую, однако преобразование можно реализовать самостоятельно.

function toJulianDate(dt) {
    return dt.toMillis() / 86400000 + 2440587.5;
}

Использование:

const jd = toJulianDate(DateTime.utc());

console.log(jd);

Высокоточные вычисления времени

Для астрономии важно избегать:

  • локального времени;
  • строковых преобразований;
  • ручного вычисления смещений;
  • смешивания UTC и local time.

Наиболее безопасный подход:

const utc = DateTime.utc();

Только при отображении:

utc.setZone("Asia/Almaty");

Сравнение солнечного и гражданского времени

Солнечный полдень редко совпадает с 12:00 гражданского времени.

Причины:

  • часовые пояса имеют фиксированные границы;
  • орбита Земли эллиптическая;
  • ось Земли наклонена;
  • используется переход на летнее время.

Luxon помогает корректно отображать эти различия.


Обработка полярного дня и ночи

В северных регионах восход или закат могут отсутствовать.

if (times.sunrise && times.sunset) {
    console.log("Обычный день");
} else {
    console.log("Полярный режим");
}

Создание расписания солнечных событий

const schedule = [
    {
        event: "Восход",
        time: sunrise.toFormat("HH:mm")
    },
    {
        event: "Полдень",
        time: solarNoon.toFormat("HH:mm")
    },
    {
        event: "Закат",
        time: sunset.toFormat("HH:mm")
    }
];

console.table(schedule);

Сортировка астрономических событий

schedule.sort((a, b) => {
    return a.time.localeCompare(b.time);
});

Сериализация в JSON

const json = JSON.stringify({
    sunrise: sunrise.toISO(),
    sunset: sunset.toISO(),
    moonrise: moonrise.toISO()
});

Десериализация

const parsed = JSON.parse(json);

const restored = DateTime.fromISO(parsed.sunrise);

Вычисление ближайшего заката

const now = DateTime.now();

let nextSunset = sunset;

if (now > sunset) {
    const tomorrow = now.plus({ days: 1 });

    const tomorrowTimes = SunCalc.getTimes(
        tomorrow.toJSDate(),
        latitude,
        longitude
    );

    nextSunset = DateTime
        .fromJSDate(tomorrowTimes.sunset)
        .setZone("Asia/Almaty");
}

Таймер до восхода

const duration = sunrise.diff(DateTime.now(), [
    "hours",
    "minutes",
    "seconds"
]);

console.log(duration.toObject());

Локализация астрономического времени

const localized = sunrise
    .setLocale("ru")
    .toLocaleString(DateTime.TIME_24_WITH_SECONDS);

console.log(localized);

Использование Duration

import { Duration } from "luxon";

const duration = Duration.fromObject({
    hours: 1,
    minutes: 30
});

console.log(duration.as("minutes"));

Проверка валидности времени

if (!sunrise.isValid) {
    console.log(sunrise.invalidReason);
}

Производительность вычислений

Астрономические вычисления могут быть ресурсоёмкими.

Практические рекомендации:

  • кешировать результаты;
  • не вычислять события каждую секунду;
  • хранить UTC;
  • использовать DateTime вместо строк;
  • избегать постоянного вызова setZone() внутри циклов.

Типичная архитектура солнечного сервиса

Распространённая схема:

SunCalc → вычисления
Luxon → работа со временем
Frontend → отображение
Backend → хранение UTC

Интеграция с погодными API

Большинство погодных сервисов возвращают:

  • sunrise;
  • sunset;
  • moonrise;
  • moonset;
  • timezone.

Luxon позволяет быстро нормализовать данные.

const sunrise = DateTime
    .fromSeconds(api.sunrise, {
        zone: "utc"
    })
    .setZone(api.timezone);

Работа с несколькими локациями

const cities = [
    {
        name: "Karaganda",
        lat: 49.8028,
        lon: 73.0877
    },
    {
        name: "Tokyo",
        lat: 35.6762,
        lon: 139.6503
    }
];

Вычисление:

cities.forEach(city => {
    const times = SunCalc.getTimes(
        new Date(),
        city.lat,
        city.lon
    );

    const sunrise = DateTime
        .fromJSDate(times.sunrise);

    console.log(
        city.name,
        sunrise.toISO()
    );
});

Точность вычислений

SunCalc обеспечивает хорошую точность для:

  • веб-приложений;
  • мобильных приложений;
  • погодных сервисов;
  • систем освещения.

Однако для профессиональной астрономии используются:

  • NASA JPL;
  • VSOP87;
  • высокоточные эфемериды;
  • специализированные обсерваторные алгоритмы.

Luxon при этом остаётся удобным уровнем обработки и представления времени.