I am new to astronomy, and trying to learn how to use Skyfield to calculate the rising sign/constellation (typically referred to as the Ascendant in astrology).

I tried a number of approaches based on what I have learnt so far:

  1. Using GMST and subtracting 90 degrees, assuming that GMST is providing the Vernal equinox/Aries cusp from the prime meridian, and 90 degree east is rising
  2. Tried subtracting azimuth from planet's longitude - again with the same logic that azimuth is measuring planet's position relative to North.

Is there an easier way to determine the rising sign/constellation - and the ascendant? This is a hobby project for me - to try to understand astronomical calculations behind astrology.

Still learning and will appreciate the guidance from the Skyfield community.

  • 3
    $\begingroup$ Sorry, but astrology questions are not popular on this site. They aren't exactly off-topic when they're purely astronomical (or related to the historical development of astronomy), but even then they're merely tolerated, not encouraged. It's probably best to ask such questions without referring to astrology at all, if possible. $\endgroup$
    – PM 2Ring
    Feb 1 at 5:10
  • $\begingroup$ Wikipedia's Ascendant page gives the equation for the ecliptic longitude on an observer's (rational) eastern horizon. You need the local sidereal time, the observer's latitude, and the obliquity of the ecliptic. You can calculate the local sidereal time from the Greenwich sidereal time and the observer's longitude. $\endgroup$
    – PM 2Ring
    Feb 1 at 5:13
  • $\begingroup$ You;re probably better off with "swisseph" package. $\endgroup$
    – James K
    Feb 1 at 6:44
  • 3
    $\begingroup$ @PM2Ring Disagree! The question asks how to determine a specific thing (a rising star or constellation at a specified time) using the Python package Skyfield. It does not ask any question about astrology. This is not an "astrology question". Don't fault the question author for explaining some context. That's the mark of a good question because it helps the answer authors calibrate their answer and perhaps add additional information helpful to the OP. If we don't add helpful background, there's always some user who will then add a "why are you asking, what are you trying to do" comment. $\endgroup$
    – uhoh
    Feb 1 at 7:05
  • 4
    $\begingroup$ @PM2Ring From Community Policy Repository see this answer to Can we consider Indian Astrology as astronomy? "That said, if the questions are motivated by astrological sources but concern only astronomical phenomena, then they are on topic." $\endgroup$
    – uhoh
    Feb 1 at 7:45

1 Answer 1


The Ascendant-Descendant axis is the line of intersection of the ecliptic plane with the rational horizon. The ecliptic is the plane of the Earth's orbit. The rational horizon is the plane through the geocentre (the Earth's centre) which is parallel to the observer's local horizon. So the Ascendant-Descendant axis passes through the geocentre. The Ascendant is to the east, the Descendant is to the west. Hence the Ascendant is the point on the ecliptic that is currently rising.

Here's a diagram from an article on the Astrodienst site, Astronomical Foundations of the Astrological Houses, by Christopher A. Weidner.

Rational horizon, ecliptic, ascendant

We can easily calculate the Ascendant using spherical trigonometry. Wikipedia gives the equation, but it's in a slightly misleading format. Here's a better version:

$$\lambda_{\rm Asc} = \arctan \left(\frac{\cos\theta_{\rm L}}{-(\sin\theta_{\rm L} \cos\varepsilon + \tan\phi \sin\varepsilon)} \right)$$

where $\theta_{\rm L}$ is the local sidereal time, expressed as an angle, $\varepsilon$ is the obliquity of the ecliptic, and $\phi$ is the latitude of the observer (north positive, south negative).

The mean rotation of the Earth relative to the stars is 360° in 24 sidereal hours. So we can convert sidereal time in hours to sidereal time in degrees by multiplying by 15.

The local sidereal time can be calculated by adding the observer's longitude to the Greenwich sidereal time (east longitude positive, west longitude negative). Of course, for this equation to make sense both quantities must be in the same units, so we need to divide the longitude in degrees by 15 to convert it to hours. Conversely, we can just multiply the sidereal time by 15 to convert it to degrees, since we need it in angle format for the Ascendant calculation.


$$\theta_{\rm L} = \theta_{\rm G} + \lambda$$

where $\theta_{\rm G}$ is the Greenwich sidereal time expressed as an angle in degrees, and $\lambda$ is the longitude of the observer (in degrees).

We also need the obliquity of the ecliptic. Wikipedia gives various polynomial expressions. The equation from the Astronomical Almanac for 2010 is good for several centuries either side of the present era. There's also an equation from Laskar which is less precise, but it's valid for a span of 10,000 years.

The Greenwich Mean Sidereal Time (GMST) can be computed using a polynomial given in the JPL Horizons manual.

I need to digress briefly to discuss the ecliptic coordinate system. The ecliptic is the Earth's orbital plane, so the Sun's apparent path lies on the ecliptic. The Moon's orbit is inclined by ~5.145° to the ecliptic, so the Moon is always within 5.145° of the ecliptic, and all the planets are within 8° of the ecliptic, as I mention in this answer.

The band 8° either side of the ecliptic is known as the zodiac. In the ecliptic coordinate system the position of a point on the celestial sphere is given in terms of ecliptic longitude and ecliptic latitude. The ecliptic longitude is zero at the March equinox point, and increases in the direction of the Sun's motion. The ecliptic latitude gives the angular distance above or below the ecliptic (north positive, south negative).

Modern astronomy prefers to locate points on the celestial sphere using the equatorial coordinate system of Right Ascension (RA) and Declination, which is based on the celestial equator plane rather than the ecliptic. The March equinox point has zero RA and zero declination. Declination gives the angular distance above or below the celestial equator. This system became prominent after the invention of the telescope. It's very convenient when using a telescope with an equatorial mount.

However, for most of history, the ecliptic coordinate system was the main way to specify celestial points. It goes back to the ancient Babylonian astronomers, if not earlier.

You can give ecliptic longitude in degrees, but it was traditional to use zodiac notation. This notation has fallen out of use in astronomy, but it's still very popular in astrology. This notation simplifies the arithmetic of calculating the angle between two longitudes.

In zodiac notation, the ecliptic is divided into 12 equal sections of 30°. These sections are called signs, and they're named after the constellations near the zodiac. In ancient times, the signs roughly corresponded with the constellations, but they've drifted out of sync in the last couple of millennia, due to the precession of the equinoxes.

The first sign is Aries, the second sign is Taurus, etc. In zodiac notation, ecliptic longitude of 10°20' is written 10 Aries 20. Ecliptic longitude 45°10` is written 15 Taurus 10. Etc. It's traditional to write zodiac notation using the traditional sign symbols. (There are Unicode glyphs for these symbols, but they're a bit bulky, in my opinion).

The March equinox point is 0 Aries 0. It's often called the First Point of Aries, even in modern astronomy. Why the first point? This system is ancient! It was devised long before zero was invented. :)

The sign containing the Ascendant is known as the rising sign. It's literally the section of the ecliptic which is currently rising on the rational horizon.

Here's a Python script which calculates the obliquity, GMST, local sidereal time, and the ascendant. You must supply the time in UTC, not local time. The script uses Sage features to read the date & time and location data from the GUI, but the rest of the program is plain Python. It uses the standard datetime module to parse the date & time, but it uses no 3rd party libraries.

The date must be given in YYYY-Mon-DD format. That is, a 4 digit year, followed by the 3 letter abbreviation of the month name, followed by a 1 or 2 digit day, with - between the fields. The UTC time field is a little more flexible. It may be specified with or without seconds, and you may specify fractional seconds, with upto 6 digits after the decimal point.

Latitude and longitude must be given in decimal degrees. However, you can write arithmetic expressions in those input boxes. For example, you can input 40°N 43' latitude as 40 + 43/60.

The script prints the Ascendant in both zodiac notation and degrees.

""" Calculate the ascendant from the UTC time and location

    The ascendant is the ecliptic longitude on the rational horizon
    See https://en.wikipedia.org/wiki/Ascendant

    Obliquity & sidereal time equations from JPL
    Written by PM 2Ring 2024.02.01

from datetime import datetime, timedelta, timezone
from math import sin, cos, tan, atan2, degrees, radians

def parse_datetime(dtstr):
    for tfmt in (' %H:%M', ' %H:%M:%S', ' %H:%M:%S.%f', ''):
            dt = datetime.strptime(dtstr, '%Y-%b-%d' + tfmt)
        except ValueError:
        print("Bad date time string:", dtstr)
        return None
    return dt.replace(tzinfo=timezone.utc)

def hms(t):
    h = int(t)
    t = 60 * (t - h)
    m = int(t)
    t = 60 * (t - m)
    return f"{h:>2}:{m:02}:{t:06.3f}"

zodiac = "Ari Tau Gem Can Leo Vir Lib Sco Sag Cap Aqu Pis".split()
def sdms(t):
    z = int(float(t) // 30)
    t -= 30 * z
    d = int(t)
    m = 60 * (t - d)
    return f"{d:2} {zodiac[z]} {m:4.1f}"

# J2000 epoch
day_zero = datetime(2000, 1, 1, 12, tzinfo=timezone.utc)

def _(txt=HtmlBox('<h3>Ascendant</h3>Date format: YYYY-Mon-DD'),
  dtstr=('Date & Time, UTC', '2000-Jan-1 12:00'),
  lat=('Latitude', 51.4773207),
  lon=('Longitude (east)', 0.0),

    dt = parse_datetime(dtstr.strip())
    if dt is None:

    lon, lat = float(lon), float(lat)

    # Convert to days since Noon, 1 Jan 2000
    d = (dt - day_zero).total_seconds() / 86400

    # Julian centuries
    T = d / 36525

    # Obliquity of the ecliptic. T should be Terrestrial Time
    oe = ((((-4.34e-8*T - 5.76e-7)*T + 0.0020034)*T - 1.831e-4)*T - 46.836769)*T / 3600 + 23.4392794444444
    oer = radians(oe)
    print("Obliquity:", oe)

    # Greenwich Mean Sidereal Time
    gmst = (67310.548 + (3155760000 + 8640184.812866) * T
      + 0.093104 * T**2 - 6.2e-6 * T**3) / 3600 % 24
    print('Greenwich sidereal time:', hms(gmst))

    lst = (gmst + lon / 15) % 24
    print('    Local sidereal time:', hms(lst))

    # Local sidereal time, in radians
    lstr = radians(lst * 15)

    # Ascendant
    ascr = atan2(cos(lstr), -(sin(lstr) * cos(oer) + tan(radians(lat)) * sin(oer)))
    asc = degrees(ascr) % 360
    print(f"Ascendant: {sdms(asc)} = {asc}°")

I've tested this script against various online calculators, including the Sidereal Time service of the USNO. My sidereal times agree with the USNO values to sub-second precision.

Here's a live version of the script, running on the SageMathCell server.


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .