Signature
date_sun_info(int $timestamp, float $latitude, float $longitude): arrayPurpose
date_sun_info() calculates solar events for one day at one place on Earth. You give it a Unix timestamp, a latitude and a longitude. It returns an array with sunrise, sunset, solar transit and the start and end of three kinds of twilight. It is useful for "is it dark yet?" features, daylight summaries, photography planners, or scheduling that depends on daylight.
The function uses only in-memory arithmetic. It does not look anything up, so the same inputs give the same answer. That makes it suitable for tests, as long as you fix the timestamp and the default timezone.
Signature
Parameters
| Parameter | Meaning |
|---|---|
$timestamp |
A Unix timestamp that identifies the day to analyse. |
$latitude |
Degrees of latitude. North is positive, south is negative. |
$longitude |
Degrees of longitude. East is positive, west is negative. |
None of the parameters has a default value, so all three are required. The function has no timezone parameter. The timestamp is a point in time, and the "day" it selects is related to local midnight in PHP's default timezone. Since PHP 7.2.0 the calculation is based on local midnight rather than local noon, which slightly changed results compared with older versions. Call date_default_timezone_set() explicitly so your code does not depend on server configuration.
Return value
The function returns an array with these keys, in this order:
sunrisesunsettransitcivil_twilight_begincivil_twilight_endnautical_twilight_beginnautical_twilight_endastronomical_twilight_beginastronomical_twilight_end
Each value is one of three things:
- An integer Unix timestamp, when the event happens.
false, when the sun stays below the relevant zenith angle for the whole day.true, when the sun stays above that angle for the whole day.
The sunrise and sunset use a zenith angle of 90°35'. Civil, nautical and astronomical twilight use 96°, 102° and 108°. The "begin" entries are dawn events and the "end" entries are dusk events.
The return type is always array. Unusual locations do not make the function return a different type. Instead, individual entries become booleans, so you must handle that in your own code.
Example 1: structure and ordering
This example uses a location near Jerusalem in December. It prints the keys and checks that the events occur in a sensible chronological order.
date_default_timezone_set('UTC');
$info = date_sun_info(1165881600, 31.7667, 35.2333);
$order = [
'astronomical_twilight_begin',
'nautical_twilight_begin',
'civil_twilight_begin',
'sunrise',
'transit',
'sunset',
'civil_twilight_end',
'nautical_twilight_end',
'astronomical_twilight_end',
];
$chronological = true;
for ($i = 1; $i < count($order); $i++) {
if ($info[$order[$i - 1]] >= $info[$order[$i]]) {
$chronological = false;
}
}
$hours = ($info['sunset'] - $info['sunrise']) / 3600;
echo json_encode([
'keys' => array_keys($info),
'chronological' => $chronological,
'day_length_ok' => $hours > 9 && $hours < 11,
]), "\n";{"keys":["sunrise","sunset","transit","civil_twilight_begin","civil_twilight_end","nautical_twilight_begin","nautical_twilight_end","astronomical_twilight_begin","astronomical_twilight_end"],"chronological":true,"day_length_ok":true}
Example 2: midnight sun and polar night
Near the poles, some events do not happen at all on a given day. Here the results are booleans rather than timestamps.
date_default_timezone_set('UTC');
$night = date_sun_info(1671580800, 70.21, -148.51);
$day = date_sun_info(1656201600, 69.68, 18.94);
echo json_encode([
'night_sunrise' => $night['sunrise'],
'night_sunset' => $night['sunset'],
'day_sunrise' => $day['sunrise'],
'day_sunset' => $day['sunset'],
'day_transit_is_int' => is_int($day['transit']),
]), "\n";{"night_sunrise":false,"night_sunset":false,"day_sunrise":true,"day_sunset":true,"day_transit_is_int":true}
During polar night the sun never reaches the sunrise zenith, so those entries are false. During midnight sun it never drops below it, so they are true. The solar transit is still a timestamp in both cases.
Example 3: normalising mixed values
A small helper keeps display code from treating true or false as a timestamp.
date_default_timezone_set('UTC');
function describe_sun_value(int|bool $value): string
{
return match ($value) {
true => 'always',
false => 'never',
default => 'timestamp',
};
}
$info = date_sun_info(1671580800, 70.21, -148.51);
$out = [];
foreach (['sunrise', 'sunset', 'transit', 'civil_twilight_begin'] as $key) {
$out[$key] = describe_sun_value($info[$key]);
}
echo json_encode($out), "\n";{"sunrise":"never","sunset":"never","transit":"timestamp","civil_twilight_begin":"timestamp"}
match compares strictly, so an integer timestamp is never mistaken for true.
Edge cases
- Polar regions: any of the eight event entries can be
trueorfalse. Twilight entries may be timestamps while sunrise and sunset are not, as in the polar night example. - Timezone sensitivity: because the day is tied to local midnight, the same timestamp can describe different days under different default timezones, especially at large longitudes.
- Event order: the order of keys in the array is not chronological. For example,
transitcomes third even though it occurs after sunrise and before sunset. - Formatting: pass a real timestamp to
date()orDateTimeImmutableonly after confirming it is an integer.
Common mistakes
- Passing
trueorfalsetodate().date('H:i', false)formats timestamp 0, not "no sunrise". Always checkis_int()first. - Swapping latitude and longitude. The latitude comes first. Swapped values give plausible-looking but wrong results, or very different polar behaviour.
- Using the wrong sign. West longitudes and south latitudes are negative.
- Relying on the server timezone. Set it explicitly, or format results with a chosen
DateTimeZone. - Assuming a fixed day length. Subtracting
sunrisefromsunsetis only meaningful when both are integers. - Comparing floats from user input as strings. Convert coordinates to floats before calling.
Related functions
date_sunrise()anddate_sunset()return a single event, but they are deprecated in newer PHP versions; preferdate_sun_info().date_default_timezone_set()controls how the day is interpreted.date()andDateTimeImmutable::setTimestamp()format timestamps for display.mktime()andstrtotime()create timestamps from calendar dates.
Recap
date_sun_info() takes a timestamp, a latitude and a longitude, and returns an array of nine solar events. Normal values are integer timestamps, but true and false mean the sun stays above or below a zenith for the whole day. Set the timezone explicitly, pass coordinates in the right order, and test each value's type before formatting it as a time.
Tested examples
date_default_timezone_set('UTC');
$info = date_sun_info(1165881600, 31.7667, 35.2333);
$order = [
'astronomical_twilight_begin',
'nautical_twilight_begin',
'civil_twilight_begin',
'sunrise',
'transit',
'sunset',
'civil_twilight_end',
'nautical_twilight_end',
'astronomical_twilight_end',
];
$chronological = true;
for ($i = 1; $i < count($order); $i++) {
if ($info[$order[$i - 1]] >= $info[$order[$i]]) {
$chronological = false;
}
}
$hours = ($info['sunset'] - $info['sunrise']) / 3600;
echo json_encode([
'keys' => array_keys($info),
'chronological' => $chronological,
'day_length_ok' => $hours > 9 && $hours < 11,
]), "\n";
{"keys":["sunrise","sunset","transit","civil_twilight_begin","civil_twilight_end","nautical_twilight_begin","nautical_twilight_end","astronomical_twilight_begin","astronomical_twilight_end"],"chronological":true,"day_length_ok":true}
date_default_timezone_set('UTC');
$night = date_sun_info(1671580800, 70.21, -148.51);
$day = date_sun_info(1656201600, 69.68, 18.94);
echo json_encode([
'night_sunrise' => $night['sunrise'],
'night_sunset' => $night['sunset'],
'day_sunrise' => $day['sunrise'],
'day_sunset' => $day['sunset'],
'day_transit_is_int' => is_int($day['transit']),
]), "\n";
{"night_sunrise":false,"night_sunset":false,"day_sunrise":true,"day_sunset":true,"day_transit_is_int":true}
date_default_timezone_set('UTC');
function describe_sun_value(int|bool $value): string
{
return match ($value) {
true => 'always',
false => 'never',
default => 'timestamp',
};
}
$info = date_sun_info(1671580800, 70.21, -148.51);
$out = [];
foreach (['sunrise', 'sunset', 'transit', 'civil_twilight_begin'] as $key) {
$out[$key] = describe_sun_value($info[$key]);
}
echo json_encode($out), "\n";
{"sunrise":"never","sunset":"never","transit":"timestamp","civil_twilight_begin":"timestamp"}