简而言之所有文件都在 waqt.qudqu.com/api/v1/,任何网站都能读取。每个文件都由本站运行的同一套代码生成,因此 API 和本站绝不会给出两个不同的日期。其中没有任何伊朗月份名称。
快速开始
在 <script type="module"> 里写一行 import 就够了:无需安装、无需密钥、无需注册。这段代码刚刚在你的浏览器里运行过,每行下面是今天的结果。
<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 KB |
| names.json | 三种历法的月份、星期和数字,七种语言 | 3 KB |
| places.json | 34 个省会:坐标,以及城市和省份在七种语言中的名称 | 4 KB |
| sun/kabul.json | 太阳历每一天的太阳;每个省会一个文件 | 4 KB |
| waqt.js | JavaScript 模块:日期的转换和书写、阿富汗时间、太阳和月亮 | 20 KB |
用日序号转换日期
每个日期都有一个日序号:自 1970 年 1 月 1 日起的天数。三种历法都能与日序号互相转换,所以日期通过日序号从一种历法转到另一种。
模块用一个函数就能做到。没有模块也很简单:在 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 保留。
- 从 1900 年 1 月 1 日到 2174 年 11 月 25 日(太阳历 1278 至 1553 年,伊斯兰历 1317 至 1600 年)。超出这个范围,函数会报错。
- 任何网站都能用。 文件带有 Access-Control-Allow-Origin: *,浏览器会保存 10 分钟。
- 没有统计,没有 Cookie。 文件不设置 Cookie,也不收集统计数据。
许可
许可:CC BY 4.0
发现错误或缺少什么?请告诉我们。