КороткоВсе файлы лежат по адресу waqt.qudqu.com/api/v1/ и открываются с любого сайта. Каждый сделан из того же кода, на котором работает этот сайт, поэтому API и сайт никогда не дают двух разных дат. Иранских названий месяцев в них нет.
Быстрый старт
Хватит одного import в <script type="module">: без установки, без ключа, без регистрации. Этот код только что выполнился в вашем браузере; сегодняшний ответ стоит под каждой строкой.
<script type="module">
import { Calendar, Language, formatDate, wallTime }
from 'https://waqt.qudqu.com/api/v1/waqt.js';
// Today on Afghan time, as a day number
const today = wallTime(Date.now()).day;
console.log(formatDate(today, Calendar.SHAMSI, Language.DARI).text);
// → …
console.log(formatDate(today, Calendar.QAMARI, Language.PASHTO).text);
// → …
console.log(formatDate(today, Calendar.GREGORIAN, Language.ENGLISH).text);
// → …
</script>Файлы
| Файл | Что в нём | Размер |
|---|---|---|
| calendar.json | Таблицы лет шамси и камари, что такое номер дня, афганское время | 3 КБ |
| names.json | Месяцы трёх календарей, дни недели и цифры на семи языках | 3 КБ |
| places.json | 34 центра провинций: координаты, названия города и провинции на семи языках | 4 КБ |
| sun/kabul.json | Солнце в каждый день года шамси; по файлу на каждый центр провинции | 4 КБ |
| waqt.js | Модуль JavaScript: перевод и запись дат, афганское время, солнце и луна | 20 КБ |
Перевод дат через номер дня
У каждой даты есть номер дня: число дней с 1 января 1970 года. Все три календаря переводятся в номер дня и обратно, поэтому дата переходит из одного календаря в другой через свой номер дня.
Модуль делает это одной функцией. Без модуля это тоже просто: найдите в calendar.json последний год, который начинается в этот день или раньше, а потом отсчитайте месяцы.
import {
Calendar, Digits, HijriAdjustment, Language,
formatDate, fromGregorian, fromShamsi,
toGregorian, toQamari, toShamsi, wallTime
} from 'https://waqt.qudqu.com/api/v1/waqt.js';
// A date is a day number: whole days since 1 January 1970
const nowruz = fromGregorian(2026, 3, 21);
console.log(nowruz);
// → 20533
console.log(toShamsi(nowruz));
// → { year: 1405, month: 1, day: 1 }
console.log(toQamari(nowruz));
// → { year: 1447, month: 10, day: 2 }
console.log(toGregorian(fromShamsi(1404, 12, 29)));
// → { year: 2026, month: 3, day: 20 }
// If the month began a day later in Afghanistan than in Umm al-Qura
console.log(toQamari(nowruz, HijriAdjustment.DAY_LATER));
// → { year: 1447, month: 10, day: 1 }
// As text, in any of the seven languages
console.log(formatDate(nowruz, Calendar.SHAMSI, Language.DARI).text);
// → ۱ حمل ۱۴۰۵
console.log(formatDate(nowruz, Calendar.SHAMSI, Language.PASHTO).text);
// → ۱ وری ۱۴۰۵
console.log(formatDate(nowruz, Calendar.QAMARI, Language.ENGLISH).text);
// → 2 Shawwal 1447
// With the weekday, in Latin digits
const options = { weekday: true, digits: Digits.LATIN };
console.log(formatDate(nowruz, Calendar.SHAMSI, Language.DARI, options).text);
// → شنبه، 1 حمل 1405
// Today, on Afghan time
const today = wallTime(Date.now()).day;
console.log(formatDate(today, Calendar.QAMARI, Language.DARI).text);<?php
$api = 'https://waqt.qudqu.com/api/v1/';
$calendar = json_decode(file_get_contents($api . 'calendar.json'), true);
$names = json_decode(file_get_contents($api . 'names.json'), true);
$names = $names['languages'];
// A date as a day number: whole days since 1 January 1970
function day_number(array $calendar, string $date): int
{
$utc = new DateTimeZone('UTC');
$day = intdiv((new DateTimeImmutable($date, $utc))->getTimestamp(), 86400);
$range = $calendar['range'];
if ($day < $range['firstDay'] || $day > $range['lastDay']) {
throw new RangeException('outside the supported range');
}
return $day;
}
// The last year that starts on or before the day
function year_index(array $starts, int $day): int
{
$i = 0;
while ($i + 1 < count($starts) && $starts[$i + 1] <= $day) {
$i++;
}
return $i;
}
function to_shamsi(array $s, int $day): array
{
$i = year_index($s['yearStarts'], $day);
$rest = $day - $s['yearStarts'][$i];
$month = 1;
while ($month < 12 && $rest >= $s['monthLengths'][$month - 1]) {
$rest -= $s['monthLengths'][$month - 1];
$month++;
}
return [$s['firstYear'] + $i, $month, $rest + 1];
}
function to_qamari(array $q, int $day): array
{
$i = year_index($q['yearStarts'], $day);
$rest = $day - $q['yearStarts'][$i];
for ($month = 1; $month < 12; $month++) {
$length = ($q['monthLengthBits'][$i] >> ($month - 1)) & 1 ? 30 : 29;
if ($rest < $length) {
break;
}
$rest -= $length;
}
return [$q['firstYear'] + $i, $month, $rest + 1];
}
$nowruz = day_number($calendar, '2026-03-21');
echo implode('-', to_shamsi($calendar['shamsi'], $nowruz)), "\n";
// → 1405-1-1
echo implode('-', to_qamari($calendar['qamari'], $nowruz)), "\n";
// → 1447-10-2
// If the month began a day later in Afghanistan than in Umm al-Qura
echo implode('-', to_qamari($calendar['qamari'], $nowruz - 1)), "\n";
// → 1447-10-1
[$year, $month, $day] = to_shamsi($calendar['shamsi'], $nowruz);
echo $day, ' ', $names['fa']['months']['shamsi'][$month - 1], ' ', $year, "\n";
// → 1 حمل 1405
echo $day, ' ', $names['ps']['months']['shamsi'][$month - 1], ' ', $year, "\n";
// → 1 وری 1405Даты камари и наблюдение луны
Таблица камари взята из календаря Умм аль-Кура. В Афганистане месяц камари начинается, когда видят новый серп луны, а это нельзя рассчитать заранее: дата иногда расходится с таблицей на день. Если месяц начался на день позже, чем в таблице, читайте таблицу на день раньше (номер дня минус 1); если на день раньше, то на день позже. Функции модуля принимают то же самое как HijriAdjustment.DAY_LATER или HijriAdjustment.DAY_EARLIER.
Названия месяцев и дней недели на семи языках
names.json содержит для каждого языка месяцы трёх календарей, дни недели начиная с субботы, «до полудня» и «после полудня», названия календарей и цифры языка. В пушту свои названия месяцев; остальные языки пишут названия дари своим письмом.
Иранских названий месяцев в этих файлах нет, и наши тесты проверяют каждый день на каждом языке. Берите названия из этого файла, а не из браузера: многие браузеры показывают для Афганистана иранские названия.
{
"languages": {
"fa": {
"months": {
"shamsi": [
"حمل", "ثور", "جوزا", "سرطان", "اسد", "سنبله",
"میزان", "عقرب", "قوس", "جدی", "دلو", "حوت"
],
"qamari": ["محرم", …],
"gregorian": ["جنوری", …]
},
"weekdays": [
"شنبه", "یکشنبه", "دوشنبه", "سهشنبه",
"چهارشنبه", "پنجشنبه", "جمعه"
],
"digits": "۰۱۲۳۴۵۶۷۸۹",
…
},
"ps": {
"months": {
"shamsi": [
"وری", "غویی", "غبرګولی", "چنګاښ", "زمری", "وږی",
"تله", "لړم", "لیندۍ", "مرغومی", "سلواغه", "کب"
],
"qamari": ["محرم", …],
"gregorian": ["جنوري", …]
},
"weekdays": [
"شنبه", "یکشنبه", "دوشنبه", "سه شنبه",
"چهارشنبه", "پنجشنبه", "جمعه"
],
"digits": "۰۱۲۳۴۵۶۷۸۹",
…
},
"en": { "months": { "shamsi": ["Hamal", "Sawr", "Jawza", …] }, … },
…
}
}Восход и закат
Для каждого центра провинции есть таблица: первый свет, восход, полдень, закат, темнота и долгота дня, на каждый день года шамси, по афганскому времени. Таблица верна для любого года с точностью до 2 минут, потому что год шамси идёт за солнцем.
Для точной минуты в конкретном году или для любого другого места используйте sunDay. Её формула здесь есть и на PHP.
// A sun table needs no code: months[m - 1][d - 1] is Shamsi month m, day d
const url = 'https://waqt.qudqu.com/api/v1/sun/herat.json';
const table = await (await fetch(url)).json();
console.log(table.columns);
// → [ 'firstLight', 'sunrise', 'noon', 'sunset', 'dark', 'dayLength' ]
console.log(table.months[0][0]); // 1 Hamal
// → [ '05:30', '06:25', '12:29', '18:33', '19:27', '12:08' ]import {
Language, findPlace, formatTime, fromShamsi, sunDay, wallTime
} from 'https://waqt.qudqu.com/api/v1/waqt.js';
// Any day, any place: sunDay(day, latitude, longitude)
const herat = findPlace('herat');
const day = fromShamsi(1405, 1, 1);
const sun = sunDay(day, herat.latitude, herat.longitude);
// Instants are milliseconds: on Afghan time, to the nearest minute
const clock = (ms) => {
const minutes = Math.round(wallTime(ms).seconds / 60);
return formatTime(minutes * 60, Language.ENGLISH).clock;
};
console.log(clock(sun.sunrise), clock(sun.sunset));
// → 06:24 18:33
console.log(clock(sun.nauticalDawn), clock(sun.nauticalDusk));
// → 05:30 19:28<?php
// First light, sunrise, solar noon, sunset and nightfall, any day and place.
// The formula of waqt.js: the Astronomical Almanac's low-precision sun (about
// a hundredth of a degree), solved for each height of the sun. Instants are
// seconds since 1970, UTC.
const DAY = 86400;
// An angle in radians, into -pi to pi
function wrap(float $a): float
{
return atan2(sin($a), cos($a));
}
// The sun's declination and hour angle at the longitude, in radians
function position(float $t, float $longitude): array
{
// Days since 1 January 2000, 12:00 UTC
$n = $t / DAY + 2440587.5 - 2451545.0;
$meanLongitude = 280.46 + 0.9856474 * $n;
$anomaly = deg2rad(357.528 + 0.9856003 * $n);
$centre = 1.915 * sin($anomaly) + 0.02 * sin(2 * $anomaly);
$ecliptic = deg2rad($meanLongitude + $centre);
$tilt = deg2rad(23.439 - 0.0000004 * $n);
$y = cos($tilt) * sin($ecliptic);
$rightAscension = atan2($y, cos($ecliptic));
$declination = asin(sin($tilt) * sin($ecliptic));
$sidereal = deg2rad(280.46061837 + 360.98564736629 * $n + $longitude);
return [$declination, wrap($sidereal - $rightAscension)];
}
function solar_noon(int $day, float $longitude, int $offsetMinutes): float
{
$t = $day * DAY - $offsetMinutes * 60 + DAY / 2;
for ($k = 0; $k < 4; $k++) {
$t -= position($t, $longitude)[1] / (2 * M_PI) * DAY;
}
return $t;
}
// When the sun passes a height (degrees) before (-1) or after (1) noon;
// null when it doesn't get there that day
function crossing(
float $noon,
float $latitude,
float $longitude,
float $height,
int $side
): ?float {
$phi = deg2rad($latitude);
$t = $noon;
for ($k = 0; $k < 8; $k++) {
[$declination, $hourAngle] = position($t, $longitude);
$top = sin(deg2rad($height)) - sin($phi) * sin($declination);
$c = $top / (cos($phi) * cos($declination));
if ($c < -1 || $c > 1) {
return null;
}
$step = wrap($side * acos($c) - $hourAngle) / (2 * M_PI) * DAY;
$t += $step;
if (abs($step) < 0.2) {
break;
}
}
return $t;
}
// The sun's day at a place; $offsetMinutes: the clock's offset from UTC
// (270 in all of Afghanistan)
function sun_day(
string $date,
float $latitude,
float $longitude,
int $offsetMinutes = 270
): array {
$utc = new DateTimeZone('UTC');
$day = intdiv((new DateTimeImmutable($date, $utc))->getTimestamp(), DAY);
$noon = solar_noon($day, $longitude, $offsetMinutes);
return [
'firstLight' => crossing($noon, $latitude, $longitude, -12, -1),
'sunrise' => crossing($noon, $latitude, $longitude, -0.833, -1),
'noon' => $noon,
'sunset' => crossing($noon, $latitude, $longitude, -0.833, 1),
'dark' => crossing($noon, $latitude, $longitude, -12, 1),
];
}
// An instant as hours and minutes on the clock, to the nearest minute
function clock(float $t, int $offsetMinutes = 270): string
{
$minutes = (int) round($t / 60) + $offsetMinutes;
return sprintf('%02d:%02d', intdiv($minutes, 60) % 24, $minutes % 60);
}
$herat = sun_day('2026-03-21', 34.342, 62.203);
echo implode(' ', array_map('clock', $herat)), "\n";
// → 05:30 06:24 12:28 18:33 19:28Луна
Модуль даёт положение луны на небе из любого места, какая её часть освещена, её восход и заход в любой день, а также ближайшие новолуние и полнолуние. Для наблюдения первого серпа месяца он не предназначен.
import {
MoonPhase, moonState, nextMoonPhase
} from 'https://waqt.qudqu.com/api/v1/waqt.js';
// The moon over Kabul on an evening (or moonState(ms, latitude, longitude))
const evening = Date.parse('2026-03-21T19:00:00+04:30');
const moon = moonState(evening);
// Percent lit, and days since the new moon
console.log(Math.round(moon.lit * 100), moon.age.toFixed(1));
// → 9 2.8
const full = nextMoonPhase(evening, MoonPhase.FULL);
console.log(new Date(full).toISOString().slice(0, 16));
// → 2026-04-02T02:14Версия 1: что не меняется
- Файлы сохраняют свою форму. В них может что-то добавиться, но ни одно имя не будет удалено или изменено. Изменение, которое не укладывается в это правило, станет /api/v2/, а версия 1 останется.
- С 1 января 1900 по 25 ноября 2174 года (шамси с 1278 по 1553, камари с 1317 по 1600). За этими пределами функции выдают ошибку.
- С любого сайта. Файлы отправляются с Access-Control-Allow-Origin: *, и браузеры хранят их 10 минут.
- Без статистики и cookie. Файлы не ставят cookie и не собирают статистику.
Лицензия
Лицензия: CC BY 4.0
Заметили ошибку или чего-то не хватает? Напишите нам.