In shortAll the files are at waqt.qudqu.com/api/v1/ and open from any site. Each one is made from the same code this site runs, so the API and the site never give two different dates. No Iranian month name is in any of them.
Quick start
One import in a <script type="module"> is all it takes: no install, no key, no sign-up. This code just ran in your browser; today's answer is under each line.
<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>The files
| File | What it holds | Size |
|---|---|---|
| calendar.json | The Shamsi and Qamari year tables, what a day number is, Afghan time | 3 KB |
| names.json | The months of the three calendars, the weekdays and the digits, in seven languages | 3 KB |
| places.json | The 34 provincial capitals: coordinates, the city's and the province's names in seven languages | 4 KB |
| sun/kabul.json | The sun on every day of the Shamsi year; one file for each provincial capital | 4 KB |
| waqt.js | The JavaScript module: converting and writing dates, Afghan time, the sun and the moon | 20 KB |
Converting dates with day numbers
Every date has a day number: the days since 1 January 1970. All three calendars convert to and from day numbers, so a date goes from one calendar to another by way of its day number.
The module does this with one function. Without it, it is simple too: in calendar.json, find the last year that starts on or before the day, then count the months.
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 وری 1405Qamari dates and moon sighting
The Qamari table is the Umm al-Qura calendar. In Afghanistan a Qamari month begins when the new crescent is seen, and that cannot be calculated in advance: a date is sometimes a day off from the table. If a month began a day later than the table says, read the table at the day before (the day number minus 1); if a day earlier, at the day after. The module's functions take the same as HijriAdjustment.DAY_LATER or HijriAdjustment.DAY_EARLIER.
Month and weekday names in seven languages
names.json has, for each language, the months of the three calendars, the weekdays from Saturday, "before noon" and "after noon", the calendars' names and the language's digits. Pashto has its own month names; the other languages write the Dari names in their own script.
No Iranian month name is in these files, and our tests check every day in every language. Take the names from this file, not from the browser: many browsers show Iranian names for Afghanistan.
{
"languages": {
"fa": {
"months": {
"shamsi": [
"حمل", "ثور", "جوزا", "سرطان", "اسد", "سنبله",
"میزان", "عقرب", "قوس", "جدی", "دلو", "حوت"
],
"qamari": ["محرم", …],
"gregorian": ["جنوری", …]
},
"weekdays": [
"شنبه", "یکشنبه", "دوشنبه", "سهشنبه",
"چهارشنبه", "پنجشنبه", "جمعه"
],
"digits": "۰۱۲۳۴۵۶۷۸۹",
…
},
"ps": {
"months": {
"shamsi": [
"وری", "غویی", "غبرګولی", "چنګاښ", "زمری", "وږی",
"تله", "لړم", "لیندۍ", "مرغومی", "سلواغه", "کب"
],
"qamari": ["محرم", …],
"gregorian": ["جنوري", …]
},
"weekdays": [
"شنبه", "یکشنبه", "دوشنبه", "سه شنبه",
"چهارشنبه", "پنجشنبه", "جمعه"
],
"digits": "۰۱۲۳۴۵۶۷۸۹",
…
},
"en": { "months": { "shamsi": ["Hamal", "Sawr", "Jawza", …] }, … },
…
}
}Sunrise and sunset
For each provincial capital there is a table: first light, sunrise, solar noon, sunset, nightfall and the length of the day, for every day of the Shamsi year, on Afghan time. The table holds for every year within 2 minutes, because the Shamsi year follows the sun.
For the exact minute in one year, or for any other place, use sunDay. Its formula is here in PHP too.
// 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:28The moon
The module gives the moon's place in the sky from anywhere, how much of it is lit, its rise and set on any day, and the next new and full moon. It is not for sighting the first crescent of a month.
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:14Version 1: what does not change
- The files keep their shape. Things may be added to them, but no name is removed or changed. A change that doesn't fit this rule becomes /api/v2/, and version 1 stays.
- From 1 January 1900 to 25 November 2174 (1278 to 1553 Shamsi, 1317 to 1600 Qamari). Outside these dates the functions give an error.
- From any site. The files are sent with Access-Control-Allow-Origin: *, and browsers keep them for 10 minutes.
- No statistics or cookies. The files set no cookies and collect no statistics.
Licence
Licence: CC BY 4.0
Seen a mistake, or is something missing? Write to us.