Dates7 min readExamples run on PHP 8.5.10

date_sun_info()

Learn how PHP 8.4 date_sun_info() returns sunrise, sunset, transit and twilight data for a timestamp and location, including the true/false polar cases.

Signature

date_sun_info(int $timestamp, float $latitude, float $longitude): array
Available since PHP 5.1.27.48.08.18.28.38.48.5All versions →

Purpose

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:

  • sunrise
  • sunset
  • transit
  • civil_twilight_begin
  • civil_twilight_end
  • nautical_twilight_begin
  • nautical_twilight_end
  • astronomical_twilight_begin
  • astronomical_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.

Example
<?php
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";
Output
{"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.

Example
<?php
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";
Output
{"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.

Example
<?php
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";
Output
{"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 true or false. 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, transit comes third even though it occurs after sunrise and before sunset.
  • Formatting: pass a real timestamp to date() or DateTimeImmutable only after confirming it is an integer.

Common mistakes

  1. Passing true or false to date(). date('H:i', false) formats timestamp 0, not "no sunrise". Always check is_int() first.
  2. Swapping latitude and longitude. The latitude comes first. Swapped values give plausible-looking but wrong results, or very different polar behaviour.
  3. Using the wrong sign. West longitudes and south latitudes are negative.
  4. Relying on the server timezone. Set it explicitly, or format results with a chosen DateTimeZone.
  5. Assuming a fixed day length. Subtracting sunrise from sunset is only meaningful when both are integers.
  6. Comparing floats from user input as strings. Convert coordinates to floats before calling.
  • date_sunrise() and date_sunset() return a single event, but they are deprecated in newer PHP versions; prefer date_sun_info().
  • date_default_timezone_set() controls how the day is interpreted.
  • date() and DateTimeImmutable::setTimestamp() format timestamps for display.
  • mktime() and strtotime() 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

Structure and chronological ordering
<?php
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";
Output
{"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}
Midnight sun and polar night booleans
<?php
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";
Output
{"night_sunrise":false,"night_sunset":false,"day_sunrise":true,"day_sunset":true,"day_transit_is_int":true}
Describing mixed timestamp and boolean values
<?php
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";
Output
{"sunrise":"never","sunset":"never","transit":"timestamp","civil_twilight_begin":"timestamp"}